Skip to content

feat: add codex provider via Responses API#349

Open
Kaxtrel wants to merge 1 commit intoclaude-code-best:mainfrom
Kaxtrel:feat/codex-provider
Open

feat: add codex provider via Responses API#349
Kaxtrel wants to merge 1 commit intoclaude-code-best:mainfrom
Kaxtrel:feat/codex-provider

Conversation

@Kaxtrel
Copy link
Copy Markdown

@Kaxtrel Kaxtrel commented Apr 24, 2026

概述

本 PR 新增独立的 codex provider,用于接入基于 OpenAI Responses API 的 Codex / GPT-5.x 编码模型。

背景

当前项目里的 openai provider 基于 Chat Completions 实现,而 Codex 风格网关依赖的是 Responses API,工具调用、多轮续轮、消息结构都不一样,继续复用 openai provider 会让实现变得混乱且难维护。

因此这里采用独立 provider 的方式接入:

  • 单独新增 codex provider
  • 固定走 Responses API
  • 固定使用原生工具调用 + full-history replay

主要改动

  • 新增独立的 codex provider
  • 基于 Responses API 实现请求、流式解析、错误处理与工具调用
  • 增加 Codex 专用的 request preflight 校验
  • 增加多轮工具调用所需的 call id 处理与 full-history replay 逻辑
  • 接入 provider 选择、模型配置、settings、状态展示、tracing 等基础设施
  • 接入首启配置流程(onboarding / provider setup)
  • 增加可选的 ImgBB 图片上传支持,用于部分网关下的图片理解场景
  • 补充 provider/settings 以及 Codex 适配层的相关测试

设计取舍

当前实现明确采用以下策略:

  • 使用 Responses API
  • 使用 native tools
  • 使用 full-history replay
  • 不使用 previous_response_id continuation
  • 不做自动 XML fallback

这样做的原因是当前第三方 Codex 网关对 previous_response_id + function_call_output 兼容性不稳定,而 full-history replay 更容易稳定落地,也更符合当前项目结构。

已知限制

  • 暂不支持 previous_response_id 续轮,默认走 replay
  • 图片能力仍依赖具体第三方网关兼容性
  • input_file 等能力是否可用,仍取决于网关实现情况
  • onboarding 中顺带包含了 Ctrl-C 退出处理修复,以保证配置流程可正常退出

验证

已执行:

  • bun test src/utils/model/__tests__/providers.test.ts
  • bun test src/utils/settings/__tests__/config.test.ts

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Codex API provider as a new selectable model option
    • Added configuration interface for Codex API settings including base URL, API key, model selection, and optional image upload service
  • Bug Fixes

    • Improved exit handling with graceful shutdown on keyboard interrupt
    • Fixed keybinding behavior in theme selection during onboarding

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive support for the Codex provider—an OpenAI Responses API-compatible service. Changes span provider configuration, OAuth setup flows, API integration with message/tool conversion, streaming adapters, error handling, image upload utilities, and utilities for model resolution. The integration is wired throughout the provider selection logic, settings schema, and API routing.

Changes

Cohort / File(s) Summary
Provider Configuration
src/commands/provider.ts, src/utils/managedEnvConstants.ts, src/utils/model/providers.ts, src/utils/model/configs.ts, src/utils/settings/types.ts, src/utils/auth.ts
Adds codex to provider routing, environment variable mapping (CLAUDE_CODE_USE_CODEX), managed env constants, model configs for all Anthropic variants, settings schema validation, and Anthropic auth disable logic.
OAuth & Onboarding UI
src/components/ConsoleOAuthFlow.tsx, src/components/Onboarding.tsx, src/components/ThemePicker.tsx
Introduces new codex_responses_api OAuth flow state with dedicated configuration form (base URL, API key, model, optional image API key), tab navigation, and validation. Updates Ctrl+C exit handling with graceful shutdown callbacks.
Codex API Implementation
src/services/api/codex/index.ts, src/services/api/codex/streaming.ts, src/services/api/codex/client.ts
Core Codex service: message streaming with attempt aggregation (up to 3 retries for incomplete responses), response aggregation from streamed blocks, client factory with request header parsing and usage tracking, and Langfuse observation recording.
Message & Tool Conversion
src/services/api/codex/convertMessages.ts, src/services/api/codex/convertTools.ts, src/services/api/codex/callIds.ts
Converts Anthropic messages to OpenAI Responses API format, including image URL/base64 handling with optional resolver, tool use → function call mapping with normalized call IDs, and deterministic fallback ID generation.
Codex Error Handling & Validation
src/services/api/codex/errors.ts, src/services/api/codex/preflight.ts, src/services/api/codex/model.ts, src/services/api/codex/imageUpload.ts
Configuration validation for required Codex keys, error normalization for HTTP and API-level failures, request sanitization and streaming conversion, model and max-token resolution with environment overrides, base64 image upload to ImgBB with caching and timeout handling.
Codex Test Suite
src/services/api/codex/__tests__/conversion.test.ts, src/services/api/codex/__tests__/errors.test.ts, src/services/api/codex/__tests__/imageUpload.test.ts, src/services/api/codex/__tests__/preflight.test.ts, src/services/api/codex/__tests__/streaming.test.ts
Comprehensive test coverage for message/tool conversion, error handling, image upload with caching and timeouts, request sanitization, and streaming behavior with multi-attempt aggregation.
API Integration & Routing
src/services/api/claude.ts, src/services/langfuse/tracing.ts
Adds early-exit branch in queryModel to dispatch codex provider to queryModelCodex, maps codex to ChatOpenAIResponses Langfuse generation name.
Utilities & Display
src/utils/status.tsx, src/utils/model/__tests__/providers.test.ts, src/utils/settings/__tests__/config.test.ts
Extends API provider status display with Codex endpoint and ImgBB configuration details, updates provider tests to recognize codex type and verify selection logic.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as ConsoleOAuthFlow
    participant Settings as Settings Service
    participant Env as Environment
    participant Validation as Config Validator

    User->>UI: Selects "Codex Responses API"
    UI->>UI: Initialize codex_responses_api state<br/>from process.env.CODEX_*
    UI->>UI: Display configuration form<br/>(base_url, api_key, model, imgbb_api_key)
    User->>UI: Fill form & save
    UI->>Validation: Validate required fields<br/>& base_url is full URL
    alt Validation Fails
        Validation-->>UI: Return validation error
        UI->>UI: Display error, set toRetry
    else Validation Passes
        Validation-->>UI: Fields valid
        UI->>Settings: updateSettingsForSource<br/>(modelType: 'codex')
        Settings-->>UI: Settings updated
        UI->>Env: Set CODEX_API_KEY,<br/>CODEX_BASE_URL, CODEX_MODEL<br/>and optionally CODEX_IMGBB_API_KEY
        Env-->>UI: Env vars synced
        UI->>UI: Transition to success state
        UI-->>User: onDone callback triggered
    end
Loading
sequenceDiagram
    participant Client
    participant Router as queryModel (claude.ts)
    participant Converter as Message/Tool Conversion
    participant CodexClient as Codex API Client
    participant Codex as OpenAI Responses API
    participant Aggregator as Stream Aggregator

    Client->>Router: queryModel(messages, tools, ...)
    Router->>Router: getAPIProvider() → 'codex'
    Router->>Converter: anthropicMessagesToCodexInput(messages)
    Converter->>Converter: Convert tool_use → function_call<br/>Normalize call IDs
    Converter-->>Router: ResponseInputItem[]
    Router->>Converter: anthropicToolsToCodex(tools)
    Converter-->>Router: CodexTool[]
    Router->>CodexClient: getCodexClient().beta.responses.stream(...)
    CodexClient->>Codex: POST /responses (stream: true)
    activate Codex
    loop Streaming Events
        Codex-->>CodexClient: response.created, deltas, done, completed
        CodexClient-->>Router: Yield stream events
        Router->>Aggregator: Accumulate blocks & usage
    end
    deactivate Codex
    alt Response Complete
        Aggregator->>Aggregator: Construct final AssistantMessage
        Aggregator-->>Router: Message
    else Response Incomplete (< 3 attempts)
        Aggregator->>Codex: Retry with new request
        Codex-->>Aggregator: Continue streaming
        Aggregator->>Aggregator: Merge with prior blocks
    else Max Attempts or Error
        Aggregator-->>Router: Normalized error as SystemAPIErrorMessage
    end
    Router-->>Client: Yield AssistantMessage or Error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A Codex hops into our humble warren,
With streaming blocks and function calls to borrow,
OAuth flows dance, settings merge with grace,
While ImgBB caches images in their place—
Three more attempts before we call it done! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: adding a new 'codex' provider via the Responses API. It accurately reflects the primary objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude-code-best
Copy link
Copy Markdown
Owner

provider 应该是写在 /packages/@ant/model-provider 里面的, 看看重构一下位置? @Kaxtrel

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (15)
src/components/Onboarding.tsx (1)

