/** * ClaudeAdapterLive - Scoped live implementation for the Claude Agent provider adapter. * * Wraps `@anthropic-ai/claude-agent-sdk` query sessions behind the generic * provider adapter contract and emits canonical runtime events. * * @module ClaudeAdapterLive */import { type CanUseTool, query, type Options as ClaudeQueryOptions, type PermissionMode, type PermissionResult, type PermissionUpdate, type SDKMessage, type SDKResultMessage, type SettingSource, type SDKUserMessage, type ModelUsage,} from "@anthropic-ai/claude-agent-sdk";import { parseCliArgs } from "@t3tools/shared/cliArgs";import { ApprovalRequestId, type CanonicalItemType, type CanonicalRequestType, type ClaudeSettings, EventId, type ProviderApprovalDecision, ProviderDriverKind, ProviderInstanceId, ProviderItemId, type ProviderRuntimeEvent, type ProviderRuntimeTurnStatus, type ProviderSendTurnInput, type ProviderSession, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, type RuntimeContentStreamKind, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ThreadId, TurnId, type UserInputQuestion,} from "@t3tools/contracts";import { applyClaudePromptEffortPrefix, getModelSelectionBooleanOptionValue, getModelSelectionStringOptionValue, getProviderOptionDescriptors, resolvePromptInjectedEffort,} from "@t3tools/shared/model";import * as Cause from "effect/Cause";import * as Crypto from "effect/Crypto";import * as DateTime from "effect/DateTime";import * as Deferred from "effect/Deferred";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Fiber from "effect/Fiber";import * as Path from "effect/Path";import * as Queue from "effect/Queue";import * as Ref from "effect/Ref";import * as Schema from "effect/Schema";import * as Stream from "effect/Stream"; import { resolveAttachmentPath } from "../../attachmentStore.ts";import { ServerConfig } from "../../config.ts";import { makeClaudeEnvironment } from "../Drivers/ClaudeHome.ts";import { getClaudeModelCapabilities, isClaudeUltracodeEffort, normalizeClaudeCliEffort, resolveClaudeApiModelId, resolveClaudeEffort,} from "./ClaudeProvider.ts";import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError,} from "../Errors.ts";import { type ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts";import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts";const encodeUnknownJsonStringExit = Schema.encodeUnknownExit(Schema.UnknownFromJsonString);const decodeUnknownJsonStringExit = Schema.decodeUnknownExit(Schema.UnknownFromJsonString); const PROVIDER = ProviderDriverKind.make("claudeAgent");type ClaudeTextStreamKind = Extract<RuntimeContentStreamKind, "assistant_text" | "reasoning_text">;type ClaudeToolResultStreamKind = Extract< RuntimeContentStreamKind, "command_output" | "file_change_output">;type ClaudeSdkEffort = NonNullable<ClaudeQueryOptions["effort"]>; function encodeJsonStringForDiagnostics(input: unknown): string | undefined { const result = encodeUnknownJsonStringExit(input); return Exit.isSuccess(result) ? result.value : undefined;} type PromptQueueItem = | { readonly type: "message"; readonly message: SDKUserMessage; } | { readonly type: "terminate"; }; interface ClaudeResumeState { readonly threadId?: ThreadId; readonly resume?: string; readonly resumeSessionAt?: string; readonly turnCount?: number;} interface ClaudeTurnState { readonly turnId: TurnId; readonly startedAt: string; readonly items: Array<unknown>; readonly assistantTextBlocks: Map<number, AssistantTextBlockState>; readonly assistantTextBlockOrder: Array<AssistantTextBlockState>; readonly capturedProposedPlanKeys: Set<string>; nextSyntheticAssistantBlockIndex: number;} interface AssistantTextBlockState { readonly itemId: string; readonly blockIndex: number; emittedTextDelta: boolean; fallbackText: string; streamClosed: boolean; completionEmitted: boolean;} interface PendingApproval { readonly requestType: CanonicalRequestType; readonly detail?: string; readonly suggestions?: ReadonlyArray<PermissionUpdate>; readonly decision: Deferred.Deferred<ProviderApprovalDecision>;} interface PendingUserInput { readonly questions: ReadonlyArray<UserInputQuestion>; readonly answers: Deferred.Deferred<ProviderUserInputAnswers>;} interface ToolInFlight { readonly itemId: string; readonly itemType: CanonicalItemType; readonly toolName: string; readonly title: string; readonly detail?: string; readonly input: Record<string, unknown>; readonly partialInputJson: string; readonly lastEmittedInputFingerprint?: string;} interface ClaudeSessionContext { session: ProviderSession; readonly promptQueue: Queue.Queue<PromptQueueItem>; readonly query: ClaudeQueryRuntime; streamFiber: Fiber.Fiber<void, Error> | undefined; readonly startedAt: string; readonly basePermissionMode: PermissionMode | undefined; currentApiModelId: string | undefined; resumeSessionId: string | undefined; readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>; readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>; readonly turns: Array<{ id: TurnId; items: Array<unknown>; }>; readonly inFlightTools: Map<number, ToolInFlight>; turnState: ClaudeTurnState | undefined; lastKnownContextWindow: number | undefined; lastKnownTokenUsage: ThreadTokenUsageSnapshot | undefined; lastAssistantUuid: string | undefined; lastThreadStartedId: string | undefined; stopped: boolean;} interface ClaudeQueryRuntime extends AsyncIterable<SDKMessage> { readonly interrupt: () => Promise<void>; readonly setModel: (model?: string) => Promise<void>; readonly setPermissionMode: (mode: PermissionMode) => Promise<void>; readonly setMaxThinkingTokens: (maxThinkingTokens: number | null) => Promise<void>; readonly close: () => void;} export interface ClaudeAdapterLiveOptions { readonly instanceId?: ProviderInstanceId; readonly environment?: NodeJS.ProcessEnv; readonly createQuery?: (input: { readonly prompt: AsyncIterable<SDKUserMessage>; readonly options: ClaudeQueryOptions; }) => ClaudeQueryRuntime; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger;} function isUuid(value: string): boolean { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);} function isSyntheticClaudeThreadId(value: string): boolean { return value.startsWith("claude-thread-");} function hasDurableClaudeSessionId(message: SDKMessage): boolean { if (message.type !== "system") { return true; } return ( message.subtype !== "hook_started" && message.subtype !== "hook_progress" && message.subtype !== "hook_response" );} function toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback;} function toProcessError( cause: unknown, fallback: string, threadId: ThreadId,): ProviderAdapterProcessError { return new ProviderAdapterProcessError({ provider: PROVIDER, threadId, detail: toMessage(cause, fallback), cause, });} function normalizeClaudeStreamMessages( cause: Cause.Cause<{ readonly message: string }>,): ReadonlyArray<string> { const errors: Array<string> = []; for (const error of Cause.prettyErrors(cause)) { const message = error.message.trim(); if (message.length > 0) { errors.push(message); } } if (errors.length > 0) { return errors; } const squashed = toMessage(Cause.squash(cause), "").trim(); return squashed.length > 0 ? [squashed] : [];} function getEffectiveClaudeAgentEffort( effort: string | null | undefined, model: string | null | undefined,): ClaudeSdkEffort | null { const normalized = normalizeClaudeCliEffort(effort, model); return normalized ? (normalized as ClaudeSdkEffort) : null;} function isClaudeInterruptedMessage(message: string): boolean { const normalized = message.toLowerCase(); return ( normalized.includes("all fibers interrupted without error") || normalized.includes("request was aborted") || normalized.includes("interrupted by user") );} function isClaudeInterruptedCause(cause: Cause.Cause<{ readonly message: string }>): boolean { return ( Cause.hasInterruptsOnly(cause) || normalizeClaudeStreamMessages(cause).some(isClaudeInterruptedMessage) );} function messageFromClaudeStreamCause( cause: Cause.Cause<{ readonly message: string }>, fallback: string,): string { return normalizeClaudeStreamMessages(cause)[0] ?? fallback;} function interruptionMessageFromClaudeCause( cause: Cause.Cause<{ readonly message: string }>,): string { const message = messageFromClaudeStreamCause(cause, "Claude runtime interrupted."); return isClaudeInterruptedMessage(message) ? "Claude runtime interrupted." : message;} function resultErrorsText(result: SDKResultMessage): string { return "errors" in result && Array.isArray(result.errors) ? result.errors.join(" ").toLowerCase() : "";} function isInterruptedResult(result: SDKResultMessage): boolean { const errors = resultErrorsText(result); if (errors.includes("interrupt")) { return true; } return ( result.subtype === "error_during_execution" && result.is_error === false && (errors.includes("request was aborted") || errors.includes("interrupted by user") || errors.includes("aborted")) );} function asRuntimeItemId(value: string): RuntimeItemId { return RuntimeItemId.make(value);} function maxClaudeContextWindowFromModelUsage( modelUsage: Record<string, ModelUsage> | undefined,): number | undefined { if (!modelUsage) return undefined; let maxContextWindow: number | undefined; for (const value of Object.values(modelUsage)) { const contextWindow = value.contextWindow; maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow); } return maxContextWindow;} function normalizeClaudeTokenUsage( value: unknown, contextWindow?: number,): ThreadTokenUsageSnapshot | undefined { if (!value || typeof value !== "object") { return undefined; } const usage = value as Record<string, unknown>; const inputTokens = (typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) ? usage.input_tokens : 0) + (typeof usage.cache_creation_input_tokens === "number" && Number.isFinite(usage.cache_creation_input_tokens) ? usage.cache_creation_input_tokens : 0) + (typeof usage.cache_read_input_tokens === "number" && Number.isFinite(usage.cache_read_input_tokens) ? usage.cache_read_input_tokens : 0); const outputTokens = typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) ? usage.output_tokens : 0; const derivedTotalProcessedTokens = inputTokens + outputTokens; const totalProcessedTokens = (typeof usage.total_tokens === "number" && Number.isFinite(usage.total_tokens) ? usage.total_tokens : undefined) ?? (derivedTotalProcessedTokens > 0 ? derivedTotalProcessedTokens : undefined); if (totalProcessedTokens === undefined || totalProcessedTokens <= 0) { return undefined; } const maxTokens = typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : undefined; const usedTokens = maxTokens !== undefined ? Math.min(totalProcessedTokens, maxTokens) : totalProcessedTokens; return { usedTokens, lastUsedTokens: usedTokens, ...(totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}), ...(inputTokens > 0 ? { inputTokens } : {}), ...(outputTokens > 0 ? { outputTokens } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), ...(typeof usage.tool_uses === "number" && Number.isFinite(usage.tool_uses) ? { toolUses: usage.tool_uses } : {}), ...(typeof usage.duration_ms === "number" && Number.isFinite(usage.duration_ms) ? { durationMs: usage.duration_ms } : {}), };} function asCanonicalTurnId(value: TurnId): TurnId { return value;} function asRuntimeRequestId(value: ApprovalRequestId): RuntimeRequestId { return RuntimeRequestId.make(value);} function readClaudeResumeState(resumeCursor: unknown): ClaudeResumeState | undefined { if (!resumeCursor || typeof resumeCursor !== "object") { return undefined; } const cursor = resumeCursor as { threadId?: unknown; resume?: unknown; sessionId?: unknown; resumeSessionAt?: unknown; turnCount?: unknown; }; const threadIdCandidate = typeof cursor.threadId === "string" ? cursor.threadId : undefined; const threadId = threadIdCandidate && !isSyntheticClaudeThreadId(threadIdCandidate) ? ThreadId.make(threadIdCandidate) : undefined; const resumeCandidate = typeof cursor.resume === "string" ? cursor.resume : typeof cursor.sessionId === "string" ? cursor.sessionId : undefined; const resume = resumeCandidate && isUuid(resumeCandidate) ? resumeCandidate : undefined; const resumeSessionAt = typeof cursor.resumeSessionAt === "string" ? cursor.resumeSessionAt : undefined; const turnCountValue = typeof cursor.turnCount === "number" ? cursor.turnCount : undefined; return { ...(threadId ? { threadId } : {}), ...(resume ? { resume } : {}), ...(resumeSessionAt ? { resumeSessionAt } : {}), ...(turnCountValue !== undefined && Number.isInteger(turnCountValue) && turnCountValue >= 0 ? { turnCount: turnCountValue } : {}), };} function classifyToolItemType(toolName: string): CanonicalItemType { const normalized = toolName.toLowerCase(); if (normalized.includes("agent")) { return "collab_agent_tool_call"; } if ( normalized === "task" || normalized === "agent" || normalized.includes("subagent") || normalized.includes("sub-agent") ) { return "collab_agent_tool_call"; } if ( normalized.includes("bash") || normalized.includes("command") || normalized.includes("shell") || normalized.includes("terminal") ) { return "command_execution"; } if ( normalized.includes("edit") || normalized.includes("write") || normalized.includes("file") || normalized.includes("patch") || normalized.includes("replace") || normalized.includes("create") || normalized.includes("delete") ) { return "file_change"; } if (normalized.includes("mcp")) { return "mcp_tool_call"; } if (normalized.includes("websearch") || normalized.includes("web search")) { return "web_search"; } if (normalized.includes("image")) { return "image_view"; } return "dynamic_tool_call";} function isReadOnlyToolName(toolName: string): boolean { const normalized = toolName.toLowerCase(); return ( normalized === "read" || normalized.includes("read file") || normalized.includes("view") || normalized.includes("grep") || normalized.includes("glob") || normalized.includes("search") );} function classifyRequestType(toolName: string): CanonicalRequestType { if (isReadOnlyToolName(toolName)) {Lines 1–500 of 3335. Load more
/** * ClaudeAdapterLive - Scoped live implementation for the Claude Agent provider adapter. * * Wraps `@anthropic-ai/claude-agent-sdk` query sessions behind the generic * provider adapter contract and emits canonical runtime events. * * @module ClaudeAdapterLive */ import { type CanUseTool, query, type Options as ClaudeQueryOptions, type PermissionMode, type PermissionResult, type PermissionUpdate, type SDKMessage, type SDKResultMessage, type SettingSource, type SDKUserMessage, type ModelUsage, } from "@anthropic-ai/claude-agent-sdk"; import { parseCliArgs } from "@t3tools/shared/cliArgs"; import { ApprovalRequestId, type CanonicalItemType, type CanonicalRequestType, type ClaudeSettings, EventId, type ProviderApprovalDecision, ProviderDriverKind, ProviderInstanceId, ProviderItemId, type ProviderRuntimeEvent, type ProviderRuntimeTurnStatus, type ProviderSendTurnInput, type ProviderSession, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, type RuntimeContentStreamKind, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ThreadId, TurnId, type UserInputQuestion, } from "@t3tools/contracts"; import { applyClaudePromptEffortPrefix, getModelSelectionBooleanOptionValue, getModelSelectionStringOptionValue, getProviderOptionDescriptors, resolvePromptInjectedEffort, } from "@t3tools/shared/model"; import * as Cause from "effect/Cause"; import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as FileSystem from "effect/FileSystem"; import * as Fiber from "effect/Fiber"; import * as Path from "effect/Path"; import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; import { makeClaudeEnvironment } from "../Drivers/ClaudeHome.ts"; import { getClaudeModelCapabilities, isClaudeUltracodeEffort, normalizeClaudeCliEffort, resolveClaudeApiModelId, resolveClaudeEffort, } from "./ClaudeProvider.ts"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError, } from "../Errors.ts"; import { type ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; const encodeUnknownJsonStringExit = Schema.encodeUnknownExit(Schema.UnknownFromJsonString); const decodeUnknownJsonStringExit = Schema.decodeUnknownExit(Schema.UnknownFromJsonString); const PROVIDER = ProviderDriverKind.make("claudeAgent"); type ClaudeTextStreamKind = Extract<RuntimeContentStreamKind, "assistant_text" | "reasoning_text">; type ClaudeToolResultStreamKind = Extract< RuntimeContentStreamKind, "command_output" | "file_change_output" >; type ClaudeSdkEffort = NonNullable<ClaudeQueryOptions["effort"]>; function encodeJsonStringForDiagnostics(input: unknown): string | undefined { const result = encodeUnknownJsonStringExit(input); return Exit.isSuccess(result) ? result.value : undefined; } type PromptQueueItem = | { readonly type: "message"; readonly message: SDKUserMessage; } | { readonly type: "terminate"; }; interface ClaudeResumeState { readonly threadId?: ThreadId; readonly resume?: string; readonly resumeSessionAt?: string; readonly turnCount?: number; } interface ClaudeTurnState { readonly turnId: TurnId; readonly startedAt: string; readonly items: Array<unknown>; readonly assistantTextBlocks: Map<number, AssistantTextBlockState>; readonly assistantTextBlockOrder: Array<AssistantTextBlockState>; readonly capturedProposedPlanKeys: Set<string>; nextSyntheticAssistantBlockIndex: number; } interface AssistantTextBlockState { readonly itemId: string; readonly blockIndex: number; emittedTextDelta: boolean; fallbackText: string; streamClosed: boolean; completionEmitted: boolean; } interface PendingApproval { readonly requestType: CanonicalRequestType; readonly detail?: string; readonly suggestions?: ReadonlyArray<PermissionUpdate>; readonly decision: Deferred.Deferred<ProviderApprovalDecision>; } interface PendingUserInput { readonly questions: ReadonlyArray<UserInputQuestion>; readonly answers: Deferred.Deferred<ProviderUserInputAnswers>; } interface ToolInFlight { readonly itemId: string; readonly itemType: CanonicalItemType; readonly toolName: string; readonly title: string; readonly detail?: string; readonly input: Record<string, unknown>; readonly partialInputJson: string; readonly lastEmittedInputFingerprint?: string; } interface ClaudeSessionContext { session: ProviderSession; readonly promptQueue: Queue.Queue<PromptQueueItem>; readonly query: ClaudeQueryRuntime; streamFiber: Fiber.Fiber<void, Error> | undefined; readonly startedAt: string; readonly basePermissionMode: PermissionMode | undefined; currentApiModelId: string | undefined; resumeSessionId: string | undefined; readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>; readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>; readonly turns: Array<{ id: TurnId; items: Array<unknown>; }>; readonly inFlightTools: Map<number, ToolInFlight>; turnState: ClaudeTurnState | undefined; lastKnownContextWindow: number | undefined; lastKnownTokenUsage: ThreadTokenUsageSnapshot | undefined; lastAssistantUuid: string | undefined; lastThreadStartedId: string | undefined; stopped: boolean; } interface ClaudeQueryRuntime extends AsyncIterable<SDKMessage> { readonly interrupt: () => Promise<void>; readonly setModel: (model?: string) => Promise<void>; readonly setPermissionMode: (mode: PermissionMode) => Promise<void>; readonly setMaxThinkingTokens: (maxThinkingTokens: number | null) => Promise<void>; readonly close: () => void; } export interface ClaudeAdapterLiveOptions { readonly instanceId?: ProviderInstanceId; readonly environment?: NodeJS.ProcessEnv; readonly createQuery?: (input: { readonly prompt: AsyncIterable<SDKUserMessage>; readonly options: ClaudeQueryOptions; }) => ClaudeQueryRuntime; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger; } function isUuid(value: string): boolean { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); } function isSyntheticClaudeThreadId(value: string): boolean { return value.startsWith("claude-thread-"); } function hasDurableClaudeSessionId(message: SDKMessage): boolean { if (message.type !== "system") { return true; } return ( message.subtype !== "hook_started" && message.subtype !== "hook_progress" && message.subtype !== "hook_response" ); } function toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback; } function toProcessError( cause: unknown, fallback: string, threadId: ThreadId, ): ProviderAdapterProcessError { return new ProviderAdapterProcessError({ provider: PROVIDER, threadId, detail: toMessage(cause, fallback), cause, }); } function normalizeClaudeStreamMessages( cause: Cause.Cause<{ readonly message: string }>, ): ReadonlyArray<string> { const errors: Array<string> = []; for (const error of Cause.prettyErrors(cause)) { const message = error.message.trim(); if (message.length > 0) { errors.push(message); } } if (errors.length > 0) { return errors; } const squashed = toMessage(Cause.squash(cause), "").trim(); return squashed.length > 0 ? [squashed] : []; } function getEffectiveClaudeAgentEffort( effort: string | null | undefined, model: string | null | undefined, ): ClaudeSdkEffort | null { const normalized = normalizeClaudeCliEffort(effort, model); return normalized ? (normalized as ClaudeSdkEffort) : null; } function isClaudeInterruptedMessage(message: string): boolean { const normalized = message.toLowerCase(); return ( normalized.includes("all fibers interrupted without error") || normalized.includes("request was aborted") || normalized.includes("interrupted by user") ); } function isClaudeInterruptedCause(cause: Cause.Cause<{ readonly message: string }>): boolean { return ( Cause.hasInterruptsOnly(cause) || normalizeClaudeStreamMessages(cause).some(isClaudeInterruptedMessage) ); } function messageFromClaudeStreamCause( cause: Cause.Cause<{ readonly message: string }>, fallback: string, ): string { return normalizeClaudeStreamMessages(cause)[0] ?? fallback; } function interruptionMessageFromClaudeCause( cause: Cause.Cause<{ readonly message: string }>, ): string { const message = messageFromClaudeStreamCause(cause, "Claude runtime interrupted."); return isClaudeInterruptedMessage(message) ? "Claude runtime interrupted." : message; } function resultErrorsText(result: SDKResultMessage): string { return "errors" in result && Array.isArray(result.errors) ? result.errors.join(" ").toLowerCase() : ""; } function isInterruptedResult(result: SDKResultMessage): boolean { const errors = resultErrorsText(result); if (errors.includes("interrupt")) { return true; } return ( result.subtype === "error_during_execution" && result.is_error === false && (errors.includes("request was aborted") || errors.includes("interrupted by user") || errors.includes("aborted")) ); } function asRuntimeItemId(value: string): RuntimeItemId { return RuntimeItemId.make(value); } function maxClaudeContextWindowFromModelUsage( modelUsage: Record<string, ModelUsage> | undefined, ): number | undefined { if (!modelUsage) return undefined; let maxContextWindow: number | undefined; for (const value of Object.values(modelUsage)) { const contextWindow = value.contextWindow; maxContextWindow = Math.max(maxContextWindow ?? 0, contextWindow); } return maxContextWindow; } function normalizeClaudeTokenUsage( value: unknown, contextWindow?: number, ): ThreadTokenUsageSnapshot | undefined { if (!value || typeof value !== "object") { return undefined; } const usage = value as Record<string, unknown>; const inputTokens = (typeof usage.input_tokens === "number" && Number.isFinite(usage.input_tokens) ? usage.input_tokens : 0) + (typeof usage.cache_creation_input_tokens === "number" && Number.isFinite(usage.cache_creation_input_tokens) ? usage.cache_creation_input_tokens : 0) + (typeof usage.cache_read_input_tokens === "number" && Number.isFinite(usage.cache_read_input_tokens) ? usage.cache_read_input_tokens : 0); const outputTokens = typeof usage.output_tokens === "number" && Number.isFinite(usage.output_tokens) ? usage.output_tokens : 0; const derivedTotalProcessedTokens = inputTokens + outputTokens; const totalProcessedTokens = (typeof usage.total_tokens === "number" && Number.isFinite(usage.total_tokens) ? usage.total_tokens : undefined) ?? (derivedTotalProcessedTokens > 0 ? derivedTotalProcessedTokens : undefined); if (totalProcessedTokens === undefined || totalProcessedTokens <= 0) { return undefined; } const maxTokens = typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0 ? contextWindow : undefined; const usedTokens = maxTokens !== undefined ? Math.min(totalProcessedTokens, maxTokens) : totalProcessedTokens; return { usedTokens, lastUsedTokens: usedTokens, ...(totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}), ...(inputTokens > 0 ? { inputTokens } : {}), ...(outputTokens > 0 ? { outputTokens } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), ...(typeof usage.tool_uses === "number" && Number.isFinite(usage.tool_uses) ? { toolUses: usage.tool_uses } : {}), ...(typeof usage.duration_ms === "number" && Number.isFinite(usage.duration_ms) ? { durationMs: usage.duration_ms } : {}), }; } function asCanonicalTurnId(value: TurnId): TurnId { return value; } function asRuntimeRequestId(value: ApprovalRequestId): RuntimeRequestId { return RuntimeRequestId.make(value); } function readClaudeResumeState(resumeCursor: unknown): ClaudeResumeState | undefined { if (!resumeCursor || typeof resumeCursor !== "object") { return undefined; } const cursor = resumeCursor as { threadId?: unknown; resume?: unknown; sessionId?: unknown; resumeSessionAt?: unknown; turnCount?: unknown; }; const threadIdCandidate = typeof cursor.threadId === "string" ? cursor.threadId : undefined; const threadId = threadIdCandidate && !isSyntheticClaudeThreadId(threadIdCandidate) ? ThreadId.make(threadIdCandidate) : undefined; const resumeCandidate = typeof cursor.resume === "string" ? cursor.resume : typeof cursor.sessionId === "string" ? cursor.sessionId : undefined; const resume = resumeCandidate && isUuid(resumeCandidate) ? resumeCandidate : undefined; const resumeSessionAt = typeof cursor.resumeSessionAt === "string" ? cursor.resumeSessionAt : undefined; const turnCountValue = typeof cursor.turnCount === "number" ? cursor.turnCount : undefined; return { ...(threadId ? { threadId } : {}), ...(resume ? { resume } : {}), ...(resumeSessionAt ? { resumeSessionAt } : {}), ...(turnCountValue !== undefined && Number.isInteger(turnCountValue) && turnCountValue >= 0 ? { turnCount: turnCountValue } : {}), }; } function classifyToolItemType(toolName: string): CanonicalItemType { const normalized = toolName.toLowerCase(); if (normalized.includes("agent")) { return "collab_agent_tool_call"; } if ( normalized === "task" || normalized === "agent" || normalized.includes("subagent") || normalized.includes("sub-agent") ) { return "collab_agent_tool_call"; } if ( normalized.includes("bash") || normalized.includes("command") || normalized.includes("shell") || normalized.includes("terminal") ) { return "command_execution"; } if ( normalized.includes("edit") || normalized.includes("write") || normalized.includes("file") || normalized.includes("patch") || normalized.includes("replace") || normalized.includes("create") || normalized.includes("delete") ) { return "file_change"; } if (normalized.includes("mcp")) { return "mcp_tool_call"; } if (normalized.includes("websearch") || normalized.includes("web search")) { return "web_search"; } if (normalized.includes("image")) { return "image_view"; } return "dynamic_tool_call"; } function isReadOnlyToolName(toolName: string): boolean { const normalized = toolName.toLowerCase(); return ( normalized === "read" || normalized.includes("read file") || normalized.includes("view") || normalized.includes("grep") || normalized.includes("glob") || normalized.includes("search") ); } function classifyRequestType(toolName: string): CanonicalRequestType { if (isReadOnlyToolName(toolName)) {