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
5 changes: 5 additions & 0 deletions .changeset/fix-vscode-context-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Restore live context-window usage updates after switching to the v2 engine.
19 changes: 18 additions & 1 deletion apps/vscode/src/runtime/event-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ function mapStatusUpdate(
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 All @@ -356,6 +357,22 @@ function mapStatusUpdate(
};
}

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

function usageDelta(current: AdapterTokenUsage, previous: AdapterTokenUsage | undefined): TokenUsage {
return {
input_other: delta(current.inputOther, previous?.inputOther),
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
44 changes: 44 additions & 0 deletions apps/vscode/test/event-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,50 @@ describe('event adapter (projects SDK events into the legacy Webview contract)',
});
});

it('derives context_usage from the v2 context token pair', () => {
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('preserves an explicit contextUsage field over the context token pair', () => {
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({
type: 'StatusUpdate',
payload: { context_usage: 0 },
});
});

it('does not derive a ratio from an invalid context capacity', () => {
const result = adaptSdkEvent(createEventAdapterState(), {
type: 'agent.status.updated',
sessionId: 'session-1',
agentId: 'main',
contextTokens: 25_600,
maxContextTokens: 0,
});

expect(result.event).toBeUndefined();
});

it('emits only new token usage when SDK status carries cumulative turn usage', () => {
const first = 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 the current context usage to a subscribed Webview", async () => {
const { runtime, broadcasts } = createRuntime();

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

expect(streamData(broadcasts)).toContainEqual({
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
8 changes: 8 additions & 0 deletions packages/node-sdk/src/v2/session-wiring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,18 @@ function withStatusSnapshot(agent: IAgentScopeHandle, event: Event2<any>): Event
const contextTokens = tokenCounting.statusSize();
const capabilities = profile.getModelCapabilities();
const maxContextTokens = capabilities.max_input_tokens ?? capabilities.max_context_tokens;
const contextUsage =
Number.isFinite(contextTokens) &&
maxContextTokens !== undefined &&
Number.isFinite(maxContextTokens) &&
maxContextTokens > 0
? contextTokens / maxContextTokens
: undefined;
return Object.assign({}, event, {
usage: usageService.status(),
contextTokens,
maxContextTokens,
contextUsage,
model: profile.getModel(),
}) as unknown as Event2<any>;
}
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({
Expand Down