-
Notifications
You must be signed in to change notification settings - Fork 899
feat(acp-adapter): Support displaying context length usage in Zed ACP like Codex #2412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
44a2049
6072663
6c903df
db9db41
2f2a3ad
894912a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@moonshot-ai/kimi-code": minor | ||
| --- | ||
|
|
||
| ACP agents now push live context window usage via the `usage_update` session notification so compatible clients (Zed, JetBrains) can render a real-time token consumption indicator. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ import { | |
| acpToolCallId, | ||
| assistantDeltaToSessionUpdate, | ||
| configOptionUpdateNotification, | ||
| contextUsageToUsageUpdate, | ||
| planFromDisplayBlock, | ||
| stringifyArgs, | ||
| thinkingDeltaToSessionUpdate, | ||
|
|
@@ -550,6 +551,41 @@ export class AcpSession { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Push a one-shot `usage_update` snapshot derived from the SDK | ||
| * `getStatus()` call. Best-effort — failures are logged and never | ||
| * surface to the client as errors. | ||
| * | ||
| * Called on session creation and after history replay so the client | ||
| * sees context usage immediately instead of waiting for the next | ||
| * `agent.status.updated` event (which only fires inside an active | ||
| * turn, via `runTurnBody`). | ||
| */ | ||
| async pushContextUsage(): Promise<void> { | ||
| if (typeof this.session.getStatus !== 'function') return; | ||
| try { | ||
| const status = await this.session.getStatus(); | ||
| const update = contextUsageToUsageUpdate( | ||
| this.id, | ||
| status.contextTokens, | ||
| status.maxContextTokens, | ||
| ); | ||
| if (update !== null) { | ||
| this.conn.sessionUpdate(update).catch((err) => { | ||
| log.warn('acp: failed to push usage_update', { | ||
| sessionId: this.id, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| log.warn('acp: failed to push usage_update', { | ||
| sessionId: this.id, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Replay the underlying SDK session's persisted history as a stream | ||
| * of ACP `session/update` notifications. | ||
|
|
@@ -964,6 +1000,34 @@ export class AcpSession { | |
| const outcome = await completion; | ||
| if (outcome.kind === 'completed') { | ||
| await this.emitLocalCommandMessage(formatCompactionCompleted(outcome.result)); | ||
| // After compaction, the core updates context token counts via | ||
| // emitStatusUpdated(), but the agent.status.updated handler is | ||
| // only registered in runTurnBody(). Push a one-shot usage_update | ||
| // so the client reflects the compacted context size immediately | ||
| // rather than waiting for the next normal prompt. | ||
| // Best-effort only — a failed status refresh must not turn a | ||
| // successful compaction into a failure. | ||
| try { | ||
| const status = await this.session.getStatus(); | ||
| const update = contextUsageToUsageUpdate( | ||
| this.id, | ||
| status.contextTokens, | ||
| status.maxContextTokens, | ||
| ); | ||
| if (update !== null) { | ||
| this.conn.sessionUpdate(update).catch((err) => { | ||
| log.warn('acp: failed to push usage_update after compaction', { | ||
| sessionId: this.id, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| log.warn('acp: failed to refresh usage after compaction', { | ||
| sessionId: this.id, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| } | ||
| } else { | ||
| await this.emitLocalCommandMessage('Compaction cancelled.'); | ||
| } | ||
|
|
@@ -1097,6 +1161,23 @@ export class AcpSession { | |
| }); | ||
| return; | ||
| } | ||
| if (event.type === 'agent.status.updated') { | ||
| if (!isFromMainAgent(event)) return; | ||
| const update = contextUsageToUsageUpdate( | ||
| sessionId, | ||
| event.contextTokens, | ||
| event.maxContextTokens, | ||
| ); | ||
|
Comment on lines
+1164
to
+1170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the user runs ACP Useful? React with 👍 / 👎.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed — runCompactCommand now pushes a one-shot usage_update via getStatus() after compaction completes. Best-effort with its own try-catch so a status refresh failure never turns a successful compaction into an error. |
||
| if (update !== null) { | ||
| conn.sessionUpdate(update).catch((err) => { | ||
| log.warn('acp: failed to push usage_update', { | ||
| sessionId, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| }); | ||
| } | ||
| return; | ||
| } | ||
| if (event.type === 'tool.call.started') { | ||
| if (!isFromMainAgent(event)) return; | ||
| // Seed the accumulator with the **stringified initial args**. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a model/provider does not advertise a context-window capability, the SDK's v1
getStatus()normalizes that tomaxContextTokens: 0, but this guard treats0as a valid ACP size. ACP clients compute the display percentage fromused / sizeand the usage RFD says agents should skipusage_updatewhen there is no meaningful context window size; after/compactor any status event carrying0, this can pushsize: 0to Zed/JetBrains and produce a bogus/undefined context indicator. Please skipmaxContextTokens <= 0as well.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already fixed — maxContextTokens <= 0 now returns null in that same commit. Also addressed the other review feedback: