@bitkyc08/opencodex 2.64.0-preview.20260923 → 2.65.0-preview.20260925
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -25
- package/bin/ocx.mjs +16 -2
- package/gui/dist/assets/App-8NMiZxT0.css +1 -0
- package/gui/dist/assets/App-CsYvvpr3.js +50 -0
- package/gui/dist/assets/Tray-DUvc_Wul.js +1 -0
- package/gui/dist/assets/{index-DiBRuK-d.css → index--EWgGQvZ.css} +1 -1
- package/gui/dist/assets/{index-BEq4OCOz.js → index-BB0iHG8-.js} +12 -12
- package/gui/dist/assets/tray-data-f0lTZ4sF.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -4
- package/src/adapters/anthropic.ts +30 -1
- package/src/adapters/claude-cli/adapter.ts +218 -0
- package/src/adapters/claude-cli/profiles.ts +38 -0
- package/src/adapters/codebuddy/profiles.ts +2 -0
- package/src/adapters/coding-agent/profile.ts +12 -4
- package/src/adapters/coding-agent/turn.ts +16 -6
- package/src/adapters/command-code-tool-text.ts +133 -23
- package/src/adapters/cursor/catalog.ts +4 -8
- package/src/adapters/cursor/discovery.ts +10 -5
- package/src/adapters/cursor/effort-map.ts +2 -6
- package/src/adapters/cursor/envelope-echo.ts +10 -5
- package/src/adapters/cursor/request-builder.ts +9 -4
- package/src/adapters/cursor/thread-continuity.ts +53 -0
- package/src/adapters/cursor.ts +29 -13
- package/src/adapters/devin/cloud-direct/chat.ts +3 -1
- package/src/adapters/google-tool-schema.ts +26 -3
- package/src/adapters/identity.ts +214 -2
- package/src/adapters/openai-chat/passthrough.ts +1 -0
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +569 -0
- package/src/adapters/openai-chat.ts +43 -15
- package/src/adapters/openai-responses/canonical-forward.ts +17 -10
- package/src/adapters/openai-responses/passthrough.ts +19 -4
- package/src/adapters/openai-responses/request-strips.ts +25 -0
- package/src/adapters/openai-responses/web-search.ts +22 -4
- package/src/adapters/qoder/profiles.ts +2 -0
- package/src/adapters/registry.ts +8 -0
- package/src/adapters/run-turn-queue.ts +7 -0
- package/src/bridge/response-json.ts +6 -4
- package/src/bridge/sse.ts +7 -5
- package/src/claude/alias.ts +73 -26
- package/src/claude/context-windows.ts +15 -6
- package/src/claude/desktop-3p-library.ts +3 -2
- package/src/claude/desktop-3p.ts +1 -1
- package/src/claude/desktop-first-party.ts +63 -22
- package/src/claude/desktop-picker-profile.ts +302 -0
- package/src/claude/desktop-picker.ts +365 -0
- package/src/claude/desktop-risk.ts +20 -0
- package/src/claude/gateway-cache.ts +13 -4
- package/src/claude/inbound.ts +5 -2
- package/src/claude/intercept/connect-proxy.ts +106 -29
- package/src/claude/intercept/local-ca.ts +76 -18
- package/src/claude/intercept/model-bindings.ts +145 -0
- package/src/claude/intercept/picker-bootstrap.ts +141 -0
- package/src/claude/intercept/picker-ca.ts +126 -0
- package/src/claude/intercept/picker-listener.ts +213 -0
- package/src/claude/intercept/picker-models.ts +101 -0
- package/src/claude/intercept/picker-runtime.ts +330 -0
- package/src/claude/intercept/picker-trust.ts +125 -0
- package/src/claude/intercept/runtime.ts +149 -2
- package/src/claude/model-info.ts +18 -4
- package/src/cli/agent.ts +16 -2
- package/src/cli/capabilities.ts +71 -2
- package/src/cli/claude-desktop.ts +319 -33
- package/src/cli/claude.ts +15 -7
- package/src/cli/codex-shim-autorestore.ts +1 -0
- package/src/cli/dispatch.ts +29 -23
- package/src/cli/effort.ts +16 -6
- package/src/cli/ensure-desired-integrations.ts +29 -4
- package/src/cli/ready.ts +1 -1
- package/src/cli/registry.ts +8 -0
- package/src/cli/restart-scope.ts +9 -1
- package/src/cli/runtime-api.ts +17 -0
- package/src/cli/system-command.ts +11 -12
- package/src/client/connect.ts +12 -2
- package/src/client/state.ts +39 -1
- package/src/clients/config-export.ts +57 -9
- package/src/codex/auth-context.ts +51 -11
- package/src/codex/catalog/derive-entry.ts +5 -5
- package/src/codex/catalog/gather-capture.ts +25 -6
- package/src/codex/catalog/metadata.ts +3 -3
- package/src/codex/catalog/parsing.ts +39 -1
- package/src/codex/catalog/provider-models.ts +8 -6
- package/src/codex/catalog/retained-sync.ts +17 -7
- package/src/codex/catalog/sync.ts +2 -2
- package/src/codex/desktop-app-restart.ts +8 -0
- package/src/codex/desktop-switches.ts +9 -1
- package/src/codex/home.ts +43 -4
- package/src/codex/inject/config-toml.ts +137 -0
- package/src/codex/inject/plan.ts +23 -0
- package/src/codex/inject/remove.ts +21 -1
- package/src/codex/inject.ts +63 -8
- package/src/codex/journal.ts +36 -0
- package/src/codex/main-account-hard-lock.ts +14 -1
- package/src/codex/main-account-policy-wait.ts +109 -0
- package/src/codex/native-profile-startup.ts +24 -6
- package/src/codex/native-residue.ts +28 -10
- package/src/codex/quota-types.ts +8 -1
- package/src/codex/quota.ts +49 -7
- package/src/codex/runtime.ts +31 -10
- package/src/combos/failover.ts +7 -6
- package/src/combos/resolve.ts +91 -14
- package/src/combos/types.ts +18 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/schema/config-schema.ts +21 -2
- package/src/config/schema/leaf-validators.ts +6 -3
- package/src/config/subagent-models.ts +3 -1
- package/src/generated/compatibility-version.json +275 -163
- package/src/images/artifacts.ts +14 -0
- package/src/integrations/owned-refresh.ts +1 -1
- package/src/integrations/ownership-policy.ts +32 -1
- package/src/integrations/state.ts +3 -1
- package/src/integrations/writer.ts +9 -0
- package/src/lib/config-ownership.ts +3 -0
- package/src/lib/errors.ts +87 -0
- package/src/lib/package-tree-integrity.ts +1 -1
- package/src/lib/request-execution-budget.ts +42 -14
- package/src/lib/request-resend-gate.ts +4 -3
- package/src/lib/retry-delay.ts +7 -1
- package/src/lib/tool-envelope-echo-filter.ts +236 -0
- package/src/lib/upstream-retry.ts +20 -12
- package/src/oauth/index.ts +1 -0
- package/src/oauth/kiro-credentials.ts +14 -1
- package/src/oauth/login-cli.ts +1 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/fast-opt-in.ts +31 -0
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/provider-id-rewrite.ts +2 -0
- package/src/providers/quota/vendor-probes-key.ts +8 -2
- package/src/providers/registry/entries-core.ts +45 -2
- package/src/providers/registry/entries-extended.ts +99 -12
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/types.ts +7 -1
- package/src/providers/resolved-model-policy.ts +11 -4
- package/src/providers/service-tier.ts +10 -3
- package/src/responses/code-mode-helper-compat.ts +3 -0
- package/src/responses/parser.ts +45 -3
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +8 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/claude-messages.ts +69 -14
- package/src/server/grok-upstream-envelope-echo.ts +120 -0
- package/src/server/index/claude-intercept-lifecycle.ts +24 -0
- package/src/server/index/serve-options.ts +2 -2
- package/src/server/index.ts +7 -0
- package/src/server/management/agent-settings-routes.ts +204 -112
- package/src/server/management/claude-desktop-picker-routes.ts +128 -0
- package/src/server/management/combo-routes.ts +59 -2
- package/src/server/management/config-routes.ts +81 -39
- package/src/server/management/context.ts +3 -0
- package/src/server/management/native-integration-routes.ts +169 -93
- package/src/server/management/provider-routes.ts +46 -4
- package/src/server/management/route-registry.ts +10 -0
- package/src/server/management/routing-profile-routes.ts +9 -0
- package/src/server/management/sidebar-routes.ts +52 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/proxy-liveness.ts +38 -3
- package/src/server/request-log-account-rotation.ts +53 -0
- package/src/server/request-log.ts +3 -16
- package/src/server/responses/adapter-dispatch.ts +17 -1
- package/src/server/responses/agent-task-recovery.ts +49 -26
- package/src/server/responses/codex-ws-exchange.ts +18 -7
- package/src/server/responses/codex-ws-wire.ts +27 -4
- package/src/server/responses/core-combo.ts +21 -3
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-opaque-recovery.ts +3 -2
- package/src/server/responses/encrypted-payload.ts +15 -14
- package/src/server/responses/fetch-helpers.ts +1 -1
- package/src/server/responses/input-admission.ts +10 -6
- package/src/server/responses/native-response-control.ts +2 -2
- package/src/server/responses/passthrough-delivery.ts +182 -12
- package/src/server/responses/passthrough-dispatch.ts +102 -44
- package/src/server/responses/policy-fallback.ts +3 -0
- package/src/server/responses/policy-refusal.ts +67 -0
- package/src/server/responses/request-send-budget.ts +4 -0
- package/src/server/responses/run-turn-execution.ts +42 -11
- package/src/server/responses/ws-upstream.ts +2 -1
- package/src/server/system-env-shell.ts +0 -1
- package/src/server/system-env.ts +25 -4
- package/src/service/cli.ts +34 -3
- package/src/tray/assets/opencodex-tray-offline-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-online-update.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning-update.ico +0 -0
- package/src/tray/windows-tray.ps1 +188 -7
- package/src/tray/windows.ts +23 -2
- package/src/types/config.ts +52 -12
- package/src/types/provider.ts +22 -5
- package/src/types/tools.ts +11 -5
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +109 -0
- package/src/update/badge.ts +11 -17
- package/src/update/check-types.ts +9 -0
- package/src/update/desktop-badge.ts +102 -0
- package/src/update/index.ts +48 -8
- package/src/update/install-detection.d.mts +29 -2
- package/src/update/install-detection.mjs +213 -7
- package/src/update/job.ts +17 -12
- package/src/update/notify.ts +16 -11
- package/src/update/pnpm-owner-worker.ts +12 -0
- package/src/update/refresh-scheduler.ts +141 -0
- package/src/usage/cost.ts +31 -4
- package/src/usage/expected-prices.ts +57 -0
- package/assets/download-linux.svg +0 -10
- package/assets/download-macos.svg +0 -10
- package/assets/download-windows.svg +0 -10
- package/gui/dist/assets/App-D5eN1TID.js +0 -50
- package/gui/dist/assets/App-I5AnaSLh.css +0 -1
- package/gui/dist/assets/Tray-Bh_ErQDh.js +0 -1
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{m as e,n as t}from"./index-BB0iHG8-.js";var n={anthropic:`claude-color.svg`,"anthropic-apikey":`claude-color.svg`,"claude-cli":`claude-color.svg`,"azure-openai":`openai.svg`,chatgpt:`openai.svg`,"cloudflare-ai-gateway":`cloudflare-ai-gateway-color.svg`,"cloudflare-workers-ai":`cloudflare-ai-gateway-color.svg`,cline:`cline-color.svg`,"cline-pass":`cline-color.svg`,"command-code":`commandcode-color.svg`,commandcode:`commandcode-color.svg`,cursor:`cursor-color.svg`,deepseek:`deepseek-color.svg`,devin:`devin.svg`,firepass:`firepass-color.svg`,fireworks:`fireworks-color.svg`,github:`github-copilot-color.svg`,"github-copilot":`copilot-color.svg`,"gitlab-duo":`gitlab-duo-color.svg`,google:`gemini-color.svg`,"google-antigravity":`antigravity-color.svg`,"google-vertex":`gemini-color.svg`,groq:`groq-color.svg`,huggingface:`huggingface-color.svg`,kimi:`kimi-color.svg`,"kimi-code":`kimi-color.svg`,"kimi-responses":`kimi-color.svg`,kiro:`kiro-color.svg`,"lm-studio":`lm-studio-color.svg`,"meta-model":`meta.svg`,"meta-muse":`meta.svg`,mistral:`mistral-color.svg`,minimax:`minimax.svg`,"minimax-cn":`minimax.svg`,moonshot:`moonshot-color.svg`,nvidia:`nvidia-color.svg`,ollama:`ollama-color.svg`,"ollama-cloud":`ollama-color.svg`,openai:`openai.svg`,"openai-apikey":`openai.svg`,"opencode-free":`opencode.svg`,"opencode-go":`opencode.svg`,"opencode-zen":`opencode.svg`,openrouter:`openrouter-color.svg`,opper:`opper.svg`,qianfan:`qianfan-color.svg`,qoder:`qoder.svg`,"qoder-cn":`qoder.svg`,alibaba:`alibaba-color.svg`,"alibaba-token-plan":`alibaba-color.svg`,"alibaba-token-plan-intl":`alibaba-color.svg`,baseten:`baseten.svg`,bizrouter:`bizrouter.svg`,cerebras:`cerebras.svg`,crusoe:`crusoe.svg`,deepinfra:`deepinfra.svg`,digitalocean:`digitalocean.svg`,featherless:`featherless.svg`,hyperbolic:`hyperbolic.svg`,kilo:`kilo.svg`,nanogpt:`nanogpt.svg`,nebius:`nebius.svg`,neuralwatt:`neuralwatt.svg`,nous:`nous.svg`,novita:`novita.svg`,orcarouter:`orcarouter.svg`,"orcarouter-oauth":`orcarouter.svg`,packycode:`packycode.svg`,parallel:`parallel.svg`,sambanova:`sambanova.svg`,scaleway:`scaleway.svg`,stepfun:`stepfun-color.svg`,siliconflow:`siliconflow.svg`,synthetic:`synthetic.svg`,together:`together.svg`,umans:`umans.svg`,venice:`venice.svg`,vultr:`vultr.svg`,litellm:`litellm.svg`,zenmux:`zenmux.svg`,zai:`zai.svg`,"zhipu-bigmodel":`zai.svg`,"zhipu-bigmodel-coding":`zai.svg`,"qwen-cloud":`qwen-portal-color.svg`,"vercel-ai-gateway":`vercel-ai-gateway-color.svg`,vllm:`vllm-color.svg`,xai:`grok.svg`,"mimo-free":`xiaomi-color.svg`,mimo:`xiaomi-color.svg`,xiaomi:`xiaomi-color.svg`,"xiaomi-mimo":`xiaomi-color.svg`},r={anthropic:`Anthropic Claude`,"anthropic-apikey":`Anthropic Claude`,chatgpt:`ChatGPT`,openai:`OpenAI (Codex login)`,"openai-apikey":`OpenAI API`,"azure-openai":`Azure OpenAI`,"cloudflare-ai-gateway":`Cloudflare AI Gateway`,"cloudflare-workers-ai":`Cloudflare Workers AI`,cline:`Cline`,"cline-pass":`ClinePass`,nvidia:`NVIDIA NIM`,ollama:`Ollama`,"ollama-cloud":`Ollama Cloud`,xai:`xAI Grok`,"mimo-free":`MiMo Free`,xiaomi:`Xiaomi`,cursor:`Cursor`,deepseek:`DeepSeek`,devin:`Devin`,github:`GitHub`,"github-copilot":`GitHub Copilot`,"gitlab-duo":`GitLab Duo`,openrouter:`OpenRouter`,opper:`Opper`,"opencode-go":`OpenCode Go`,"opencode-free":`OpenCode Free`,"opencode-zen":`OpenCode Zen`,orcarouter:`OrcaRouter - API`,"orcarouter-oauth":`OrcaRouter - Auth`,packycode:`PackyCode`,mistral:`Mistral`,groq:`Groq`,"meta-model":`Meta Model API`,"meta-muse":`Muse Code`,alibaba:`Alibaba Coding Plan`,"alibaba-token-plan":`Alibaba Token Plan`,"alibaba-token-plan-intl":`Alibaba Token Plan (Intl)`,kimi:`Kimi`,"kimi-code":`Kimi`,"kimi-responses":`Kimi`,moonshot:`Moonshot`,google:`Google`,"google-vertex":`Google Vertex`,"lm-studio":`LM Studio`,huggingface:`Hugging Face`,"qwen-cloud":`Qwen Cloud`,siliconflow:`SiliconFlow`,stepfun:`StepFun`,"tencent-coding-plan":`Tencent Cloud Coding Plan`,codebuddy:`CodeBuddy`,"codebuddy-cn":`CodeBuddy CN`,qoder:`Qoder`,"qoder-cn":`Qoder CN`,"vercel-ai-gateway":`Vercel AI Gateway`,vllm:`vLLM`,litellm:`LiteLLM`},i={"command-code":`provider.name.commandCodeAuth`,commandcode:`provider.name.commandCodeApi`,orcarouter:`provider.name.orcaRouterApi`,"orcarouter-oauth":`provider.name.orcaRouterAuth`,volcengine:`provider.name.volcengine`,"volcengine-coding-plan":`provider.name.volcengineCodingPlan`,"volcengine-agent-plan":`provider.name.volcengineAgentPlan`},a=new Set([...Object.keys(r),...Object.keys(i)]);function o(e){let t=e.toLowerCase();return Object.hasOwn(n,t)?n[t]:void 0}function s(e,t){let n=o(e);return n?`/provider-icons/${n}`:void 0}var c=new Set([`cerebras.svg`,`deepinfra.svg`,`neuralwatt.svg`,`nous.svg`,`novita.svg`,`packycode.svg`,`opper.svg`,`siliconflow.svg`,`synthetic.svg`,`zenmux.svg`,`grok.svg`,`kimi-color.svg`,`ollama-color.svg`,`opencode.svg`,`vercel-ai-gateway-color.svg`]),l=new Set([`baseten.svg`,`kilo.svg`,`sambanova.svg`,`venice.svg`,`zai.svg`]),u=new Set([`bizrouter.svg`,`featherless.svg`,`hyperbolic.svg`,`nebius.svg`,`parallel.svg`,`umans.svg`]);function d(e){if(!e)return`image`;let t=e.split(`/`).pop()??``;return c.has(t)?`mask`:l.has(t)?`plate`:u.has(t)?`dark-plate`:`image`}function f(e,t){let n=e.toLowerCase(),a=Object.hasOwn(i,n)?i[n]:void 0;return a?t(a):(Object.hasOwn(r,n)?r[n]:void 0)||(e===n&&/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e)?e.split(`-`).map(e=>e&&e[0].toUpperCase()+e.slice(1)).join(` `):e)}function p(e){return a.has(e.toLowerCase())}function m(e){return e===`command-code`?`commandcode-auth`:e===`commandcode`?`commandcode-api`:e}function h(e,t){let n=e.indexOf(`/`);if(n<=0)return e;let r=e.slice(0,n),i=e.slice(n+1);if(r===`command-code`||r===`commandcode`){let e=i.match(/^([a-z0-9]+)-([a-z0-9]+(?:-[a-z0-9]+)+)$/i);return e&&i.startsWith(`${e[1]}-${e[1]}-`)&&(i=i.slice(e[1].length+1)),`${r===`command-code`?`commandcode-auth`:`commandcode-api`}/${i}`}return e}var g={ko:[{v:0x2386f26fc10000,s:`경`},{v:0xe8d4a51000,s:`조`},{v:1e8,s:`억`},{v:1e4,s:`만`}],zh:[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`亿`},{v:1e4,s:`万`}],"zh-TW":[{v:0x2386f26fc10000,s:`京`},{v:0xe8d4a51000,s:`兆`},{v:1e8,s:`億`},{v:1e4,s:`萬`}]};function _(e){return e.replace(/\.0+$/,``).replace(/(\.\d*?)0+$/,`$1`)}function v(e,t){let n=g[t];if(n){for(let t of n)if(e>=t.v)return`${_((e/t.v).toFixed(1))}${t.s}`;return String(e)}return e<1e4?String(e):e<1e6?`${_((e/1e3).toFixed(1))}K`:e<1e9?`${_((e/1e6).toFixed(1))}M`:e<0xe8d4a51000?`${_((e/1e9).toFixed(1))}B`:`${_((e/0xe8d4a51000).toFixed(1))}T`}var y=t(),b=1e10;function x(e){return e<b?e*1e3:e}function S(e,t){let n=e=>typeof e==`number`&&Number.isFinite(e);if(!n(e.shortPercent)||e.shortPercent<100)return!1;let r=e.shortResetAt;if(!n(r)||r<=0){let r=e.shortObservedAt;if(!n(r))return!1;let i=t-r;return i>=0&&i<=3e5}return x(r)>t}function C(e){return{fiveHourAvailable:e?.shortWindowSeconds===18e3&&typeof e.shortResetAt==`number`,weeklyAvailable:typeof e?.weeklyResetAt==`number`}}function w(e){let t=e?.trim().toLowerCase();return t===`go`||t===`free`}function T(e,t){if(!e)return null;let n=e.shortPercent===void 0&&e.shortResetAt===void 0?e:{...e,fiveHourPercent:e.fiveHourPercent??e.shortPercent,fiveHourResetAt:e.fiveHourResetAt??e.shortResetAt};return w(t)?{...n.monthlyPercent===void 0?{}:{monthlyPercent:n.monthlyPercent},...n.monthlyResetAt===void 0?{}:{monthlyResetAt:n.monthlyResetAt},...n.creditsUsd===void 0?{}:{creditsUsd:n.creditsUsd},...n.resetCredits===void 0?{}:{resetCredits:n.resetCredits},updatedAt:n.updatedAt}:n}function E(e,t,n=Date.now()){if(!e)return null;let r=e=>typeof e==`number`&&Number.isFinite(e),i=r(e.fiveHourPercent)?e.fiveHourPercent:r(e.shortPercent)?e.shortPercent:void 0,a=(w(t)?[e.monthlyPercent]:[e.weeklyPercent,e.monthlyPercent]).filter(r);if(a.length===0)return S({...r(i)?{shortPercent:i}:{},...r(e.fiveHourResetAt??e.shortResetAt)?{shortResetAt:e.fiveHourResetAt??e.shortResetAt}:{},...r(e.shortObservedAt)?{shortObservedAt:e.shortObservedAt}:{}},n)?100:null;let o=r(i)?[...a,i]:a;return o.length?Math.max(...o):null}function D(e){let t=new URLSearchParams({hours:String(e.chartHours),bucketMinutes:String(e.bucketMinutes),metric:e.tokenMetric,aggregation:e.aggregation,grouping:e.chartGrouping});e.models?.length&&t.set(`models`,e.models.join(`,`));for(let n of e.hiddenProviders)t.append(`hiddenProvider`,n);return t}function O(e){return Array.isArray(e)&&e.length<=100&&e.every(e=>typeof e==`string`)?JSON.stringify([...new Set(e)].sort()):void 0}function k(e,t){let n=new Set(t.hiddenProviders),r=e.availableModels.filter(e=>!n.has(e.slice(0,e.indexOf(`/`))));if(t.models?.length===0)return{...e,series:[],availableModels:r};let i=t.models===null?null:new Set(t.models),a=i!==null||n.size>0,o=e.appliedFilters,s=!!o&&O(o.hiddenProviders)===O(t.hiddenProviders)&&(t.models===null?o.models===null:O(o.models)===O(t.models)),c=e.series.filter(e=>e.id===`other`&&e.provider===``?!a||s:!n.has(e.provider)&&(i===null||i.has(`${e.provider}/${e.model}`)||i.has(e.model)));return{...e,series:c,availableModels:r,truncated:e.truncated||(a||o!==void 0)&&!s}}var A={6:15,24:60,72:180,168:360};function j(e){return A[e]}function M(e){if(e<1e3)return String(Math.round(e));let t=[[0xe8d4a51000,`T`],[1e9,`B`],[1e6,`M`],[1e3,`K`]];for(let n=0;n<t.length;n+=1){let[r,i]=t[n];if(e>=r){let a=Math.round(e/r);if(a>=1e3&&n>0){let[r,i]=t[n-1];return`${Math.round(e/r)}${i}`}return`${a}${i}`}}return String(Math.round(e))}function N(e,t){let n=new Map;for(let r of e){let e=r.includes(`/`)?r.slice(0,r.indexOf(`/`)):r,i=n.get(e)??{provider:e,models:[],total:0},a=t.get(r)??0;i.models.push({id:r,total:a}),i.total+=a,n.set(e,i)}return Array.from(n.values()).map(e=>({...e,models:e.models.toSorted((e,t)=>t.total-e.total||e.id.localeCompare(t.id))})).toSorted((e,t)=>t.total-e.total||e.provider.localeCompare(t.provider))}function P(e,t,n,r){let i=new Set(t),a=new Set((e??t).filter(e=>i.has(e)));for(let e of n)r?a.add(e):a.delete(e);return t.length>0&&t.every(e=>a.has(e))?null:t.filter(e=>a.has(e))}function F(e,t=[]){let n={...e};if(typeof n.menuBarTemplate==`string`&&n.menuBarTemplate.trim()===``&&(n.menuBarTemplate=null),n.models!==void 0&&t.length>0){let e=n.models??[],r=new Set(e);e.length===t.length&&t.every(e=>r.has(e))&&(n.models=null)}return n}function I(e,t,n,r,i=8){let a=Math.max(0,t-i*2),o=Math.max(0,n-i*2),s=Math.max(r,1),c=Math.max(e.length-1,1);return e.map((e,t)=>`${i+a*t/c},${i+o*(1-Math.max(0,e)/s)}`).join(` `)}function L(e,t,n,r,i=8){let a=e[0]?.points.length??0;if(a===0)return[];let o=Math.max(0,t-i*2),s=Math.max(0,n-i*2),c=Math.max(r,1),l=Math.min(3,o/Math.max(a*8,1)),u=Math.max(0,o/a-l),d=[];for(let t=0;t<a;t+=1){let n=0;for(let r=0;r<e.length;r+=1){let f=s*Math.max(0,e[r]?.points[t]??0)/c;f>0&&d.push({x:i+o/a*t+l/2,y:i+s-n-f,width:u,height:f,seriesIndex:r,bucketIndex:t}),n+=f}}return d}var R=[`#0A84FF`,`#FF9F0A`,`#30D158`,`#BF5AF2`,`#FF453A`,`#64D2FF`],z=640,B=160,V=28;function H(e,t){return Math.max(...t===`stackedBar`?Array.from({length:e.buckets},(t,n)=>e.series.reduce((e,t)=>e+(t.points[n]??0),0)):e.series.flatMap(e=>e.points),0)}function U(e,t){let n=new Intl.DateTimeFormat(t,{month:`short`,day:`numeric`}),r=Math.max(1,Math.floor((e.buckets-1)/3));return[0,1,2,3].map(t=>{let i=Math.min(e.buckets-1,t*r);return n.format(new Date((e.start+i*e.bucketSeconds)*1e3))})}function W({timeline:e,chartStyle:t,hours:n,loading:r,error:i,onRetry:a,locale:o,t:s}){if(r)return(0,y.jsx)(`div`,{className:`usage-companion-chart-skeleton`,"aria-busy":`true`,"aria-label":s(`usage.companion.loading`)});if(i)return(0,y.jsxs)(`div`,{className:`usage-companion-chart-state`,role:`alert`,children:[(0,y.jsx)(`span`,{children:s(`usage.companion.timelineUnavailable`)}),(0,y.jsx)(`button`,{type:`button`,className:`btn btn-ghost btn-sm`,onClick:a,children:s(`common.retry`)})]});if(!e||e.series.length===0)return(0,y.jsx)(`div`,{className:`usage-companion-chart-state`,children:s(`usage.companion.empty`,{hours:e?.buckets?Math.round(e.buckets*e.bucketSeconds/3600):n})});let c=H(e,t),l=U(e,o),u=V,d=l.map((e,t)=>(0,y.jsx)(`text`,{x:V+584*t/3,y:156,textAnchor:t===0?`start`:t===3?`end`:`middle`,className:`usage-companion-axis-label`,children:e},`${e}-${t}`)),f=t===`line`?e.series.map((e,t)=>(0,y.jsx)(`polyline`,{points:I(e.points,584,104,c,0),transform:`translate(${V},${V})`,fill:`none`,stroke:R[t%R.length],strokeWidth:`2`,strokeLinejoin:`round`,strokeLinecap:`round`},e.id)):L(e.series,584,104,c,0).map(e=>(0,y.jsx)(`rect`,{x:V+e.x,y:V+e.y,width:e.width,height:e.height,fill:R[e.seriesIndex%R.length],rx:`1`},`${e.bucketIndex}-${e.seriesIndex}`));return(0,y.jsxs)(`div`,{className:`usage-companion-chart`,children:[(0,y.jsxs)(`svg`,{viewBox:`0 0 ${z} ${B}`,role:`img`,"aria-label":s(`usage.companion.chartLabel`),children:[(0,y.jsx)(`line`,{x1:V,y1:u,x2:V,y2:132,className:`usage-companion-axis`}),(0,y.jsx)(`line`,{x1:V,y1:132,x2:612,y2:132,className:`usage-companion-axis`}),(0,y.jsx)(`text`,{x:24,y:32,textAnchor:`end`,className:`usage-companion-axis-label`,children:M(c)}),f,d]}),(0,y.jsx)(`div`,{className:`usage-companion-legend`,children:e.series.map((e,t)=>(0,y.jsxs)(`span`,{className:`usage-companion-legend-item`,children:[(0,y.jsx)(`span`,{className:`usage-companion-swatch`,style:{background:R[t%R.length]},"aria-hidden":`true`}),(0,y.jsx)(`span`,{children:e.id})]},e.id))}),e.truncated&&(0,y.jsx)(`p`,{className:`muted text-caption`,children:s(`usage.companion.olderRecordsSkipped`)})]})}var G=e=>typeof e==`number`&&Number.isFinite(e)&&e>=0,K=e=>typeof e==`object`&&e&&!Array.isArray(e)?e:{};function q(e){return Object.entries(K(K(e).providers)).flatMap(([e,t])=>{let n=K(t);return n.disabled===!0?[]:[{name:e,path:e===`openai`?`/api/codex-auth/accounts`:n.authMode===`oauth`?`/api/oauth/accounts?${new URLSearchParams({provider:e,quota:`1`})}`:n.hasApiKey===!0&&n.authMode!==`forward`?`/api/providers/keys?${new URLSearchParams({name:e,quota:`1`})}`:null}]})}function J(e){let t=K(e),n=t.accounts??t.keys;if(!Array.isArray(n))throw Error(`Invalid account roster`);return n.map(e=>{let n=K(e);if(typeof n.id!=`string`)throw Error(`Invalid account identifier`);let r=typeof n.email==`string`?X(n.email):void 0,i=[n.alias,n.label,r,n.logLabel,n.id].find(e=>typeof e==`string`&&e.length),a=n.quotaUnavailable===!0||n.quotaMode===`unsupported`||!n.quota?null:K(n.quota),o=typeof n.plan==`string`?n.plan:void 0,s=t.activeAccountId??t.activeId??t.activeCodexAccountId;return{id:n.id,label:i,email:r,plan:o,unavailable:n.quotaUnavailable===!0,active:typeof s==`string`?s===n.id:n.active===!0,status:typeof K(n.health).status==`string`?K(n.health).status:void 0,quotaFailure:typeof n.quotaFailure==`string`?n.quotaFailure:void 0,quota:T(a,o)}})}function Y(e){if(!G(e)||e===0)return null;let t=e<0xe8d4a51000?e*1e3:e;return Number.isFinite(new Date(t).getTime())?t:null}function X(e){let[t,n]=e.split(`@`);if(!n)return`•••`;let r=n.includes(`.`)?n.slice(n.lastIndexOf(`.`)):``;return`${t?.slice(0,1)||`•`}•••@${n.slice(0,1)}•••${r}`}function Z(e,t,n=Date.now()){let r=Y(e);if(r===null||r<=n)return{text:`—`};let i=Math.ceil((r-n)/6e4),a=(e,n)=>new Intl.NumberFormat(t,{style:`unit`,unit:n,unitDisplay:`narrow`}).format(e);return{text:i<60?a(i,`minute`):i<1440?`${a(Math.floor(i/60),`hour`)} ${a(i%60,`minute`)}`:i<10080?`${a(Math.floor(i/1440),`day`)} ${a(Math.floor(i%1440/60),`hour`)}`:new Intl.DateTimeFormat(t,{month:`short`,day:`numeric`}).format(r),exact:new Date(r).toLocaleString(t)}}function Q(e){if(!e)return[];let t=[{id:`quota.fiveHourLimit`,key:`quota.fiveHourLimit`,percent:e.fiveHourPercent??e.shortPercent,reset:e.fiveHourResetAt??e.shortResetAt},{id:`quota.weeklyLimit`,key:`quota.weeklyLimit`,percent:e.weeklyPercent,reset:e.weeklyResetAt},{id:`quota.monthlyLimit`,key:`quota.monthlyLimit`,percent:e.monthlyPercent,reset:e.monthlyResetAt},...Array.isArray(e.customWindows)?e.customWindows.filter(e=>e&&typeof e.label==`string`).map(e=>({id:e.label,label:e.label,percent:e.percent,reset:e.resetAt})):[]].filter((t,n)=>n===0&&e.monthlyPercent===void 0||G(t.percent)||Y(t.reset)!==null),n=new Map;return t.map(e=>{let t=n.get(e.id)??0;return n.set(e.id,t+1),t===0?e:{...e,id:`${e.id}#${t}`}})}function $(e,t){let n=new Set(t.hiddenProviders),r=t.models===null?null:new Set(t.models),i=e.models.filter(e=>!n.has(e.provider)&&(r===null||r.has(`${e.provider}/${e.model}`)||r.has(e.model)));if(t.models===null&&t.hiddenProviders.length===0)return{...e,models:i};if(t.models?.length!==0&&e.models.some(e=>!e.provider||!e.model||e.provider===`other`&&e.model===`other`))return{...e,models:i.filter(e=>e.provider!==`other`||e.model!==`other`),summary:{},usageIncomplete:!0};let a={};for(let e of[`requests`,`totalTokens`,`inputTokens`,`outputTokens`,`cachedInputTokens`,`cacheReadInputTokens`,`estimatedCostUsd`,`measuredRequests`,`pricedRequests`])i.length&&i.every(t=>G(t[e]))&&(a[e]=i.reduce((t,n)=>t+n[e],0));return{...e,models:i,summary:a}}function ee(e){let t={...e};if((e.requests??0)>0&&(e.measuredRequests===0||e.coverageRatio===0))for(let e of[`totalTokens`,`inputTokens`,`outputTokens`,`cachedInputTokens`,`cacheReadInputTokens`])delete t[e];return(e.requests??0)>0&&e.pricedRequests===0&&delete t.estimatedCostUsd,t}async function te(t,n){let r=e(2e4),i=()=>r.controller.abort();n.aborted&&i(),n.addEventListener(`abort`,i,{once:!0});try{let e=await fetch(t,{signal:r.signal,cache:`no-store`});if(!e.ok)throw Error(String(e.status));let n=await e.json();if(r.signal.aborted)throw Error(`Tray request cancelled`);return n}finally{r.clear(),n.removeEventListener(`abort`,i)}}function ne(e){let t=K(e);if(t.error||!t.summary||typeof t.summary!=`object`||Array.isArray(t.summary)||!Array.isArray(t.models))throw Error(`Invalid usage`);return t}export{h as C,d as D,m as E,s as O,v as S,p as T,N as _,J as a,T as b,Q as c,W as d,j as f,M as g,D as h,ee as i,Z as l,k as m,$ as n,ne as o,F as p,G as r,q as s,te as t,Y as u,P as v,f as w,C as x,E as y};
|
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-BB0iHG8-.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index--EWgGQvZ.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.65.0-preview.20260925",
|
|
4
4
|
"description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/package-main.mjs",
|
|
@@ -20,9 +20,6 @@
|
|
|
20
20
|
"src",
|
|
21
21
|
"gui/dist",
|
|
22
22
|
"assets/banner.png",
|
|
23
|
-
"assets/download-macos.svg",
|
|
24
|
-
"assets/download-windows.svg",
|
|
25
|
-
"assets/download-linux.svg",
|
|
26
23
|
"assets/architecture.png",
|
|
27
24
|
"assets/claude-code-models.gif",
|
|
28
25
|
"assets/codex-app-picker.png",
|
|
@@ -613,6 +613,11 @@ function supportsExplicitThinkingDisable(modelId: string): boolean {
|
|
|
613
613
|
return meetsFamilyMinimum(modelId, EXPLICIT_THINKING_DISABLE_FAMILY_MINIMUMS);
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
+
function rejectsForcedToolChoice(modelId: string): boolean {
|
|
617
|
+
const parsed = claudeFamilyVersion(modelId);
|
|
618
|
+
return parsed?.family === "opus" && parsed.major === 5 && parsed.minor === 5;
|
|
619
|
+
}
|
|
620
|
+
|
|
616
621
|
/** `output_config.effort` accepts low|medium|high|xhigh|max — "minimal" is rejected with a 400. */
|
|
617
622
|
function adaptiveEffort(effort: string): string {
|
|
618
623
|
return effort === "minimal" ? "low" : effort;
|
|
@@ -1098,6 +1103,20 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1098
1103
|
// has to be stated before the flag has somewhere to live.
|
|
1099
1104
|
body.tool_choice = { type: "auto" };
|
|
1100
1105
|
}
|
|
1106
|
+
const selectedToolChoice = body.tool_choice as { type?: string; name?: string } | undefined;
|
|
1107
|
+
if (rejectsForcedToolChoice(parsed.modelId) && (selectedToolChoice?.type === "any" || selectedToolChoice?.type === "tool")) {
|
|
1108
|
+
// Claude Opus 5.5 rejects forced tool use regardless of whether adaptive thinking is
|
|
1109
|
+
// explicit. Anthropic's migration guidance recommends auto plus a prompt instruction;
|
|
1110
|
+
// this keeps the request usable but cannot preserve the caller's forced-tool guarantee.
|
|
1111
|
+
if (selectedToolChoice.type === "tool" && Array.isArray(body.tools)) {
|
|
1112
|
+
// A named choice still narrows the candidate set even though the upstream cannot
|
|
1113
|
+
// enforce the forced call. Do not let the compatibility downgrade widen it to every
|
|
1114
|
+
// declared tool.
|
|
1115
|
+
body.tools = body.tools.filter(tool => tool && typeof tool === "object" && "name" in tool
|
|
1116
|
+
&& (tool as { name?: unknown }).name === selectedToolChoice.name);
|
|
1117
|
+
}
|
|
1118
|
+
body.tool_choice = { type: "auto" };
|
|
1119
|
+
}
|
|
1101
1120
|
// disable_parallel_tool_use is nested in tool_choice and caps the model at one
|
|
1102
1121
|
// tool call for auto/any/tool. Under type "none" tool use is already off, so the
|
|
1103
1122
|
// flag is irrelevant there, and with no tools on the wire no tool_choice exists.
|
|
@@ -1222,7 +1241,11 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1222
1241
|
|
|
1223
1242
|
try {
|
|
1224
1243
|
for await (const record of decodeServerSentEvents(response.body, { includeComments: true, translatorBudget: budget })) {
|
|
1225
|
-
|
|
1244
|
+
// Anthropic's streaming API sends `event: ping` / `{"type":"ping"}` records alongside
|
|
1245
|
+
// SSE comments ("Event streams may also include any number of ping events"). Both mean the
|
|
1246
|
+
// upstream is still alive, so a long silent thinking block must not look like a dead
|
|
1247
|
+
// upstream to the bridge stall watchdog (#5707).
|
|
1248
|
+
if (record.kind === "comment" || record.event === "ping") {
|
|
1226
1249
|
yield { type: "heartbeat" };
|
|
1227
1250
|
continue;
|
|
1228
1251
|
}
|
|
@@ -1350,6 +1373,12 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
|
|
|
1350
1373
|
yield* emitDone();
|
|
1351
1374
|
break;
|
|
1352
1375
|
}
|
|
1376
|
+
case "ping": {
|
|
1377
|
+
// A data-only `{"type":"ping"}` record carries no SSE `event:` line, so the
|
|
1378
|
+
// liveness check above cannot see it (#5707).
|
|
1379
|
+
yield { type: "heartbeat" };
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1353
1382
|
case "error": {
|
|
1354
1383
|
const err = data.error as { message?: string } | undefined;
|
|
1355
1384
|
yield { type: "error", message: err?.message ?? "Anthropic error" };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
5
|
+
import type { AdapterRequest, ProviderAdapter } from "../base";
|
|
6
|
+
import { mapReasoningEffort } from "../../reasoning-effort";
|
|
7
|
+
import { buildSystemPrompt } from "../coding-agent/protocol";
|
|
8
|
+
import { baseScopedEnv, runCodingAgentTurn, type CodingAgentDeps } from "../coding-agent/turn";
|
|
9
|
+
import { CLAUDE_CLI_PROFILES, type ClaudeCliProfile } from "./profiles";
|
|
10
|
+
|
|
11
|
+
export type { SpawnFn } from "../coding-agent/turn";
|
|
12
|
+
export type ClaudeCliAdapterDeps = CodingAgentDeps;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Quiet the CLI's own outbound traffic.
|
|
16
|
+
*
|
|
17
|
+
* The spawned turn is infrastructure, not somebody's editor: nobody reads its usage metrics, its
|
|
18
|
+
* crash reports describe a process the operator never launched by hand, and an auto-updater
|
|
19
|
+
* swapping the binary underneath a running proxy is skew rather than a feature. The shared scoped
|
|
20
|
+
* env inherits none of these keys, so these values are the ones the turn runs with.
|
|
21
|
+
*/
|
|
22
|
+
export const CLAUDE_CLI_QUIET_ENV: Readonly<Record<string, string>> = {
|
|
23
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
24
|
+
CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY: "1",
|
|
25
|
+
CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL: "1",
|
|
26
|
+
DISABLE_AUTOUPDATER: "1",
|
|
27
|
+
DISABLE_TELEMETRY: "1",
|
|
28
|
+
DISABLE_ERROR_REPORTING: "1",
|
|
29
|
+
DISABLE_FEEDBACK_COMMAND: "1",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build the scoped child-process environment for one Claude Code turn.
|
|
34
|
+
*
|
|
35
|
+
* No credential is layered here on purpose. Claude Code reads the operator's own sign-in (the
|
|
36
|
+
* macOS Keychain entry, or `~/.claude/.credentials.json` elsewhere), which is exactly the property
|
|
37
|
+
* this provider exists for: the token never enters OpenCodex, its config, or a child environment.
|
|
38
|
+
*
|
|
39
|
+
* The shared base env also drops every inherited `ANTHROPIC_*` variable, which is what keeps a
|
|
40
|
+
* `claude` the operator already points at this proxy from looping back into it.
|
|
41
|
+
*
|
|
42
|
+
* `USER` is the one inherited name added back, and it is not a credential: the CLI resolves its own
|
|
43
|
+
* sign-in by account name, so a scoped env without it makes a signed-in machine answer "not logged
|
|
44
|
+
* in". Measured with `claude auth status` under `env -i`: `USER` alone reports `loggedIn: true`,
|
|
45
|
+
* `LOGNAME` alone or neither reports `loggedIn: false`.
|
|
46
|
+
*/
|
|
47
|
+
export function buildChildEnv(_profile: ClaudeCliProfile, _apiKey: string): Record<string, string> {
|
|
48
|
+
const env: Record<string, string> = {
|
|
49
|
+
...baseScopedEnv(),
|
|
50
|
+
...CLAUDE_CLI_QUIET_ENV,
|
|
51
|
+
};
|
|
52
|
+
const user = process.env.USER;
|
|
53
|
+
if (user) env.USER = user;
|
|
54
|
+
return env;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build the headless Claude Code arguments for one turn.
|
|
59
|
+
*
|
|
60
|
+
* Tool ownership stays with the client: `--tools ""` disables every built-in tool and
|
|
61
|
+
* `--strict-mcp-config` (with no `--mcp-config`) keeps user, project and plugin MCP servers out, so
|
|
62
|
+
* the harness can neither read, write, exec nor browse the operator's tree. `--setting-sources ""`
|
|
63
|
+
* stops the CLI from loading CLAUDE.md, skills, hooks, plugins and output styles into a proxied
|
|
64
|
+
* turn, which is what makes the request deterministic instead of dependent on the host's setup.
|
|
65
|
+
*
|
|
66
|
+
* The system prompt REPLACES the Claude Code preset rather than appending to it. The caller's system
|
|
67
|
+
* and developer prompts are the contract this turn answers under; leaving the harness preset in
|
|
68
|
+
* place would put a second, contradictory instruction set in front of them and would describe tools
|
|
69
|
+
* this turn deliberately does not have.
|
|
70
|
+
*
|
|
71
|
+
* It travels as a `--system-prompt-file` path rather than inline, because argv is world-readable
|
|
72
|
+
* through process listing — the same reason the CodeBuddy adapter stages its folded prompt. The
|
|
73
|
+
* staging file is passed by `runTurn`, which always writes one: omitting the flag is not "no system
|
|
74
|
+
* prompt", it is "Claude Code's preset", so a caller that sends neither a system nor a developer
|
|
75
|
+
* prompt gets an empty file instead. Verified against 2.1.270 by reading the `prompt_snapshot`
|
|
76
|
+
* attachment the CLI writes into a session transcript — a file holding MARKER snapshots `["MARKER"]`,
|
|
77
|
+
* an empty file snapshots `[""]`, and an omitted flag snapshots the fourteen-block harness preset.
|
|
78
|
+
*
|
|
79
|
+
* `--no-session-persistence` keeps every turn stateless. The client replays its own conversation
|
|
80
|
+
* and `buildConversationInput` projects it into the single stream-json user frame the CLI accepts.
|
|
81
|
+
*
|
|
82
|
+
* There is deliberately no `--max-turns` here: the Claude Code CLI exposes no such flag (the Agent
|
|
83
|
+
* SDK sets it on the turn budget instead), and with no tool channel a single `-p` turn cannot loop.
|
|
84
|
+
*/
|
|
85
|
+
export function buildArgs(
|
|
86
|
+
_profile: ClaudeCliProfile,
|
|
87
|
+
parsed: OcxParsedRequest,
|
|
88
|
+
provider: OcxProviderConfig,
|
|
89
|
+
systemPromptFile?: string,
|
|
90
|
+
): string[] {
|
|
91
|
+
const args: string[] = [
|
|
92
|
+
"-p",
|
|
93
|
+
"--output-format", "stream-json",
|
|
94
|
+
"--input-format", "stream-json",
|
|
95
|
+
"--include-partial-messages",
|
|
96
|
+
"--verbose",
|
|
97
|
+
"--no-session-persistence",
|
|
98
|
+
"--tools", "",
|
|
99
|
+
"--strict-mcp-config",
|
|
100
|
+
"--setting-sources", "",
|
|
101
|
+
"--model", parsed.modelId,
|
|
102
|
+
];
|
|
103
|
+
const effort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
104
|
+
if (effort) args.push("--effort", effort);
|
|
105
|
+
if (systemPromptFile) args.push("--system-prompt-file", systemPromptFile);
|
|
106
|
+
return args;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Refuse image input the way the Qoder presets do.
|
|
111
|
+
*
|
|
112
|
+
* The CLI parses an image frame in its stream-json input without complaint (verified against
|
|
113
|
+
* 2.1.270), but nothing verifies that a headless turn hands those bytes to the model, and an image
|
|
114
|
+
* the harness drops produces a confident answer to the wrong question. v1 therefore publishes
|
|
115
|
+
* text-only models — `noVisionModels` on the registry row — and refuses a direct image here; an
|
|
116
|
+
* operator with the vision sidecar on the request path still gets images captioned into text before
|
|
117
|
+
* they reach this adapter.
|
|
118
|
+
*/
|
|
119
|
+
function hasImageInput(parsed: OcxParsedRequest): boolean {
|
|
120
|
+
return parsed.context.messages.some(message =>
|
|
121
|
+
Array.isArray(message.content) && message.content.some(part => part.type === "image"),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Turn the CLI's unauthenticated turn into the one action a subscription user can take.
|
|
127
|
+
*
|
|
128
|
+
* An unauthenticated `claude` does not fail the process: it emits an ordinary terminal `result`
|
|
129
|
+
* frame with `is_error: true` and the text "Not logged in · Please run /login", which the shared
|
|
130
|
+
* mapper reports as a generic 401. Nothing in that reaches for the CLI's own sign-in, so the
|
|
131
|
+
* operator is left guessing whether the key, the provider row or the account is wrong.
|
|
132
|
+
*/
|
|
133
|
+
export function withClaudeLoginHint(emit: (event: AdapterEvent) => void): (event: AdapterEvent) => void {
|
|
134
|
+
return event => {
|
|
135
|
+
if (event.type === "error" && event.status === 401 && /not logged in|please run \/login/i.test(event.message)) {
|
|
136
|
+
emit({
|
|
137
|
+
...event,
|
|
138
|
+
code: "claude_cli_not_logged_in",
|
|
139
|
+
message:
|
|
140
|
+
"Claude Code is not signed in, so this subscription provider has no account to spend. " +
|
|
141
|
+
"Run `claude` once and sign in (or `claude setup-token`), then retry. " +
|
|
142
|
+
`CLI reported: ${event.message}`,
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
emit(event);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create the Claude Code CLI adapter: one headless, tools-disabled, sessionless turn per request.
|
|
152
|
+
*
|
|
153
|
+
* As with CodeBuddy and Qoder, `runTurn` owns the turn and the HTTP path is disabled — the CLI
|
|
154
|
+
* performs the transport, and OpenCodex contributes the request projection, the stream mapping and
|
|
155
|
+
* the process lifecycle.
|
|
156
|
+
*/
|
|
157
|
+
export function createClaudeCliAdapter(provider: OcxProviderConfig, deps: ClaudeCliAdapterDeps = {}): ProviderAdapter {
|
|
158
|
+
return {
|
|
159
|
+
name: "claude-cli",
|
|
160
|
+
|
|
161
|
+
buildRequest(): AdapterRequest {
|
|
162
|
+
return { url: provider.baseUrl, method: "POST", headers: {}, body: "" };
|
|
163
|
+
},
|
|
164
|
+
async *parseStream(): AsyncGenerator<AdapterEvent> {
|
|
165
|
+
yield { type: "error", message: "Claude Code CLI adapter uses runTurn; the fetch/parseStream path is disabled." };
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
async runTurn(parsed, incoming, emit): Promise<void> {
|
|
169
|
+
if (hasImageInput(parsed)) {
|
|
170
|
+
emit({
|
|
171
|
+
type: "error",
|
|
172
|
+
message: "Claude Code CLI image input is not enabled because the CLI provider route has no verified multimodal contract.",
|
|
173
|
+
status: 400,
|
|
174
|
+
errorType: "invalid_request_error",
|
|
175
|
+
code: "unsupported_input_modality",
|
|
176
|
+
retryable: false,
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// argv is world-readable via process listing, so the folded system+developer prompt is staged
|
|
181
|
+
// in a private per-turn file and passed by path. The file is written even when the caller
|
|
182
|
+
// sends no prompt at all: the flag has to be present either way, and an empty replacement is
|
|
183
|
+
// what keeps the harness preset out of the turn.
|
|
184
|
+
let promptDir: string | undefined;
|
|
185
|
+
let promptFile: string | undefined;
|
|
186
|
+
try {
|
|
187
|
+
promptDir = await mkdtemp(join(tmpdir(), "ocx-claude-cli-prompt-"));
|
|
188
|
+
promptFile = join(promptDir, "system-prompt.txt");
|
|
189
|
+
await writeFile(promptFile, buildSystemPrompt(parsed) ?? "", { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
190
|
+
} catch {
|
|
191
|
+
if (promptDir) await rm(promptDir, { recursive: true, force: true }).catch(() => {});
|
|
192
|
+
emit({
|
|
193
|
+
type: "error",
|
|
194
|
+
message: "Claude Code system prompt could not be staged securely.",
|
|
195
|
+
status: 500,
|
|
196
|
+
errorType: "upstream_error",
|
|
197
|
+
code: "system_prompt_staging_failed",
|
|
198
|
+
retryable: false,
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await runCodingAgentTurn({
|
|
204
|
+
profiles: CLAUDE_CLI_PROFILES,
|
|
205
|
+
provider,
|
|
206
|
+
parsed,
|
|
207
|
+
incoming,
|
|
208
|
+
emit: withClaudeLoginHint(emit),
|
|
209
|
+
buildArgs: (profile, req, prov) => buildArgs(profile as ClaudeCliProfile, req, prov, promptFile),
|
|
210
|
+
buildEnv: (profile, apiKey) => buildChildEnv(profile as ClaudeCliProfile, apiKey),
|
|
211
|
+
deps,
|
|
212
|
+
});
|
|
213
|
+
} finally {
|
|
214
|
+
if (promptDir) await rm(promptDir, { recursive: true, force: true }).catch(() => {});
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { clearCodingAgentBinaryCache, type CodingAgentProviderProfile } from "../coding-agent/profile";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Profile for the official Claude Code CLI — Anthropic's own harness (§三十一).
|
|
5
|
+
*
|
|
6
|
+
* The CLI owns the account: OpenCodex stores no Claude token, reads none, and injects none, so a
|
|
7
|
+
* subscription turn is spent through the harness Anthropic ships instead of a replayed Claude Code
|
|
8
|
+
* identity against the Messages API. There is exactly one destination, so the profile carries the
|
|
9
|
+
* single-binary shape of the shared coding-agent seam and no `tokenEnv`.
|
|
10
|
+
*
|
|
11
|
+
* Evidence (verified 2026-09-24 against the installed CLI 2.1.270): the full argument set built by
|
|
12
|
+
* `./adapter.ts` is accepted — including `--tools ""`, `--setting-sources ""`, `--effort` and
|
|
13
|
+
* `--system-prompt` — and a stream-json turn reaches the account check, ending on a terminal
|
|
14
|
+
* `result` frame with `is_error: true` and "Not logged in · Please run /login" rather than on an
|
|
15
|
+
* unknown option. The success-path frame shapes are the shared parser's, unchanged from the family
|
|
16
|
+
* this profile joins (`../coding-agent/protocol.ts`).
|
|
17
|
+
*/
|
|
18
|
+
export interface ClaudeCliProfile extends CodingAgentProviderProfile {
|
|
19
|
+
family: "claude";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const CLAUDE_CLI_PROFILE: ClaudeCliProfile = {
|
|
23
|
+
providerId: "claude-cli",
|
|
24
|
+
family: "claude",
|
|
25
|
+
// Not a vendor region switch: Claude Code has one destination, and the shared seam's region slot
|
|
26
|
+
// carries the neutral value. The profile stays the single authority either way.
|
|
27
|
+
region: "global",
|
|
28
|
+
label: "Claude Code",
|
|
29
|
+
canonicalBaseUrl: "https://api.anthropic.com",
|
|
30
|
+
binaryCandidates: ["claude"],
|
|
31
|
+
installHint: "npm install -g @anthropic-ai/claude-code",
|
|
32
|
+
documentationUrl: "https://docs.claude.com/en/docs/claude-code/cli-reference",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const CLAUDE_CLI_PROFILES: readonly ClaudeCliProfile[] = [CLAUDE_CLI_PROFILE];
|
|
36
|
+
|
|
37
|
+
/** Binary-discovery cache is shared across coding-agent families; re-exported for test isolation. */
|
|
38
|
+
export const clearClaudeCliBinaryCache = clearCodingAgentBinaryCache;
|
|
@@ -16,6 +16,8 @@ import { clearCodingAgentBinaryCache, type CodingAgentProviderProfile } from "..
|
|
|
16
16
|
*/
|
|
17
17
|
export interface CodeBuddyProfile extends CodingAgentProviderProfile {
|
|
18
18
|
family: "codebuddy";
|
|
19
|
+
/** Required for this family: the CLI is driven with a vendor API key. */
|
|
20
|
+
tokenEnv: string;
|
|
19
21
|
/** Official `CODEBUDDY_INTERNET_ENVIRONMENT` value for this region. */
|
|
20
22
|
internetEnvironment: "public" | "internal";
|
|
21
23
|
}
|
|
@@ -13,8 +13,11 @@ export interface CodingAgentProviderProfile {
|
|
|
13
13
|
/** Canonical OpenCodex provider id this profile serves. */
|
|
14
14
|
providerId: string;
|
|
15
15
|
/** Vendor family; selects the arg/env builder in the family adapter. */
|
|
16
|
-
family: "codebuddy" | "qoder";
|
|
17
|
-
/**
|
|
16
|
+
family: "claude" | "codebuddy" | "qoder";
|
|
17
|
+
/**
|
|
18
|
+
* Region; drives the vendor's own region switch and keeps credentials deterministic.
|
|
19
|
+
* A family with a single destination carries `global`.
|
|
20
|
+
*/
|
|
18
21
|
region: "global" | "cn";
|
|
19
22
|
/** Human label for diagnostics/error copy (never sent upstream). */
|
|
20
23
|
label: string;
|
|
@@ -26,8 +29,13 @@ export interface CodingAgentProviderProfile {
|
|
|
26
29
|
canonicalBaseUrl: string;
|
|
27
30
|
/** Executable names to resolve on PATH, in preference order. */
|
|
28
31
|
binaryCandidates: readonly string[];
|
|
29
|
-
/**
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Official credential environment variable consumed by the CLI.
|
|
34
|
+
*
|
|
35
|
+
* Omitted when the CLI owns its own sign-in (Claude Code reads the operator's `claude` session),
|
|
36
|
+
* where the adapter neither requires nor injects a key and has no secret to redact.
|
|
37
|
+
*/
|
|
38
|
+
tokenEnv?: string;
|
|
31
39
|
/** Install command surfaced when the CLI is missing (§二十六). */
|
|
32
40
|
installHint: string;
|
|
33
41
|
/** Official documentation for the automation surface. */
|
|
@@ -79,13 +79,20 @@ export function baseScopedEnv(): Record<string, string> {
|
|
|
79
79
|
return env;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Redact the profile's credential and common secret shapes before surfacing diagnostics.
|
|
84
|
+
*
|
|
85
|
+
* `tokenEnv` is absent for a credentialless profile (the CLI owns its sign-in), which only drops
|
|
86
|
+
* the `NAME=value` rule; the generic secret shapes are redacted either way.
|
|
87
|
+
*/
|
|
88
|
+
export function redactSecrets(text: string, tokenEnv: string | undefined, credential?: string): string {
|
|
85
89
|
let redacted = text;
|
|
86
90
|
if (credential) redacted = redacted.split(credential).join("[redacted]");
|
|
91
|
+
if (tokenEnv) {
|
|
92
|
+
const escaped = tokenEnv.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
93
|
+
redacted = redacted.replace(new RegExp(`(${escaped}\\s*[:=]\\s*)\\S+`, "gi"), "$1[redacted]");
|
|
94
|
+
}
|
|
87
95
|
return redacted
|
|
88
|
-
.replace(new RegExp(`(${escaped}\\s*[:=]\\s*)\\S+`, "gi"), "$1[redacted]")
|
|
89
96
|
.replace(/(authorization\s*[:=]\s*)\S+/gi, "$1[redacted]")
|
|
90
97
|
.replace(/\b(sk-[A-Za-z0-9_-]{6,})\b/g, "[redacted]");
|
|
91
98
|
}
|
|
@@ -173,8 +180,11 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
173
180
|
});
|
|
174
181
|
return;
|
|
175
182
|
}
|
|
176
|
-
const apiKey = provider.apiKey;
|
|
177
|
-
|
|
183
|
+
const apiKey = provider.apiKey ?? "";
|
|
184
|
+
// A profile WITHOUT a tokenEnv owns no credential to pre-flight: the CLI reads the operator's
|
|
185
|
+
// own sign-in (Claude Code), so an unauthenticated session is reported by the CLI itself as a
|
|
186
|
+
// terminal result frame and surfaces through the family's error mapping (§二十六).
|
|
187
|
+
if (profile.tokenEnv && !apiKey) {
|
|
178
188
|
emit({
|
|
179
189
|
type: "error",
|
|
180
190
|
message: `${profile.label} credential missing — add an API key for this provider (${profile.tokenEnv}).`,
|