/** * CodexAdapterLive - Scoped live implementation for the Codex provider adapter. * * Wraps `CodexAppServerManager` behind the `CodexAdapter` service contract and * maps manager failures into the shared `ProviderAdapterError` algebra. * * @module CodexAdapterLive */import { type CanonicalItemType, type CanonicalToolLifecycleData, type CanonicalRequestType, type ProviderEvent, type ProviderRuntimeEvent, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ProviderApprovalDecision, ProviderItemId, ThreadId, TurnId,} from "@t3tools/contracts";import { Effect, FileSystem, Layer, Queue, Schema, ServiceMap, Stream } from "effect"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError,} from "../Errors.ts";import { CodexAdapter, type CodexAdapterShape } from "../Services/CodexAdapter.ts";import { CodexAppServerManager, type CodexAppServerStartSessionInput,} from "../../codexAppServerManager.ts";import { resolveAttachmentPath } from "../../attachmentStore.ts";import { ServerConfig } from "../../config.ts";import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; const PROVIDER = "codex" as const; export interface CodexAdapterLiveOptions { readonly manager?: CodexAppServerManager; readonly makeManager?: (services?: ServiceMap.ServiceMap<never>) => CodexAppServerManager; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger;} function toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback;} function toSessionError( threadId: ThreadId, cause: unknown,): ProviderAdapterSessionNotFoundError | ProviderAdapterSessionClosedError | undefined { const normalized = toMessage(cause, "").toLowerCase(); if (normalized.includes("unknown session") || normalized.includes("unknown provider session")) { return new ProviderAdapterSessionNotFoundError({ provider: PROVIDER, threadId, cause, }); } if (normalized.includes("session is closed")) { return new ProviderAdapterSessionClosedError({ provider: PROVIDER, threadId, cause, }); } return undefined;} function toRequestError(threadId: ThreadId, method: string, cause: unknown): ProviderAdapterError { const sessionError = toSessionError(threadId, cause); if (sessionError) { return sessionError; } return new ProviderAdapterRequestError({ provider: PROVIDER, method, detail: toMessage(cause, `${method} failed`), cause, });} function asObject(value: unknown): Record<string, unknown> | undefined { if (!value || typeof value !== "object") { return undefined; } return value as Record<string, unknown>;} function asString(value: unknown): string | undefined { return typeof value === "string" ? value : undefined;} function asArray(value: unknown): unknown[] | undefined { return Array.isArray(value) ? value : undefined;} function asNumber(value: unknown): number | undefined { return typeof value === "number" && Number.isFinite(value) ? value : undefined;} function normalizeCommandValue(value: unknown): string | undefined { if (typeof value === "string") { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; } if (!Array.isArray(value)) { return undefined; } const parts = value .map((entry) => (typeof entry === "string" ? entry.trim() : "")) .filter((entry) => entry.length > 0); return parts.length > 0 ? parts.join(" ") : undefined;} function pushChangedFile(target: string[], seen: Set<string>, value: unknown) { if (typeof value !== "string") { return; } const normalized = value.trim(); if (normalized.length === 0 || seen.has(normalized)) { return; } seen.add(normalized); target.push(normalized);} function collectChangedFiles(value: unknown, target: string[], seen: Set<string>, depth: number) { if (depth > 4 || target.length >= 12) { return; } if (Array.isArray(value)) { for (const entry of value) { collectChangedFiles(entry, target, seen, depth + 1); if (target.length >= 12) { return; } } return; } const record = asObject(value); if (!record) { return; } pushChangedFile(target, seen, record.path); pushChangedFile(target, seen, record.filePath); pushChangedFile(target, seen, record.relativePath); pushChangedFile(target, seen, record.filename); pushChangedFile(target, seen, record.newPath); pushChangedFile(target, seen, record.oldPath); for (const nestedKey of ["item", "result", "input", "changes", "files", "edits", "patches"]) { if (!(nestedKey in record)) { continue; } collectChangedFiles(record[nestedKey], target, seen, depth + 1); }} function buildCanonicalToolLifecycleData( itemType: CanonicalItemType, source: Record<string, unknown>, payload: Record<string, unknown>,): CanonicalToolLifecycleData | undefined { const result = asObject(source.result); const input = asObject(source.input); const command = [ normalizeCommandValue(source.command), normalizeCommandValue(input?.command), normalizeCommandValue(result?.command), normalizeCommandValue(payload.command), ].find((candidate) => candidate !== undefined); const changedFiles: string[] = []; const seen = new Set<string>(); collectChangedFiles(source, changedFiles, seen, 0); collectChangedFiles(payload, changedFiles, seen, 0); if (itemType === "command_execution" && command) { return { kind: "command_execution", command, ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), ...(typeof result?.content === "string" ? { output: result.content } : {}), ...(asNumber(result?.exitCode) !== undefined ? { exitCode: asNumber(result?.exitCode) } : {}), }; } if (itemType === "file_change" && changedFiles.length > 0) { return { kind: "file_change", changedFiles, ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), }; } if (input || result) { return { kind: "generic", ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), }; } return undefined;} function normalizeCodexTokenUsage(value: unknown): ThreadTokenUsageSnapshot | undefined { const usage = asObject(value); const totalUsage = asObject(usage?.total_token_usage ?? usage?.total); const lastUsage = asObject(usage?.last_token_usage ?? usage?.last); const totalProcessedTokens = asNumber(totalUsage?.total_tokens) ?? asNumber(totalUsage?.totalTokens); const usedTokens = asNumber(lastUsage?.total_tokens) ?? asNumber(lastUsage?.totalTokens) ?? totalProcessedTokens; if (usedTokens === undefined || usedTokens <= 0) { return undefined; } const maxTokens = asNumber(usage?.model_context_window) ?? asNumber(usage?.modelContextWindow); const inputTokens = asNumber(lastUsage?.input_tokens) ?? asNumber(lastUsage?.inputTokens); const cachedInputTokens = asNumber(lastUsage?.cached_input_tokens) ?? asNumber(lastUsage?.cachedInputTokens); const outputTokens = asNumber(lastUsage?.output_tokens) ?? asNumber(lastUsage?.outputTokens); const reasoningOutputTokens = asNumber(lastUsage?.reasoning_output_tokens) ?? asNumber(lastUsage?.reasoningOutputTokens); return { usedTokens, ...(totalProcessedTokens !== undefined && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), ...(inputTokens !== undefined ? { inputTokens } : {}), ...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}), ...(outputTokens !== undefined ? { outputTokens } : {}), ...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}), ...(usedTokens !== undefined ? { lastUsedTokens: usedTokens } : {}), ...(inputTokens !== undefined ? { lastInputTokens: inputTokens } : {}), ...(cachedInputTokens !== undefined ? { lastCachedInputTokens: cachedInputTokens } : {}), ...(outputTokens !== undefined ? { lastOutputTokens: outputTokens } : {}), ...(reasoningOutputTokens !== undefined ? { lastReasoningOutputTokens: reasoningOutputTokens } : {}), compactsAutomatically: true, };} function toTurnId(value: string | undefined): TurnId | undefined { return value?.trim() ? TurnId.makeUnsafe(value) : undefined;} function toProviderItemId(value: string | undefined): ProviderItemId | undefined { return value?.trim() ? ProviderItemId.makeUnsafe(value) : undefined;} function toTurnStatus(value: unknown): "completed" | "failed" | "cancelled" | "interrupted" { switch (value) { case "completed": case "failed": case "cancelled": case "interrupted": return value; default: return "completed"; }} function normalizeItemType(raw: unknown): string { const type = asString(raw); if (!type) return "item"; return type .replace(/([a-z0-9])([A-Z])/g, "$1 $2") .replace(/[._/-]/g, " ") .replace(/\s+/g, " ") .trim() .toLowerCase();} function toCanonicalItemType(raw: unknown): CanonicalItemType { const type = normalizeItemType(raw); if (type.includes("user")) return "user_message"; if (type.includes("agent message") || type.includes("assistant")) return "assistant_message"; if (type.includes("reasoning") || type.includes("thought")) return "reasoning"; if (type.includes("plan") || type.includes("todo")) return "plan"; if (type.includes("command")) return "command_execution"; if (type.includes("file change") || type.includes("patch") || type.includes("edit")) return "file_change"; if (type.includes("mcp")) return "mcp_tool_call"; if (type.includes("dynamic tool")) return "dynamic_tool_call"; if (type.includes("collab")) return "collab_agent_tool_call"; if (type.includes("web search")) return "web_search"; if (type.includes("image")) return "image_view"; if (type.includes("review entered")) return "review_entered"; if (type.includes("review exited")) return "review_exited"; if (type.includes("compact")) return "context_compaction"; if (type.includes("error")) return "error"; return "unknown";} function itemTitle(itemType: CanonicalItemType): string | undefined { switch (itemType) { case "assistant_message": return "Assistant message"; case "user_message": return "User message"; case "reasoning": return "Reasoning"; case "plan": return "Plan"; case "command_execution": return "Ran command"; case "file_change": return "File change"; case "mcp_tool_call": return "MCP tool call"; case "dynamic_tool_call": return "Tool call"; case "web_search": return "Web search"; case "image_view": return "Image view"; case "error": return "Error"; default: return undefined; }} function itemDetail( item: Record<string, unknown>, payload: Record<string, unknown>,): string | undefined { const nestedResult = asObject(item.result); const candidates = [ asString(item.command), asString(item.title), asString(item.summary), asString(item.text), asString(item.path), asString(item.prompt), asString(nestedResult?.command), asString(payload.command), asString(payload.message), asString(payload.prompt), ]; for (const candidate of candidates) { if (!candidate) continue; const trimmed = candidate.trim(); if (trimmed.length === 0) continue; return trimmed; } return undefined;} function toRequestTypeFromMethod(method: string): CanonicalRequestType { switch (method) { case "item/commandExecution/requestApproval": return "command_execution_approval"; case "item/fileRead/requestApproval": return "file_read_approval"; case "item/fileChange/requestApproval": return "file_change_approval"; case "applyPatchApproval": return "apply_patch_approval"; case "execCommandApproval": return "exec_command_approval"; case "item/tool/requestUserInput": return "tool_user_input"; case "item/tool/call": return "dynamic_tool_call"; case "account/chatgptAuthTokens/refresh": return "auth_tokens_refresh"; default: return "unknown"; }} function toRequestTypeFromKind(kind: unknown): CanonicalRequestType { switch (kind) { case "command": return "command_execution_approval"; case "file-read": return "file_read_approval"; case "file-change": return "file_change_approval"; default: return "unknown"; }} function toRequestTypeFromResolvedPayload( payload: Record<string, unknown> | undefined,): CanonicalRequestType { const request = asObject(payload?.request); const method = asString(request?.method) ?? asString(payload?.method); if (method) { return toRequestTypeFromMethod(method); } const requestKind = asString(request?.kind) ?? asString(payload?.requestKind); if (requestKind) { return toRequestTypeFromKind(requestKind); } return "unknown";} function toCanonicalUserInputAnswers( answers: ProviderUserInputAnswers | undefined,): ProviderUserInputAnswers { if (!answers) { return {}; } const normalized: Record<string, string | readonly string[]> = {}; for (const [questionId, value] of Object.entries(answers)) { if (typeof value === "string") { normalized[questionId] = value; continue; } if (Array.isArray(value)) { const entries = value.filter((entry): entry is string => typeof entry === "string"); normalized[questionId] = entries.length === 1 ? (entries[0] ?? entries) : entries; continue; } const answerObject = asObject(value); const answerList = asArray(answerObject?.answers)?.filter( (entry): entry is string => typeof entry === "string", ); if (answerList) { normalized[questionId] = answerList.length === 1 ? (answerList[0] ?? answerList) : answerList; } } return normalized;} function toUserInputQuestions(payload: Record<string, unknown> | undefined) { const questions = asArray(payload?.questions); if (!questions) { return undefined; } const parsedQuestions = questions .map((entry) => { const question = asObject(entry); if (!question) return undefined; const options = asArray(question.options) ?.map((option) => { const optionRecord = asObject(option); if (!optionRecord) return undefined; const label = asString(optionRecord.label)?.trim(); const description = asString(optionRecord.description)?.trim(); if (!label || !description) { return undefined; } return { label, description }; }) .filter((option): option is { label: string; description: string } => option !== undefined); const id = asString(question.id)?.trim(); const header = asString(question.header)?.trim(); const prompt = asString(question.question)?.trim(); if (!id || !header || !prompt || !options || options.length === 0) { return undefined; } return { id, header, question: prompt, options, }; }) .filter( ( question, ): question is { id: string; header: string; question: string; options: Array<{ label: string; description: string }>; } => question !== undefined, ); return parsedQuestions.length > 0 ? parsedQuestions : undefined;} function toThreadState(Lines 1–500 of 1531. Load more
/** * CodexAdapterLive - Scoped live implementation for the Codex provider adapter. * * Wraps `CodexAppServerManager` behind the `CodexAdapter` service contract and * maps manager failures into the shared `ProviderAdapterError` algebra. * * @module CodexAdapterLive */ import { type CanonicalItemType, type CanonicalToolLifecycleData, type CanonicalRequestType, type ProviderEvent, type ProviderRuntimeEvent, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, ProviderApprovalDecision, ProviderItemId, ThreadId, TurnId, } from "@t3tools/contracts"; import { Effect, FileSystem, Layer, Queue, Schema, ServiceMap, Stream } from "effect"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError, } from "../Errors.ts"; import { CodexAdapter, type CodexAdapterShape } from "../Services/CodexAdapter.ts"; import { CodexAppServerManager, type CodexAppServerStartSessionInput, } from "../../codexAppServerManager.ts"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; const PROVIDER = "codex" as const; export interface CodexAdapterLiveOptions { readonly manager?: CodexAppServerManager; readonly makeManager?: (services?: ServiceMap.ServiceMap<never>) => CodexAppServerManager; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger; } function toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; } return fallback; } function toSessionError( threadId: ThreadId, cause: unknown, ): ProviderAdapterSessionNotFoundError | ProviderAdapterSessionClosedError | undefined { const normalized = toMessage(cause, "").toLowerCase(); if (normalized.includes("unknown session") || normalized.includes("unknown provider session")) { return new ProviderAdapterSessionNotFoundError({ provider: PROVIDER, threadId, cause, }); } if (normalized.includes("session is closed")) { return new ProviderAdapterSessionClosedError({ provider: PROVIDER, threadId, cause, }); } return undefined; } function toRequestError(threadId: ThreadId, method: string, cause: unknown): ProviderAdapterError { const sessionError = toSessionError(threadId, cause); if (sessionError) { return sessionError; } return new ProviderAdapterRequestError({ provider: PROVIDER, method, detail: toMessage(cause, `${method} failed`), cause, }); } function asObject(value: unknown): Record<string, unknown> | undefined { if (!value || typeof value !== "object") { return undefined; } return value as Record<string, unknown>; } function asString(value: unknown): string | undefined { return typeof value === "string" ? value : undefined; } function asArray(value: unknown): unknown[] | undefined { return Array.isArray(value) ? value : undefined; } function asNumber(value: unknown): number | undefined { return typeof value === "number" && Number.isFinite(value) ? value : undefined; } function normalizeCommandValue(value: unknown): string | undefined { if (typeof value === "string") { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; } if (!Array.isArray(value)) { return undefined; } const parts = value .map((entry) => (typeof entry === "string" ? entry.trim() : "")) .filter((entry) => entry.length > 0); return parts.length > 0 ? parts.join(" ") : undefined; } function pushChangedFile(target: string[], seen: Set<string>, value: unknown) { if (typeof value !== "string") { return; } const normalized = value.trim(); if (normalized.length === 0 || seen.has(normalized)) { return; } seen.add(normalized); target.push(normalized); } function collectChangedFiles(value: unknown, target: string[], seen: Set<string>, depth: number) { if (depth > 4 || target.length >= 12) { return; } if (Array.isArray(value)) { for (const entry of value) { collectChangedFiles(entry, target, seen, depth + 1); if (target.length >= 12) { return; } } return; } const record = asObject(value); if (!record) { return; } pushChangedFile(target, seen, record.path); pushChangedFile(target, seen, record.filePath); pushChangedFile(target, seen, record.relativePath); pushChangedFile(target, seen, record.filename); pushChangedFile(target, seen, record.newPath); pushChangedFile(target, seen, record.oldPath); for (const nestedKey of ["item", "result", "input", "changes", "files", "edits", "patches"]) { if (!(nestedKey in record)) { continue; } collectChangedFiles(record[nestedKey], target, seen, depth + 1); } } function buildCanonicalToolLifecycleData( itemType: CanonicalItemType, source: Record<string, unknown>, payload: Record<string, unknown>, ): CanonicalToolLifecycleData | undefined { const result = asObject(source.result); const input = asObject(source.input); const command = [ normalizeCommandValue(source.command), normalizeCommandValue(input?.command), normalizeCommandValue(result?.command), normalizeCommandValue(payload.command), ].find((candidate) => candidate !== undefined); const changedFiles: string[] = []; const seen = new Set<string>(); collectChangedFiles(source, changedFiles, seen, 0); collectChangedFiles(payload, changedFiles, seen, 0); if (itemType === "command_execution" && command) { return { kind: "command_execution", command, ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), ...(typeof result?.content === "string" ? { output: result.content } : {}), ...(asNumber(result?.exitCode) !== undefined ? { exitCode: asNumber(result?.exitCode) } : {}), }; } if (itemType === "file_change" && changedFiles.length > 0) { return { kind: "file_change", changedFiles, ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), }; } if (input || result) { return { kind: "generic", ...(input ? { input: input as CanonicalToolLifecycleData["input"] } : {}), ...(result ? { result: result as CanonicalToolLifecycleData["result"] } : {}), }; } return undefined; } function normalizeCodexTokenUsage(value: unknown): ThreadTokenUsageSnapshot | undefined { const usage = asObject(value); const totalUsage = asObject(usage?.total_token_usage ?? usage?.total); const lastUsage = asObject(usage?.last_token_usage ?? usage?.last); const totalProcessedTokens = asNumber(totalUsage?.total_tokens) ?? asNumber(totalUsage?.totalTokens); const usedTokens = asNumber(lastUsage?.total_tokens) ?? asNumber(lastUsage?.totalTokens) ?? totalProcessedTokens; if (usedTokens === undefined || usedTokens <= 0) { return undefined; } const maxTokens = asNumber(usage?.model_context_window) ?? asNumber(usage?.modelContextWindow); const inputTokens = asNumber(lastUsage?.input_tokens) ?? asNumber(lastUsage?.inputTokens); const cachedInputTokens = asNumber(lastUsage?.cached_input_tokens) ?? asNumber(lastUsage?.cachedInputTokens); const outputTokens = asNumber(lastUsage?.output_tokens) ?? asNumber(lastUsage?.outputTokens); const reasoningOutputTokens = asNumber(lastUsage?.reasoning_output_tokens) ?? asNumber(lastUsage?.reasoningOutputTokens); return { usedTokens, ...(totalProcessedTokens !== undefined && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), ...(inputTokens !== undefined ? { inputTokens } : {}), ...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}), ...(outputTokens !== undefined ? { outputTokens } : {}), ...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}), ...(usedTokens !== undefined ? { lastUsedTokens: usedTokens } : {}), ...(inputTokens !== undefined ? { lastInputTokens: inputTokens } : {}), ...(cachedInputTokens !== undefined ? { lastCachedInputTokens: cachedInputTokens } : {}), ...(outputTokens !== undefined ? { lastOutputTokens: outputTokens } : {}), ...(reasoningOutputTokens !== undefined ? { lastReasoningOutputTokens: reasoningOutputTokens } : {}), compactsAutomatically: true, }; } function toTurnId(value: string | undefined): TurnId | undefined { return value?.trim() ? TurnId.makeUnsafe(value) : undefined; } function toProviderItemId(value: string | undefined): ProviderItemId | undefined { return value?.trim() ? ProviderItemId.makeUnsafe(value) : undefined; } function toTurnStatus(value: unknown): "completed" | "failed" | "cancelled" | "interrupted" { switch (value) { case "completed": case "failed": case "cancelled": case "interrupted": return value; default: return "completed"; } } function normalizeItemType(raw: unknown): string { const type = asString(raw); if (!type) return "item"; return type .replace(/([a-z0-9])([A-Z])/g, "$1 $2") .replace(/[._/-]/g, " ") .replace(/\s+/g, " ") .trim() .toLowerCase(); } function toCanonicalItemType(raw: unknown): CanonicalItemType { const type = normalizeItemType(raw); if (type.includes("user")) return "user_message"; if (type.includes("agent message") || type.includes("assistant")) return "assistant_message"; if (type.includes("reasoning") || type.includes("thought")) return "reasoning"; if (type.includes("plan") || type.includes("todo")) return "plan"; if (type.includes("command")) return "command_execution"; if (type.includes("file change") || type.includes("patch") || type.includes("edit")) return "file_change"; if (type.includes("mcp")) return "mcp_tool_call"; if (type.includes("dynamic tool")) return "dynamic_tool_call"; if (type.includes("collab")) return "collab_agent_tool_call"; if (type.includes("web search")) return "web_search"; if (type.includes("image")) return "image_view"; if (type.includes("review entered")) return "review_entered"; if (type.includes("review exited")) return "review_exited"; if (type.includes("compact")) return "context_compaction"; if (type.includes("error")) return "error"; return "unknown"; } function itemTitle(itemType: CanonicalItemType): string | undefined { switch (itemType) { case "assistant_message": return "Assistant message"; case "user_message": return "User message"; case "reasoning": return "Reasoning"; case "plan": return "Plan"; case "command_execution": return "Ran command"; case "file_change": return "File change"; case "mcp_tool_call": return "MCP tool call"; case "dynamic_tool_call": return "Tool call"; case "web_search": return "Web search"; case "image_view": return "Image view"; case "error": return "Error"; default: return undefined; } } function itemDetail( item: Record<string, unknown>, payload: Record<string, unknown>, ): string | undefined { const nestedResult = asObject(item.result); const candidates = [ asString(item.command), asString(item.title), asString(item.summary), asString(item.text), asString(item.path), asString(item.prompt), asString(nestedResult?.command), asString(payload.command), asString(payload.message), asString(payload.prompt), ]; for (const candidate of candidates) { if (!candidate) continue; const trimmed = candidate.trim(); if (trimmed.length === 0) continue; return trimmed; } return undefined; } function toRequestTypeFromMethod(method: string): CanonicalRequestType { switch (method) { case "item/commandExecution/requestApproval": return "command_execution_approval"; case "item/fileRead/requestApproval": return "file_read_approval"; case "item/fileChange/requestApproval": return "file_change_approval"; case "applyPatchApproval": return "apply_patch_approval"; case "execCommandApproval": return "exec_command_approval"; case "item/tool/requestUserInput": return "tool_user_input"; case "item/tool/call": return "dynamic_tool_call"; case "account/chatgptAuthTokens/refresh": return "auth_tokens_refresh"; default: return "unknown"; } } function toRequestTypeFromKind(kind: unknown): CanonicalRequestType { switch (kind) { case "command": return "command_execution_approval"; case "file-read": return "file_read_approval"; case "file-change": return "file_change_approval"; default: return "unknown"; } } function toRequestTypeFromResolvedPayload( payload: Record<string, unknown> | undefined, ): CanonicalRequestType { const request = asObject(payload?.request); const method = asString(request?.method) ?? asString(payload?.method); if (method) { return toRequestTypeFromMethod(method); } const requestKind = asString(request?.kind) ?? asString(payload?.requestKind); if (requestKind) { return toRequestTypeFromKind(requestKind); } return "unknown"; } function toCanonicalUserInputAnswers( answers: ProviderUserInputAnswers | undefined, ): ProviderUserInputAnswers { if (!answers) { return {}; } const normalized: Record<string, string | readonly string[]> = {}; for (const [questionId, value] of Object.entries(answers)) { if (typeof value === "string") { normalized[questionId] = value; continue; } if (Array.isArray(value)) { const entries = value.filter((entry): entry is string => typeof entry === "string"); normalized[questionId] = entries.length === 1 ? (entries[0] ?? entries) : entries; continue; } const answerObject = asObject(value); const answerList = asArray(answerObject?.answers)?.filter( (entry): entry is string => typeof entry === "string", ); if (answerList) { normalized[questionId] = answerList.length === 1 ? (answerList[0] ?? answerList) : answerList; } } return normalized; } function toUserInputQuestions(payload: Record<string, unknown> | undefined) { const questions = asArray(payload?.questions); if (!questions) { return undefined; } const parsedQuestions = questions .map((entry) => { const question = asObject(entry); if (!question) return undefined; const options = asArray(question.options) ?.map((option) => { const optionRecord = asObject(option); if (!optionRecord) return undefined; const label = asString(optionRecord.label)?.trim(); const description = asString(optionRecord.description)?.trim(); if (!label || !description) { return undefined; } return { label, description }; }) .filter((option): option is { label: string; description: string } => option !== undefined); const id = asString(question.id)?.trim(); const header = asString(question.header)?.trim(); const prompt = asString(question.question)?.trim(); if (!id || !header || !prompt || !options || options.length === 0) { return undefined; } return { id, header, question: prompt, options, }; }) .filter( ( question, ): question is { id: string; header: string; question: string; options: Array<{ label: string; description: string }>; } => question !== undefined, ); return parsedQuestions.length > 0 ? parsedQuestions : undefined; } function toThreadState(