Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fix-context-usage-meter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/kimi-code-sdk": patch
"@moonshot-ai/kimi-code": patch
---

Restore live context-window usage on agent status so the TUI and VS Code meters update after attach and during a turn.
14 changes: 13 additions & 1 deletion apps/vscode/src/runtime/event-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,24 @@ function mapLegacyWireEvent(
}
}

function contextUsageRatio(
sdkEvent: Extract<Event, { type: 'agent.status.updated' }>,
): number | undefined {
const tokens = sdkEvent.contextTokens;
const max = sdkEvent.maxContextTokens;
if (typeof tokens === 'number' && typeof max === 'number' && max > 0) {
return tokens / max;
}
return sdkEvent.contextUsage;
}

function mapStatusUpdate(
state: EventAdapterState,
sdkEvent: Extract<Event, { type: 'agent.status.updated' }>,
): MappedLegacyWireEvent {
const payload: StatusUpdate = {};
if (sdkEvent.contextUsage !== undefined) payload.context_usage = sdkEvent.contextUsage;
const contextUsage = contextUsageRatio(sdkEvent);
if (contextUsage !== undefined) payload.context_usage = contextUsage;
if (sdkEvent.planMode !== undefined) payload.plan_mode = sdkEvent.planMode;
if (sdkEvent.model !== undefined) payload.model = sdkEvent.model;
if (sdkEvent.thinkingEffort !== undefined) payload.thinking_effort = sdkEvent.thinkingEffort;
Expand Down
1 change: 1 addition & 0 deletions apps/vscode/src/runtime/session-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class SessionRuntime {
model: status.model,
thinking_effort: status.thinkingEffort,
plan_mode: status.planMode,
context_usage: status.contextUsage,
},
_sessionId: this.id,
},
Expand Down
31 changes: 31 additions & 0 deletions apps/vscode/test/event-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,37 @@ describe('event adapter (projects SDK events into the legacy Webview contract)',
});
});

it('derives context_usage from token counts when the SDK omits contextUsage', () => {
const result = adaptSdkEvent(createEventAdapterState(), {
type: 'agent.status.updated',
sessionId: 'session-1',
agentId: 'main',
contextTokens: 25_600,
maxContextTokens: 256_000,
});

expect(result.event).toEqual({
type: 'StatusUpdate',
payload: { context_usage: 0.1 },
_sessionId: 'session-1',
});
});

it('prefers live token counts over an explicit contextUsage of 0', () => {
const result = adaptSdkEvent(createEventAdapterState(), {
type: 'agent.status.updated',
sessionId: 'session-1',
agentId: 'main',
contextUsage: 0,
contextTokens: 25_600,
maxContextTokens: 256_000,
});

expect(result.event).toMatchObject({
payload: { context_usage: 0.1 },
});
});

it('emits snake-case status fields when agent status changes', () => {
const result = adaptSdkEvent(createEventAdapterState(), {
type: 'agent.status.updated',
Expand Down
7 changes: 6 additions & 1 deletion apps/vscode/test/kimi-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,12 @@ describe("Kimi runtime (owns shared SDK sessions for Webviews)", () => {
event: Events.StreamEvent,
data: {
type: "StatusUpdate",
payload: { model: "kimi-test", thinking_effort: "max", plan_mode: true },
payload: {
model: "kimi-test",
thinking_effort: "max",
plan_mode: true,
context_usage: 0,
},
_sessionId: "saved-1",
},
webviewId: "view-1",
Expand Down
16 changes: 16 additions & 0 deletions apps/vscode/test/session-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,22 @@ function turnEnded(
}

describe("session runtime (adapts one SDK session for subscribed Webviews)", () => {
it("announces context_usage from the session status snapshot", async () => {
const { runtime, broadcasts } = createRuntime();

await runtime.announceStatus("view-1");

expect(streamData(broadcasts)[0]).toMatchObject({
type: "StatusUpdate",
payload: {
thinking_effort: "off",
plan_mode: false,
context_usage: 0,
},
_sessionId: "session-1",
});
});

it("renders a host-only command without making it a forkable core turn", () => {
const { runtime, broadcasts } = createRuntime();

Expand Down
5 changes: 5 additions & 0 deletions packages/node-sdk/src/v2/session-wiring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,16 @@ function withStatusSnapshot(agent: IAgentScopeHandle, event: DomainEvent): Domai
const contextTokens = tokenCounting.statusSize();
const capabilities = profile.getModelCapabilities();
const maxContextTokens = capabilities.max_input_tokens ?? capabilities.max_context_tokens;
const contextUsage =
typeof maxContextTokens === 'number' && maxContextTokens > 0
? contextTokens / maxContextTokens
: 0;
return {
...event,
usage: usageService.status(),
contextTokens,
maxContextTokens,
contextUsage,
model: profile.getModel(),
} as unknown as DomainEvent;
}
1 change: 1 addition & 0 deletions packages/node-sdk/test/session-event-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ describe('SessionEventWiring status snapshot fold', () => {
usage: USAGE,
contextTokens: 10,
maxContextTokens: 128_000,
contextUsage: 10 / 128_000,
model: 'sub-model',
});
expect(events[1]).toMatchObject({ type: 'assistant.delta', delta: 'Hi' });
Expand Down