diff --git a/apps/code/src/shared/types.ts b/apps/code/src/shared/types.ts index 3250c473f..4620da515 100644 --- a/apps/code/src/shared/types.ts +++ b/apps/code/src/shared/types.ts @@ -14,7 +14,13 @@ export const executionModeSchema = z.enum([ export type ExecutionMode = z.infer; // Effort level schema and type - shared between main and renderer -export const effortLevelSchema = z.enum(["low", "medium", "high", "max"]); +export const effortLevelSchema = z.enum([ + "low", + "medium", + "high", + "xhigh", + "max", +]); export type EffortLevel = z.infer; interface UserBasic { @@ -72,7 +78,7 @@ export interface TaskRun { branch: string | null; runtime_adapter?: "claude" | "codex" | null; model?: string | null; - reasoning_effort?: "low" | "medium" | "high" | "max" | null; + reasoning_effort?: "low" | "medium" | "high" | "xhigh" | "max" | null; stage?: string | null; // Current stage (e.g., 'research', 'plan', 'build') environment?: "local" | "cloud"; status: TaskRunStatus; diff --git a/packages/agent/package.json b/packages/agent/package.json index f6f10d63f..02792c92a 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -99,10 +99,10 @@ "vitest": "^2.1.8" }, "dependencies": { - "@agentclientprotocol/sdk": "0.16.1", + "@agentclientprotocol/sdk": "0.19.0", "ajv": "^8.17.1", "@anthropic-ai/claude-agent-sdk": "0.2.112", - "@anthropic-ai/sdk": "^0.78.0", + "@anthropic-ai/sdk": "0.89.0", "@hono/node-server": "^1.19.9", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", diff --git a/packages/agent/src/adapters/claude/UPSTREAM.md b/packages/agent/src/adapters/claude/UPSTREAM.md index ffb7fe894..ef2de941c 100644 --- a/packages/agent/src/adapters/claude/UPSTREAM.md +++ b/packages/agent/src/adapters/claude/UPSTREAM.md @@ -5,8 +5,8 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth ## Fork Point - **Forked**: v0.10.9, commit `5411e0f4`, Dec 2 2025 -- **Last sync**: v0.22.2, commit `07db59e`, March 25 2026 -- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.76, `@agentclientprotocol/sdk` 0.16.1 +- **Last sync**: v0.30.0, commit `e9dd452`, April 20 2026 +- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.112 (0.2.114 breaks session init, see agentclientprotocol/claude-agent-acp#575), `@agentclientprotocol/sdk` 0.19.0 ## File Mapping @@ -50,11 +50,32 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth | permissionMode | Hardcoded `"default"` | Reads from `meta.permissionMode` | More flexible mode selection | | Session storage | `this.sessions[sessionId]` (multi) | `this.session` (single) | Architectural choice | | bypassPermissions | `updatedPermissions` with `destination: "session"` | No `updatedPermissions` | Different permission persistence | -| Auth methods | Always returns `claude-login` auth method | Returns empty `authMethods` | Auth handled externally | +| Auth methods | `claude-ai-login` + `console-login` | Returns empty `authMethods` | Auth handled externally | +| `auto` mode | Model classifier for auto-approval | Not implemented | PostHog uses its own permission model | +| Session fingerprinting | Implicit teardown on cwd/mcp change | Explicit `refreshSession()` | Caller-initiated is more predictable | +| Shutdown on ACP close | Process exits | No standalone process | Agent is embedded in server | + +## Changes Ported in v0.30.0 Sync + +- **SDK bumps**: claude-agent-sdk 0.2.112 -> 0.2.114, ACP SDK 0.16.1 -> 0.19.0, anthropic SDK -> 0.89.0 +- **Null-safe usage tokens** (v0.29.2): Guard against null usage fields from SDK +- **SettingsManager race fix** (v0.25.0): `initPromise` prevents concurrent `initialize()`/`setCwd()` corruption +- **Malformed settings warning** (v0.25.0): Log warning for non-ENOENT settings file errors +- **Idle state end-of-turn** (v0.23.0): `CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1` + `session_state_changed` idle handler +- **Mid-stream usage updates** (v0.29.1): Fire `usage_update` from `message_start`/`message_delta` stream events +- **Raw SDK message relay** (v0.27.0): `emitRawSDKMessages` on `NewSessionMeta` for opt-in diagnostics +- **Effort level sync** (v0.25.x): `xhigh` level added, `applyFlagSettings` on effort change + +## Skipped in v0.30.0 Sync + +- **`auto` permission mode** (v0.25.0): PostHog has its own permission model +- **Separate auth methods** (v0.25.0): PostHog returns empty authMethods +- **Session fingerprinting** (v0.25.3): PostHog uses explicit `refreshSession()` instead +- **Process exit on ACP close** (v0.27.0): PostHog embeds agent in server ## Next Sync -1. Check upstream changelog since v0.22.2 +1. Check upstream changelog since v0.30.0 2. Diff upstream source against PostHog Code using the file mapping above 3. Port in phases: bug fixes first, then features 4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint` diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index d7d0e30bc..f5051d938 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -99,6 +99,7 @@ import type { BackgroundTerminal, EffortLevel, NewSessionMeta, + SDKMessageFilter, Session, ToolUseCache, } from "./types"; @@ -118,6 +119,19 @@ function sanitizeTitle(text: string): string { return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}…`; } +function shouldEmitRawMessage( + config: boolean | SDKMessageFilter[], + message: { type: string; subtype?: string }, +): boolean { + if (config === true) return true; + if (config === false) return false; + return config.some( + (f) => + f.type === message.type && + (f.subtype === undefined || f.subtype === message.subtype), + ); +} + export interface ClaudeAcpAgentOptions { onProcessSpawned?: (info: ProcessSpawnedInfo) => void; onProcessExited?: (pid: number) => void; @@ -356,6 +370,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent { this.session.promptRunning = true; let handedOff = false; let lastAssistantTotalUsage: number | null = null; + let lastStreamUsage = { + input_tokens: 0, + output_tokens: 0, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }; if (this.session.lastContextWindowSize == null) { this.session.lastContextWindowSize = this.getContextWindowForModel( this.session.modelId ?? "", @@ -401,6 +421,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent { break; } + if ( + this.session.emitRawSDKMessages && + shouldEmitRawMessage(this.session.emitRawSDKMessages, message) + ) { + await this.client.extNotification("_claude/sdkMessage", { + sessionId: params.sessionId, + message: message as Record, + }); + } + switch (message.type) { case "system": if (message.subtype === "compact_boundary") { @@ -420,6 +450,35 @@ export class ClaudeAcpAgent extends BaseAcpAgent { if (message.subtype === "local_command_output") { promptReplayed = true; } + if ( + message.subtype === "session_state_changed" && + (message as Record).state === "idle" + ) { + if (!promptReplayed) { + this.logger.debug("Skipping idle state before prompt replay", { + sessionId: params.sessionId, + }); + break; + } + + const acc = this.session.accumulatedUsage; + const totalUsed = + acc.inputTokens + + acc.outputTokens + + acc.cachedReadTokens + + acc.cachedWriteTokens; + + await this.client.sessionUpdate({ + sessionId: params.sessionId, + update: { + sessionUpdate: "usage_update", + used: totalUsed, + size: lastContextWindowSize, + }, + }); + + return { stopReason: "end_turn" }; + } await handleSystemMessage(message, context); break; @@ -437,15 +496,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent { return { stopReason: "cancelled" }; } - // Accumulate usage from this result + // Accumulate usage from this result (guard against null from SDK) this.session.accumulatedUsage.inputTokens += - message.usage.input_tokens; + message.usage.input_tokens ?? 0; this.session.accumulatedUsage.outputTokens += - message.usage.output_tokens; + message.usage.output_tokens ?? 0; this.session.accumulatedUsage.cachedReadTokens += - message.usage.cache_read_input_tokens; + message.usage.cache_read_input_tokens ?? 0; this.session.accumulatedUsage.cachedWriteTokens += - message.usage.cache_creation_input_tokens; + message.usage.cache_creation_input_tokens ?? 0; // SDK can underreport context window (e.g. 200k for 1M models). // Use SDK value only if it's larger than what gateway reported. @@ -540,9 +599,56 @@ export class ClaudeAcpAgent extends BaseAcpAgent { return { stopReason: result.stopReason ?? "end_turn", usage }; } - case "stream_event": + case "stream_event": { + if ( + message.parent_tool_use_id === null && + (message.event.type === "message_start" || + message.event.type === "message_delta") + ) { + if (message.event.type === "message_start") { + const u = message.event.message.usage; + lastStreamUsage = { + input_tokens: u.input_tokens ?? 0, + output_tokens: u.output_tokens ?? 0, + cache_read_input_tokens: u.cache_read_input_tokens ?? 0, + cache_creation_input_tokens: + u.cache_creation_input_tokens ?? 0, + }; + } else { + const u = message.event.usage; + lastStreamUsage = { + input_tokens: u.input_tokens ?? lastStreamUsage.input_tokens, + output_tokens: u.output_tokens, + cache_read_input_tokens: + u.cache_read_input_tokens ?? + lastStreamUsage.cache_read_input_tokens, + cache_creation_input_tokens: + u.cache_creation_input_tokens ?? + lastStreamUsage.cache_creation_input_tokens, + }; + } + + const nextTotal = + lastStreamUsage.input_tokens + + lastStreamUsage.output_tokens + + lastStreamUsage.cache_read_input_tokens + + lastStreamUsage.cache_creation_input_tokens; + + if (nextTotal !== lastAssistantTotalUsage) { + lastAssistantTotalUsage = nextTotal; + await this.client.sessionUpdate({ + sessionId: params.sessionId, + update: { + sessionUpdate: "usage_update", + used: nextTotal, + size: lastContextWindowSize, + }, + }); + } + } await handleStreamEvent(message, context); break; + } case "user": case "assistant": { @@ -591,16 +697,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent { const usage = ( message.message as unknown as Record ).usage as { - input_tokens: number; - output_tokens: number; - cache_read_input_tokens: number; - cache_creation_input_tokens: number; + input_tokens: number | null; + output_tokens: number | null; + cache_read_input_tokens: number | null; + cache_creation_input_tokens: number | null; }; lastAssistantTotalUsage = - usage.input_tokens + - usage.output_tokens + - usage.cache_read_input_tokens + - usage.cache_creation_input_tokens; + (usage.input_tokens ?? 0) + + (usage.output_tokens ?? 0) + + (usage.cache_read_input_tokens ?? 0) + + (usage.cache_creation_input_tokens ?? 0); await this.client.sessionUpdate({ sessionId: params.sessionId, @@ -884,6 +990,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent { const newEffort = resolvedValue as EffortLevel; this.session.effort = newEffort; this.session.queryOptions.effort = newEffort; + await this.session.query.applyFlagSettings({ + // @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it + effortLevel: newEffort, + }); } this.session.configOptions = this.session.configOptions.map((o) => @@ -1047,6 +1157,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent { promptRunning: false, pendingMessages: new Map(), nextPendingOrder: 0, + emitRawSDKMessages: meta?.claudeCode?.emitRawSDKMessages ?? false, // Custom properties cwd, @@ -1325,6 +1436,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent { if (this.session.effort) { this.session.effort = undefined; this.session.queryOptions.effort = undefined; + void this.session.query.applyFlagSettings({ + effortLevel: undefined, + }); } return; } @@ -1338,6 +1452,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent { if (resolvedValue !== currentValue && this.session.effort) { this.session.effort = resolvedValue as EffortLevel; this.session.queryOptions.effort = resolvedValue as EffortLevel; + void this.session.query.applyFlagSettings({ + // @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it + effortLevel: resolvedValue, + }); } const effortConfig: SessionConfigOption = { diff --git a/packages/agent/src/adapters/claude/hooks.ts b/packages/agent/src/adapters/claude/hooks.ts index 3d704d5ff..eab122d48 100644 --- a/packages/agent/src/adapters/claude/hooks.ts +++ b/packages/agent/src/adapters/claude/hooks.ts @@ -154,9 +154,6 @@ export const createPostToolUseHook = ); delete toolUseCallbacks[toolUseID]; } else { - logger?.error( - `No onPostToolUseHook found for tool use ID: ${toolUseID}`, - ); delete toolUseCallbacks[toolUseID]; } } diff --git a/packages/agent/src/adapters/claude/session/models.ts b/packages/agent/src/adapters/claude/session/models.ts index aaeea33c3..4fae2001d 100644 --- a/packages/agent/src/adapters/claude/session/models.ts +++ b/packages/agent/src/adapters/claude/session/models.ts @@ -30,14 +30,17 @@ const MODELS_WITH_EFFORT = new Set([ "claude-sonnet-4-6", ]); -const MODELS_WITH_MAX_EFFORT = new Set(["claude-opus-4-6", "claude-opus-4-7"]); +const MODELS_WITH_XHIGH_EFFORT = new Set([ + "claude-opus-4-6", + "claude-opus-4-7", +]); export function supportsEffort(modelId: string): boolean { return MODELS_WITH_EFFORT.has(modelId); } -export function supportsMaxEffort(modelId: string): boolean { - return MODELS_WITH_MAX_EFFORT.has(modelId); +export function supportsXhighEffort(modelId: string): boolean { + return MODELS_WITH_XHIGH_EFFORT.has(modelId); } const MODELS_TO_EXCLUDE_MCP_TOOLS = new Set(["claude-haiku-4-5"]); @@ -60,8 +63,11 @@ export function getEffortOptions(modelId: string): EffortOption[] | null { { value: "high", name: "High" }, ]; - if (supportsMaxEffort(modelId)) { - options.push({ value: "max", name: "Max" }); + if (supportsXhighEffort(modelId)) { + options.push( + { value: "xhigh", name: "Extra High" }, + { value: "max", name: "Max" }, + ); } return options; diff --git a/packages/agent/src/adapters/claude/session/options.ts b/packages/agent/src/adapters/claude/session/options.ts index 0f9d80186..ed786e2cd 100644 --- a/packages/agent/src/adapters/claude/session/options.ts +++ b/packages/agent/src/adapters/claude/session/options.ts @@ -105,6 +105,8 @@ function buildEnvironment(): Record { CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true", // Offload all MCP tools by default ENABLE_TOOL_SEARCH: "auto:0", + // Enable idle state as end-of-turn signal (required for SDK 0.2.114+) + CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1", }; } diff --git a/packages/agent/src/adapters/claude/session/settings.ts b/packages/agent/src/adapters/claude/session/settings.ts index 56b992fd1..583f4cee9 100644 --- a/packages/agent/src/adapters/claude/session/settings.ts +++ b/packages/agent/src/adapters/claude/session/settings.ts @@ -132,7 +132,13 @@ async function loadSettingsFile( try { const content = await fs.promises.readFile(filePath, "utf-8"); return JSON.parse(content) as ClaudeCodeSettings; - } catch { + } catch (error) { + if (error instanceof Error && "code" in error && error.code === "ENOENT") { + return {}; + } + process.stderr.write( + `[SettingsManager] Failed to load settings from ${filePath}: ${error}\n`, + ); return {}; } } @@ -179,17 +185,20 @@ export class SettingsManager { private enterpriseSettings: ClaudeCodeSettings = {}; private mergedSettings: ClaudeCodeSettings = {}; private initialized = false; + private initPromise: Promise | null = null; constructor(cwd: string) { this.cwd = cwd; } async initialize(): Promise { - if (this.initialized) { - return; - } - await this.loadAllSettings(); - this.initialized = true; + if (this.initialized) return; + if (this.initPromise) return this.initPromise; + this.initPromise = this.loadAllSettings().then(() => { + this.initialized = true; + this.initPromise = null; + }); + return this.initPromise; } private getUserSettingsPath(): string { @@ -311,9 +320,8 @@ export class SettingsManager { } async setCwd(cwd: string): Promise { - if (this.cwd === cwd) { - return; - } + if (this.cwd === cwd) return; + if (this.initPromise) await this.initPromise; this.dispose(); this.cwd = cwd; this.initialized = false; diff --git a/packages/agent/src/adapters/claude/types.ts b/packages/agent/src/adapters/claude/types.ts index 31e18d235..354b43353 100644 --- a/packages/agent/src/adapters/claude/types.ts +++ b/packages/agent/src/adapters/claude/types.ts @@ -13,7 +13,7 @@ import type { BaseSession } from "../base-acp-agent"; import type { SettingsManager } from "./session/settings"; import type { CodeExecutionMode } from "./tools"; -export type EffortLevel = "low" | "medium" | "high" | "max"; +export type EffortLevel = "low" | "medium" | "high" | "xhigh" | "max"; export type AccumulatedUsage = { inputTokens: number; @@ -62,6 +62,7 @@ export type Session = BaseSession & { promptRunning: boolean; pendingMessages: Map; nextPendingOrder: number; + emitRawSDKMessages: boolean | SDKMessageFilter[]; }; export type ToolUseCache = { @@ -99,6 +100,11 @@ export type ToolUpdateMeta = { terminal_exit?: TerminalExit; }; +export type SDKMessageFilter = { + type: string; + subtype?: string; +}; + export type NewSessionMeta = { taskRunId?: string; disableBuiltInTools?: boolean; @@ -113,5 +119,6 @@ export type NewSessionMeta = { jsonSchema?: Record | null; claudeCode?: { options?: Options; + emitRawSDKMessages?: boolean | SDKMessageFilter[]; }; }; diff --git a/packages/agent/src/adapters/reasoning-effort.ts b/packages/agent/src/adapters/reasoning-effort.ts index 29f0798ad..2c031d024 100644 --- a/packages/agent/src/adapters/reasoning-effort.ts +++ b/packages/agent/src/adapters/reasoning-effort.ts @@ -3,7 +3,12 @@ import { getReasoningEffortOptions as getCodexReasoningEffortOptions } from "./c export type RuntimeAdapter = "claude" | "codex"; -export type SupportedReasoningEffort = "low" | "medium" | "high" | "max"; +export type SupportedReasoningEffort = + | "low" + | "medium" + | "high" + | "xhigh" + | "max"; export interface ReasoningEffortOption { value: SupportedReasoningEffort; diff --git a/packages/agent/src/server/bin.ts b/packages/agent/src/server/bin.ts index 02c6157ed..d72a91ba3 100644 --- a/packages/agent/src/server/bin.ts +++ b/packages/agent/src/server/bin.ts @@ -30,7 +30,7 @@ const envSchema = z.object({ POSTHOG_CODE_RUNTIME_ADAPTER: z.enum(["claude", "codex"]).optional(), POSTHOG_CODE_MODEL: z.string().optional(), POSTHOG_CODE_REASONING_EFFORT: z - .enum(["low", "medium", "high", "max"]) + .enum(["low", "medium", "high", "xhigh", "max"]) .optional(), }); diff --git a/packages/agent/src/server/types.ts b/packages/agent/src/server/types.ts index f22349fff..10cf96fc7 100644 --- a/packages/agent/src/server/types.ts +++ b/packages/agent/src/server/types.ts @@ -26,5 +26,5 @@ export interface AgentServerConfig { allowedDomains?: string[]; runtimeAdapter?: "claude" | "codex"; model?: string; - reasoningEffort?: "low" | "medium" | "high" | "max"; + reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max"; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f0a160c7c..3947182d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -631,14 +631,14 @@ importers: packages/agent: dependencies: '@agentclientprotocol/sdk': - specifier: 0.16.1 - version: 0.16.1(zod@4.3.6) + specifier: 0.19.0 + version: 0.19.0(zod@4.3.6) '@anthropic-ai/claude-agent-sdk': specifier: 0.2.112 version: 0.2.112(zod@4.3.6) '@anthropic-ai/sdk': - specifier: ^0.78.0 - version: 0.78.0(zod@4.3.6) + specifier: 0.89.0 + version: 0.89.0(zod@4.3.6) '@hono/node-server': specifier: ^1.19.9 version: 1.19.9(hono@4.11.7) @@ -832,6 +832,11 @@ packages: peerDependencies: zod: ^3.25.0 || ^4.0.0 + '@agentclientprotocol/sdk@0.19.0': + resolution: {integrity: sha512-U9I8ws9WTOk6jCBAWpXefGSDgVXn14/kV6HFzwWGcstQ02mOQgClMAROHmoIn9GqZbDBDEOkdIbP4P4TEMQdug==} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -846,8 +851,8 @@ packages: peerDependencies: zod: ^4.0.0 - '@anthropic-ai/sdk@0.78.0': - resolution: {integrity: sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w==} + '@anthropic-ai/sdk@0.81.0': + resolution: {integrity: sha512-D4K5PvEV6wPiRtVlVsJHIUhHAmOZ6IT/I9rKlTf84gR7GyyAurPJK7z9BOf/AZqC5d1DhYQGJNKRmV+q8dGhgw==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -855,8 +860,8 @@ packages: zod: optional: true - '@anthropic-ai/sdk@0.81.0': - resolution: {integrity: sha512-D4K5PvEV6wPiRtVlVsJHIUhHAmOZ6IT/I9rKlTf84gR7GyyAurPJK7z9BOf/AZqC5d1DhYQGJNKRmV+q8dGhgw==} + '@anthropic-ai/sdk@0.89.0': + resolution: {integrity: sha512-nyGau0zex62EpU91hsHa0zod973YEoiMgzWZ9hC55WdiOLrE4AGpcg4wXI7lFqtvMLqMcLfewQU9sHgQB6psow==} hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -11768,6 +11773,10 @@ snapshots: dependencies: zod: 4.3.6 + '@agentclientprotocol/sdk@0.19.0(zod@4.3.6)': + dependencies: + zod: 4.3.6 + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -11794,13 +11803,13 @@ snapshots: - '@cfworker/json-schema' - supports-color - '@anthropic-ai/sdk@0.78.0(zod@4.3.6)': + '@anthropic-ai/sdk@0.81.0(zod@4.3.6)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: zod: 4.3.6 - '@anthropic-ai/sdk@0.81.0(zod@4.3.6)': + '@anthropic-ai/sdk@0.89.0(zod@4.3.6)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: