fix(coding-agent/modes): hardened context breakdown against absent session fields

- Hardened context usage accounting to tolerate missing session fields by defaulting skills and tools to empty arrays.
- Guarded message and system-prompt token counting with presence checks to avoid access errors on partial session objects.
This commit is contained in:
can1357
2026-05-14 06:37:25 +02:00
parent 6d00cc1f08
commit 88e486be29
2 changed files with 15 additions and 18 deletions
@@ -26,10 +26,7 @@ function createCodexToken(accountId: string): string {
* is exercised by its own targeted tests; these history-replay tests assert raw
* payload shape and should stay independent of it.
*/
function getOpenAIReasoningModel<Provider extends string>(
provider: Provider,
id: string,
): Model<"openai-responses"> {
function getOpenAIReasoningModel<Provider extends string>(provider: Provider, id: string): Model<"openai-responses"> {
const base = getBundledModel(provider, id) as Model<"openai-responses">;
return { ...base, name: "Reasoning Mini" };
}
@@ -324,7 +321,7 @@ describe("OpenAI responses history payload", () => {
it("falls back to system instructions for OpenAI-compatible endpoints without developer-role support", async () => {
const model = {
...(getOpenAIReasoningModel("openai", "gpt-5-mini")),
...getOpenAIReasoningModel("openai", "gpt-5-mini"),
baseUrl: "https://proxy.example.com/v1",
};
const payload = (await captureResponsesPayload(model, {
@@ -60,14 +60,6 @@ function estimateToolSchemaTokens(tools: ReadonlyArray<Pick<Tool, "name" | "desc
return countTokens(fragments);
}
function estimateMessagesTokens(session: AgentSession): number {
let total = 0;
for (const message of session.messages) {
total += estimateTokens(message);
}
return total;
}
/**
* Compute a breakdown of estimated context usage by category for the active
* session and model.
@@ -76,9 +68,16 @@ export function computeContextBreakdown(session: AgentSession): ContextBreakdown
const model = session.model;
const contextWindow = model?.contextWindow ?? 0;
const skillsTokens = estimateSkillsTokens(session.skills);
const toolsTokens = estimateToolSchemaTokens(session.agent.state.tools);
const messagesTokens = estimateMessagesTokens(session);
const skillsTokens = estimateSkillsTokens(session.skills ?? []);
const toolsTokens = estimateToolSchemaTokens(session.agent?.state?.tools ?? []);
let messagesTokens = 0;
const convo = session.messages;
if (convo) {
for (const message of convo) {
messagesTokens += estimateTokens(message);
}
}
// The rendered system prompt already contains the skill descriptions and the
// markdown tool descriptions. To present a non-overlapping breakdown:
@@ -86,8 +85,9 @@ export function computeContextBreakdown(session: AgentSession): ContextBreakdown
// Tools = JSON tool schema sent separately on the wire
// Skills = the skill list embedded in the system prompt
// Messages = conversation messages
const systemPromptTokens = Math.max(0, countTokens(session.systemPrompt[0] ?? "") - skillsTokens);
const systemContextTokens = countTokens(session.systemPrompt.slice(1));
const systemPromptParts = session.systemPrompt;
const systemPromptTokens = Math.max(0, countTokens(systemPromptParts?.[0] ?? "") - skillsTokens);
const systemContextTokens = countTokens(systemPromptParts?.slice(1) ?? []);
const categories: CategoryInfo[] = [
{ id: "systemPrompt", label: "System prompt", tokens: systemPromptTokens, color: "accent", glyph: CELL_FILLED },