Reduce prompt token overhead with leaner context injections
This commit is contained in:
committed by
Peter Steinberger
parent
7a917602c5
commit
412990a139
@@ -16,6 +16,25 @@ import type { WorkspaceBootstrapFile } from "./workspace.js";
|
|||||||
|
|
||||||
export type EmbeddedContextFile = { path: string; content: string };
|
export type EmbeddedContextFile = { path: string; content: string };
|
||||||
|
|
||||||
|
const MAX_BOOTSTRAP_CHARS = 4000;
|
||||||
|
const BOOTSTRAP_HEAD_CHARS = 2800;
|
||||||
|
const BOOTSTRAP_TAIL_CHARS = 800;
|
||||||
|
|
||||||
|
function trimBootstrapContent(content: string, fileName: string): string {
|
||||||
|
const trimmed = content.trimEnd();
|
||||||
|
if (trimmed.length <= MAX_BOOTSTRAP_CHARS) return trimmed;
|
||||||
|
|
||||||
|
const head = trimmed.slice(0, BOOTSTRAP_HEAD_CHARS);
|
||||||
|
const tail = trimmed.slice(-BOOTSTRAP_TAIL_CHARS);
|
||||||
|
return [
|
||||||
|
head,
|
||||||
|
"",
|
||||||
|
`[...truncated, read ${fileName} for full content...]`,
|
||||||
|
"",
|
||||||
|
tail,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
export async function ensureSessionHeader(params: {
|
export async function ensureSessionHeader(params: {
|
||||||
sessionFile: string;
|
sessionFile: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -88,12 +107,18 @@ export async function sanitizeSessionMessagesImages(
|
|||||||
export function buildBootstrapContextFiles(
|
export function buildBootstrapContextFiles(
|
||||||
files: WorkspaceBootstrapFile[],
|
files: WorkspaceBootstrapFile[],
|
||||||
): EmbeddedContextFile[] {
|
): EmbeddedContextFile[] {
|
||||||
return files.map((file) => ({
|
const result: EmbeddedContextFile[] = [];
|
||||||
path: file.name,
|
for (const file of files) {
|
||||||
content: file.missing
|
if (file.missing) continue;
|
||||||
? `[MISSING] Expected at: ${file.path}`
|
const content = file.content ?? "";
|
||||||
: (file.content ?? ""),
|
const trimmed = content.trimEnd();
|
||||||
}));
|
if (!trimmed) continue;
|
||||||
|
result.push({
|
||||||
|
path: file.name,
|
||||||
|
content: trimBootstrapContent(trimmed, file.name),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatAssistantErrorText(
|
export function formatAssistantErrorText(
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe("buildAgentSystemPromptAppend", () => {
|
|||||||
expect(prompt).toContain("<final>...</final>");
|
expect(prompt).toContain("<final>...</final>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists available and unavailable tools when provided", () => {
|
it("lists available tools when provided", () => {
|
||||||
const prompt = buildAgentSystemPromptAppend({
|
const prompt = buildAgentSystemPromptAppend({
|
||||||
workspaceDir: "/tmp/clawd",
|
workspaceDir: "/tmp/clawd",
|
||||||
toolNames: ["bash", "sessions_list", "sessions_history", "sessions_send"],
|
toolNames: ["bash", "sessions_list", "sessions_history", "sessions_send"],
|
||||||
@@ -44,7 +44,6 @@ describe("buildAgentSystemPromptAppend", () => {
|
|||||||
expect(prompt).toContain("sessions_list");
|
expect(prompt).toContain("sessions_list");
|
||||||
expect(prompt).toContain("sessions_history");
|
expect(prompt).toContain("sessions_history");
|
||||||
expect(prompt).toContain("sessions_send");
|
expect(prompt).toContain("sessions_send");
|
||||||
expect(prompt).toContain("Unavailable tools (do not call):");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes user time when provided", () => {
|
it("includes user time when provided", () => {
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ export function buildAgentSystemPromptAppend(params: {
|
|||||||
new Set(normalizedTools.filter((tool) => !toolOrder.includes(tool))),
|
new Set(normalizedTools.filter((tool) => !toolOrder.includes(tool))),
|
||||||
);
|
);
|
||||||
const enabledTools = toolOrder.filter((tool) => availableTools.has(tool));
|
const enabledTools = toolOrder.filter((tool) => availableTools.has(tool));
|
||||||
const disabledTools = toolOrder.filter((tool) => !availableTools.has(tool));
|
|
||||||
const toolLines = enabledTools.map((tool) => {
|
const toolLines = enabledTools.map((tool) => {
|
||||||
const summary = toolSummaries[tool];
|
const summary = toolSummaries[tool];
|
||||||
return summary ? `- ${tool}: ${summary}` : `- ${tool}`;
|
return summary ? `- ${tool}: ${summary}` : `- ${tool}`;
|
||||||
@@ -160,9 +159,6 @@ export function buildAgentSystemPromptAppend(params: {
|
|||||||
"- sessions_history: fetch session history",
|
"- sessions_history: fetch session history",
|
||||||
"- sessions_send: send to another session",
|
"- sessions_send: send to another session",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
disabledTools.length > 0
|
|
||||||
? `Unavailable tools (do not call): ${disabledTools.join(", ")}`
|
|
||||||
: "",
|
|
||||||
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
|
"TOOLS.md does not control tool availability; it is user guidance for how to use external tools.",
|
||||||
"",
|
"",
|
||||||
params.modelAliasLines && params.modelAliasLines.length > 0
|
params.modelAliasLines && params.modelAliasLines.length > 0
|
||||||
|
|||||||
@@ -1006,7 +1006,7 @@ describe("trigger handling", () => {
|
|||||||
|
|
||||||
describe("group intro prompts", () => {
|
describe("group intro prompts", () => {
|
||||||
const groupParticipationNote =
|
const groupParticipationNote =
|
||||||
"Be a good group participant: lurk and follow the conversation, but only chime in when you have something genuinely helpful or relevant to add. Don't feel obligated to respond to every message — quality over quantity. Even when lurking silently, you can use emoji reactions to acknowledge messages, show support, or react to humor — reactions are always appreciated and don't clutter the chat.";
|
"In groups, respond only when helpful; reactions are ok when available.";
|
||||||
it("labels Discord groups using the surface metadata", async () => {
|
it("labels Discord groups using the surface metadata", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||||
|
|||||||
@@ -161,10 +161,10 @@ export function buildGroupIntro(params: {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const cautionLine =
|
const cautionLine =
|
||||||
activation === "always"
|
activation === "always"
|
||||||
? "Be extremely selective: reply only when you are directly addressed, asked a question, or can add clear value. Otherwise stay silent."
|
? "Be extremely selective: reply only when directly addressed or clearly helpful. Otherwise stay silent."
|
||||||
: undefined;
|
: undefined;
|
||||||
const lurkLine =
|
const lurkLine =
|
||||||
"Be a good group participant: lurk and follow the conversation, but only chime in when you have something genuinely helpful or relevant to add. Don't feel obligated to respond to every message — quality over quantity. Even when lurking silently, you can use emoji reactions to acknowledge messages, show support, or react to humor — reactions are always appreciated and don't clutter the chat.";
|
"In groups, respond only when helpful; reactions are ok when available.";
|
||||||
return [
|
return [
|
||||||
subjectLine,
|
subjectLine,
|
||||||
membersLine,
|
membersLine,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import chalk from "chalk";
|
|
||||||
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
|
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
|
||||||
import { resolveTelegramToken } from "../telegram/token.js";
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
|
||||||
import {
|
import {
|
||||||
getWebAuthAgeMs,
|
getWebAuthAgeMs,
|
||||||
readWebSelfId,
|
readWebSelfId,
|
||||||
@@ -16,37 +14,33 @@ export async function buildProviderSummary(
|
|||||||
|
|
||||||
const webEnabled = effective.web?.enabled !== false;
|
const webEnabled = effective.web?.enabled !== false;
|
||||||
if (!webEnabled) {
|
if (!webEnabled) {
|
||||||
lines.push(chalk.cyan("WhatsApp: disabled"));
|
lines.push("WhatsApp: disabled");
|
||||||
} else {
|
} else {
|
||||||
const webLinked = await webAuthExists();
|
const webLinked = await webAuthExists();
|
||||||
const authAgeMs = getWebAuthAgeMs();
|
const authAgeMs = getWebAuthAgeMs();
|
||||||
const authAge = authAgeMs === null ? "unknown" : formatAge(authAgeMs);
|
const authAge = authAgeMs === null ? "" : ` auth ${formatAge(authAgeMs)}`;
|
||||||
const { e164 } = readWebSelfId();
|
const { e164 } = readWebSelfId();
|
||||||
lines.push(
|
lines.push(
|
||||||
webLinked
|
webLinked
|
||||||
? chalk.green(
|
? `WhatsApp: linked${e164 ? ` ${e164}` : ""}${authAge}`
|
||||||
`WhatsApp: linked${e164 ? ` as ${e164}` : ""} (auth ${authAge})`,
|
: "WhatsApp: not linked",
|
||||||
)
|
|
||||||
: chalk.red("WhatsApp: not linked"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const telegramEnabled = effective.telegram?.enabled !== false;
|
const telegramEnabled = effective.telegram?.enabled !== false;
|
||||||
if (!telegramEnabled) {
|
if (!telegramEnabled) {
|
||||||
lines.push(chalk.cyan("Telegram: disabled"));
|
lines.push("Telegram: disabled");
|
||||||
} else {
|
} else {
|
||||||
const { token: telegramToken } = resolveTelegramToken(effective);
|
const { token: telegramToken } = resolveTelegramToken(effective);
|
||||||
const telegramConfigured = Boolean(telegramToken?.trim());
|
const telegramConfigured = Boolean(telegramToken?.trim());
|
||||||
lines.push(
|
lines.push(
|
||||||
telegramConfigured
|
telegramConfigured ? "Telegram: configured" : "Telegram: not configured",
|
||||||
? chalk.green("Telegram: configured")
|
|
||||||
: chalk.cyan("Telegram: not configured"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const signalEnabled = effective.signal?.enabled !== false;
|
const signalEnabled = effective.signal?.enabled !== false;
|
||||||
if (!signalEnabled) {
|
if (!signalEnabled) {
|
||||||
lines.push(chalk.cyan("Signal: disabled"));
|
lines.push("Signal: disabled");
|
||||||
} else {
|
} else {
|
||||||
const signalConfigured =
|
const signalConfigured =
|
||||||
Boolean(effective.signal) &&
|
Boolean(effective.signal) &&
|
||||||
@@ -59,31 +53,20 @@ export async function buildProviderSummary(
|
|||||||
typeof effective.signal?.autoStart === "boolean",
|
typeof effective.signal?.autoStart === "boolean",
|
||||||
);
|
);
|
||||||
lines.push(
|
lines.push(
|
||||||
signalConfigured
|
signalConfigured ? "Signal: configured" : "Signal: not configured",
|
||||||
? chalk.green("Signal: configured")
|
|
||||||
: chalk.cyan("Signal: not configured"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imessageEnabled = effective.imessage?.enabled !== false;
|
const imessageEnabled = effective.imessage?.enabled !== false;
|
||||||
if (!imessageEnabled) {
|
if (!imessageEnabled) {
|
||||||
lines.push(chalk.cyan("iMessage: disabled"));
|
lines.push("iMessage: disabled");
|
||||||
} else {
|
} else {
|
||||||
const imessageConfigured = Boolean(effective.imessage);
|
const imessageConfigured = Boolean(effective.imessage);
|
||||||
lines.push(
|
lines.push(
|
||||||
imessageConfigured
|
imessageConfigured ? "iMessage: configured" : "iMessage: not configured",
|
||||||
? chalk.green("iMessage: configured")
|
|
||||||
: chalk.cyan("iMessage: not configured"),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowFrom = effective.whatsapp?.allowFrom?.length
|
|
||||||
? effective.whatsapp.allowFrom.map(normalizeE164).filter(Boolean)
|
|
||||||
: [];
|
|
||||||
if (allowFrom.length) {
|
|
||||||
lines.push(chalk.cyan(`AllowFrom: ${allowFrom.join(", ")}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user