/** * 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 SDKUserMessage,} from "@anthropic-ai/claude-agent-sdk";import { ApprovalRequestId, type CanonicalItemType, type CanonicalRequestType, EventId, type ProviderApprovalDecision, ProviderItemId, type ProviderRuntimeEvent, type ProviderRuntimeTurnStatus, type ProviderSendTurnInput, type ProviderSession, type ProviderUserInputAnswers, type RuntimeContentStreamKind, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ThreadId, TurnId, type UserInputQuestion,} from "@t3tools/contracts";import { applyClaudePromptEffortPrefix, getEffectiveClaudeCodeEffort, getReasoningEffortOptions, resolveReasoningEffortForProvider, supportsClaudeFastMode, supportsClaudeThinkingToggle, supportsClaudeUltrathinkKeyword,} from "@t3tools/shared/model";import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Ref, Stream } from "effect"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError,} from "../Errors.ts";import { ClaudeAdapter, type ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts";import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; const PROVIDER = "claudeAgent" as const;type ClaudeTextStreamKind = Extract<RuntimeContentStreamKind, "assistant_text" | "reasoning_text">;type ClaudeToolResultStreamKind = Extract< RuntimeContentStreamKind, "command_output" | "file_change_output">; 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<number>; readonly capturedProposedPlanKeys: Set<string>; readonly taskItemIds: Map<string, string>; nextSyntheticAssistantBlockIndex: number;} interface AssistantTextBlockState { readonly itemId: string; 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; readonly startedAt: string; readonly basePermissionMode: PermissionMode | 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; 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 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 toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback;} 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.makeUnsafe(value);} function asCanonicalTurnId(value: TurnId): TurnId { return value;} function asRuntimeRequestId(value: ApprovalRequestId): RuntimeRequestId { return RuntimeRequestId.makeUnsafe(value);} function toPermissionMode(value: unknown): PermissionMode | undefined { switch (value) { case "default": case "acceptEdits": case "bypassPermissions": case "plan": case "dontAsk": return value; default: return undefined; }} 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.makeUnsafe(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)) { return "file_read_approval"; } const itemType = classifyToolItemType(toolName); return itemType === "command_execution" ? "command_execution_approval" : itemType === "file_change" ? "file_change_approval" : "dynamic_tool_call";} function summarizeToolRequest(toolName: string, input: Record<string, unknown>): string { const commandValue = input.command ?? input.cmd; const command = typeof commandValue === "string" ? commandValue : undefined; if (command && command.trim().length > 0) { return `${toolName}: ${command.trim().slice(0, 400)}`; } const serialized = JSON.stringify(input); if (serialized.length <= 400) { return `${toolName}: ${serialized}`; } return `${toolName}: ${serialized.slice(0, 397)}...`;} function titleForTool(itemType: CanonicalItemType): string { switch (itemType) { case "command_execution": return "Command run"; case "file_change": return "File change"; case "mcp_tool_call": return "MCP tool call"; case "collab_agent_tool_call": return "Subagent task"; case "web_search": return "Web search"; case "image_view": return "Image view"; case "dynamic_tool_call": return "Tool call"; default: return "Item"; }} function buildUserMessage(input: ProviderSendTurnInput): SDKUserMessage { const fragments: string[] = []; if (input.input && input.input.trim().length > 0) { fragments.push(input.input.trim()); } for (const attachment of input.attachments ?? []) { if (attachment.type === "image") { fragments.push( `Attached image: ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes).`, ); } } const requestedEffort = resolveReasoningEffortForProvider( "claudeAgent", input.modelOptions?.claudeAgent?.effort ?? null, ); const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model); const promptEffort = requestedEffort === "ultrathink" && supportsClaudeUltrathinkKeyword(input.model) ? "ultrathink" : requestedEffort && supportedEffortOptions.includes(requestedEffort) ? requestedEffort : null; const text = applyClaudePromptEffortPrefix(fragments.join("\n\n"), promptEffort); return { type: "user", session_id: "", parent_tool_use_id: null, message: { role: "user", content: [{ type: "text", text }], }, } as SDKUserMessage;} function turnStatusFromResult(result: SDKResultMessage): ProviderRuntimeTurnStatus { if (result.subtype === "success") { return "completed"; } const errors = resultErrorsText(result); if (isInterruptedResult(result)) { return "interrupted"; } if (errors.includes("cancel")) { return "cancelled"; } return "failed";} function streamKindFromDeltaType(deltaType: string): ClaudeTextStreamKind { return deltaType.includes("thinking") ? "reasoning_text" : "assistant_text";} function nativeProviderRefs( _context: ClaudeSessionContext, options?: { readonly providerItemId?: string | undefined; },): NonNullable<ProviderRuntimeEvent["providerRefs"]> { if (options?.providerItemId) { return { providerItemId: ProviderItemId.makeUnsafe(options.providerItemId), }; } return {};} function taskEventParentItemFields( context: ClaudeSessionContext, taskId: string,): Pick<ProviderRuntimeEvent, "itemId" | "providerRefs"> | undefined { const providerItemId = context.turnState?.taskItemIds.get(taskId); if (!providerItemId) { return undefined; } return { itemId: asRuntimeItemId(providerItemId), providerRefs: nativeProviderRefs(context, { providerItemId }), };} function extractAssistantTextBlocks(message: SDKMessage): Array<string> { if (message.type !== "assistant") { return []; } const content = (message.message as { content?: unknown } | undefined)?.content; if (!Array.isArray(content)) { return []; } const fragments: string[] = []; for (const block of content) { if (!block || typeof block !== "object") { continue; } const candidate = block as { type?: unknown; text?: unknown }; if ( candidate.type === "text" && typeof candidate.text === "string" && candidate.text.length > 0 ) { fragments.push(candidate.text); } } return fragments;} function extractContentBlockText(block: unknown): string { if (!block || typeof block !== "object") { return ""; } const candidate = block as { type?: unknown; text?: unknown }; return candidate.type === "text" && typeof candidate.text === "string" ? candidate.text : "";} function extractTextContent(value: unknown): string { if (typeof value === "string") { return value; } if (Array.isArray(value)) { return value.map((entry) => extractTextContent(entry)).join(""); } if (!value || typeof value !== "object") { return ""; }Lines 1–500 of 2745. 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 SDKUserMessage, } from "@anthropic-ai/claude-agent-sdk"; import { ApprovalRequestId, type CanonicalItemType, type CanonicalRequestType, EventId, type ProviderApprovalDecision, ProviderItemId, type ProviderRuntimeEvent, type ProviderRuntimeTurnStatus, type ProviderSendTurnInput, type ProviderSession, type ProviderUserInputAnswers, type RuntimeContentStreamKind, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ThreadId, TurnId, type UserInputQuestion, } from "@t3tools/contracts"; import { applyClaudePromptEffortPrefix, getEffectiveClaudeCodeEffort, getReasoningEffortOptions, resolveReasoningEffortForProvider, supportsClaudeFastMode, supportsClaudeThinkingToggle, supportsClaudeUltrathinkKeyword, } from "@t3tools/shared/model"; import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Ref, Stream } from "effect"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError, } from "../Errors.ts"; import { ClaudeAdapter, type ClaudeAdapterShape } from "../Services/ClaudeAdapter.ts"; import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; const PROVIDER = "claudeAgent" as const; type ClaudeTextStreamKind = Extract<RuntimeContentStreamKind, "assistant_text" | "reasoning_text">; type ClaudeToolResultStreamKind = Extract< RuntimeContentStreamKind, "command_output" | "file_change_output" >; 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<number>; readonly capturedProposedPlanKeys: Set<string>; readonly taskItemIds: Map<string, string>; nextSyntheticAssistantBlockIndex: number; } interface AssistantTextBlockState { readonly itemId: string; 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; readonly startedAt: string; readonly basePermissionMode: PermissionMode | 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; 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 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 toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback; } 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.makeUnsafe(value); } function asCanonicalTurnId(value: TurnId): TurnId { return value; } function asRuntimeRequestId(value: ApprovalRequestId): RuntimeRequestId { return RuntimeRequestId.makeUnsafe(value); } function toPermissionMode(value: unknown): PermissionMode | undefined { switch (value) { case "default": case "acceptEdits": case "bypassPermissions": case "plan": case "dontAsk": return value; default: return undefined; } } 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.makeUnsafe(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)) { return "file_read_approval"; } const itemType = classifyToolItemType(toolName); return itemType === "command_execution" ? "command_execution_approval" : itemType === "file_change" ? "file_change_approval" : "dynamic_tool_call"; } function summarizeToolRequest(toolName: string, input: Record<string, unknown>): string { const commandValue = input.command ?? input.cmd; const command = typeof commandValue === "string" ? commandValue : undefined; if (command && command.trim().length > 0) { return `${toolName}: ${command.trim().slice(0, 400)}`; } const serialized = JSON.stringify(input); if (serialized.length <= 400) { return `${toolName}: ${serialized}`; } return `${toolName}: ${serialized.slice(0, 397)}...`; } function titleForTool(itemType: CanonicalItemType): string { switch (itemType) { case "command_execution": return "Command run"; case "file_change": return "File change"; case "mcp_tool_call": return "MCP tool call"; case "collab_agent_tool_call": return "Subagent task"; case "web_search": return "Web search"; case "image_view": return "Image view"; case "dynamic_tool_call": return "Tool call"; default: return "Item"; } } function buildUserMessage(input: ProviderSendTurnInput): SDKUserMessage { const fragments: string[] = []; if (input.input && input.input.trim().length > 0) { fragments.push(input.input.trim()); } for (const attachment of input.attachments ?? []) { if (attachment.type === "image") { fragments.push( `Attached image: ${attachment.name} (${attachment.mimeType}, ${attachment.sizeBytes} bytes).`, ); } } const requestedEffort = resolveReasoningEffortForProvider( "claudeAgent", input.modelOptions?.claudeAgent?.effort ?? null, ); const supportedEffortOptions = getReasoningEffortOptions("claudeAgent", input.model); const promptEffort = requestedEffort === "ultrathink" && supportsClaudeUltrathinkKeyword(input.model) ? "ultrathink" : requestedEffort && supportedEffortOptions.includes(requestedEffort) ? requestedEffort : null; const text = applyClaudePromptEffortPrefix(fragments.join("\n\n"), promptEffort); return { type: "user", session_id: "", parent_tool_use_id: null, message: { role: "user", content: [{ type: "text", text }], }, } as SDKUserMessage; } function turnStatusFromResult(result: SDKResultMessage): ProviderRuntimeTurnStatus { if (result.subtype === "success") { return "completed"; } const errors = resultErrorsText(result); if (isInterruptedResult(result)) { return "interrupted"; } if (errors.includes("cancel")) { return "cancelled"; } return "failed"; } function streamKindFromDeltaType(deltaType: string): ClaudeTextStreamKind { return deltaType.includes("thinking") ? "reasoning_text" : "assistant_text"; } function nativeProviderRefs( _context: ClaudeSessionContext, options?: { readonly providerItemId?: string | undefined; }, ): NonNullable<ProviderRuntimeEvent["providerRefs"]> { if (options?.providerItemId) { return { providerItemId: ProviderItemId.makeUnsafe(options.providerItemId), }; } return {}; } function taskEventParentItemFields( context: ClaudeSessionContext, taskId: string, ): Pick<ProviderRuntimeEvent, "itemId" | "providerRefs"> | undefined { const providerItemId = context.turnState?.taskItemIds.get(taskId); if (!providerItemId) { return undefined; } return { itemId: asRuntimeItemId(providerItemId), providerRefs: nativeProviderRefs(context, { providerItemId }), }; } function extractAssistantTextBlocks(message: SDKMessage): Array<string> { if (message.type !== "assistant") { return []; } const content = (message.message as { content?: unknown } | undefined)?.content; if (!Array.isArray(content)) { return []; } const fragments: string[] = []; for (const block of content) { if (!block || typeof block !== "object") { continue; } const candidate = block as { type?: unknown; text?: unknown }; if ( candidate.type === "text" && typeof candidate.text === "string" && candidate.text.length > 0 ) { fragments.push(candidate.text); } } return fragments; } function extractContentBlockText(block: unknown): string { if (!block || typeof block !== "object") { return ""; } const candidate = block as { type?: unknown; text?: unknown }; return candidate.type === "text" && typeof candidate.text === "string" ? candidate.text : ""; } function extractTextContent(value: unknown): string { if (typeof value === "string") { return value; } if (Array.isArray(value)) { return value.map((entry) => extractTextContent(entry)).join(""); } if (!value || typeof value !== "object") { return ""; }