/** * 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 ToolActivityIcon, type ToolActivityNativeAppReference, type ToolActivitySource, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, type RuntimeTaskUsage, ProviderApprovalDecision, ThreadId, ProviderSendTurnInput,} from "@t3tools/contracts";import * as Effect from "effect/Effect";import * as NodeCrypto from "node:crypto";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, describeMcpElicitation, 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;} function asUnknownRecord(value: unknown): Record<string, unknown> | undefined { return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined;} function normalizeMcpIntentTitle(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const normalized = value.trim().replace(/\s+/gu, " "); if (!normalized) return undefined; const characters = Array.from(normalized); return characters.length <= 80 ? normalized : `${characters.slice(0, 79).join("")}…`;} function normalizedHttpUrl(value: unknown): string | undefined { if (typeof value !== "string" || value.length > 4096) return undefined; try { const url = new URL(value); const href = url.href; return (url.protocol === "http:" || url.protocol === "https:") && href.length <= 4096 ? href : undefined; } catch { return undefined; }} function normalizedImageUrl(value: unknown): string | undefined { if (typeof value !== "string" || value.length > 4096) return undefined; try { const url = new URL(value); return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:" ? url.href : undefined; } catch { return undefined; }} function normalizedAppId(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const appId = value.trim(); return appId.length > 0 && appId.length <= 512 && /^[A-Za-z0-9._-]+$/u.test(appId) ? appId : undefined;} function normalizedDisplayName(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const displayName = value.trim().replace(/\s+/gu, " "); return displayName && displayName.length <= 160 ? displayName : undefined;} function normalizedSourceKeyPart(value: string): string { return value.trim().toLowerCase();} function nativeAppSourceKey(appId: string): string { const key = `native-app:${appId.toLowerCase()}`; if (key.length <= 512) return key; const digest = NodeCrypto.createHash("sha256").update(key).digest("hex"); return `${key.slice(0, 512 - digest.length - 1)}:${digest}`;} function browserDisplayName(value: unknown): string | undefined { const normalized = normalizedDisplayName(value)?.toLowerCase(); if (!normalized) return undefined; if (normalized.includes("chrome") || normalized === "chromium") return "Chrome"; if (normalized.includes("edge")) return "Microsoft Edge"; if (normalized.includes("firefox")) return "Firefox"; if (normalized.includes("safari")) return "Safari"; if (normalized.includes("arc")) return "Arc"; if (normalized === "iab" || normalized.includes("in-app")) return "Browser"; return normalizedDisplayName(value);} function browserNativeAppReference(name: string): ToolActivityNativeAppReference | undefined { switch (name) { case "Chrome": return { _tag: "display-name", displayName: "Google Chrome" }; case "Microsoft Edge": case "Firefox": case "Safari": case "Arc": return { _tag: "display-name", displayName: name }; default: return undefined; }} function appDisplayNameFromId(appId: string): string | undefined { const knownNames: Readonly<Record<string, string>> = { "com.apple.finder": "Finder", "com.apple.safari": "Safari", "com.google.chrome": "Chrome", "com.microsoft.edgemac": "Microsoft Edge", "org.mozilla.firefox": "Firefox", "company.thebrowser.browser": "Arc", }; return knownNames[appId.toLowerCase()];} function nativeAppReference(value: unknown): ToolActivityNativeAppReference | undefined { const app = asUnknownRecord(value); if (app?.kind === "appId") { const appId = normalizedAppId(app.appId); return appId ? { _tag: "app-id", appId } : undefined; } if (app?.kind === "displayName") { const displayName = normalizedDisplayName(app.displayName); return displayName ? { _tag: "display-name", displayName } : undefined; } return undefined;} function themedLogoIcon( ...records: ReadonlyArray<Record<string, unknown> | undefined>): ToolActivityIcon | undefined { for (const record of records) { const logoUrl = normalizedImageUrl(record?.logoUrl); if (!logoUrl) continue; const logoUrlDark = normalizedImageUrl(record?.logoUrlDark ?? record?.logoDarkUrl); return { _tag: "themed-logo", logoUrl, ...(logoUrlDark ? { logoUrlDark } : {}), }; } return undefined;} interface McpToolPresentation { readonly toolSurface?: "browser" | "computer"; readonly toolIcon?: ToolActivityIcon; readonly toolSource?: ToolActivitySource;} function mcpToolPresentation( item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>,): McpToolPresentation { const result = asUnknownRecord(item.result); const metadata = asUnknownRecord(result?._meta); const surface = asUnknownRecord(metadata?.["codex/toolSurface"]); const sourceMetadata = asUnknownRecord(metadata?.source); const appContext = asUnknownRecord(item.appContext); const sourceLogo = themedLogoIcon(surface, sourceMetadata, appContext); if (surface?.kind === "browserUse") { const screenshot = asUnknownRecord(surface.screenshot); const browserUse = asUnknownRecord(metadata?.browser_use); const openTabs = Array.isArray(surface.openTabs) ? surface.openTabs : []; const latestOpenTab = openTabs .toReversed() .map(asUnknownRecord) .find((tab) => normalizedHttpUrl(tab?.url) !== undefined); const selectedPage = [ { record: screenshot, url: screenshot?.pageUrl }, { record: browserUse, url: browserUse?.url }, { record: latestOpenTab, url: latestOpenTab?.url }, ] .map((candidate) => ({ ...candidate, pageUrl: normalizedHttpUrl(candidate.url) })) .find((candidate) => candidate.pageUrl !== undefined); const pageUrl = selectedPage?.pageUrl; const faviconUrl = normalizedImageUrl( selectedPage?.record?.faviconUrl ?? selectedPage?.record?.favIconUrl, ); const faviconUrlDark = normalizedImageUrl( selectedPage?.record?.faviconUrlDark ?? selectedPage?.record?.favIconUrlDark, ); const name = browserDisplayName(appContext?.appName) ?? browserDisplayName(surface.browserFamily) ?? browserDisplayName(surface.backend) ?? "Browser"; const nativeBrowserIcon = browserNativeAppReference(name); const sourceIcon = sourceLogo ?? (nativeBrowserIcon ? ({ _tag: "native-app", app: nativeBrowserIcon } as const) : undefined); const sourceKeyPart = normalizedSourceKeyPart(name) || "browser"; return { toolSurface: "browser", ...(pageUrl ? { toolIcon: { _tag: "website", pageUrl, ...(faviconUrl ? { faviconUrl } : {}), ...(faviconUrlDark ? { faviconUrlDark } : {}), } as const, } : {}), toolSource: { key: `browser-use:${sourceKeyPart}`, name, kind: name === "Browser" ? "browser" : "integration", ...(sourceIcon ? { icon: sourceIcon } : {}), }, }; } if (surface?.kind === "computerUse") { const app = nativeAppReference(surface.app); const args = asUnknownRecord(item.arguments); const argumentAppName = normalizedDisplayName(args?.appName) ?? normalizedDisplayName(args?.application) ?? normalizedDisplayName(typeof args?.app === "string" ? args.app : undefined); const name = normalizedDisplayName(appContext?.appName) ?? argumentAppName ?? (app?._tag === "display-name" ? app.displayName : undefined) ?? (app?._tag === "app-id" ? appDisplayNameFromId(app.appId) : undefined) ?? "Computer Use"; const sourceIcon = sourceLogo ?? (app ? ({ _tag: "native-app", app } as const) : undefined); const sourceKey = app ? app._tag === "app-id" ? nativeAppSourceKey(app.appId) : `native-app-name:${normalizedSourceKeyPart(app.displayName)}` : "computer-use"; return { toolSurface: "computer", ...(app ? { toolIcon: { _tag: "native-app", app } as const } : {}), toolSource: { key: sourceKey, name, kind: "computer", ...(sourceIcon ? { icon: sourceIcon } : {}), }, }; } return {};} 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 boundedToolArgument(value: unknown): string | undefined { const normalized = typeof value === "string" ? value.trim().replace(/\s+/gu, " ") : ""; if (!normalized) return undefined; return normalized.length <= 48 ? normalized : `${normalized.slice(0, 47)}…`;} function normalizedMcpToolName(value: string): string { return ( value .split(/__|[./:]/u) .at(-1) ?.trim() ?? value.trim() );} function computerUseToolTitle( item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>, presentation: McpToolPresentation,): string | undefined { if (normalizeItemType(item.server) !== "computer use") return undefined; if (item.status === "failed") return undefined; const tool = normalizeItemType(normalizedMcpToolName(item.tool)).replace(/ /gu, "_"); const inProgress = item.status === "inProgress"; const args = asUnknownRecord(item.arguments); const appName =Lines 1–500 of 2349. 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 ToolActivityIcon, type ToolActivityNativeAppReference, type ToolActivitySource, type ProviderUserInputAnswers, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, type RuntimeTaskUsage, ProviderApprovalDecision, ThreadId, ProviderSendTurnInput, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as NodeCrypto from "node:crypto"; 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, describeMcpElicitation, 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; } function asUnknownRecord(value: unknown): Record<string, unknown> | undefined { return value && typeof value === "object" ? (value as Record<string, unknown>) : undefined; } function normalizeMcpIntentTitle(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const normalized = value.trim().replace(/\s+/gu, " "); if (!normalized) return undefined; const characters = Array.from(normalized); return characters.length <= 80 ? normalized : `${characters.slice(0, 79).join("")}…`; } function normalizedHttpUrl(value: unknown): string | undefined { if (typeof value !== "string" || value.length > 4096) return undefined; try { const url = new URL(value); const href = url.href; return (url.protocol === "http:" || url.protocol === "https:") && href.length <= 4096 ? href : undefined; } catch { return undefined; } } function normalizedImageUrl(value: unknown): string | undefined { if (typeof value !== "string" || value.length > 4096) return undefined; try { const url = new URL(value); return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "data:" ? url.href : undefined; } catch { return undefined; } } function normalizedAppId(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const appId = value.trim(); return appId.length > 0 && appId.length <= 512 && /^[A-Za-z0-9._-]+$/u.test(appId) ? appId : undefined; } function normalizedDisplayName(value: unknown): string | undefined { if (typeof value !== "string") return undefined; const displayName = value.trim().replace(/\s+/gu, " "); return displayName && displayName.length <= 160 ? displayName : undefined; } function normalizedSourceKeyPart(value: string): string { return value.trim().toLowerCase(); } function nativeAppSourceKey(appId: string): string { const key = `native-app:${appId.toLowerCase()}`; if (key.length <= 512) return key; const digest = NodeCrypto.createHash("sha256").update(key).digest("hex"); return `${key.slice(0, 512 - digest.length - 1)}:${digest}`; } function browserDisplayName(value: unknown): string | undefined { const normalized = normalizedDisplayName(value)?.toLowerCase(); if (!normalized) return undefined; if (normalized.includes("chrome") || normalized === "chromium") return "Chrome"; if (normalized.includes("edge")) return "Microsoft Edge"; if (normalized.includes("firefox")) return "Firefox"; if (normalized.includes("safari")) return "Safari"; if (normalized.includes("arc")) return "Arc"; if (normalized === "iab" || normalized.includes("in-app")) return "Browser"; return normalizedDisplayName(value); } function browserNativeAppReference(name: string): ToolActivityNativeAppReference | undefined { switch (name) { case "Chrome": return { _tag: "display-name", displayName: "Google Chrome" }; case "Microsoft Edge": case "Firefox": case "Safari": case "Arc": return { _tag: "display-name", displayName: name }; default: return undefined; } } function appDisplayNameFromId(appId: string): string | undefined { const knownNames: Readonly<Record<string, string>> = { "com.apple.finder": "Finder", "com.apple.safari": "Safari", "com.google.chrome": "Chrome", "com.microsoft.edgemac": "Microsoft Edge", "org.mozilla.firefox": "Firefox", "company.thebrowser.browser": "Arc", }; return knownNames[appId.toLowerCase()]; } function nativeAppReference(value: unknown): ToolActivityNativeAppReference | undefined { const app = asUnknownRecord(value); if (app?.kind === "appId") { const appId = normalizedAppId(app.appId); return appId ? { _tag: "app-id", appId } : undefined; } if (app?.kind === "displayName") { const displayName = normalizedDisplayName(app.displayName); return displayName ? { _tag: "display-name", displayName } : undefined; } return undefined; } function themedLogoIcon( ...records: ReadonlyArray<Record<string, unknown> | undefined> ): ToolActivityIcon | undefined { for (const record of records) { const logoUrl = normalizedImageUrl(record?.logoUrl); if (!logoUrl) continue; const logoUrlDark = normalizedImageUrl(record?.logoUrlDark ?? record?.logoDarkUrl); return { _tag: "themed-logo", logoUrl, ...(logoUrlDark ? { logoUrlDark } : {}), }; } return undefined; } interface McpToolPresentation { readonly toolSurface?: "browser" | "computer"; readonly toolIcon?: ToolActivityIcon; readonly toolSource?: ToolActivitySource; } function mcpToolPresentation( item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>, ): McpToolPresentation { const result = asUnknownRecord(item.result); const metadata = asUnknownRecord(result?._meta); const surface = asUnknownRecord(metadata?.["codex/toolSurface"]); const sourceMetadata = asUnknownRecord(metadata?.source); const appContext = asUnknownRecord(item.appContext); const sourceLogo = themedLogoIcon(surface, sourceMetadata, appContext); if (surface?.kind === "browserUse") { const screenshot = asUnknownRecord(surface.screenshot); const browserUse = asUnknownRecord(metadata?.browser_use); const openTabs = Array.isArray(surface.openTabs) ? surface.openTabs : []; const latestOpenTab = openTabs .toReversed() .map(asUnknownRecord) .find((tab) => normalizedHttpUrl(tab?.url) !== undefined); const selectedPage = [ { record: screenshot, url: screenshot?.pageUrl }, { record: browserUse, url: browserUse?.url }, { record: latestOpenTab, url: latestOpenTab?.url }, ] .map((candidate) => ({ ...candidate, pageUrl: normalizedHttpUrl(candidate.url) })) .find((candidate) => candidate.pageUrl !== undefined); const pageUrl = selectedPage?.pageUrl; const faviconUrl = normalizedImageUrl( selectedPage?.record?.faviconUrl ?? selectedPage?.record?.favIconUrl, ); const faviconUrlDark = normalizedImageUrl( selectedPage?.record?.faviconUrlDark ?? selectedPage?.record?.favIconUrlDark, ); const name = browserDisplayName(appContext?.appName) ?? browserDisplayName(surface.browserFamily) ?? browserDisplayName(surface.backend) ?? "Browser"; const nativeBrowserIcon = browserNativeAppReference(name); const sourceIcon = sourceLogo ?? (nativeBrowserIcon ? ({ _tag: "native-app", app: nativeBrowserIcon } as const) : undefined); const sourceKeyPart = normalizedSourceKeyPart(name) || "browser"; return { toolSurface: "browser", ...(pageUrl ? { toolIcon: { _tag: "website", pageUrl, ...(faviconUrl ? { faviconUrl } : {}), ...(faviconUrlDark ? { faviconUrlDark } : {}), } as const, } : {}), toolSource: { key: `browser-use:${sourceKeyPart}`, name, kind: name === "Browser" ? "browser" : "integration", ...(sourceIcon ? { icon: sourceIcon } : {}), }, }; } if (surface?.kind === "computerUse") { const app = nativeAppReference(surface.app); const args = asUnknownRecord(item.arguments); const argumentAppName = normalizedDisplayName(args?.appName) ?? normalizedDisplayName(args?.application) ?? normalizedDisplayName(typeof args?.app === "string" ? args.app : undefined); const name = normalizedDisplayName(appContext?.appName) ?? argumentAppName ?? (app?._tag === "display-name" ? app.displayName : undefined) ?? (app?._tag === "app-id" ? appDisplayNameFromId(app.appId) : undefined) ?? "Computer Use"; const sourceIcon = sourceLogo ?? (app ? ({ _tag: "native-app", app } as const) : undefined); const sourceKey = app ? app._tag === "app-id" ? nativeAppSourceKey(app.appId) : `native-app-name:${normalizedSourceKeyPart(app.displayName)}` : "computer-use"; return { toolSurface: "computer", ...(app ? { toolIcon: { _tag: "native-app", app } as const } : {}), toolSource: { key: sourceKey, name, kind: "computer", ...(sourceIcon ? { icon: sourceIcon } : {}), }, }; } return {}; } 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 boundedToolArgument(value: unknown): string | undefined { const normalized = typeof value === "string" ? value.trim().replace(/\s+/gu, " ") : ""; if (!normalized) return undefined; return normalized.length <= 48 ? normalized : `${normalized.slice(0, 47)}…`; } function normalizedMcpToolName(value: string): string { return ( value .split(/__|[./:]/u) .at(-1) ?.trim() ?? value.trim() ); } function computerUseToolTitle( item: Extract<CodexLifecycleItem, { readonly type: "mcpToolCall" }>, presentation: McpToolPresentation, ): string | undefined { if (normalizeItemType(item.server) !== "computer use") return undefined; if (item.status === "failed") return undefined; const tool = normalizeItemType(normalizedMcpToolName(item.tool)).replace(/ /gu, "_"); const inProgress = item.status === "inProgress"; const args = asUnknownRecord(item.arguments); const appName =