mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
fix: keep the usage-limit error when it arrives after compaction (#1377)
Fixes #1370 After a compaction in the same turn, the synthetic usage-limit frame is dropped by the stale-frame suppression before its failure details are read. The prompt then ends with a generic internal error instead of the quota error, so clients can't show the limit UI. The suppression branch now records the usage-limit title and error before it skips the frame. Only recognized usage-limit frames are read there, so a stale replayed frame still can't overwrite the failure of the current turn. Added a test for a usage-limit frame after a completed compaction, for both the legacy `errorKind` and the typed session failure, plus a stale frame arriving after it. It fails without the change. `npm run check`, `npm run build` and `npm run test:run` pass.
This commit is contained in:
@@ -8047,6 +8047,13 @@ export class ClaudeAcpAgent {
|
|||||||
message.message.model === "<synthetic>" &&
|
message.message.model === "<synthetic>" &&
|
||||||
compaction.hasDeliveredOutput
|
compaction.hasDeliveredOutput
|
||||||
) {
|
) {
|
||||||
|
if (isSyntheticUsageLimitMessage(message.message)) {
|
||||||
|
lastAssistantWasUsageLimit = true;
|
||||||
|
lastAssistantFailureTitle = assistantMessageText(message.message);
|
||||||
|
if (message.error) {
|
||||||
|
lastAssistantError = message.error;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12631,6 +12631,56 @@ describe("stop reason propagation", () => {
|
|||||||
expect((err as { data: unknown }).data).toEqual({ errorKind: "rate_limit" });
|
expect((err as { data: unknown }).data).toEqual({ errorKind: "rate_limit" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(
|
||||||
|
[false, true].flatMap((typedFailures) =>
|
||||||
|
[undefined, "authentication_failed" as const].map((staleError) => ({
|
||||||
|
typedFailures,
|
||||||
|
staleError,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)(
|
||||||
|
"preserves a usage-limit failure after compaction (typed: $typedFailures, stale error: $staleError)",
|
||||||
|
async ({ typedFailures, staleError }) => {
|
||||||
|
const agent = createMockAgent();
|
||||||
|
if (typedFailures) await initializeClient(agent, airSessionFailureCapabilities);
|
||||||
|
const staleFrame = createAssistantError(staleError, "stale compaction output");
|
||||||
|
staleFrame.message.model = "<synthetic>";
|
||||||
|
injectSession(agent, [
|
||||||
|
{
|
||||||
|
type: "system",
|
||||||
|
subtype: "status",
|
||||||
|
status: null,
|
||||||
|
compact_result: "success",
|
||||||
|
uuid: "compact-completed",
|
||||||
|
session_id: "test-session",
|
||||||
|
},
|
||||||
|
createUsageLimitAssistantError(),
|
||||||
|
staleFrame,
|
||||||
|
createResultMessage({
|
||||||
|
subtype: "success",
|
||||||
|
stop_reason: "end_turn",
|
||||||
|
is_error: true,
|
||||||
|
result: "private provider detail",
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const prompt = agent.prompt({
|
||||||
|
sessionId: "test-session",
|
||||||
|
prompt: [{ type: "text", text: "test" }],
|
||||||
|
});
|
||||||
|
if (typedFailures) {
|
||||||
|
expect(sessionFailureFromResponse(await prompt)).toMatchObject({
|
||||||
|
category: "limit",
|
||||||
|
actions: [],
|
||||||
|
title:
|
||||||
|
"You've hit your individual spend limit · run /usage-credits to ask your admin for a higher limit",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await expect(prompt).rejects.toMatchObject({ data: { errorKind: "rate_limit" } });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("omits errorKind data when no SDKAssistantMessage.error was observed", async () => {
|
it("omits errorKind data when no SDKAssistantMessage.error was observed", async () => {
|
||||||
const agent = createMockAgent();
|
const agent = createMockAgent();
|
||||||
injectSession(agent, [
|
injectSession(agent, [
|
||||||
|
|||||||
Reference in New Issue
Block a user