mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 10:42:45 +00:00
fix(acp): surface context-window usage from the ACP usage_update notification (#4848)
* fix(acp): show context-window usage for generic ACP agents
Generic ACP providers never populated Paseo's context meter: the standard ACP
`usage_update` notification was discarded by `handleUsageUpdate` (`void
update`), while the meter only reads `lastUsage.contextWindowMaxTokens` /
`contextWindowUsedTokens`, which `usage_updated` stream events write.
Forward `size` and `used` on a `usage_updated` event for the active turn,
guarded by the same validity rules the meter applies so a provider cannot push
a window the meter rejects, and preserve the turn usage already recorded.
Verified with vitest acp-agent.test.ts (106 passed), the server typecheck, and
an isolated daemon run where a dsh agent reported
`lastUsage={contextWindowMaxTokens:1000000,contextWindowUsedTokens:13623}`.
* fix(acp): emit context-window usage only with both meter fields
Require a valid size and used together so a partial usage_update cannot
wipe the composer meter. Drive the tests through sessionUpdate instead
of the private translator.
This commit is contained in:
@@ -825,6 +825,46 @@ describe("mapACPUsage", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("ACP context-window usage", () => {
|
||||
async function emitUsageUpdate(update: {
|
||||
used: number;
|
||||
size: number;
|
||||
}): Promise<{ events: unknown[] }> {
|
||||
const session = createSessionWithConfig({ provider: "dsh" });
|
||||
asInternals<ACPSessionInternals>(session).sessionId = "session-1";
|
||||
const events: unknown[] = [];
|
||||
session.subscribe((event) => {
|
||||
if (event.type === "usage_updated") events.push(event);
|
||||
});
|
||||
await session.sessionUpdate({
|
||||
sessionId: "session-1",
|
||||
update: { sessionUpdate: "usage_update", used: update.used, size: update.size },
|
||||
});
|
||||
return { events };
|
||||
}
|
||||
|
||||
test("forwards usage_update as context-window usage state", async () => {
|
||||
const { events } = await emitUsageUpdate({ used: 13_759, size: 1_000_000 });
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
type: "usage_updated",
|
||||
provider: "dsh",
|
||||
usage: { contextWindowMaxTokens: 1_000_000, contextWindowUsedTokens: 13_759 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("emits nothing when size and used cannot both drive a meter", async () => {
|
||||
await expect(
|
||||
emitUsageUpdate({ used: -1, size: 0 }).then((result) => result.events),
|
||||
).resolves.toEqual([]);
|
||||
await expect(
|
||||
emitUsageUpdate({ used: 13_759, size: 0 }).then((result) => result.events),
|
||||
).resolves.toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deriveModesFromACP", () => {
|
||||
test("prefers explicit ACP mode state", () => {
|
||||
const result = deriveModesFromACP(
|
||||
|
||||
@@ -3178,8 +3178,29 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
}
|
||||
}
|
||||
|
||||
// ACP reports context occupancy out of band from the prompt response, so the
|
||||
// composer's context meter cannot read it from `turn_completed`. Forward it as
|
||||
// the same `usage_updated` snapshot the native adapters emit, preserving the
|
||||
// turn usage this session already recorded. The guards match the meter's own
|
||||
// validity rules, so a provider cannot push a window the meter would reject.
|
||||
private handleUsageUpdate(update: UsageUpdate): void {
|
||||
void update;
|
||||
const contextWindowMaxTokens =
|
||||
Number.isFinite(update.size) && update.size > 0 ? update.size : undefined;
|
||||
const contextWindowUsedTokens =
|
||||
Number.isFinite(update.used) && update.used >= 0 ? update.used : undefined;
|
||||
if (contextWindowMaxTokens === undefined || contextWindowUsedTokens === undefined) {
|
||||
return;
|
||||
}
|
||||
this.pushEvent({
|
||||
type: "usage_updated",
|
||||
provider: this.provider,
|
||||
usage: {
|
||||
...this.currentTurnUsage,
|
||||
contextWindowMaxTokens,
|
||||
contextWindowUsedTokens,
|
||||
},
|
||||
turnId: this.activeForegroundTurnId ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
private handlePromptResponse(response: PromptResponse, turnId: string): void {
|
||||
|
||||
Reference in New Issue
Block a user