summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/src/runtime')
-rw-r--r--packages/kernel/src/runtime/events.ts13
-rw-r--r--packages/kernel/src/runtime/run-turn.test.ts97
-rw-r--r--packages/kernel/src/runtime/run-turn.ts7
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,
),
);