18-18: Optional: prefer src/* alias for this new import.

Line 5 already imports via the src/* alias. Since this import line is new, consider using the alias here as well for consistency.

♻️ Proposed change
-import { gracefulShutdownSync } from '../utils/gracefulShutdown.js'
+import { gracefulShutdownSync } from 'src/utils/gracefulShutdown.js'

As per coding guidelines: "Import from src/* path alias instead of relative paths".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Onboarding.tsx` at line 18, The new import uses a relative
path for gracefulShutdownSync; update the import to use the project alias
(src/*) for consistency with other imports in Onboarding.tsx. Locate the import
statement for gracefulShutdownSync and change its path to the src alias (e.g.,
import { gracefulShutdownSync } from 'src/utils/gracefulShutdown' or the
project’s canonical alias form) so it matches the existing aliased imports in
this file.
src/services/api/codex/model.ts (1)

25-39: resolveCodexMaxTokens silently accepts negative/malformed env values.

parseInt('abc', 10) returns NaN; NaN || undefined correctly falls through. However, parseInt('-1', 10) || undefined returns -1, which would be returned as the max-output-token cap and likely cause an API 400. Consider validating > 0 before returning (or routing through validateBoundedIntEnvVar as done elsewhere in claude.ts for CLAUDE_CODE_MAX_OUTPUT_TOKENS).

♻️ Proposed fix
 export function resolveCodexMaxTokens(
   upperLimit: number,
   maxOutputTokensOverride?: number,
 ): number {
+  const parsePositive = (v: string | undefined): number | undefined => {
+    if (!v) return undefined
+    const n = parseInt(v, 10)
+    return Number.isFinite(n) && n > 0 ? n : undefined
+  }
   return (
     maxOutputTokensOverride ??
-    (process.env.CODEX_MAX_TOKENS
-      ? parseInt(process.env.CODEX_MAX_TOKENS, 10) || undefined
-      : undefined) ??
-    (process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS
-      ? parseInt(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS, 10) || undefined
-      : undefined) ??
+    parsePositive(process.env.CODEX_MAX_TOKENS) ??
+    parsePositive(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) ??
     upperLimit
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/model.ts` around lines 25 - 39, The
resolveCodexMaxTokens function currently returns parsed env values without
validating they are positive integers, allowing negative or zero values (e.g.,
CODEX_MAX_TOKENS or CLAUDE_CODE_MAX_OUTPUT_TOKENS) to become the cap; update
resolveCodexMaxTokens to validate parsed values are > 0 (or reuse the existing
validateBoundedIntEnvVar helper used in claude.ts) before using them, falling
back to the next source or upperLimit if the env value is invalid or
non-positive, and ensure maxOutputTokensOverride is still honored first.
src/services/api/codex/__tests__/errors.test.ts (1)

35-103: Consider adding coverage for generic 5xx and non-Error inputs.

The suite covers 401, 404, 429, the specific 502 "Upstream request failed" branch, preflight, and the Error fallback, but normalizeCodexError also has a status !== null && status >= 500 generic server-error branch (e.g., 500/503/504, or a 502 without the "upstream request failed" phrase) and handles non-Error inputs (plain strings, unknowns). A couple of extra cases here would prevent regressions if the branch ordering is later reshuffled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/__tests__/errors.test.ts` around lines 35 - 103, Add
unit tests for normalizeCodexError to cover the generic 5xx server-error branch
and non-Error inputs: add a test that passes an object with status: 500 (or 503)
and a message that is not the specific 502 upstream text and assert it returns
content matching the generic server error branch and error: 'server_error'; and
add a test that passes a plain string (e.g., 'service unavailable' or 'some
string') and assert it is handled like the non-Error fallback (producing 'API
Error: <string>' or the appropriate fallback error code) to prevent regressions
in branch ordering and input type handling.
src/utils/settings/types.ts (1)

378-381: Description omits the optional CODEX_IMGBB_API_KEY variable.

The AI summary states this description documents "the optional CODEX_IMGBB_API_KEY" along with the other CODEX_* keys, but the actual string only references CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL. Given that CODEX_IMGBB_API_KEY (and related CODEX_IMAGE_UPLOAD_TIMEOUT_MS) is part of the provider-managed env var set and enables the image-upload feature for gateways that require it, consider mentioning it here so users discover the option.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/settings/types.ts` around lines 378 - 381, Update the
.describe(...) string for the API provider type in src/utils/settings/types.ts
to mention the optional CODEX_IMGBB_API_KEY (and related
CODEX_IMAGE_UPLOAD_TIMEOUT_MS) alongside CODEX_API_KEY, CODEX_BASE_URL, and
CODEX_MODEL so users know the image-upload feature and timeout can be configured
when using "codex"; edit the existing descriptive text inside the .describe call
that documents provider-specific env vars to append these CODEX_* entries with
brief context that they enable image uploads and timeout control.
src/services/api/codex/imageUpload.ts (1)

4-4: In-memory cache is unbounded for the process lifetime.

resolvedImageUrls grows one entry per distinct (mediaType, data) pair and is never evicted. In a long-running session that pastes/screenshots many images, the Map retains the full base64-derived SHA-256 key set indefinitely. The values are short URLs (cheap), but the key count itself is unbounded. Consider a small LRU (e.g., capped at 100–500 entries) to bound memory without meaningfully reducing hit rate for the common "same screenshot referenced multiple turns" case.

Also applies to: 119-131

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/imageUpload.ts` at line 4, resolvedImageUrls is an
unbounded Map that can grow indefinitely; replace it with a bounded LRU cache
(cap ~100–500 entries) so keys get evicted when capacity is reached. Update the
declaration and all uses of resolvedImageUrls (the Map referenced by the
resolvedImageUrls symbol and the other occurrence handling image resolution) to
use an LRU cache implementation (either an npm lru-cache or a tiny in-file LRU)
with the same get/set API so lookup and insert logic remains unchanged, and
initialize it with the chosen max size to prevent unbounded memory growth.
src/services/api/codex/__tests__/streaming.test.ts (1)

3-3: Use src/* path alias for the non-mock import.

The asSystemPrompt import uses a deep relative path. Per the coding guideline, src/** files should prefer the src/* alias over relative traversal. The mock.module(...) specifiers must match what the SUT imports and can be left alone, but this regular import can be converted:

♻️ Suggested change
-import { asSystemPrompt } from '../../../../utils/systemPromptType.js'
+import { asSystemPrompt } from 'src/utils/systemPromptType.js'

As per coding guidelines: "Import from src/* path alias instead of relative paths (e.g., import { ... } from 'src/utils/...' rather than import { ... } from '../../utils/...')".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/__tests__/streaming.test.ts` at line 3, Update the
non-mock import of asSystemPrompt in streaming.test.ts to use the project path
alias instead of the deep relative path: replace the relative import of
asSystemPrompt with an import from 'src/utils/systemPromptType' (keep any
mock.module(...) specifiers untouched so mocks still match the SUT). Locate the
import statement that references asSystemPrompt and update its module specifier
to the src/* alias.
src/components/ConsoleOAuthFlow.tsx (2)

1362-1438: Minor: doCodexSave dependency array is missing setOAuthStatus.

For consistency with doOpenAISave (line 950) and doGeminiSave (line 1183) — both of which include setOAuthStatus in their deps — consider adding it here too. setOAuthStatus is a stable prop-bound setter, so this is mostly a hygiene/lint concern rather than a correctness bug.

♻️ Suggested change
-      }, [activeField, codexDisplayValues, codexInputValue, onDone])
+      }, [activeField, codexDisplayValues, codexInputValue, onDone, setOAuthStatus])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ConsoleOAuthFlow.tsx` around lines 1362 - 1438, The
useCallback for doCodexSave is missing setOAuthStatus in its dependency array;
update the dependency array for doCodexSave to include setOAuthStatus (alongside
activeField, codexDisplayValues, codexInputValue, onDone) so the hook deps match
doOpenAISave and doGeminiSave and satisfy linting/stability expectations.

1306-1551: Consider extracting the provider-setup form into a shared component.

The codex_responses_api case duplicates nearly all of the scaffolding from custom_platform / openai_chat_api / gemini_api (field list, display map, buildState, handleEnter, tab keybindings, renderRow, save flow). A generic <ProviderSetupForm fields={...} onSave={...} /> helper would substantially reduce surface area and keep these flows in sync as the UX evolves. Not a blocker for this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ConsoleOAuthFlow.tsx` around lines 1306 - 1551, This case
duplicates the provider-setup form logic across providers; extract the repeated
behavior into a reusable component (e.g., ProviderSetupForm) and wire the codex
case to it: move field list (CODEX_FIELDS), display map (codexDisplayValues),
state builder (buildCodexState), save handler (doCodexSave), enter handler
(handleCodexEnter), keybindings logic, and row renderer (renderCodexRow) into
the new component so codex_responses_api only passes props like fields,
initialValues, fieldMasks, validate/save callback and onDone; ensure the new
component exposes controlled input callbacks (value, onChange, onSubmit) and
returns the final env to be passed to updateSettingsForSource from the codex
save callback.
src/services/api/codex/preflight.ts (1)

21-30: Optional: tighten message-content validation in sanitizeMessageItem.

The current check only asserts that content is an array, leaving per-item shape validation to the remote API. Since normalizeCodexError maps /^Codex preflight:/ to invalid_request but bare server rejections fall through to server_error/unknown, validating the content item types here would give users a clearer, faster-failing diagnostic (e.g., "Codex preflight: unsupported content part …") rather than a 400 from the gateway.

src/services/api/codex/index.ts (2)

254-254: Avoid as any on the usage assignment.

totalUsage is already typed as CodexUsage and the message's usage field is a structurally compatible Anthropic usage shape. Use a double assertion (or widen the AssistantMessage usage type) so a drift in either type is caught by the compiler.

♻️ Suggested fix
-      assistantMessage.message.usage = totalUsage as any
+      assistantMessage.message.usage =
+        totalUsage as unknown as AssistantMessage['message']['usage']

As per coding guidelines: "Avoid using as any in production code; use as unknown as SpecificType for type mismatches or extend interface definitions instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/index.ts` at line 254, The assignment uses a blanket
cast; replace the `as any` on the line where you set
assistantMessage.message.usage = totalUsage with a safe double assertion or
widen the target type so the compiler can catch drifts: cast totalUsage as
unknown as the exact usage type expected by AssistantMessage (e.g.,
AssistantMessage['message']['usage']) or update the AssistantMessage definition
to accept CodexUsage, referencing assistantMessage.message.usage, totalUsage,
CodexUsage and AssistantMessage in your change.

6-6: Use node: protocol and src/* alias for imports.

fs should be node:fs, and the ../../../... imports should be rewritten through the src/* alias. Consolidating here so all imports in this file follow the repo conventions.

♻️ Suggested changes
-import { appendFileSync } from 'fs'
-import type { SystemPrompt } from '../../../utils/systemPromptType.js'
+import { appendFileSync } from 'node:fs'
+import type { SystemPrompt } from 'src/utils/systemPromptType.js'
 import type {
   AssistantMessage,
   Message,
   StreamEvent,
   SystemAPIErrorMessage,
-} from '../../../types/message.js'
-import type { Tools } from '../../../Tool.js'
-import type { SDKAssistantMessageError } from '../../../entrypoints/agentSdkTypes.js'
-import { toolToAPISchema } from '../../../utils/api.js'
+} from 'src/types/message.js'
+import type { Tools } from 'src/Tool.js'
+import type { SDKAssistantMessageError } from 'src/entrypoints/agentSdkTypes.js'
+import { toolToAPISchema } from 'src/utils/api.js'
 import {
   createAssistantAPIErrorMessage,
   normalizeMessagesForAPI,
-} from '../../../utils/messages.js'
-import { logForDebugging } from '../../../utils/debug.js'
-import { getModelMaxOutputTokens } from '../../../utils/context.js'
+} from 'src/utils/messages.js'
+import { logForDebugging } from 'src/utils/debug.js'
+import { getModelMaxOutputTokens } from 'src/utils/context.js'
 import type { Options } from '../claude.js'
-import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
+import { recordLLMObservation } from 'src/services/langfuse/tracing.js'
 import {
   convertMessagesToLangfuse,
   convertOutputToLangfuse,
   convertToolsToLangfuse,
-} from '../../../services/langfuse/convert.js'
+} from 'src/services/langfuse/convert.js'

As per coding guidelines: "Use path alias src/* in imports (configured in tsconfig); avoid relative path imports from src/ modules".

Also applies to: 7-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/index.ts` at line 6, Update the import statements in
this module to use the node: protocol and the project path alias: change "fs"
import to "node:fs" (where appendFileSync is imported) and replace all relative
imports from the src tree (the three-dot ../../../... imports between lines
7-38) with the configured tsconfig path alias form "src/..." so every import in
this file follows the repo convention.
src/services/api/codex/streaming.ts (2)

247-278: Avoid as any on content/usage/stop_reason shaping.

This function threads several as any casts (rawBlocks as any, content as any, agentId as any, usage … as any, getCodexStopReason(...) as any). These hide real type mismatches between the raw Codex blocks and the Anthropic SDK content types, and if the upstream SDK tightens its types these drifts won't be caught at compile time.

Prefer explicit double assertions (as unknown as ContentBlock[]) or narrow helper signatures. Same note applies to the many as any casts in handleCodexStreamEvent (stream_event payloads, Lines 313/361/376/408/426/513/519).

As per coding guidelines: "Avoid using as any in production code; use as unknown as SpecificType for type mismatches or extend interface definitions instead".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/streaming.ts` around lines 247 - 278, The function
rawAssistantBlocksToAssistantMessage (and related spots in
handleCodexStreamEvent) uses multiple unsafe "as any" casts that hide type
mismatches; replace each "as any" with explicit double assertions or proper
narrow helper signatures (e.g., rawBlocks as unknown as ContentBlock[], content
as unknown as AssistantContent[], agentId as unknown as string|null, and the
usage object as unknown as UsageType) and cast getCodexStopReason(...) as
unknown as StopReasonType; update normalizeContentFromAPI and
createAssistantMessage helper signatures if needed so their inputs/outputs use
concrete types instead of any, ensuring all stream_event payloads in
handleCodexStreamEvent are similarly tightened (use as unknown as
SpecificPayloadType) to satisfy the SDK types without losing runtime behavior.

10-18: Use src/* alias and node: protocol for imports.

../../../types/message.js, ../../../Tool.js, ../../../utils/messages.js should go through src/*, and crypto (Line 1) should use the node:crypto protocol.

♻️ Suggested change
-import { randomUUID } from 'crypto'
+import { randomUUID } from 'node:crypto'
 ...
-import type { AssistantMessage, StreamEvent } from '../../../types/message.js'
-import type { Tools } from '../../../Tool.js'
+import type { AssistantMessage, StreamEvent } from 'src/types/message.js'
+import type { Tools } from 'src/Tool.js'
 import {
   createAssistantMessage,
   normalizeContentFromAPI,
-} from '../../../utils/messages.js'
+} from 'src/utils/messages.js'

As per coding guidelines: "Use path alias src/* in imports (configured in tsconfig); avoid relative path imports from src/ modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/streaming.ts` around lines 10 - 18, Update the top
imports in streaming.ts to use the configured path alias and node protocol:
replace relative imports for types and utilities (the imports that bring in
AssistantMessage, StreamEvent, Tools, createAssistantMessage,
normalizeContentFromAPI) to import from their src/* aliased locations instead of
'../../../...' and change any standard crypto import to use the node:crypto
protocol; keep external module imports (getCodexClient, resolveCodexCallId,
toStreamingCodexRequest) as-is but verify their paths also use the src alias if
they come from the local codebase.
src/services/api/codex/__tests__/conversion.test.ts (2)

18-18: Replace as any with typed content fixtures.

The suite uses as any in many places (content arrays, tool-schema fixtures, and the items[0] as any property read at Line 126). Guidelines direct us to prefer as unknown as SpecificType or explicit typed literals. Given these fixtures closely mirror ContentBlock/BetaToolUnion, this also makes the suite resistant to upstream SDK shape drift.

Example narrowing for the fallback-id assertion:

♻️ Suggested change
-    expect((items[0] as any).call_id).toMatch(/^call_[a-f0-9]{24}$/)
+    const first = items[0] as { call_id?: string }
+    expect(first.call_id).toMatch(/^call_[a-f0-9]{24}$/)

As per coding guidelines: "Avoid using as any in production code; use as unknown as SpecificType for type mismatches or extend interface definitions instead".

Also applies to: 31-31, 82-82, 91-91, 116-116, 126-126, 141-141, 172-172, 208-208, 246-246, 264-264, 292-292, 310-310, 332-332, 350-350, 387-387, 390-390

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/__tests__/conversion.test.ts` at line 18, Replace all
occurrences of "as any" in the conversion.test.ts fixtures with proper typed
casts or typed literals (use "as unknown as ContentBlock" for content-block
fixtures and "as unknown as BetaToolUnion" for tool-schema fixtures) and update
the one-off read "items[0] as any" to "items[0] as unknown as ContentBlock" (or
the correct concrete type) so the tests assert against the real SDK shapes;
locate the fixture arrays and the items[0] usage in the test file
(conversion.test.ts) and modify each cast to "as unknown as <SpecificType>" or
convert the literal to match the ContentBlock/BetaToolUnion interfaces.

2-2: Use src/* path alias instead of deep relative imports.

Line 2 reaches out with ../../../../utils/messages.js. Project guidelines require the src/* alias for modules under src/.

♻️ Suggested change
-import { createAssistantMessage, createUserMessage } from '../../../../utils/messages.js'
+import { createAssistantMessage, createUserMessage } from 'src/utils/messages.js'

As per coding guidelines: "Use path alias src/* in imports (configured in tsconfig); avoid relative path imports from src/ modules".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/__tests__/conversion.test.ts` at line 2, Replace the
deep relative import for the message helpers with the project path alias: update
the import that currently pulls createAssistantMessage and createUserMessage via
'../../../../utils/messages.js' to use the src/* alias (e.g., import {
createAssistantMessage, createUserMessage } from 'src/utils/messages') so tests
import the utilities through the configured tsconfig path mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/api/codex/client.ts`:
- Line 40: The timeout value for the OpenAI client is using
parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10) which can produce
NaN for invalid env values; update the client initialization in
src/services/api/codex/client.ts to parse and validate the value from
process.env.API_TIMEOUT_MS (use Number.parseInt or Number, then check isFinite
or Number.isInteger and > 0) and if the result is not a finite positive integer
fall back to the default 600000 before passing it into the OpenAI client timeout
option; ensure you reference the existing parseInt usage so the fallback logic
replaces that expression.
- Line 6: The cached Codex client (cachedClient in
src/services/api/codex/client.ts) holds credentials from
process.env.CODEX_API_KEY/CODEX_BASE_URL and isn't invalidated when those env
vars change; call clearCodexClientCache() whenever environment-based credentials
are mutated (e.g., add clearCodexClientCache() to
applyConfigEnvironmentVariables in provider.ts and after direct process.env
assignments in ConsoleOAuthFlow.tsx), or alternatively modify the client cache
logic (where cachedClient is created/returned) to include
process.env.CODEX_API_KEY and process.env.CODEX_BASE_URL in the cache key so the
client self-invalidates when those variables change.

In `@src/services/api/codex/convertMessages.ts`:
- Around line 270-290: When resolveToolResultCallId(toolResultBlock.tool_use_id,
callIdState) returns null, do not silently continue; instead preserve the
function_call/function_call_output pair by generating a deterministic fallback
id (use the existing createCodexFallbackCallId/seed pattern) and proceed to push
the function_call_output, or alternatively remove any previously pushed orphaned
function_call from items before returning; at minimum emit a log entry
indicating the unresolved tool_result (include tool_use_id and block metadata)
so skips are observable. Ensure this change touches the block handling in
convertMessages (the tool_result branch that calls resolveToolResultCallId,
pushUserMessage, and convertToolResultOutput) and maintains consistent call_id
usage so Codex replays remain balanced.

In `@src/services/api/codex/convertTools.ts`:
- Around line 20-38: The current filter in the tools.flatMap callback lets any
tool with a name pass (via isClientFunctionTool) but only explicitly excludes
two server-side types; change the guard to only allow client-defined function
tools by requiring isClientFunctionTool(tool) AND that value.type is either
undefined, 'custom', or 'function' (i.e., replace the existing if (value.type
=== 'advisor_20260301' || value.type === 'computer_20250124' ||
!isClientFunctionTool(tool)) return [] with an inverted check that returns []
unless isClientFunctionTool(tool) && (value.type === undefined || value.type ===
'custom' || value.type === 'function')). Ensure the rest of the returned shape
(type: 'function', name: tool.name, description, parameters, strict,
defer_loading) remains unchanged.

In `@src/services/api/codex/errors.ts`:
- Around line 49-59: Update getCodexConfigurationError to validate
CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL (in addition to the existing
CODEX_API_KEY check) and return a NormalizedCodexError when any are missing;
reference the function getCodexConfigurationError and the env vars
CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL, aggregating missing variable
names into a clear content message (e.g., "Missing CODEX_* variables: ...") and
use an appropriate error field like 'authentication_failed' so callers receive
the same actionable pre-flight validation as in provider.ts.

In `@src/services/api/codex/imageUpload.ts`:
- Around line 43-59: The pickImgbbImageUrl function currently prefers
payload.data.thumb over payload.data.image which returns lower-fidelity
thumbnails; update pickImgbbImageUrl to prefer the full-resolution image variant
by reordering the candidates to check payload.data.image (and
payload.data.medium if desired) before payload.data.thumb, or remove thumb from
the candidates list so the function returns full-resolution image.url (or
medium) when present; update the candidates array in pickImgbbImageUrl
accordingly.

In `@src/services/api/codex/index.ts`:
- Around line 152-159: The loop repeatedly re-uploads base64 images because
anthropicMessagesToCodexInput is called with resolveBase64ImageUrl:
uploadCodexBase64Image on every attempt; fix by memoizing resolved URLs for the
lifetime of the call: create a Map keyed by (data + mediaType) or a
deterministic fingerprint in the surrounding function, wrap or replace
uploadCodexBase64Image with a cached resolver that first checks the Map and only
uploads if missing, and then pass that cached resolver to
anthropicMessagesToCodexInput (or alternatively pre-resolve all images from
replayMessages into URLs before the continuation loop) so repeats use the cached
URLs and avoid multiple ImgBB uploads across attempts.
- Around line 206-208: The loop currently does "if (!attemptResult?.response) {
continue }", which causes repeated retries of the identical payload up to
MAX_CODEX_CONTINUATIONS; instead, stop retrying so the outer "empty response"
handler can run (or at minimum log the failed attempt) — change the logic around
attemptResult?.response to log the missing-response event and break out of the
continuation loop (or rethrow/return to let the outer empty-response path handle
it) so you don't re-upload the same request repeatedly; touch the block
referencing attemptResult, response, and MAX_CODEX_CONTINUATIONS to implement
this behavior.

In `@src/services/api/codex/model.ts`:
- Line 13: The current cleanModel assignment uses a case-sensitive regex (const
cleanModel = model.replace(/\[1m\]$/, '')) so suffixes like “[1M]” or “[2m]”
aren’t removed; update the replacement to strip both 1m and 2m variants
case-insensitively (e.g., use a regex like /\[(1|2)m\]$/i) so cleanModel
consistently matches parseUserSpecifiedModel and normalizeModelStringForAPI
behavior and allows getModelFamily/override logic to run correctly.

In `@src/services/api/codex/streaming.ts`:
- Around line 90-102: The generated call_id is unstable because
resolveCodexCallId is invoked with different seeds in createToolUseBlock,
responseToRawAssistantBlocks, and the response.output_item.done handler, causing
mismatched ids across stream phases; fix by generating a stable id seed (e.g.,
combine output_index and a sanitized name or item_id) or by caching the
first-computed id for each item_id/output_index in the stream state and reusing
that cached id in resolveCodexCallId across createToolUseBlock,
responseToRawAssistantBlocks, and the response.output_item.done handler so all
phases produce the same deterministic id.
- Around line 648-672: The fallback branch currently consumes the entire
responses.create stream with consumeCodexStream and returns only the aggregated
CodexStreamResult (via selectResponse), which loses incremental StreamEvent
emission when emitPrimaryEvents is true; update the fallback path (after
params.client.responses.create) to mirror the primary-stream behavior by
piping/adapting the AsyncIterable<ResponseStreamEvent> through the same
stream-adapter that produces StreamEvent objects and forward those events to the
caller when params.emitPrimaryEvents (or when attempt === 0 in queryModelCodex)
instead of only calling consumeCodexStream/selectResponse — specifically use the
same conversion logic used for the primary stream (the adapter invoked in the
primary path before consumeCodexStream) so events from
params.client.responses.create are emitted incrementally to the caller and only
fall back to aggregating with consumeCodexStream/selectResponse if emission is
not required.
- Around line 47-57: Replace incorrect usage of usage.input_tokens_details with
usage.prompt_tokens_details in both getCodexUsage and
rawAssistantBlocksToAssistantMessage so cached tokens are read from
usage.prompt_tokens_details.cached_tokens; keep the existing optional chaining
and ?? 0 fallback (e.g., response?.usage?.prompt_tokens_details?.cached_tokens
?? 0) to avoid runtime errors and restore correct cache_read_input_tokens
accounting.

In `@src/utils/managedEnvConstants.ts`:
- Around line 49-50: PROVIDER_MANAGED_ENV_VARS contains duplicate entries for
CODEX_IMAGE_UPLOAD_TIMEOUT_MS and CODEX_IMAGE_URL_TIMEOUT_MS; remove the
duplicate occurrences from the model-defaults section so each constant appears
only once (keep the auth/endpoint placement where they belong). Locate the Set
named PROVIDER_MANAGED_ENV_VARS in src/utils/managedEnvConstants.ts and delete
the lower entries for CODEX_IMAGE_UPLOAD_TIMEOUT_MS and
CODEX_IMAGE_URL_TIMEOUT_MS, leaving the originals in the auth/endpoint group
intact.

---

Nitpick comments:
In `@src/components/ConsoleOAuthFlow.tsx`:
- Around line 1362-1438: The useCallback for doCodexSave is missing
setOAuthStatus in its dependency array; update the dependency array for
doCodexSave to include setOAuthStatus (alongside activeField,
codexDisplayValues, codexInputValue, onDone) so the hook deps match doOpenAISave
and doGeminiSave and satisfy linting/stability expectations.
- Around line 1306-1551: This case duplicates the provider-setup form logic
across providers; extract the repeated behavior into a reusable component (e.g.,
ProviderSetupForm) and wire the codex case to it: move field list
(CODEX_FIELDS), display map (codexDisplayValues), state builder
(buildCodexState), save handler (doCodexSave), enter handler (handleCodexEnter),
keybindings logic, and row renderer (renderCodexRow) into the new component so
codex_responses_api only passes props like fields, initialValues, fieldMasks,
validate/save callback and onDone; ensure the new component exposes controlled
input callbacks (value, onChange, onSubmit) and returns the final env to be
passed to updateSettingsForSource from the codex save callback.

In `@src/components/Onboarding.tsx`:
- Line 18: The new import uses a relative path for gracefulShutdownSync; update
the import to use the project alias (src/*) for consistency with other imports
in Onboarding.tsx. Locate the import statement for gracefulShutdownSync and
change its path to the src alias (e.g., import { gracefulShutdownSync } from
'src/utils/gracefulShutdown' or the project’s canonical alias form) so it
matches the existing aliased imports in this file.

In `@src/services/api/codex/__tests__/conversion.test.ts`:
- Line 18: Replace all occurrences of "as any" in the conversion.test.ts
fixtures with proper typed casts or typed literals (use "as unknown as
ContentBlock" for content-block fixtures and "as unknown as BetaToolUnion" for
tool-schema fixtures) and update the one-off read "items[0] as any" to "items[0]
as unknown as ContentBlock" (or the correct concrete type) so the tests assert
against the real SDK shapes; locate the fixture arrays and the items[0] usage in
the test file (conversion.test.ts) and modify each cast to "as unknown as
<SpecificType>" or convert the literal to match the ContentBlock/BetaToolUnion
interfaces.
- Line 2: Replace the deep relative import for the message helpers with the
project path alias: update the import that currently pulls
createAssistantMessage and createUserMessage via '../../../../utils/messages.js'
to use the src/* alias (e.g., import { createAssistantMessage, createUserMessage
} from 'src/utils/messages') so tests import the utilities through the
configured tsconfig path mapping.

In `@src/services/api/codex/__tests__/errors.test.ts`:
- Around line 35-103: Add unit tests for normalizeCodexError to cover the
generic 5xx server-error branch and non-Error inputs: add a test that passes an
object with status: 500 (or 503) and a message that is not the specific 502
upstream text and assert it returns content matching the generic server error
branch and error: 'server_error'; and add a test that passes a plain string
(e.g., 'service unavailable' or 'some string') and assert it is handled like the
non-Error fallback (producing 'API Error: <string>' or the appropriate fallback
error code) to prevent regressions in branch ordering and input type handling.

In `@src/services/api/codex/__tests__/streaming.test.ts`:
- Line 3: Update the non-mock import of asSystemPrompt in streaming.test.ts to
use the project path alias instead of the deep relative path: replace the
relative import of asSystemPrompt with an import from
'src/utils/systemPromptType' (keep any mock.module(...) specifiers untouched so
mocks still match the SUT). Locate the import statement that references
asSystemPrompt and update its module specifier to the src/* alias.

In `@src/services/api/codex/imageUpload.ts`:
- Line 4: resolvedImageUrls is an unbounded Map that can grow indefinitely;
replace it with a bounded LRU cache (cap ~100–500 entries) so keys get evicted
when capacity is reached. Update the declaration and all uses of
resolvedImageUrls (the Map referenced by the resolvedImageUrls symbol and the
other occurrence handling image resolution) to use an LRU cache implementation
(either an npm lru-cache or a tiny in-file LRU) with the same get/set API so
lookup and insert logic remains unchanged, and initialize it with the chosen max
size to prevent unbounded memory growth.

In `@src/services/api/codex/index.ts`:
- Line 254: The assignment uses a blanket cast; replace the `as any` on the line
where you set assistantMessage.message.usage = totalUsage with a safe double
assertion or widen the target type so the compiler can catch drifts: cast
totalUsage as unknown as the exact usage type expected by AssistantMessage
(e.g., AssistantMessage['message']['usage']) or update the AssistantMessage
definition to accept CodexUsage, referencing assistantMessage.message.usage,
totalUsage, CodexUsage and AssistantMessage in your change.
- Line 6: Update the import statements in this module to use the node: protocol
and the project path alias: change "fs" import to "node:fs" (where
appendFileSync is imported) and replace all relative imports from the src tree
(the three-dot ../../../... imports between lines 7-38) with the configured
tsconfig path alias form "src/..." so every import in this file follows the repo
convention.

In `@src/services/api/codex/model.ts`:
- Around line 25-39: The resolveCodexMaxTokens function currently returns parsed
env values without validating they are positive integers, allowing negative or
zero values (e.g., CODEX_MAX_TOKENS or CLAUDE_CODE_MAX_OUTPUT_TOKENS) to become
the cap; update resolveCodexMaxTokens to validate parsed values are > 0 (or
reuse the existing validateBoundedIntEnvVar helper used in claude.ts) before
using them, falling back to the next source or upperLimit if the env value is
invalid or non-positive, and ensure maxOutputTokensOverride is still honored
first.

In `@src/services/api/codex/streaming.ts`:
- Around line 247-278: The function rawAssistantBlocksToAssistantMessage (and
related spots in handleCodexStreamEvent) uses multiple unsafe "as any" casts
that hide type mismatches; replace each "as any" with explicit double assertions
or proper narrow helper signatures (e.g., rawBlocks as unknown as
ContentBlock[], content as unknown as AssistantContent[], agentId as unknown as
string|null, and the usage object as unknown as UsageType) and cast
getCodexStopReason(...) as unknown as StopReasonType; update
normalizeContentFromAPI and createAssistantMessage helper signatures if needed
so their inputs/outputs use concrete types instead of any, ensuring all
stream_event payloads in handleCodexStreamEvent are similarly tightened (use as
unknown as SpecificPayloadType) to satisfy the SDK types without losing runtime
behavior.
- Around line 10-18: Update the top imports in streaming.ts to use the
configured path alias and node protocol: replace relative imports for types and
utilities (the imports that bring in AssistantMessage, StreamEvent, Tools,
createAssistantMessage, normalizeContentFromAPI) to import from their src/*
aliased locations instead of '../../../...' and change any standard crypto
import to use the node:crypto protocol; keep external module imports
(getCodexClient, resolveCodexCallId, toStreamingCodexRequest) as-is but verify
their paths also use the src alias if they come from the local codebase.

In `@src/utils/settings/types.ts`:
- Around line 378-381: Update the .describe(...) string for the API provider
type in src/utils/settings/types.ts to mention the optional CODEX_IMGBB_API_KEY
(and related CODEX_IMAGE_UPLOAD_TIMEOUT_MS) alongside CODEX_API_KEY,
CODEX_BASE_URL, and CODEX_MODEL so users know the image-upload feature and
timeout can be configured when using "codex"; edit the existing descriptive text
inside the .describe call that documents provider-specific env vars to append
these CODEX_* entries with brief context that they enable image uploads and
timeout control.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76bd3dc7-13bc-4a1d-9b69-a506b5ba93bd

📥 Commits

Reviewing files that changed from the base of the PR and between d09f363 and ff41b30.

📒 Files selected for processing (29)
  • src/commands/provider.ts
  • src/components/ConsoleOAuthFlow.tsx
  • src/components/Onboarding.tsx
  • src/components/ThemePicker.tsx
  • src/services/api/claude.ts
  • src/services/api/codex/__tests__/conversion.test.ts
  • src/services/api/codex/__tests__/errors.test.ts
  • src/services/api/codex/__tests__/imageUpload.test.ts
  • src/services/api/codex/__tests__/preflight.test.ts
  • src/services/api/codex/__tests__/streaming.test.ts
  • src/services/api/codex/callIds.ts
  • src/services/api/codex/client.ts
  • src/services/api/codex/convertMessages.ts
  • src/services/api/codex/convertTools.ts
  • src/services/api/codex/errors.ts
  • src/services/api/codex/imageUpload.ts
  • src/services/api/codex/index.ts
  • src/services/api/codex/model.ts
  • src/services/api/codex/preflight.ts
  • src/services/api/codex/streaming.ts
  • src/services/langfuse/tracing.ts
  • src/utils/auth.ts
  • src/utils/managedEnvConstants.ts
  • src/utils/model/__tests__/providers.test.ts
  • src/utils/model/configs.ts
  • src/utils/model/providers.ts
  • src/utils/settings/__tests__/config.test.ts
  • src/utils/settings/types.ts
  • src/utils/status.tsx

import { updateProviderBuckets } from 'src/services/providerUsage/store.js'
import { getProxyFetchOptions } from 'src/utils/proxy.js'

let cachedClient: OpenAI | null = null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm every place that mutates CODEX_* env vars also calls clearCodexClientCache.
rg -nP -C3 '\bCODEX_(API_KEY|BASE_URL|MODEL|IMGBB_API_KEY)\b\s*='
echo '----'
rg -nP -C2 '\bclearCodexClientCache\s*\('

Repository: claude-code-best/claude-code

Length of output: 3769


🏁 Script executed:

# Search for updateSettingsForSource and applyConfigEnvironmentVariables
rg -l "updateSettingsForSource|applyConfigEnvironmentVariables" --type ts --type tsx

# Search for ConsoleOAuthFlow file
fd -i "ConsoleOAuthFlow" --type f

# Search for any direct mutations of process.env in non-test files
rg "process\.env\[.*CODEX" --type ts --type tsx -g "!__tests__" -g "!*.test.ts"

Repository: claude-code-best/claude-code

Length of output: 182


🏁 Script executed:

# Check if there are settings-related functions that might mutate env
rg "process\.env\s*=" --type ts --type tsx -g "!__tests__" -g "!*.test.ts" | head -30

# Find all calls to getCodexClient to understand usage
rg "getCodexClient\s*\(" --type ts --type tsx -C 2

Repository: claude-code-best/claude-code

Length of output: 128


🏁 Script executed:

# Search for all references to applyConfigEnvironmentVariables or similar config functions
rg "applyConfig|updateSettings" --type ts --type tsx -g "!__tests__"

Repository: claude-code-best/claude-code

Length of output: 100


🏁 Script executed:

# Find the mentioned files and functions
fd "ConsoleOAuthFlow" 

# Find provider.ts
fd "provider\.ts"

# Search for updateSettingsForSource
rg "updateSettingsForSource" -C 3

# Search for applyConfigEnvironmentVariables
rg "applyConfigEnvironmentVariables" -C 3

Repository: claude-code-best/claude-code

Length of output: 50384


🏁 Script executed:

# Check for any process.env mutations outside tests
rg "process\.env.*=" -g "!__tests__" -g "!*.test.ts" | grep -i codex

# Look for all places calling clearCodexClientCache
rg "clearCodexClientCache"

Repository: claude-code-best/claude-code

Length of output: 153


🏁 Script executed:

# Search for applyConfigEnvironmentVariables
rg "applyConfigEnvironmentVariables" -C 5

# Search for functions that might apply env vars to process.env
rg "process\.env\[.*\].*=" -g "!__tests__" -g "!*.test.ts" -C 2 | head -40

# Check if updateSettingsForSource implementation mutates process.env
cat src/utils/settings/settings.ts | head -400

Repository: claude-code-best/claude-code

Length of output: 28347


🏁 Script executed:

# Search specifically in ConsoleOAuthFlow.tsx for clearCodexClientCache
cat src/components/ConsoleOAuthFlow.tsx | grep -n "clearCodexClientCache"

# Count calls to updateSettingsForSource in ConsoleOAuthFlow
rg "updateSettingsForSource" src/components/ConsoleOAuthFlow.tsx | wc -l

Repository: claude-code-best/claude-code

Length of output: 74


🏁 Script executed:

# Look for any functions that apply settings to environment variables
rg "Object\.assign.*process\.env|for.*process\.env" -C 3

# Check what happens when settings are saved - look for side effects
rg "saveGlobalConfig|applyEnv|setEnv" -C 2 | head -50

Repository: claude-code-best/claude-code

Length of output: 12946


🏁 Script executed:

# Let's examine getCodexClient implementation in client.ts
cat src/services/api/codex/client.ts

Repository: claude-code-best/claude-code

Length of output: 1674


🏁 Script executed:

# Also check if there's any code path in provider.ts that specifically handles codex provider
grep -A 20 "codex" src/commands/provider.ts | head -40

Repository: claude-code-best/claude-code

Length of output: 1085


🏁 Script executed:

# Find all calls to clearCodexClientCache in production code (not tests)
rg "clearCodexClientCache" -g "!__tests__" -g "!*.test.ts" -C 5

Repository: claude-code-best/claude-code

Length of output: 432


🏁 Script executed:

# Check if getCodexClient is called anywhere that would detect the stale cache
rg "getCodexClient" -g "!__tests__" -g "!*.test.ts" -C 3 | head -60

Repository: claude-code-best/claude-code

Length of output: 2348


🏁 Script executed:

# Look at full ConsoleOAuthFlow.tsx to see all env mutation patterns
cat src/components/ConsoleOAuthFlow.tsx | grep -A 10 -B 5 "for (const \[k, v\] of Object.entries(env))"

Repository: claude-code-best/claude-code

Length of output: 1987


🏁 Script executed:

# Verify lines 27-29 and 46-48 in client.ts correspond to getCodexClient and clearCodexClientCache
sed -n '20,55p' src/services/api/codex/client.ts | cat -n

Repository: claude-code-best/claude-code

Length of output: 1269


🏁 Script executed:

# Check if there's any mechanism that clears caches when settings change
rg "clearCodexClientCache|clearCACertsCache|clearGcpCredentialsCache" -B 3 -A 1 | head -40

Repository: claude-code-best/claude-code

Length of output: 2122


🏁 Script executed:

# Verify the issue: trace from settings mutations to getCodexClient calls
rg "onChangeAppState|settingsChangeDetector" -C 3 | head -50

Repository: claude-code-best/claude-code

Length of output: 3200


clearCodexClientCache() is not called after CODEX_* environment variable mutations.

The cached client caches credentials from process.env.CODEX_API_KEY/CODEX_BASE_URL at creation time. When these are updated at runtime (e.g., ConsoleOAuthFlow.tsx directly assigns to process.env or after updateSettingsForSourceapplyConfigEnvironmentVariables in provider.ts), the stale cache continues to use the original credentials unless clearCodexClientCache() is explicitly invoked.

The invalidation pattern exists in the codebase (e.g., clearCACertsCache() and clearMTLSCache() are called in applyConfigEnvironmentVariables, and onChangeAppState clears other credential caches), but clearCodexClientCache() is missing from both paths. Either ensure all env-mutation paths call this hook, or incorporate CODEX_API_KEY/CODEX_BASE_URL into the cache key so it self-invalidates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/client.ts` at line 6, The cached Codex client
(cachedClient in src/services/api/codex/client.ts) holds credentials from
process.env.CODEX_API_KEY/CODEX_BASE_URL and isn't invalidated when those env
vars change; call clearCodexClientCache() whenever environment-based credentials
are mutated (e.g., add clearCodexClientCache() to
applyConfigEnvironmentVariables in provider.ts and after direct process.env
assignments in ConsoleOAuthFlow.tsx), or alternatively modify the client cache
logic (where cachedClient is created/returned) to include
process.env.CODEX_API_KEY and process.env.CODEX_BASE_URL in the cache key so the
client self-invalidates when those variables change.

apiKey,
...(baseURL && { baseURL }),
maxRetries: options?.maxRetries ?? 0,
timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

parseInt without a NaN guard can pass NaN as the OpenAI client timeout.

If process.env.API_TIMEOUT_MS is set but not a valid integer (e.g., "abc", "10s"), parseInt(..., 10) yields NaN which is then passed through to the OpenAI SDK's timeout. Suggest validating the parsed value and falling back to the default on non-finite results.

🛡️ Suggested hardening
-    timeout: parseInt(process.env.API_TIMEOUT_MS || String(600 * 1000), 10),
+    timeout: (() => {
+      const parsed = Number.parseInt(process.env.API_TIMEOUT_MS ?? '', 10)
+      return Number.isFinite(parsed) && parsed > 0 ? parsed : 600 * 1000
+    })(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/client.ts` at line 40, The timeout value for the
OpenAI client is using parseInt(process.env.API_TIMEOUT_MS || String(600 *
1000), 10) which can produce NaN for invalid env values; update the client
initialization in src/services/api/codex/client.ts to parse and validate the
value from process.env.API_TIMEOUT_MS (use Number.parseInt or Number, then check
isFinite or Number.isInteger and > 0) and if the result is not a finite positive
integer fall back to the default 600000 before passing it into the OpenAI client
timeout option; ensure you reference the existing parseInt usage so the fallback
logic replaces that expression.

Comment on lines +270 to +290
if (block.type === 'tool_result') {
pushUserMessage(items, textParts, imageUrls)
textParts.length = 0
imageUrls.length = 0

const toolResultBlock = block as ToolResultLikeBlock
const callId = resolveToolResultCallId(
toolResultBlock.tool_use_id,
callIdState,
)
if (!callId) {
continue
}

items.push({
type: 'function_call_output',
call_id: callId,
output: await convertToolResultOutput(toolResultBlock.content, options),
})
continue
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silently dropping unresolved tool_result can imbalance the replayed Codex transcript.

When resolveToolResultCallId returns null (non-string tool_use_id or a value that normalizes to empty), the function_call_output item is skipped (continue), but any earlier assistant function_call with the matching id remains in the replay. Codex/Responses API rejects replays where function_call items aren't paired with a function_call_output, which would surface to the user as a preflight/server error rather than a recovery.

Consider one of:

  • Fall back to a deterministic id (e.g., reuse the createCodexFallbackCallId/seed pattern) so the pair is preserved even when the original id is unusable.
  • Track the drop and retroactively remove the orphaned function_call from items before returning.

At minimum, log the skip so regressions are observable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/convertMessages.ts` around lines 270 - 290, When
resolveToolResultCallId(toolResultBlock.tool_use_id, callIdState) returns null,
do not silently continue; instead preserve the
function_call/function_call_output pair by generating a deterministic fallback
id (use the existing createCodexFallbackCallId/seed pattern) and proceed to push
the function_call_output, or alternatively remove any previously pushed orphaned
function_call from items before returning; at minimum emit a log entry
indicating the unresolved tool_result (include tool_use_id and block metadata)
so skips are observable. Ensure this change touches the block handling in
convertMessages (the tool_result branch that calls resolveToolResultCallId,
pushUserMessage, and convertToolResultOutput) and maintains consistent call_id
usage so Codex replays remain balanced.

Comment on lines +20 to +38
return tools.flatMap(tool => {
const value = tool as unknown as Record<string, unknown>
if (
value.type === 'advisor_20260301' ||
value.type === 'computer_20250124' ||
!isClientFunctionTool(tool)
) {
return []
}

return [{
type: 'function',
name: tool.name,
description: tool.description,
parameters: tool.input_schema ?? {},
strict: tool.strict ?? null,
...(tool.defer_loading && { defer_loading: true }),
}]
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the BetaToolUnion members to enumerate server-side tool types.
fd -t f 'messages.d.mts' -p 'beta'
rg -nP 'BetaToolUnion\s*=' --type=ts -C2
rg -nP "type:\s*['\"][a-z_]+_\d{8}['\"]" --type=ts -C1

Repository: claude-code-best/claude-code

Length of output: 2441


🏁 Script executed:

# Read the convertTools.ts file completely
cat -n src/services/api/codex/convertTools.ts

Repository: claude-code-best/claude-code

Length of output: 1421


🏁 Script executed:

# Find the isClientFunctionTool function definition
rg -n "isClientFunctionTool" --type=ts -B2 -A5

Repository: claude-code-best/claude-code

Length of output: 1153


🏁 Script executed:

# Find BetaToolUnion type definition
rg -n "type BetaToolUnion\|interface BetaToolUnion" --type=ts -B1 -A10

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check for test files related to convertTools
fd -t f "conversion.test" -o "*convertTools*test*"

Repository: claude-code-best/claude-code

Length of output: 230


🏁 Script executed:

# Search for tool type definitions with dates to enumerate all server-side tools
rg -n "type:\s*['\"].*_\d{8}['\"]" --type=ts | head -30

Repository: claude-code-best/claude-code

Length of output: 745


🏁 Script executed:

# Check the web search tool structure
cat -n packages/builtin-tools/src/tools/WebSearchTool/adapters/apiAdapter.ts | head -40

Repository: claude-code-best/claude-code

Length of output: 1952


🏁 Script executed:

# Search for bash tool definition
rg -n "bash_20250124\|type: 'bash'" --type=ts -B5 -A10

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Search for text_editor tool definition  
rg -n "text_editor\|type: 'text_editor'" --type=ts -B5 -A10

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Check the conversion test file for examples of tool structures
cat -n src/services/api/codex/__tests__/conversion.test.ts

Repository: claude-code-best/claude-code

Length of output: 13559


Server-side tool filter is incomplete — excludes only two specific types, allowing others through.

The filter at line 22–26 explicitly checks only advisor_20260301 and computer_20250124, but isClientFunctionTool (line 4–15) only verifies the presence of a name field—it never checks type. Server-side Anthropic tools like web_search_20250305 carry a name field (e.g., "web_search") and will pass the guard, then be emitted as Codex function tools. Codex cannot call these native Anthropic tools, so they become false callable entries in the tool schema.

Invert the logic: only emit tools whose type is undefined, 'custom', or 'function' (shapes used by client-defined function tools). This way, any future server-side type: '<name>_<date>' variant is automatically excluded by default instead of requiring piecemeal updates.

♻️ Proposed refactor
 export function anthropicToolsToCodex(
   tools: BetaToolUnion[],
 ): CodexTool[] {
   return tools.flatMap(tool => {
     const value = tool as unknown as Record<string, unknown>
-    if (
-      value.type === 'advisor_20260301' ||
-      value.type === 'computer_20250124' ||
-      !isClientFunctionTool(tool)
-    ) {
+    // Only client-defined function tools are safe to forward: server-side
+    // tools (web_search_*, bash_*, text_editor_*, advisor_*, computer_*,
+    // code_execution_*, memory_*, ...) carry a versioned `type` and aren't
+    // executable by Codex.
+    const type = value.type
+    const isClientFunction =
+      type === undefined || type === 'custom' || type === 'function'
+    if (!isClientFunction || !isClientFunctionTool(tool)) {
       return []
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/convertTools.ts` around lines 20 - 38, The current
filter in the tools.flatMap callback lets any tool with a name pass (via
isClientFunctionTool) but only explicitly excludes two server-side types; change
the guard to only allow client-defined function tools by requiring
isClientFunctionTool(tool) AND that value.type is either undefined, 'custom', or
'function' (i.e., replace the existing if (value.type === 'advisor_20260301' ||
value.type === 'computer_20250124' || !isClientFunctionTool(tool)) return []
with an inverted check that returns [] unless isClientFunctionTool(tool) &&
(value.type === undefined || value.type === 'custom' || value.type ===
'function')). Ensure the rest of the returned shape (type: 'function', name:
tool.name, description, parameters, strict, defer_loading) remains unchanged.

Comment on lines +49 to +59
export function getCodexConfigurationError(): NormalizedCodexError | null {
if (!process.env.CODEX_API_KEY) {
return {
content:
'Missing CODEX_API_KEY. Configure it in settings or your environment before using the codex provider.',
error: 'authentication_failed',
}
}

return null
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incomplete configuration check — also validate CODEX_BASE_URL and CODEX_MODEL.

src/commands/provider.ts (lines 100-116) treats all three of CODEX_API_KEY, CODEX_BASE_URL, and CODEX_MODEL as required, and src/services/api/codex/client.ts silently falls back to the OpenAI default baseURL when CODEX_BASE_URL is unset — which will send Codex traffic to api.openai.com and produce a confusing downstream failure. Please surface the same three-variable check here so the user gets an actionable "Missing CODEX_*" message before the request is issued.

🛡️ Suggested fix
 export function getCodexConfigurationError(): NormalizedCodexError | null {
-  if (!process.env.CODEX_API_KEY) {
+  const missing: string[] = []
+  if (!process.env.CODEX_API_KEY) missing.push('CODEX_API_KEY')
+  if (!process.env.CODEX_BASE_URL) missing.push('CODEX_BASE_URL')
+  if (!process.env.CODEX_MODEL) missing.push('CODEX_MODEL')
+  if (missing.length > 0) {
     return {
-      content:
-        'Missing CODEX_API_KEY. Configure it in settings or your environment before using the codex provider.',
+      content: `Missing ${missing.join(', ')}. Configure via /login, settings.json env, or your shell before using the codex provider.`,
       error: 'authentication_failed',
     }
   }
 
   return null
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getCodexConfigurationError(): NormalizedCodexError | null {
if (!process.env.CODEX_API_KEY) {
return {
content:
'Missing CODEX_API_KEY. Configure it in settings or your environment before using the codex provider.',
error: 'authentication_failed',
}
}
return null
}
export function getCodexConfigurationError(): NormalizedCodexError | null {
const missing: string[] = []
if (!process.env.CODEX_API_KEY) missing.push('CODEX_API_KEY')
if (!process.env.CODEX_BASE_URL) missing.push('CODEX_BASE_URL')
if (!process.env.CODEX_MODEL) missing.push('CODEX_MODEL')
if (missing.length > 0) {
return {
content: `Missing ${missing.join(', ')}. Configure via /login, settings.json env, or your shell before using the codex provider.`,
error: 'authentication_failed',
}
}
return null
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/errors.ts` around lines 49 - 59, Update
getCodexConfigurationError to validate CODEX_API_KEY, CODEX_BASE_URL, and
CODEX_MODEL (in addition to the existing CODEX_API_KEY check) and return a
NormalizedCodexError when any are missing; reference the function
getCodexConfigurationError and the env vars CODEX_API_KEY, CODEX_BASE_URL, and
CODEX_MODEL, aggregating missing variable names into a clear content message
(e.g., "Missing CODEX_* variables: ...") and use an appropriate error field like
'authentication_failed' so callers receive the same actionable pre-flight
validation as in provider.ts.

return process.env.CODEX_MODEL
}

const cleanModel = model.replace(/\[1m\]$/, '')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

[1m] suffix stripping is case-sensitive, diverging from the rest of the codebase.

Elsewhere [1m] is matched case-insensitively (e.g. parseUserSpecifiedModel uses /\[1m]$/i, and normalizeModelStringForAPI uses /\[(1|2)m\]/gi). If a user-specified model arrives as [1M] (or later a [2m] variant flows through here), the suffix will not be stripped and the environment-based family override will be skipped (since getModelFamily still detects the family, but the returned fallback cleanModel will retain the suffix).

♻️ Proposed fix
-  const cleanModel = model.replace(/\[1m\]$/, '')
+  const cleanModel = model.replace(/\[(1|2)m\]$/i, '')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cleanModel = model.replace(/\[1m\]$/, '')
const cleanModel = model.replace(/\[(1|2)m\]$/i, '')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/model.ts` at line 13, The current cleanModel
assignment uses a case-sensitive regex (const cleanModel =
model.replace(/\[1m\]$/, '')) so suffixes like “[1M]” or “[2m]” aren’t removed;
update the replacement to strip both 1m and 2m variants case-insensitively
(e.g., use a regex like /\[(1|2)m\]$/i) so cleanModel consistently matches
parseUserSpecifiedModel and normalizeModelStringForAPI behavior and allows
getModelFamily/override logic to run correctly.

Comment on lines +47 to +57
export function getCodexUsage(
response: Pick<Response, 'usage'> | null | undefined,
): CodexUsage {
return {
input_tokens: response?.usage?.input_tokens ?? 0,
output_tokens: response?.usage?.output_tokens ?? 0,
cache_creation_input_tokens: 0,
cache_read_input_tokens:
response?.usage?.input_tokens_details.cached_tokens ?? 0,
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

OpenAI Responses API usage input_tokens_details optional cached_tokens

💡 Result:

In the OpenAI Responses API, the usage object in API responses includes an optional prompt_tokens_details (or possibly input_tokens_details) field containing a cached_tokens value. This field reports the number of prompt/input tokens served from the prompt cache (cache hits), which qualifies for discounted pricing (e.g., 50% off input tokens). It is present in all requests but is zero for prompts under 1024 tokens, as caching requires at least 1024 tokens. Prompt caching is automatic for supported models (gpt-4o and newer) on prompts >=1024 tokens, with no code changes needed. To optimize, place static content (instructions, tools) at the prompt start. In Responses API, use prompt_cache_key and prompt_cache_retention ("in-memory" or "24h") parameters for better control. Example usage object: "usage": { "prompt_tokens": 2006, "completion_tokens": 300, "total_tokens": 2306, "prompt_tokens_details": { "cached_tokens": 1920 } } This feature reduces latency (up to 80%) and input costs (up to 90% discount on cached portion). Caches last 5-10 minutes to 1 hour or longer with extended retention, scoped to the organization.

Citations:


🏁 Script executed:

cd /tmp && find . -name "streaming.ts" -path "*/services/api/*" 2>/dev/null | head -5

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Locate the file
fd -t f "streaming.ts" | grep -E "services/api"

Repository: claude-code-best/claude-code

Length of output: 108


🏁 Script executed:

# Check file structure
git ls-files | grep -E "services/api.*streaming"

Repository: claude-code-best/claude-code

Length of output: 159


🏁 Script executed:

# Look at the specific file
cat -n src/services/api/codex/streaming.ts | sed -n '40,75p'

Repository: claude-code-best/claude-code

Length of output: 1429


🏁 Script executed:

# Also check for type definitions
fd -t f "Response" -o -name "*.ts" | grep -i type | head -10

Repository: claude-code-best/claude-code

Length of output: 303


🏁 Script executed:

# Find Response type definition
rg -t ts "type Response\s*=|interface Response\s*{" -A 10 | head -50

Repository: claude-code-best/claude-code

Length of output: 216


🏁 Script executed:

# Search for line 266 in the same file
cat -n src/services/api/codex/streaming.ts | sed -n '260,275p'

Repository: claude-code-best/claude-code

Length of output: 817


🏁 Script executed:

# Check imports to understand what Response is
head -30 src/services/api/codex/streaming.ts

Repository: claude-code-best/claude-code

Length of output: 977


🏁 Script executed:

# Check if there are type definitions available for OpenAI Response
rg -t ts "input_tokens_details|prompt_tokens_details" | head -20

Repository: claude-code-best/claude-code

Length of output: 1790


🏁 Script executed:

# Check the actual OpenAI package or type files
fd -t f "responses.mjs" -o -name "*responses*"

Repository: claude-code-best/claude-code

Length of output: 303


🏁 Script executed:

# Look at what the Response type looks like - search in node_modules if available
find . -name "*.d.ts" -path "*/openai/*" -type f 2>/dev/null | head -5

Repository: claude-code-best/claude-code

Length of output: 54


🏁 Script executed:

# Verify the structure more clearly by looking at openaiStreamAdapter
cat src/packages/@ant/model-provider/src/shared/openaiStreamAdapter.ts | sed -n '1,50p'

Repository: claude-code-best/claude-code

Length of output: 171


🏁 Script executed:

# Check what Response type actually contains
rg -A 5 "response\.usage\?" src/services/api/codex/streaming.ts | head -30

Repository: claude-code-best/claude-code

Length of output: 432


🏁 Script executed:

# Find comments explaining the correct field mapping
rg -B 2 -A 2 "prompt_tokens_details" packages/@ant/model-provider/src/shared/openaiStreamAdapter.ts

Repository: claude-code-best/claude-code

Length of output: 799


Incorrect field name breaks cache token tracking — use prompt_tokens_details, not input_tokens_details.

The OpenAI Responses API provides cached token data in usage.prompt_tokens_details.cached_tokens, not usage.input_tokens_details.cached_tokens. Accessing the wrong field will throw a TypeError at runtime (since optional chaining stops after usage), and even if masked by the ?? 0 fallback, would cause cache_read_input_tokens to always be 0, breaking usage and cost tracking.

This occurs at line 55 in getCodexUsage() and line 266 in rawAssistantBlocksToAssistantMessage().

Fix
export function getCodexUsage(
  response: Pick<Response, 'usage'> | null | undefined,
): CodexUsage {
  return {
    input_tokens: response?.usage?.input_tokens ?? 0,
    output_tokens: response?.usage?.output_tokens ?? 0,
    cache_creation_input_tokens: 0,
    cache_read_input_tokens:
-     response?.usage?.input_tokens_details.cached_tokens ?? 0,
+     response?.usage?.prompt_tokens_details?.cached_tokens ?? 0,
  }
}
     cache_read_input_tokens:
-      response.usage?.input_tokens_details.cached_tokens ?? 0,
+      response.usage?.prompt_tokens_details?.cached_tokens ?? 0,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/streaming.ts` around lines 47 - 57, Replace incorrect
usage of usage.input_tokens_details with usage.prompt_tokens_details in both
getCodexUsage and rawAssistantBlocksToAssistantMessage so cached tokens are read
from usage.prompt_tokens_details.cached_tokens; keep the existing optional
chaining and ?? 0 fallback (e.g.,
response?.usage?.prompt_tokens_details?.cached_tokens ?? 0) to avoid runtime
errors and restore correct cache_read_input_tokens accounting.

Comment on lines +90 to +102
function createToolUseBlock(
item: Partial<ResponseFunctionToolCall> & { id?: string },
): RawAssistantBlock {
return {
type: 'tool_use',
id: resolveCodexCallId(
item.call_id ?? item.id,
`tool:${item.name ?? ''}:${item.arguments ?? ''}:${item.id ?? ''}`,
),
name: item.name ?? '',
input: item.arguments ?? '',
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fallback call_id can change across stream phases.

resolveCodexCallId is called with three different seeds depending on which handler runs:

  • createToolUseBlock (Line 97): `tool:${name}:${arguments}:${id}`
  • responseToRawAssistantBlocks (Line 206): `output:${name}:${arguments}`
  • response.output_item.done (Line 481): `done:${name}:${arguments}:${id}`

If the gateway omits call_id (the fallback path's reason to exist), each stage produces a different deterministic id. The consumer sees content_block_start with one id, then rawAssistantBlocksToAssistantMessagenormalizeContentFromAPI uses the done-stage id, and any later function_call_output the host sends must match whichever id the assistant message ultimately carries. Correlation by id can break when call_id is truly missing.

Consider deriving a seed that's stable across phases (e.g., output_index + sanitized name) or caching the first-computed id per item_id/output_index inside the stream state and reusing it in later phases.

Also applies to: 196-212, 458-493

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/streaming.ts` around lines 90 - 102, The generated
call_id is unstable because resolveCodexCallId is invoked with different seeds
in createToolUseBlock, responseToRawAssistantBlocks, and the
response.output_item.done handler, causing mismatched ids across stream phases;
fix by generating a stable id seed (e.g., combine output_index and a sanitized
name or item_id) or by caching the first-computed id for each
item_id/output_index in the stream state and reusing that cached id in
resolveCodexCallId across createToolUseBlock, responseToRawAssistantBlocks, and
the response.output_item.done handler so all phases produce the same
deterministic id.

Comment on lines +648 to +672
} catch (error) {
primaryError = error
}

try {
const fallbackStream = await params.client.responses.create(
toStreamingCodexRequest(params.requestBody),
{ signal: params.signal },
)

const fallbackState = await consumeCodexStream(
fallbackStream as AsyncIterable<ResponseStreamEvent>,
params.start,
)
const fallbackResult = selectResponse(fallbackState)

if (fallbackResult.assistantBlocks.length > 0 || fallbackResult.response) {
return fallbackResult
}
} catch (fallbackError) {
if (primaryError) {
throw primaryError
}
throw fallbackError
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fallback streaming path never yields StreamEvents to the caller.

When the primary stream throws (or returns with no blocks/response) and the fallback via responses.create({ stream: true }) succeeds, events are consumed with consumeCodexStream, which discards all converted StreamEvents — only the aggregated CodexStreamResult is returned. On attempt === 0 (the only attempt where emitPrimaryEvents is true), the caller in queryModelCodex will therefore see no message_start / content_block_delta / message_stop for that attempt, then suddenly a full AssistantMessage at the end. Any UI or observer that renders the assistant's output incrementally loses streaming fidelity whenever the primary path fails.

Consider threading events from the fallback through when emitPrimaryEvents is true:

♻️ Sketch
-  try {
-    const fallbackStream = await params.client.responses.create(
-      toStreamingCodexRequest(params.requestBody),
-      { signal: params.signal },
-    )
-
-    const fallbackState = await consumeCodexStream(
-      fallbackStream as AsyncIterable<ResponseStreamEvent>,
-      params.start,
-    )
-    const fallbackResult = selectResponse(fallbackState)
+  try {
+    const fallbackStream = (await params.client.responses.create(
+      toStreamingCodexRequest(params.requestBody),
+      { signal: params.signal },
+    )) as AsyncIterable<ResponseStreamEvent>
+
+    const fallbackState: CodexStreamState = {
+      contentBlocks: {},
+      completedBlocks: [],
+    }
+    for await (const event of fallbackStream) {
+      const handled = handleCodexStreamEvent({
+        event,
+        partialMessage: fallbackState.partialMessage,
+        contentBlocks: fallbackState.contentBlocks,
+        completedBlocks: fallbackState.completedBlocks,
+        start: params.start,
+      })
+      fallbackState.partialMessage = handled.partialMessage
+      fallbackState.finalResponse = handled.finalResponse ?? fallbackState.finalResponse
+      fallbackState.incompleteResponse =
+        handled.incompleteResponse ?? fallbackState.incompleteResponse
+      fallbackState.failedResponse = handled.failedResponse ?? fallbackState.failedResponse
+      if (params.emitPrimaryEvents !== false) {
+        yield* handled.output
+      }
+    }
+    const fallbackResult = selectResponse(fallbackState)

As per learnings: "Provider-specific API implementations … must use stream adapter pattern to convert third-party API format to Anthropic internal format" — losing the incremental adapter output on the fallback path breaks that contract for the calling UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/api/codex/streaming.ts` around lines 648 - 672, The fallback
branch currently consumes the entire responses.create stream with
consumeCodexStream and returns only the aggregated CodexStreamResult (via
selectResponse), which loses incremental StreamEvent emission when
emitPrimaryEvents is true; update the fallback path (after
params.client.responses.create) to mirror the primary-stream behavior by
piping/adapting the AsyncIterable<ResponseStreamEvent> through the same
stream-adapter that produces StreamEvent objects and forward those events to the
caller when params.emitPrimaryEvents (or when attempt === 0 in queryModelCodex)
instead of only calling consumeCodexStream/selectResponse — specifically use the
same conversion logic used for the primary stream (the adapter invoked in the
primary path before consumeCodexStream) so events from
params.client.responses.create are emitted incrementally to the caller and only
fall back to aggregating with consumeCodexStream/selectResponse if emission is
not required.

Comment on lines +49 to +50
'CODEX_IMAGE_UPLOAD_TIMEOUT_MS',
'CODEX_IMAGE_URL_TIMEOUT_MS',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate entries in PROVIDER_MANAGED_ENV_VARS.

CODEX_IMAGE_UPLOAD_TIMEOUT_MS and CODEX_IMAGE_URL_TIMEOUT_MS appear twice in the same Set — once in the auth/endpoint block (Lines 49-50) and again in the model-defaults block (Lines 98-99). Harmless at runtime (set dedupes) but misleading for readers and likely a merge/rebase artifact. They're conceptually timeout configs, not model defaults, so the auth-block placement is the right one — drop the lower copies.

🧹 Proposed cleanup
   'ANTHROPIC_SMALL_FAST_MODEL',
-  'CODEX_IMAGE_UPLOAD_TIMEOUT_MS',
-  'CODEX_IMAGE_URL_TIMEOUT_MS',
   'ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION',

Also applies to: 98-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/managedEnvConstants.ts` around lines 49 - 50,
PROVIDER_MANAGED_ENV_VARS contains duplicate entries for
CODEX_IMAGE_UPLOAD_TIMEOUT_MS and CODEX_IMAGE_URL_TIMEOUT_MS; remove the
duplicate occurrences from the model-defaults section so each constant appears
only once (keep the auth/endpoint placement where they belong). Locate the Set
named PROVIDER_MANAGED_ENV_VARS in src/utils/managedEnvConstants.ts and delete
the lower entries for CODEX_IMAGE_UPLOAD_TIMEOUT_MS and
CODEX_IMAGE_URL_TIMEOUT_MS, leaving the originals in the auth/endpoint group
intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants