diff options
Diffstat (limited to 'packages/kernel/src/runtime')
| -rw-r--r-- | packages/kernel/src/runtime/events.ts | 13 | ||||
| -rw-r--r-- | packages/kernel/src/runtime/run-turn.test.ts | 97 | ||||
| -rw-r--r-- | packages/kernel/src/runtime/run-turn.ts | 7 |
3 files changed, 117 insertions, 0 deletions
diff --git a/packages/kernel/src/runtime/events.ts b/packages/kernel/src/runtime/events.ts index 300e711..b194577 100644 --- a/packages/kernel/src/runtime/events.ts +++ b/packages/kernel/src/runtime/events.ts @@ -127,16 +127,29 @@ export function doneEvent( reason: string, durationMs?: number, usage?: Usage, + contextSize?: number, ): AgentEvent { + if (durationMs !== undefined && usage !== undefined && contextSize !== undefined) { + return { type: "done", conversationId, turnId, reason, durationMs, usage, contextSize }; + } if (durationMs !== undefined && usage !== undefined) { return { type: "done", conversationId, turnId, reason, durationMs, usage }; } + if (durationMs !== undefined && contextSize !== undefined) { + return { type: "done", conversationId, turnId, reason, durationMs, contextSize }; + } + if (usage !== undefined && contextSize !== undefined) { + return { type: "done", conversationId, turnId, reason, usage, contextSize }; + } if (durationMs !== undefined) { return { type: "done", conversationId, turnId, reason, durationMs }; } if (usage !== undefined) { return { type: "done", conversationId, turnId, reason, usage }; } + if (contextSize !== undefined) { + return { type: "done", conversationId, turnId, reason, contextSize }; + } return { type: "done", conversationId, turnId, reason }; } diff --git a/packages/kernel/src/runtime/run-turn.test.ts b/packages/kernel/src/runtime/run-turn.test.ts index fa2aba4..dcaea7f 100644 --- a/packages/kernel/src/runtime/run-turn.test.ts +++ b/packages/kernel/src/runtime/run-turn.test.ts @@ -2480,4 +2480,101 @@ describe("runTurn", () => { } }); }); + + describe("contextSize", () => { + it("single-step turn: contextSize equals step inputTokens + outputTokens", async () => { + const provider = createFakeProvider([ + [ + { type: "text-delta", delta: "Hello" }, + { type: "usage", usage: { inputTokens: 100, outputTokens: 50 } }, + { type: "finish", reason: "stop" }, + ], + ]); + + const { events, emit } = createCollectingEmit(); + + await runTurn({ + provider, + messages: [userMessage], + tools: [], + dispatch: { maxConcurrent: 1, eager: false }, + conversationId: "conv-1", + turnId: "turn-1", + emit, + }); + + const doneEvt = events.find((e) => e.type === "done"); + expect(doneEvt).toBeDefined(); + if (doneEvt?.type === "done") { + expect(doneEvt.contextSize).toBe(150); + } + }); + + it("multi-step turn: contextSize equals ONLY the last step's inputTokens + outputTokens", async () => { + const tool = createFakeTool("echo", async () => ({ content: "echoed" })); + + const provider = createFakeProvider([ + [ + { type: "tool-call", toolCallId: "tc1", toolName: "echo", input: {} }, + { type: "usage", usage: { inputTokens: 100, outputTokens: 20 } }, + { type: "finish", reason: "tool-calls" }, + ], + [ + { type: "text-delta", delta: "done" }, + { type: "usage", usage: { inputTokens: 300, outputTokens: 80 } }, + { type: "finish", reason: "stop" }, + ], + ]); + + const { events, emit } = createCollectingEmit(); + + await runTurn({ + provider, + messages: [userMessage], + tools: [tool], + dispatch: { maxConcurrent: 1, eager: false }, + conversationId: "conv-1", + turnId: "turn-1", + emit, + }); + + const doneEvt = events.find((e) => e.type === "done"); + expect(doneEvt).toBeDefined(); + if (doneEvt?.type === "done") { + expect(doneEvt.contextSize).toBe(380); + expect(doneEvt.usage).toBeDefined(); + if (doneEvt.usage !== undefined) { + expect(doneEvt.contextSize).not.toBe(doneEvt.usage.inputTokens); + } + } + }); + + it("no usage reported: contextSize is undefined", async () => { + const provider = createFakeProvider([ + [ + { type: "text-delta", delta: "Hello" }, + { type: "finish", reason: "stop" }, + ], + ]); + + const { events, emit } = createCollectingEmit(); + + await runTurn({ + provider, + messages: [userMessage], + tools: [], + dispatch: { maxConcurrent: 1, eager: false }, + conversationId: "conv-1", + turnId: "turn-1", + emit, + }); + + const doneEvt = events.find((e) => e.type === "done"); + expect(doneEvt).toBeDefined(); + if (doneEvt?.type === "done") { + expect(doneEvt.contextSize).toBeUndefined(); + expect(doneEvt.usage).toBeUndefined(); + } + }); + }); }); diff --git a/packages/kernel/src/runtime/run-turn.ts b/packages/kernel/src/runtime/run-turn.ts index b50e8ee..bf57854 100644 --- a/packages/kernel/src/runtime/run-turn.ts +++ b/packages/kernel/src/runtime/run-turn.ts @@ -449,6 +449,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> { const messages: ChatMessage[] = [...input.messages]; const resultMessages: ChatMessage[] = []; let totalUsage = zeroUsage(); + let lastStepUsage: Usage | undefined; let finishReason = "stop"; const toolMap = new Map<string, ToolContract>(); @@ -513,6 +514,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> { }); totalUsage = addUsage(totalUsage, stepResult.usage); + lastStepUsage = stepResult.usage; if (stepResult.assistantMessage !== undefined) { messages.push(stepResult.assistantMessage); @@ -571,6 +573,10 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> { totalUsage.outputTokens > 0 || totalUsage.cacheReadTokens !== undefined || totalUsage.cacheWriteTokens !== undefined; + const contextSize = + hasUsage && lastStepUsage !== undefined + ? lastStepUsage.inputTokens + lastStepUsage.outputTokens + : undefined; input.emit( doneEvent( conversationId, @@ -578,6 +584,7 @@ export async function runTurn(input: RunTurnInput): Promise<RunTurnResult> { finishReason, turnDurationMs, hasUsage ? totalUsage : undefined, + contextSize, ), ); |
