/** * CodexAdapterLive - Scoped live implementation for the Codex provider adapter. * * Wraps the typed Codex session runtime behind the `CodexAdapter` service * contract and maps runtime failures into the shared `ProviderAdapterError` * algebra. * * @module CodexAdapterLive */import { type CanonicalItemType, type CanonicalRequestType, type CodexSettings, ProviderDriverKind, type ProviderEvent, ProviderInstanceId, type ProviderRuntimeEvent, type ProviderRequestKind, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, ProviderApprovalDecision, ThreadId, ProviderSendTurnInput,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as Crypto from "effect/Crypto";import * as Exit from "effect/Exit";import * as Fiber from "effect/Fiber";import * as FileSystem from "effect/FileSystem";import * as Queue from "effect/Queue";import * as Schema from "effect/Schema";import * as Scope from "effect/Scope";import * as Stream from "effect/Stream";import { ChildProcessSpawner } from "effect/unstable/process";import * as CodexErrors from "effect-codex-app-server/errors";import * as EffectCodexSchema from "effect-codex-app-server/schema"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model";import { getCodexServiceTierOptionValue } from "../../codexModelOptions.ts";import * as McpProviderSession from "../../mcp/McpProviderSession.ts"; import { ProviderAdapterRequestError, ProviderAdapterProcessError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError,} from "../Errors.ts";import { type CodexAdapterShape } from "../Services/CodexAdapter.ts";import { resolveAttachmentPath } from "../../attachmentStore.ts";import { ServerConfig } from "../../config.ts";import { CodexResumeCursorSchema, CodexSessionRuntimeThreadIdMissingError, makeCodexSessionRuntime, type CodexSessionRuntimeError, type CodexSessionRuntimeOptions, type CodexSessionRuntimeShape,} from "./CodexSessionRuntime.ts";import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts";import { resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";const isCodexAppServerProcessExitedError = Schema.is(CodexErrors.CodexAppServerProcessExitedError);const isCodexAppServerTransportError = Schema.is(CodexErrors.CodexAppServerTransportError);const isCodexSessionRuntimeThreadIdMissingError = Schema.is( CodexSessionRuntimeThreadIdMissingError,);const isCodexResumeCursorSchema = Schema.is(CodexResumeCursorSchema); const PROVIDER = ProviderDriverKind.make("codex"); export interface CodexAdapterLiveOptions { readonly instanceId?: ProviderInstanceId; readonly environment?: NodeJS.ProcessEnv; readonly makeRuntime?: ( options: CodexSessionRuntimeOptions, ) => Effect.Effect< CodexSessionRuntimeShape, CodexSessionRuntimeError, ChildProcessSpawner.ChildProcessSpawner | Scope.Scope >; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger;} interface CodexAdapterSessionContext { readonly threadId: ThreadId; readonly scope: Scope.Closeable; readonly runtime: CodexSessionRuntimeShape; readonly eventFiber: Fiber.Fiber<void, never>; stopped: boolean;} function mapCodexRuntimeError( threadId: ThreadId, method: string, error: CodexSessionRuntimeError,): ProviderAdapterError { if (isCodexAppServerProcessExitedError(error) || isCodexAppServerTransportError(error)) { return new ProviderAdapterSessionClosedError({ provider: PROVIDER, threadId, cause: error, }); } if (isCodexSessionRuntimeThreadIdMissingError(error)) { return new ProviderAdapterSessionNotFoundError({ provider: PROVIDER, threadId, cause: error, }); } return new ProviderAdapterRequestError({ provider: PROVIDER, method, detail: error.message, cause: error, });} type CodexLifecycleItem = | EffectCodexSchema.V2ItemStartedNotification["item"] | EffectCodexSchema.V2ItemCompletedNotification["item"]; type CodexToolUserInputQuestion = | EffectCodexSchema.ServerRequest__ToolRequestUserInputQuestion | EffectCodexSchema.ToolRequestUserInputParams__ToolRequestUserInputQuestion; const ApprovalDecisionPayload = Schema.Struct({ decision: ProviderApprovalDecision,}); function readPayload<A>( schema: Schema.Schema<A>, payload: ProviderEvent["payload"],): A | undefined { const isPayload = Schema.is(schema); return isPayload(payload) ? payload : undefined;} function trimText(value: string | undefined | null): string | undefined { const trimmed = value?.trim(); return trimmed && trimmed.length > 0 ? trimmed : undefined;} const FATAL_CODEX_STDERR_SNIPPETS = ["failed to connect to websocket"]; function isFatalCodexProcessStderrMessage(message: string): boolean { const normalized = message.toLowerCase(); return FATAL_CODEX_STDERR_SNIPPETS.some((snippet) => normalized.includes(snippet));} function normalizeCodexTokenUsage( usage: EffectCodexSchema.V2ThreadTokenUsageUpdatedNotification["tokenUsage"],): ThreadTokenUsageSnapshot | undefined { const totalProcessedTokens = usage.total.totalTokens; const usedTokens = usage.last.totalTokens; if (usedTokens === undefined || usedTokens <= 0) { return undefined; } const maxTokens = usage.modelContextWindow ?? undefined; const inputTokens = usage.last.inputTokens; const cachedInputTokens = usage.last.cachedInputTokens; const outputTokens = usage.last.outputTokens; const reasoningOutputTokens = usage.last.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 toTurnStatus( value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"] | "cancelled",): "completed" | "failed" | "cancelled" | "interrupted" { switch (value) { case "completed": case "failed": case "cancelled": case "interrupted": return value; default: return "completed"; }} function normalizeItemType(raw: string | undefined | null): string { const type = trimText(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: string | undefined | null): 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, item?: CodexLifecycleItem): string | undefined { if (itemType === "mcp_tool_call" && item?.type === "mcpToolCall") { return `${item.server} · ${item.tool}`; } 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(itemType: CanonicalItemType, item: CodexLifecycleItem): string | undefined { const itemRecord = item as Record<string, unknown>; const action = itemRecord.action as Record<string, unknown> | undefined; const actionQueries = Array.isArray(action?.queries) ? action.queries : []; const candidates = [ ...(itemType === "web_search" ? [itemRecord.query, action?.query, ...actionQueries, action?.pattern, action?.url] : []), "command" in item ? item.command : undefined, "title" in item ? item.title : undefined, "summary" in item ? item.summary : undefined, "text" in item ? item.text : undefined, "path" in item ? item.path : undefined, "prompt" in item ? item.prompt : undefined, ]; for (const candidate of candidates) { const trimmed = typeof candidate === "string" ? trimText(candidate) : undefined; if (!trimmed) 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: ProviderRequestKind | undefined): 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 toCanonicalUserInputAnswers( answers: EffectCodexSchema.ToolRequestUserInputResponse["answers"],): ProviderUserInputAnswers { return Object.fromEntries( Object.entries(answers).map(([questionId, value]) => { const normalizedAnswers = value.answers.length === 1 ? value.answers[0]! : [...value.answers]; return [questionId, normalizedAnswers] as const; }), );} function toUserInputQuestions(questions: ReadonlyArray<CodexToolUserInputQuestion>) { const parsedQuestions = questions .map((question) => { const options = question.options ?.map((option) => { const label = trimText(option.label); const description = trimText(option.description); if (!label || !description) { return undefined; } return { label, description }; }) .filter((option) => option !== undefined) ?? []; const id = trimText(question.id); const header = trimText(question.header); const prompt = trimText(question.question); if (!id || !header || !prompt || options.length === 0) { return undefined; } return { id, header, question: prompt, options, multiSelect: false, }; }) .filter((question) => question !== undefined); return parsedQuestions.length > 0 ? parsedQuestions : undefined;} function toThreadState( status: EffectCodexSchema.V2ThreadStatusChangedNotification["status"],): "active" | "idle" | "archived" | "closed" | "compacted" | "error" { switch (status.type) { case "idle": return "idle"; case "systemError": return "error"; default: return "active"; }} function contentStreamKindFromMethod( method: string,): | "assistant_text" | "reasoning_text" | "reasoning_summary_text" | "plan_text" | "command_output" | "file_change_output" { switch (method) { case "item/agentMessage/delta": return "assistant_text"; case "item/reasoning/textDelta": return "reasoning_text"; case "item/reasoning/summaryTextDelta": return "reasoning_summary_text"; case "item/commandExecution/outputDelta": return "command_output"; case "item/fileChange/outputDelta": return "file_change_output"; default: return "assistant_text"; }} function asRuntimeItemId(itemId: ProviderEvent["itemId"] & string): RuntimeItemId { return RuntimeItemId.make(itemId);} function asRuntimeRequestId(requestId: string): RuntimeRequestId { return RuntimeRequestId.make(requestId);} function eventRawSource(event: ProviderEvent): NonNullable<ProviderRuntimeEvent["raw"]>["source"] { return event.kind === "request" ? "codex.app-server.request" : "codex.app-server.notification";} function providerRefsFromEvent( event: ProviderEvent,): ProviderRuntimeEvent["providerRefs"] | undefined { const refs: Record<string, string> = {}; if (event.turnId) refs.providerTurnId = event.turnId; if (event.itemId) refs.providerItemId = event.itemId; if (event.requestId) refs.providerRequestId = event.requestId; return Object.keys(refs).length > 0 ? (refs as ProviderRuntimeEvent["providerRefs"]) : undefined;} function runtimeEventBase( event: ProviderEvent, canonicalThreadId: ThreadId,): Omit<ProviderRuntimeEvent, "type" | "payload"> { const refs = providerRefsFromEvent(event); return { eventId: event.id, provider: event.provider, threadId: canonicalThreadId, createdAt: event.createdAt, ...(event.turnId ? { turnId: event.turnId } : {}), ...(event.itemId ? { itemId: asRuntimeItemId(event.itemId) } : {}), ...(event.requestId ? { requestId: asRuntimeRequestId(event.requestId) } : {}), ...(refs ? { providerRefs: refs } : {}), raw: { source: eventRawSource(event), method: event.method, payload: event.payload ?? {}, }, };} function mapItemLifecycle( event: ProviderEvent, canonicalThreadId: ThreadId, lifecycle: "item.started" | "item.updated" | "item.completed",): ProviderRuntimeEvent | undefined { const payload = readPayload(EffectCodexSchema.V2ItemStartedNotification, event.payload) ?? readPayload(EffectCodexSchema.V2ItemCompletedNotification, event.payload); const item = payload?.item; if (!item) { return undefined; } const itemType = toCanonicalItemType(item.type); if (itemType === "unknown" && lifecycle !== "item.updated") { return undefined; } const detail = itemDetail(itemType, item); const status = lifecycle === "item.started" ? "inProgress" : lifecycle === "item.completed" ? "completed" : undefined; return { ...runtimeEventBase(event, canonicalThreadId), type: lifecycle, payload: { itemType, ...(status ? { status } : {}), ...(itemTitle(itemType, item) ? { title: itemTitle(itemType, item) } : {}), ...(detail ? { detail } : {}), ...(event.payload !== undefined ? { data: event.payload } : {}), }, };} function mapToRuntimeEvents( event: ProviderEvent, canonicalThreadId: ThreadId,): ReadonlyArray<ProviderRuntimeEvent> {Lines 1–500 of 1729. Load more
/** * CodexAdapterLive - Scoped live implementation for the Codex provider adapter. * * Wraps the typed Codex session runtime behind the `CodexAdapter` service * contract and maps runtime failures into the shared `ProviderAdapterError` * algebra. * * @module CodexAdapterLive */ import { type CanonicalItemType, type CanonicalRequestType, type CodexSettings, ProviderDriverKind, type ProviderEvent, ProviderInstanceId, type ProviderRuntimeEvent, type ProviderRequestKind, type ThreadTokenUsageSnapshot, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, ProviderApprovalDecision, ThreadId, ProviderSendTurnInput, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Crypto from "effect/Crypto"; import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Queue from "effect/Queue"; import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as Stream from "effect/Stream"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as CodexErrors from "effect-codex-app-server/errors"; import * as EffectCodexSchema from "effect-codex-app-server/schema"; import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; import { getCodexServiceTierOptionValue } from "../../codexModelOptions.ts"; import * as McpProviderSession from "../../mcp/McpProviderSession.ts"; import { ProviderAdapterRequestError, ProviderAdapterProcessError, ProviderAdapterSessionClosedError, ProviderAdapterSessionNotFoundError, ProviderAdapterValidationError, type ProviderAdapterError, } from "../Errors.ts"; import { type CodexAdapterShape } from "../Services/CodexAdapter.ts"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; import { CodexResumeCursorSchema, CodexSessionRuntimeThreadIdMissingError, makeCodexSessionRuntime, type CodexSessionRuntimeError, type CodexSessionRuntimeOptions, type CodexSessionRuntimeShape, } from "./CodexSessionRuntime.ts"; import { type EventNdjsonLogger, makeEventNdjsonLogger } from "./EventNdjsonLogger.ts"; import { resolveCodexLaunchArgs } from "./codexLaunchArgs.ts"; const isCodexAppServerProcessExitedError = Schema.is(CodexErrors.CodexAppServerProcessExitedError); const isCodexAppServerTransportError = Schema.is(CodexErrors.CodexAppServerTransportError); const isCodexSessionRuntimeThreadIdMissingError = Schema.is( CodexSessionRuntimeThreadIdMissingError, ); const isCodexResumeCursorSchema = Schema.is(CodexResumeCursorSchema); const PROVIDER = ProviderDriverKind.make("codex"); export interface CodexAdapterLiveOptions { readonly instanceId?: ProviderInstanceId; readonly environment?: NodeJS.ProcessEnv; readonly makeRuntime?: ( options: CodexSessionRuntimeOptions, ) => Effect.Effect< CodexSessionRuntimeShape, CodexSessionRuntimeError, ChildProcessSpawner.ChildProcessSpawner | Scope.Scope >; readonly nativeEventLogPath?: string; readonly nativeEventLogger?: EventNdjsonLogger; } interface CodexAdapterSessionContext { readonly threadId: ThreadId; readonly scope: Scope.Closeable; readonly runtime: CodexSessionRuntimeShape; readonly eventFiber: Fiber.Fiber<void, never>; stopped: boolean; } function mapCodexRuntimeError( threadId: ThreadId, method: string, error: CodexSessionRuntimeError, ): ProviderAdapterError { if (isCodexAppServerProcessExitedError(error) || isCodexAppServerTransportError(error)) { return new ProviderAdapterSessionClosedError({ provider: PROVIDER, threadId, cause: error, }); } if (isCodexSessionRuntimeThreadIdMissingError(error)) { return new ProviderAdapterSessionNotFoundError({ provider: PROVIDER, threadId, cause: error, }); } return new ProviderAdapterRequestError({ provider: PROVIDER, method, detail: error.message, cause: error, }); } type CodexLifecycleItem = | EffectCodexSchema.V2ItemStartedNotification["item"] | EffectCodexSchema.V2ItemCompletedNotification["item"]; type CodexToolUserInputQuestion = | EffectCodexSchema.ServerRequest__ToolRequestUserInputQuestion | EffectCodexSchema.ToolRequestUserInputParams__ToolRequestUserInputQuestion; const ApprovalDecisionPayload = Schema.Struct({ decision: ProviderApprovalDecision, }); function readPayload<A>( schema: Schema.Schema<A>, payload: ProviderEvent["payload"], ): A | undefined { const isPayload = Schema.is(schema); return isPayload(payload) ? payload : undefined; } function trimText(value: string | undefined | null): string | undefined { const trimmed = value?.trim(); return trimmed && trimmed.length > 0 ? trimmed : undefined; } const FATAL_CODEX_STDERR_SNIPPETS = ["failed to connect to websocket"]; function isFatalCodexProcessStderrMessage(message: string): boolean { const normalized = message.toLowerCase(); return FATAL_CODEX_STDERR_SNIPPETS.some((snippet) => normalized.includes(snippet)); } function normalizeCodexTokenUsage( usage: EffectCodexSchema.V2ThreadTokenUsageUpdatedNotification["tokenUsage"], ): ThreadTokenUsageSnapshot | undefined { const totalProcessedTokens = usage.total.totalTokens; const usedTokens = usage.last.totalTokens; if (usedTokens === undefined || usedTokens <= 0) { return undefined; } const maxTokens = usage.modelContextWindow ?? undefined; const inputTokens = usage.last.inputTokens; const cachedInputTokens = usage.last.cachedInputTokens; const outputTokens = usage.last.outputTokens; const reasoningOutputTokens = usage.last.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 toTurnStatus( value: EffectCodexSchema.V2TurnCompletedNotification["turn"]["status"] | "cancelled", ): "completed" | "failed" | "cancelled" | "interrupted" { switch (value) { case "completed": case "failed": case "cancelled": case "interrupted": return value; default: return "completed"; } } function normalizeItemType(raw: string | undefined | null): string { const type = trimText(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: string | undefined | null): 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, item?: CodexLifecycleItem): string | undefined { if (itemType === "mcp_tool_call" && item?.type === "mcpToolCall") { return `${item.server} · ${item.tool}`; } 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(itemType: CanonicalItemType, item: CodexLifecycleItem): string | undefined { const itemRecord = item as Record<string, unknown>; const action = itemRecord.action as Record<string, unknown> | undefined; const actionQueries = Array.isArray(action?.queries) ? action.queries : []; const candidates = [ ...(itemType === "web_search" ? [itemRecord.query, action?.query, ...actionQueries, action?.pattern, action?.url] : []), "command" in item ? item.command : undefined, "title" in item ? item.title : undefined, "summary" in item ? item.summary : undefined, "text" in item ? item.text : undefined, "path" in item ? item.path : undefined, "prompt" in item ? item.prompt : undefined, ]; for (const candidate of candidates) { const trimmed = typeof candidate === "string" ? trimText(candidate) : undefined; if (!trimmed) 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: ProviderRequestKind | undefined): 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 toCanonicalUserInputAnswers( answers: EffectCodexSchema.ToolRequestUserInputResponse["answers"], ): ProviderUserInputAnswers { return Object.fromEntries( Object.entries(answers).map(([questionId, value]) => { const normalizedAnswers = value.answers.length === 1 ? value.answers[0]! : [...value.answers]; return [questionId, normalizedAnswers] as const; }), ); } function toUserInputQuestions(questions: ReadonlyArray<CodexToolUserInputQuestion>) { const parsedQuestions = questions .map((question) => { const options = question.options ?.map((option) => { const label = trimText(option.label); const description = trimText(option.description); if (!label || !description) { return undefined; } return { label, description }; }) .filter((option) => option !== undefined) ?? []; const id = trimText(question.id); const header = trimText(question.header); const prompt = trimText(question.question); if (!id || !header || !prompt || options.length === 0) { return undefined; } return { id, header, question: prompt, options, multiSelect: false, }; }) .filter((question) => question !== undefined); return parsedQuestions.length > 0 ? parsedQuestions : undefined; } function toThreadState( status: EffectCodexSchema.V2ThreadStatusChangedNotification["status"], ): "active" | "idle" | "archived" | "closed" | "compacted" | "error" { switch (status.type) { case "idle": return "idle"; case "systemError": return "error"; default: return "active"; } } function contentStreamKindFromMethod( method: string, ): | "assistant_text" | "reasoning_text" | "reasoning_summary_text" | "plan_text" | "command_output" | "file_change_output" { switch (method) { case "item/agentMessage/delta": return "assistant_text"; case "item/reasoning/textDelta": return "reasoning_text"; case "item/reasoning/summaryTextDelta": return "reasoning_summary_text"; case "item/commandExecution/outputDelta": return "command_output"; case "item/fileChange/outputDelta": return "file_change_output"; default: return "assistant_text"; } } function asRuntimeItemId(itemId: ProviderEvent["itemId"] & string): RuntimeItemId { return RuntimeItemId.make(itemId); } function asRuntimeRequestId(requestId: string): RuntimeRequestId { return RuntimeRequestId.make(requestId); } function eventRawSource(event: ProviderEvent): NonNullable<ProviderRuntimeEvent["raw"]>["source"] { return event.kind === "request" ? "codex.app-server.request" : "codex.app-server.notification"; } function providerRefsFromEvent( event: ProviderEvent, ): ProviderRuntimeEvent["providerRefs"] | undefined { const refs: Record<string, string> = {}; if (event.turnId) refs.providerTurnId = event.turnId; if (event.itemId) refs.providerItemId = event.itemId; if (event.requestId) refs.providerRequestId = event.requestId; return Object.keys(refs).length > 0 ? (refs as ProviderRuntimeEvent["providerRefs"]) : undefined; } function runtimeEventBase( event: ProviderEvent, canonicalThreadId: ThreadId, ): Omit<ProviderRuntimeEvent, "type" | "payload"> { const refs = providerRefsFromEvent(event); return { eventId: event.id, provider: event.provider, threadId: canonicalThreadId, createdAt: event.createdAt, ...(event.turnId ? { turnId: event.turnId } : {}), ...(event.itemId ? { itemId: asRuntimeItemId(event.itemId) } : {}), ...(event.requestId ? { requestId: asRuntimeRequestId(event.requestId) } : {}), ...(refs ? { providerRefs: refs } : {}), raw: { source: eventRawSource(event), method: event.method, payload: event.payload ?? {}, }, }; } function mapItemLifecycle( event: ProviderEvent, canonicalThreadId: ThreadId, lifecycle: "item.started" | "item.updated" | "item.completed", ): ProviderRuntimeEvent | undefined { const payload = readPayload(EffectCodexSchema.V2ItemStartedNotification, event.payload) ?? readPayload(EffectCodexSchema.V2ItemCompletedNotification, event.payload); const item = payload?.item; if (!item) { return undefined; } const itemType = toCanonicalItemType(item.type); if (itemType === "unknown" && lifecycle !== "item.updated") { return undefined; } const detail = itemDetail(itemType, item); const status = lifecycle === "item.started" ? "inProgress" : lifecycle === "item.completed" ? "completed" : undefined; return { ...runtimeEventBase(event, canonicalThreadId), type: lifecycle, payload: { itemType, ...(status ? { status } : {}), ...(itemTitle(itemType, item) ? { title: itemTitle(itemType, item) } : {}), ...(detail ? { detail } : {}), ...(event.payload !== undefined ? { data: event.payload } : {}), }, }; } function mapToRuntimeEvents( event: ProviderEvent, canonicalThreadId: ThreadId, ): ReadonlyArray<ProviderRuntimeEvent> {