diff --git a/src/acp-agent.ts b/src/acp-agent.ts index 1ad57477..24f85cd5 100644 --- a/src/acp-agent.ts +++ b/src/acp-agent.ts @@ -486,7 +486,6 @@ export class ClaudeAcpAgent implements Agent { cachedReadTokens: 0, cachedWriteTokens: 0, }; - let lastAssistantTotalUsage: number | null = null; let lastAssistantModel: string | null = null; let lastContextWindowSize: number = 200000; @@ -602,9 +601,26 @@ export class ClaudeAcpAgent implements Agent { case "task_notification": case "task_progress": case "elicitation_complete": - case "api_retry": - // Todo: process via status api: https://docs.claude.com/en/docs/claude-code/hooks#hook-output break; + case "api_retry": { + // Forward API retry events via extNotification so clients can observe + // transient failures including HTTP status code and typed error category. + const retryMsg = message as any; + this.logger.error( + `API retry: attempt=${retryMsg.attempt}/${retryMsg.max_retries}, ` + + `httpStatus=${retryMsg.error_status}, error=${retryMsg.error}, ` + + `delay=${retryMsg.retry_delay_ms}ms`, + ); + await this.client.extNotification("_claude/api-retry", { + sessionId: params.sessionId, + httpStatus: retryMsg.error_status ?? null, + errorType: retryMsg.error ?? "unknown", + attempt: retryMsg.attempt ?? 0, + maxRetries: retryMsg.max_retries ?? 0, + retryDelayMs: retryMsg.retry_delay_ms ?? 0, + }); + break; + } default: unreachable(message, this.logger); break; @@ -835,11 +851,57 @@ export class ClaudeAcpAgent implements Agent { } break; } - case "tool_progress": - case "tool_use_summary": + case "tool_progress": { + // Forward tool execution progress via extNotification so clients can + // show elapsed time for long-running tools without polluting tool output. + const toolUseId = "tool_use_id" in message ? (message as any).tool_use_id : undefined; + if (toolUseId) { + await this.client.extNotification("_claude/tool-progress", { + sessionId: params.sessionId, + toolUseId, + toolName: (message as any).tool_name ?? null, + elapsedTimeSeconds: (message as any).elapsed_time_seconds ?? 0, + }); + } + break; + } + case "tool_use_summary": { + // Forward collapsed tool-use summaries via extNotification so clients + // can display a high-level overview of tool activity. + const summary = "summary" in message ? (message as any).summary : undefined; + if (summary) { + await this.client.extNotification("_claude/tool-use-summary", { + sessionId: params.sessionId, + summary, + precedingToolUseIds: + "preceding_tool_use_ids" in message + ? (message as any).preceding_tool_use_ids ?? [] + : [], + }); + } + break; + } + case "rate_limit_event": { + // Forward rate limit info via extNotification so clients can detect + // approaching limits and rejections without parsing error message strings. + const info = "rate_limit_info" in message ? (message as any).rate_limit_info : undefined; + if (info) { + await this.client.extNotification("_claude/rate-limit", { + sessionId: params.sessionId, + status: info.status ?? "unknown", + resetsAt: info.resetsAt ?? null, + rateLimitType: info.rateLimitType ?? null, + utilization: info.utilization ?? null, + overageStatus: info.overageStatus ?? null, + overageDisabledReason: info.overageDisabledReason ?? null, + isUsingOverage: info.isUsingOverage ?? false, + surpassedThreshold: info.surpassedThreshold ?? null, + }); + } + break; + } case "auth_status": case "prompt_suggestion": - case "rate_limit_event": break; default: unreachable(message); diff --git a/src/tests/acp-agent.test.ts b/src/tests/acp-agent.test.ts index 6d77aad9..4e12b4d8 100644 --- a/src/tests/acp-agent.test.ts +++ b/src/tests/acp-agent.test.ts @@ -1337,6 +1337,7 @@ describe("stop reason propagation", () => { availableModels: [], }, settingsManager: { dispose: vi.fn() } as any, + accumulatedUsage: { inputTokens: 0, outputTokens: 0, @@ -1476,6 +1477,7 @@ describe("stop reason propagation", () => { availableModels: [], }, settingsManager: { dispose: vi.fn() } as any, + accumulatedUsage: { inputTokens: 0, outputTokens: 0, @@ -1549,6 +1551,7 @@ describe("session/close", () => { availableModels: [], }, settingsManager: { dispose: vi.fn() } as any, + accumulatedUsage: { inputTokens: 0, outputTokens: 0, @@ -1719,6 +1722,7 @@ describe("usage_update computation", () => { availableModels: [], }, settingsManager: {} as any, + accumulatedUsage: { inputTokens: 0, outputTokens: 0,