Quelle commands-context-report.ts Sprache: unbekannt
import { analyzeBootstrapBudget } from "../../agents/bootstrap-budget.js" ;
import {
resolveBootstrapMaxChars,
resolveBootstrapTotalMaxChars,
} from "../../agents/pi-embedded-helpers/bootstrap.js" ;
import { buildSystemPromptReport } from "../../agents/system-prompt-report.js" ;
import {
resolveFreshSessionTotalTokens,
type SessionSystemPromptReport,
} from "../../config/sessions/types.js" ;
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js" ;
import { estimateTokensFromChars } from "../../utils/cjk-chars.js" ;
import type { ReplyPayload } from "../types.js" ;
import type { HandleCommandsParams } from "./commands-types.js" ;
function formatInt(n: number): string {
return new Intl.NumberFormat("en-US" ).format(n);
}
function formatCharsAndTokens(chars: number): string {
return `${formatInt(chars)} chars (~${formatInt(estimateTokensFromChars(chars))} tok)`;
}
function parseContextArgs(commandBodyNormalized: string): string {
if (commandBodyNormalized === "/context" ) {
return "" ;
}
if (commandBodyNormalized.startsWith("/context " )) {
return commandBodyNormalized.slice(8 ).trim();
}
return "" ;
}
function formatListTop(
entries: Array<{ name: string; value: number }>,
cap: number,
): { lines: string[]; omitted: number } {
const sorted = [...entries].toSorted((a, b) => b.value - a.value);
const top = sorted.slice(0 , cap);
const omitted = Math.max(0 , sorted.length - top.length);
const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`);
return { lines, omitted };
}
async function resolveContextReport(
params: HandleCommandsParams,
): Promise<SessionSystemPromptReport> {
const targetSessionEntry = params.sessionStore?.[params.sessionKey] ?? params.sessionEntry;
const existing = targetSessionEntry?.systemPromptReport;
if (existing && existing.source === "run" ) {
return existing;
}
const bootstrapMaxChars = resolveBootstrapMaxChars(params.cfg);
const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.cfg);
const { resolveCommandsSystemPromptBundle } = await import ("./commands-system-prompt.js" );
const { systemPrompt, tools, skillsPrompt, bootstrapFiles, injectedFiles, sandboxRuntime } =
await resolveCommandsSystemPromptBundle(params);
return buildSystemPromptReport({
source: "estimate" ,
generatedAt: Date.now(),
sessionId: targetSessionEntry?.sessionId,
sessionKey: params.sessionKey,
provider: params.provider,
model: params.model,
workspaceDir: params.workspaceDir,
bootstrapMaxChars,
bootstrapTotalMaxChars,
sandbox: { mode: sandboxRuntime.mode, sandboxed: sandboxRuntime.sandboxed },
systemPrompt,
bootstrapFiles,
injectedFiles,
skillsPrompt,
tools,
});
}
export async function buildContextReply(params: HandleCommandsParams): Promise<ReplyPayload> {
const targetSessionEntry = params.sessionStore?.[params.sessionKey] ?? params.sessionEntry;
const args = parseContextArgs(params.command.commandBodyNormalized);
const sub = normalizeLowercaseStringOrEmpty(args.split(/\s+/).find(Boolean ));
if (!sub || sub === "help" ) {
return {
text: [
" /context" ,
"" ,
"What counts as context (high-level), plus a breakdown mode." ,
"" ,
"Try:" ,
"- /context list (short breakdown)" ,
"- /context detail (per-file + per-tool + per-skill + system prompt size)" ,
"- /context json (same, machine-readable)" ,
"" ,
"Inline shortcut = a command token inside a normal message (e.g. “hey /status”). It runs immediately (allowlisted senders only) and is stripped before the model sees the remaining text." ,
].join("\n" ),
};
}
const report = await resolveContextReport(params);
const cachedContextUsageTokens = resolveFreshSessionTotalTokens(targetSessionEntry);
const session = {
totalTokens: targetSessionEntry?.totalTokens ?? null ,
totalTokensFresh: targetSessionEntry?.totalTokensFresh ?? null ,
inputTokens: targetSessionEntry?.inputTokens ?? null ,
outputTokens: targetSessionEntry?.outputTokens ?? null ,
contextTokens: params.contextTokens ?? null ,
} as const ;
if (sub === "json" ) {
return { text: JSON.stringify({ report, session }, null , 2 ) };
}
if (sub !== "list" && sub !== "show" && sub !== "detail" && sub !== "deep" ) {
return {
text: [
"Unknown /context mode." ,
"Use: /context, /context list, /context detail, or /context json" ,
].join("\n" ),
};
}
const fileLines = report.injectedWorkspaceFiles.map((f) => {
const status = f.missing ? "MISSING" : f.truncated ? "TRUNCATED" : "OK" ;
const raw = f.missing ? "0" : formatCharsAndTokens(f.rawChars);
const injected = f.missing ? "0" : formatCharsAndTokens(f.injectedChars);
return `- ${f.name}: ${status} | raw ${raw} | injected ${injected}`;
});
const sandboxLine = `Sandbox: mode=${report.sandbox?.mode ?? "unknown" } sandboxed=${report.sandbox?.sandboxed ?? false }`;
const toolSchemaLine = `Tool schemas (JSON): ${formatCharsAndTokens(report.tools.schemaChars)} (counts toward context; not shown as text)`;
const toolListLine = `Tool list (system prompt text): ${formatCharsAndTokens(report.tools.listChars)}`;
const skillNameSet = new Set(report.skills.entries.map((s) => s.name));
const skillNames = Array.from(skillNameSet);
const toolNames = report.tools.entries.map((t) => t.name);
const formatNameList = (names: string[], cap: number) =>
names.length <= cap
? names.join(", " )
: `${names.slice(0 , cap).join(", " )}, … (+${names.length - cap} more)`;
const skillsLine = `Skills list (system prompt text): ${formatCharsAndTokens(report.skills.promptChars)} (${skillNameSet.size} skills)`;
const skillsNamesLine = skillNameSet.size
? `Skills: ${formatNameList(skillNames, 20 )}`
: "Skills: (none)" ;
const toolsNamesLine = toolNames.length
? `Tools: ${formatNameList(toolNames, 30 )}`
: "Tools: (none)" ;
const systemPromptLine = `System prompt (${report.source}): ${formatCharsAndTokens(report.systemPrompt.chars)} (Project Context ${formatCharsAndTokens(report.systemPrompt.projectContextChars)})`;
const workspaceLabel = report.workspaceDir ?? params.workspaceDir;
const bootstrapMaxChars =
typeof report.bootstrapMaxChars === "number" &&
Number.isFinite(report.bootstrapMaxChars) &&
report.bootstrapMaxChars > 0
? report.bootstrapMaxChars
: resolveBootstrapMaxChars(params.cfg);
const bootstrapTotalMaxChars =
typeof report.bootstrapTotalMaxChars === "number" &&
Number.isFinite(report.bootstrapTotalMaxChars) &&
report.bootstrapTotalMaxChars > 0
? report.bootstrapTotalMaxChars
: resolveBootstrapTotalMaxChars(params.cfg);
const bootstrapMaxLabel = `${formatInt(bootstrapMaxChars)} chars`;
const bootstrapTotalLabel = `${formatInt(bootstrapTotalMaxChars)} chars`;
const bootstrapAnalysis = analyzeBootstrapBudget({
files: report.injectedWorkspaceFiles,
bootstrapMaxChars,
bootstrapTotalMaxChars,
});
const truncatedBootstrapFiles = bootstrapAnalysis.truncatedFiles;
const truncationCauseCounts = truncatedBootstrapFiles.reduce(
(acc, file) => {
for (const cause of file.causes) {
if (cause === "per-file-limit" ) {
acc.perFile += 1 ;
} else if (cause === "total-limit" ) {
acc.total += 1 ;
}
}
return acc;
},
{ perFile: 0 , total: 0 },
);
const truncationCauseParts = [
truncationCauseCounts.perFile > 0
? `${truncationCauseCounts.perFile} file(s) exceeded max/file`
: null ,
truncationCauseCounts.total > 0 ? `${truncationCauseCounts.total} file(s) hit max/total` : null ,
].filter(Boolean );
const bootstrapWarningLines =
truncatedBootstrapFiles.length > 0
? [
`⚠ Bootstrap context is over configured limits: ${truncatedBootstrapFiles.length} file(s) truncated (${formatInt(bootstrapAnalysis.totals.rawChars)} raw chars -> ${formatInt(bootstrapAnalysis.totals.injectedChars)} injected chars).`,
...(truncationCauseParts.length ? [`Causes: ${truncationCauseParts.join("; " )}.`] : []),
"Tip: increase `agents.defaults.bootstrapMaxChars` and/or `agents.defaults.bootstrapTotalMaxChars` if this truncation is not intentional." ,
]
: [];
const contextWindowLabel = session.contextTokens != null ? formatInt(session.contextTokens) : "?" ;
const totalsLine =
cachedContextUsageTokens != null
? `Session tokens (cached): ${formatInt(cachedContextUsageTokens)} total / ctx=${contextWindowLabel}`
: `Session tokens (cached): unknown / ctx=${contextWindowLabel}`;
const sharedContextLines = [
`Workspace: ${workspaceLabel}`,
`Bootstrap max/file: ${bootstrapMaxLabel}`,
`Bootstrap max/total: ${bootstrapTotalLabel}`,
sandboxLine,
systemPromptLine,
...(bootstrapWarningLines.length ? ["" , ...bootstrapWarningLines] : []),
"" ,
"Injected workspace files:" ,
...fileLines,
"" ,
skillsLine,
skillsNamesLine,
];
if (sub === "detail" || sub === "deep" ) {
const perSkill = formatListTop(
report.skills.entries.map((s) => ({ name: s.name, value: s.blockChars })),
30 ,
);
const perToolSchema = formatListTop(
report.tools.entries.map((t) => ({ name: t.name, value: t.schemaChars })),
30 ,
);
const perToolSummary = formatListTop(
report.tools.entries.map((t) => ({ name: t.name, value: t.summaryChars })),
30 ,
);
const toolPropsLines = report.tools.entries
.filter((t) => t.propertiesCount != null )
.toSorted((a, b) => (b.propertiesCount ?? 0 ) - (a.propertiesCount ?? 0 ))
.slice(0 , 30 )
.map((t) => `- ${t.name}: ${t.propertiesCount} params`);
// `systemPrompt.chars` already includes injected files, skills, and tool-list text.
// Add only tool schemas here so the tracked estimate stays disjoint.
const trackedPromptChars = report.systemPrompt.chars + report.tools.schemaChars;
const trackedPromptLine = `Tracked prompt estimate: ${formatCharsAndTokens(trackedPromptChars)}`;
const actualContextLine =
cachedContextUsageTokens != null
? `Actual context usage (cached): ${formatInt(cachedContextUsageTokens)} tok`
: "Actual context usage (cached): unavailable" ;
const overheadTokens =
cachedContextUsageTokens != null
? cachedContextUsageTokens - estimateTokensFromChars(trackedPromptChars)
: null ;
const overheadLine =
overheadTokens == null
? null
: overheadTokens > 0
? `Untracked provider/runtime overhead: ~${formatInt(overheadTokens)} tok`
: "Untracked provider/runtime overhead: not observed in cached usage" ;
return {
text: [
" Context breakdown (detailed)" ,
...sharedContextLines,
...(perSkill.lines.length ? ["Top skills (prompt entry size):" , ...perSkill.lines] : []),
...(perSkill.omitted ? [`… (+${perSkill.omitted} more skills)`] : []),
"" ,
toolListLine,
toolSchemaLine,
toolsNamesLine,
"Top tools (schema size):" ,
...perToolSchema.lines,
...(perToolSchema.omitted ? [`… (+${perToolSchema.omitted} more tools)`] : []),
"" ,
"Top tools (summary text size):" ,
...perToolSummary.lines,
...(perToolSummary.omitted ? [`… (+${perToolSummary.omitted} more tools)`] : []),
...(toolPropsLines.length ? ["" , "Tools (param count):" , ...toolPropsLines] : []),
"" ,
trackedPromptLine,
actualContextLine,
...(overheadLine ? [overheadLine] : []),
"" ,
totalsLine,
"" ,
"Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message." ,
]
.filter(Boolean )
.join("\n" ),
};
}
return {
text: [
" Context breakdown" ,
...sharedContextLines,
toolListLine,
toolSchemaLine,
toolsNamesLine,
"" ,
totalsLine,
"" ,
"Inline shortcut: a command token inside normal text (e.g. “hey /status”) that runs immediately (allowlisted senders only) and is stripped before the model sees the remaining message." ,
].join("\n" ),
};
}
Messung V0.5 in Prozent C=100 H=99 G=99
[Dauer der Verarbeitung: 0.20 Sekunden, vorverarbeitet 2026-10-01]
2026-10-10
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126438
#Domains=1897691