@bitkyc08/opencodex 2.66.0 → 2.67.0

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.
Files changed (60) hide show
  1. package/bin/ocx.mjs +24 -11
  2. package/gui/dist/assets/{App-gkoJIEOV.css → App-BJsT8Icc.css} +1 -1
  3. package/gui/dist/assets/{App-DyHlFeNB.js → App-D3pNGiN4.js} +15 -15
  4. package/gui/dist/assets/{Tray-CI8cVKWu.js → Tray-yb05wCjb.js} +1 -1
  5. package/gui/dist/assets/index-CouvvtMV.js +86 -0
  6. package/gui/dist/assets/index-DtNmX7hW.css +1 -0
  7. package/gui/dist/assets/{tray-data-_WyYzbMt.js → tray-data-CYGdjCJ7.js} +1 -1
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +1 -1
  10. package/src/chat/inbound.ts +82 -3
  11. package/src/chat/outbound.ts +5 -3
  12. package/src/claude/desktop-first-party.ts +12 -3
  13. package/src/claude/first-party-settings.ts +97 -0
  14. package/src/claude/intercept/client-class.ts +18 -0
  15. package/src/claude/intercept/listener.ts +5 -0
  16. package/src/claude/intercept/runtime.ts +6 -0
  17. package/src/cli/capabilities.ts +12 -0
  18. package/src/cli/claude-desktop.ts +11 -6
  19. package/src/cli/claude.ts +25 -1
  20. package/src/cli/ensure-desired-integrations.ts +14 -1
  21. package/src/cli/integrations.ts +9 -1
  22. package/src/cli/uninstall-client-state.ts +6 -0
  23. package/src/cli/uninstall-integrations.ts +115 -0
  24. package/src/codex/history-job.ts +2 -1
  25. package/src/codex/main-account-hard-lock.ts +36 -19
  26. package/src/codex/quota.ts +11 -1
  27. package/src/combos/resolve.ts +11 -3
  28. package/src/config/load-degrade.ts +4 -0
  29. package/src/generated/compatibility-version.json +67 -43
  30. package/src/generated/worker-bundles.gen.ts +4 -0
  31. package/src/images/loop.ts +10 -3
  32. package/src/integrations/aside-profile-context.ts +17 -0
  33. package/src/lab/fabric/executor.ts +25 -3
  34. package/src/lab/fabric/producer-isolate.ts +199 -32
  35. package/src/lab/fabric/scratch.ts +9 -2
  36. package/src/lib/shadow-call.ts +11 -4
  37. package/src/lib/worker-embed.ts +33 -0
  38. package/src/oauth/generic-account-failover.ts +5 -3
  39. package/src/oauth/index.ts +19 -29
  40. package/src/oauth/store.ts +106 -34
  41. package/src/remote-control/workspace-rpc.ts +114 -19
  42. package/src/router.ts +7 -1
  43. package/src/server/chat-completions.ts +2 -1
  44. package/src/server/index/claude-intercept-lifecycle.ts +13 -0
  45. package/src/server/management/agent-settings-routes.ts +123 -5
  46. package/src/server/management/context.ts +4 -0
  47. package/src/server/management/native-integration-routes.ts +19 -8
  48. package/src/server/responses/core-normalize.ts +13 -1
  49. package/src/server/responses/request-prepare.ts +5 -3
  50. package/src/server/responses/sidecar-execution.ts +9 -2
  51. package/src/server/search.ts +28 -7
  52. package/src/storage/policy-job.ts +2 -1
  53. package/src/storage/restore-job.ts +2 -1
  54. package/src/types/config.ts +11 -5
  55. package/src/update/transactional-install.mjs +5 -1
  56. package/src/web-search/alpha-search.ts +71 -0
  57. package/src/web-search/devin-executor.ts +192 -0
  58. package/src/web-search/loop.ts +10 -3
  59. package/gui/dist/assets/index-BiwnVAUm.js +0 -86
  60. package/gui/dist/assets/index-ComVsdSk.css +0 -1
@@ -1 +1 @@
1
- import{h as e,n as t}from"./index-BiwnVAUm.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};
1
+ import{h as e,n as t}from"./index-CouvvtMV.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};
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-BiwnVAUm.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-ComVsdSk.css">
19
+ <script type="module" crossorigin src="/assets/index-CouvvtMV.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-DtNmX7hW.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.66.0",
3
+ "version": "2.67.0",
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",
@@ -211,6 +211,27 @@ function toolCallsToItems(
211
211
  }
212
212
  }
213
213
 
214
+ function legacyFunctionCallToItem(
215
+ value: unknown,
216
+ input: Rec[],
217
+ knownNameByCallId: Map<string, string>,
218
+ awaitingToolResult: Set<string>,
219
+ sequence: number,
220
+ ): { callId: string; name: string } | null {
221
+ if (value === undefined) return null;
222
+ if (!isRec(value) || typeof value.name !== "string" || value.name.length === 0) {
223
+ throw new ChatCompletionsRequestError("assistant function_call requires a name");
224
+ }
225
+ const args = typeof value.arguments === "string"
226
+ ? value.arguments
227
+ : JSON.stringify(value.arguments ?? {});
228
+ const callId = `call_legacy_${String(sequence).padStart(4, "0")}`;
229
+ knownNameByCallId.set(callId, value.name);
230
+ awaitingToolResult.add(callId);
231
+ input.push({ type: "function_call", call_id: callId, name: value.name, arguments: args });
232
+ return { callId, name: value.name };
233
+ }
234
+
214
235
  function toolsToResponses(tools: unknown): Rec[] | undefined {
215
236
  if (!Array.isArray(tools) || tools.length === 0) return undefined;
216
237
  const out: Rec[] = [];
@@ -243,6 +264,24 @@ function toolsToResponses(tools: unknown): Rec[] | undefined {
243
264
  return out.length > 0 ? out : undefined;
244
265
  }
245
266
 
267
+ function legacyFunctionsToResponses(functions: unknown): Rec[] | undefined {
268
+ if (functions === undefined) return undefined;
269
+ if (!Array.isArray(functions)) throw new ChatCompletionsRequestError("functions must be an array");
270
+ const out: Rec[] = [];
271
+ for (const raw of functions) {
272
+ if (!isRec(raw) || typeof raw.name !== "string" || raw.name.length === 0) {
273
+ throw new ChatCompletionsRequestError("functions entries require a name");
274
+ }
275
+ out.push({
276
+ type: "function",
277
+ name: raw.name,
278
+ ...(typeof raw.description === "string" ? { description: raw.description } : {}),
279
+ ...(isRec(raw.parameters) ? { parameters: raw.parameters } : {}),
280
+ });
281
+ }
282
+ return out.length > 0 ? out : undefined;
283
+ }
284
+
246
285
  function toolChoiceToResponses(choice: unknown, body: Rec): void {
247
286
  if (choice === undefined || choice === null) return;
248
287
  if (choice === "auto" || choice === "none" || choice === "required") {
@@ -269,6 +308,18 @@ function toolChoiceToResponses(choice: unknown, body: Rec): void {
269
308
  }
270
309
  }
271
310
 
311
+ function legacyFunctionChoiceToResponses(choice: unknown, body: Rec): void {
312
+ if (choice === undefined || choice === null) return;
313
+ if (choice === "auto" || choice === "none") {
314
+ body.tool_choice = choice;
315
+ return;
316
+ }
317
+ if (!isRec(choice) || typeof choice.name !== "string" || choice.name.length === 0) {
318
+ throw new ChatCompletionsRequestError("function_call requires auto, none, or a function name");
319
+ }
320
+ body.tool_choice = { type: "function", name: choice.name };
321
+ }
322
+
272
323
  /**
273
324
  * Chat Completions nests the subset under `allowed_tools`, Responses carries `mode`/`tools`
274
325
  * on the choice itself, and each entry names its tool under a member keyed by its own type
@@ -389,6 +440,8 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
389
440
  // Recover replace-style tool calls incrementally instead of rebuilding the
390
441
  // call-id index from the entire translated transcript for every message.
391
442
  const knownNameByCallId = new Map<string, string>();
443
+ const legacyAwaiting: Array<{ callId: string; name: string }> = [];
444
+ let legacyCallSequence = 0;
392
445
  // Tool calls whose result has not arrived yet. Several adapters need a call and its output
393
446
  // to stay adjacent — Kiro refuses an interrupted pair (src/adapters/kiro/payload.ts) and the
394
447
  // Anthropic and Google mappers synthesize a missing result — so an instruction that arrives
@@ -405,6 +458,7 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
405
458
  const beginConversationTurn = (): void => {
406
459
  releaseHeldInstructions();
407
460
  awaitingToolResult.clear();
461
+ legacyAwaiting.length = 0;
408
462
  };
409
463
 
410
464
  for (const msg of raw.messages) {
@@ -462,6 +516,16 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
462
516
  if (msg.tool_calls !== undefined) {
463
517
  toolCallsToItems(msg.tool_calls, input, knownNameByCallId, awaitingToolResult);
464
518
  }
519
+ if (msg.function_call !== undefined && msg.function_call !== null) {
520
+ const call = legacyFunctionCallToItem(
521
+ msg.function_call,
522
+ input,
523
+ knownNameByCallId,
524
+ awaitingToolResult,
525
+ ++legacyCallSequence,
526
+ );
527
+ if (call) legacyAwaiting.push(call);
528
+ }
465
529
  break;
466
530
  }
467
531
  case "function": {
@@ -472,6 +536,17 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
472
536
  "Legacy function-result image translation is not implemented. Use tool_calls and role:tool with tool_call_id.",
473
537
  );
474
538
  }
539
+ const name = typeof msg.name === "string" ? msg.name : "";
540
+ if (!name) throw new ChatCompletionsRequestError("function messages require a name");
541
+ const pendingIndex = legacyAwaiting.findIndex(call => call.name === name);
542
+ if (pendingIndex < 0) {
543
+ throw new ChatCompletionsRequestError(`function result has no pending call named ${name}`);
544
+ }
545
+ const [call] = legacyAwaiting.splice(pendingIndex, 1);
546
+ const output = contentToText(msg.content);
547
+ input.push({ type: "function_call_output", call_id: call!.callId, output });
548
+ awaitingToolResult.delete(call!.callId);
549
+ if (awaitingToolResult.size === 0) releaseHeldInstructions();
475
550
  break;
476
551
  }
477
552
  case "tool": {
@@ -507,9 +582,13 @@ export function chatCompletionsToResponsesBody(raw: unknown): Rec {
507
582
 
508
583
  if (systemParts.length > 0) body.instructions = systemParts.join("\n\n");
509
584
 
510
- const tools = toolsToResponses(raw.tools);
511
- if (tools) body.tools = tools;
512
- toolChoiceToResponses(raw.tool_choice, body);
585
+ const tools = [
586
+ ...(toolsToResponses(raw.tools) ?? []),
587
+ ...(legacyFunctionsToResponses(raw.functions) ?? []),
588
+ ];
589
+ if (tools.length > 0) body.tools = tools;
590
+ if (raw.tool_choice !== undefined) toolChoiceToResponses(raw.tool_choice, body);
591
+ else legacyFunctionChoiceToResponses(raw.function_call, body);
513
592
 
514
593
  const maxTokens = typeof raw.max_completion_tokens === "number"
515
594
  ? raw.max_completion_tokens
@@ -154,9 +154,11 @@ function appendedUtf8Bytes(previous: string, previousBytes: number, fragment: st
154
154
  const fragmentFirst = fragment.charCodeAt(0);
155
155
  if (previousLast >= 0xd800 && previousLast <= 0xdbff
156
156
  && fragmentFirst >= 0xdc00 && fragmentFirst <= 0xdfff) {
157
- // Buffer.byteLength() replaces each isolated surrogate with three bytes, while the joined
158
- // pair is one four-byte scalar. Preserve full-string sizing without re-encoding the prefix.
159
- nextBytes -= 2;
157
+ // Buffer implementations disagree on the encoded size of an isolated surrogate. Measure
158
+ // the join delta so incremental accounting equals the completed scalar on every runtime.
159
+ const tail = previous[previous.length - 1]!;
160
+ const head = fragment[0]!;
161
+ nextBytes += Buffer.byteLength(tail + head) - Buffer.byteLength(tail) - Buffer.byteLength(head);
160
162
  }
161
163
  return nextBytes;
162
164
  }
@@ -20,7 +20,9 @@
20
20
  * to gateway never flips a working Desktop under the operator.
21
21
  */
22
22
  import { getConfigDir } from "../config/paths";
23
+ import { join } from "node:path";
23
24
  import type { OcxConfig } from "../types";
25
+ import { claudeConfigDir } from "./auth-detect";
24
26
  import { inspectDesktop3pConfigLibrary } from "./desktop-3p";
25
27
  import { claudeInterceptCaCertPath, ensureLocalInterceptCa } from "./intercept/local-ca";
26
28
  import { claudeInterceptEnabled, claudeInterceptProxyPort } from "./intercept/runtime";
@@ -201,7 +203,8 @@ export function observeClaudeDesktopMode(
201
203
  }
202
204
  try {
203
205
  const kind = inspectDesktopFirstParty(config, options).settings.kind;
204
- observed.ownedFirstPartySettings = kind === "applied" || kind === "stale";
206
+ observed.ownedFirstPartySettings = config.claudeCode?.cliFirstParty === true
207
+ ? false : kind === "applied" || kind === "stale";
205
208
  } catch { // no-excuse-ok: catch -- unreadable settings are no first-party evidence.
206
209
  observed.ownedFirstPartySettings = false;
207
210
  }
@@ -234,8 +237,14 @@ export function applyDesktopFirstParty(
234
237
  return { ok: true, changed: written.changed, path: written.path, env: target.env, proxyPort: target.proxyPort };
235
238
  }
236
239
 
237
- /** Remove the first-party env. Only values anchored on our CA path are touched. */
238
- export function removeDesktopFirstParty(options: DesktopFirstPartyOptions = {}): ClaudeInterceptSettingsWrite {
240
+ /** Remove Desktop's share of the env, retaining the shared pair for a desired CLI. */
241
+ export function removeDesktopFirstParty(
242
+ config: Pick<OcxConfig, "claudeCode" | "runtimeRole">,
243
+ options: DesktopFirstPartyOptions = {},
244
+ ): ClaudeInterceptSettingsWrite & { retainedFor?: "cli" } {
245
+ if (config.claudeCode?.cliFirstParty === true) {
246
+ return { ok: true, changed: false, path: join(options.claudeConfigDir ?? claudeConfigDir(), "settings.json"), retainedFor: "cli" };
247
+ }
239
248
  const caCertPath = claudeInterceptCaCertPath(options.opencodexConfigDir ?? getConfigDir());
240
249
  return removeClaudeInterceptSettings(caCertPath, options.claudeConfigDir);
241
250
  }
@@ -0,0 +1,97 @@
1
+ import { getConfigDir } from "../config/paths";
2
+ import { claudeDesktopIntegrationEnabled } from "../codex/desired-state";
3
+ import { join } from "node:path";
4
+ import type { OcxConfig } from "../types";
5
+ import { claudeConfigDir } from "./auth-detect";
6
+ import {
7
+ applyDesktopFirstParty,
8
+ inspectDesktopFirstParty,
9
+ resolveClaudeDesktopMode,
10
+ type ClaudeDesktopModeObservation,
11
+ type DesktopFirstPartyOptions,
12
+ } from "./desktop-first-party";
13
+ import { claudeInterceptCaCertPath } from "./intercept/local-ca";
14
+ import { claudeInterceptEnabled } from "./intercept/runtime";
15
+ import { isClaudeInterceptProxyUrl, removeClaudeInterceptSettings, type ClaudeInterceptSettingsState } from "./intercept/settings";
16
+
17
+ export type ClaudeFirstPartyClient = "desktop" | "cli";
18
+ export interface ClaudeFirstPartyDesired { desktop: boolean; cli: boolean }
19
+
20
+ export function cliFirstPartyDesired(config: Pick<OcxConfig, "claudeCode">): boolean {
21
+ return config.claudeCode?.cliFirstParty === true;
22
+ }
23
+
24
+ export function desktopFirstPartyDesired(
25
+ config: Pick<OcxConfig, "claudeCode" | "clientIntegrations">,
26
+ observed?: ClaudeDesktopModeObservation,
27
+ ): boolean {
28
+ return claudeDesktopIntegrationEnabled(config)
29
+ && resolveClaudeDesktopMode(config, observed) === "first-party";
30
+ }
31
+
32
+ export function firstPartyDesired(
33
+ config: Pick<OcxConfig, "claudeCode" | "clientIntegrations">,
34
+ observed?: ClaudeDesktopModeObservation,
35
+ ): ClaudeFirstPartyDesired {
36
+ return { desktop: desktopFirstPartyDesired(config, observed), cli: cliFirstPartyDesired(config) };
37
+ }
38
+
39
+ export type ClaudeFirstPartyReconcileResult =
40
+ | { ok: true; action: "applied" | "removed" | "unchanged"; changed: boolean; path: string }
41
+ | { ok: false; reason: "intercept_disabled" | "ca_unavailable" | "unreadable" | "foreign_env"; path: string };
42
+
43
+ export function reconcileClaudeFirstPartySettings(
44
+ config: Pick<OcxConfig, "claudeCode" | "port" | "runtimeRole" | "clientIntegrations">,
45
+ desired: ClaudeFirstPartyDesired,
46
+ options: DesktopFirstPartyOptions = {},
47
+ ): ClaudeFirstPartyReconcileResult {
48
+ if (!desired.desktop && !desired.cli) {
49
+ const ownedCa = claudeInterceptCaCertPath(options.opencodexConfigDir ?? getConfigDir());
50
+ const removed = removeClaudeInterceptSettings(ownedCa, options.claudeConfigDir);
51
+ if (!removed.ok) return removed;
52
+ return { ok: true, action: removed.changed ? "removed" : "unchanged", changed: removed.changed, path: removed.path };
53
+ }
54
+ if (!claudeInterceptEnabled(config)) {
55
+ return { ok: true, action: "unchanged", changed: false,
56
+ path: join(options.claudeConfigDir ?? claudeConfigDir(), "settings.json") };
57
+ }
58
+ const written = applyDesktopFirstParty(config, options);
59
+ if (!written.ok) return written;
60
+ return { ok: true, action: written.changed ? "applied" : "unchanged", changed: written.changed, path: written.path };
61
+ }
62
+
63
+ export type FirstPartyProxyStatus = "none" | "live" | "stopped" | "disabled" | "broken" | "foreign" | "local" | "unknown";
64
+ export interface FirstPartyProxyStatusInput {
65
+ settings: ClaudeInterceptSettingsState;
66
+ boundProxyPort: number | null;
67
+ eligible: boolean;
68
+ }
69
+
70
+ /** Classify the settings URL against the listener actually bound in this process. */
71
+ export function firstPartyProxyStatus({ settings, boundProxyPort, eligible }: FirstPartyProxyStatusInput): FirstPartyProxyStatus {
72
+ if (settings.kind === "unreadable") return "unknown";
73
+ if (settings.kind === "absent") return "none";
74
+ const proxy = settings.env.HTTPS_PROXY;
75
+ if (!isClaudeInterceptProxyUrl(proxy)) return "none";
76
+ // A foreign CA cannot establish ownership; preserve tokenless loopback as an uncertain local proxy.
77
+ if (settings.kind === "foreign") {
78
+ return /^http:\/\/opencodex:[^@/]+@/.test(proxy) ? "foreign" : "local";
79
+ }
80
+ if (boundProxyPort === null) return "stopped";
81
+ // The URL shape is owned; a malformed/out-of-range port cannot match the bound listener.
82
+ let port: number;
83
+ try { port = Number(new URL(proxy).port || 80); } catch { return "broken"; }
84
+ const usable = settings.kind === "applied" && port === boundProxyPort;
85
+ if (!eligible) return usable ? "disabled" : "broken";
86
+ return usable ? "live" : "broken";
87
+ }
88
+
89
+ /** Read-only: inspectDesktopFirstParty reads an existing token; it never creates one. */
90
+ export function readFirstPartyProxyStatus(
91
+ config: Pick<OcxConfig, "claudeCode" | "port" | "runtimeRole">,
92
+ boundProxyPort: number | null,
93
+ options: DesktopFirstPartyOptions = {},
94
+ ): FirstPartyProxyStatus {
95
+ const settings = inspectDesktopFirstParty(config, options).settings;
96
+ return firstPartyProxyStatus({ settings, boundProxyPort, eligible: claudeInterceptEnabled(config) });
97
+ }
@@ -0,0 +1,18 @@
1
+ import type { ClaudeFirstPartyDesired } from "../first-party-settings";
2
+
3
+ export type InterceptClient = "desktop" | "cli" | "unknown";
4
+ export const DESKTOP_ENTRYPOINTS = ["claude-desktop", "claude-desktop-3p", "local-agent"] as const;
5
+
6
+ export function interceptEntrypoint(userAgent: string | null): string | null {
7
+ return userAgent?.match(/^claude-cli\/[^\s()]+ \(external, ([A-Za-z0-9._-]+)(?:, [^,()]+)*\)$/)?.[1] ?? null;
8
+ }
9
+
10
+ export function classifyInterceptClient(userAgent: string | null): InterceptClient {
11
+ const entrypoint = interceptEntrypoint(userAgent);
12
+ if (entrypoint === null) return "unknown";
13
+ return (DESKTOP_ENTRYPOINTS as readonly string[]).includes(entrypoint) ? "desktop" : "cli";
14
+ }
15
+
16
+ export function interceptRouteFor(client: InterceptClient, desired: ClaudeFirstPartyDesired): "router" | "relay-native" {
17
+ return client !== "unknown" && desired[client] ? "router" : "relay-native";
18
+ }
@@ -91,6 +91,8 @@ export interface ClaudeInterceptListenerOptions<T = undefined> {
91
91
  leaf: PemKeyPair;
92
92
  /** Router request handler; receives the loopback-rewritten request and the intercept server. */
93
93
  dispatch: (req: Request, server: Server<T>) => Promise<Response>;
94
+ /** Per-request decision for every path; absent preserves the existing path split. */
95
+ route?: (req: Request) => "router" | "relay-native";
94
96
  upstreamBase?: string;
95
97
  maxRequestBodySize?: number;
96
98
  idleTimeout?: number;
@@ -111,6 +113,9 @@ export function startClaudeInterceptListener<T = undefined>(options: ClaudeInter
111
113
  ...(options.maxRequestBodySize !== undefined ? { maxRequestBodySize: options.maxRequestBodySize } : {}),
112
114
  async fetch(req, requestServer) {
113
115
  const url = new URL(req.url);
116
+ if (options.route?.(req) === "relay-native") {
117
+ return relayToUpstream(req, CLAUDE_INTERCEPT_UPSTREAM, options.fetchImpl);
118
+ }
114
119
  if (isClaudeInterceptedPath(url.pathname, req.method)) {
115
120
  return options.dispatch(rewriteInterceptedRequest(req, loopbackOrigin), requestServer);
116
121
  }
@@ -2,6 +2,8 @@ import type { Server } from "bun";
2
2
  import type { OcxConfig } from "../../types";
3
3
  import { getConfigDir } from "../../config/paths";
4
4
  import type { DesktopPickerController } from "../desktop-picker";
5
+ import type { ClaudeFirstPartyDesired } from "../first-party-settings";
6
+ import { classifyInterceptClient, interceptRouteFor } from "./client-class";
5
7
  import { CLAUDE_INTERCEPT_HOSTS, isBrowserConnect, startConnectProxy, type ConnectProxyHandle } from "./connect-proxy";
6
8
  import { startClaudeInterceptListener } from "./listener";
7
9
  import { claudeInterceptCaCertPath, ensureLocalInterceptCaForStartup, issueLocalInterceptLeaf } from "./local-ca";
@@ -107,6 +109,8 @@ export interface StartClaudeInterceptOptions<T> {
107
109
  */
108
110
  requestedPort?: number;
109
111
  dispatch: (req: Request, server: Server<T>) => Promise<Response>;
112
+ /** Live first-party intent; absent preserves router behavior. */
113
+ desiredClients?: () => ClaudeFirstPartyDesired;
110
114
  maxRequestBodySize?: number;
111
115
  configDir?: string;
112
116
  /** Routes for Desktop's Code-tab picker. Picker mode is wired only when this is given. */
@@ -145,6 +149,8 @@ export async function startClaudeIntercept<T>(options: StartClaudeInterceptOptio
145
149
  const listener = startClaudeInterceptListener<T>({
146
150
  leaf,
147
151
  dispatch: options.dispatch,
152
+ ...(options.desiredClients ? { route: (req: Request) =>
153
+ interceptRouteFor(classifyInterceptClient(req.headers.get("user-agent")), options.desiredClients!()) } : {}),
148
154
  upstreamBase: options.config.claudeCode?.anthropicBaseUrl,
149
155
  ...(options.maxRequestBodySize !== undefined ? { maxRequestBodySize: options.maxRequestBodySize } : {}),
150
156
  });
@@ -836,6 +836,18 @@ export const CAPABILITIES: readonly Capability[] = [
836
836
  "--yes is mandatory because this interrupts a running editor session and may discard unsaved composer drafts, model-picker selections, and pending approval prompts; it must never happen because an agent guessed a subcommand.",
837
837
  ],
838
838
  },
839
+ {
840
+ command: ["claude", "config"],
841
+ summary: "Read or update Claude Code settings, including independent CLI first-party routing.",
842
+ routes: [{ method: "GET", path: "/api/claude-code" }, { method: "PUT", path: "/api/claude-code" }],
843
+ flags: [
844
+ { name: "--first-party", value: "string", summary: "For `set`, on or off; route standalone Claude CLI subscription requests through the intercept." },
845
+ { name: "--json", value: "boolean", summary: "Emit the management response as JSON." },
846
+ ],
847
+ mutates: true,
848
+ json: "payload",
849
+ details: ["`status` reads the route; `set` writes only submitted fields. Enabling first-party requires a running Claude intercept."],
850
+ },
839
851
  {
840
852
  command: ["claude", "desktop", "status"],
841
853
  summary: "Applied-vs-desired Claude Desktop state, including staleness, drift, and health.",
@@ -53,10 +53,12 @@ function printDesktopHelp(): void {
53
53
  console.log(`Usage:
54
54
  ocx claude desktop [apply] [--first-party | --gateway [--static|--hybrid|--discovery-only]]
55
55
  --gateway (default) install the third-party gateway profile for the whole app
56
- --first-party keep Desktop on claude.ai; route only the Code tab's Claude Code through the
57
- local intercept proxy via ~/.claude/settings.json env. Account risk: this sends
58
- Claude subscription traffic through a local interception proxy, and Anthropic
59
- may suspend the account.
56
+ --first-party keep Desktop on claude.ai; route its Code tab through the local
57
+ intercept proxy via shared ~/.claude/settings.json env. A standalone
58
+ claude CLI also reads that env and transits the proxy unchanged when
59
+ CLI first-party is off. For fully native shell use, set NO_PROXY='*'.
60
+ Account risk: Claude subscription traffic crosses local TLS interception;
61
+ Anthropic may suspend the account.
60
62
  ocx claude desktop show [--json]
61
63
  ocx claude desktop status [--json]
62
64
  ocx claude desktop picker on|off|status|trust
@@ -353,7 +355,7 @@ export function gatewayModeExplanation(input: {
353
355
  : "because gateway is the default for Claude Desktop";
354
356
  return [
355
357
  `Applied the gateway profile ${reason}.`,
356
- "First-party keeps Desktop on your claude.ai account and routes only the Code tab through the local proxy:",
358
+ "First-party keeps Desktop on your claude.ai account and routes its Code tab through the local proxy. The standalone claude CLI reads the same settings env and may transit the proxy unchanged; use NO_PROXY='*' in the shell for fully native traffic:",
357
359
  " ocx claude desktop apply --first-party",
358
360
  `Account risk: ${FIRST_PARTY_ACCOUNT_RISK.message}`,
359
361
  ];
@@ -425,9 +427,12 @@ export async function applyDesktop(
425
427
  const modeSaved = saveDesktopMode("gateway", deps);
426
428
  const warning = [result.warning, modeSaved ? "" : "desktop mode marker was not saved"].filter(Boolean).join(" ");
427
429
  // The gateway mode is committed before retiring first-party settings.
428
- const removed = removeDesktopFirstParty();
430
+ const removed = removeDesktopFirstParty(loadConfig());
429
431
  if (!removed.ok) return { ok: false, path: removed.path, reason: "first_party_settings_unreadable",
430
432
  warning: ["gateway applied; first-party cleanup remains incomplete", warning].filter(Boolean).join(" ") };
433
+ if (removed.retainedFor === "cli") {
434
+ return { ...result, warning: [warning, "Shared first-party settings remain for Claude Code CLI."].filter(Boolean).join(" ") };
435
+ }
431
436
  if (warning) return { ...result, warning };
432
437
  return result;
433
438
  }
package/src/cli/claude.ts CHANGED
@@ -32,6 +32,8 @@ import { readFileSync } from "node:fs";
32
32
  import { join } from "node:path";
33
33
  import { aliasForNative, aliasForRoute, legacyAliasForNative, legacyAliasForRoute } from "../claude/alias";
34
34
  import { desktop3pAlias } from "../claude/desktop-3p";
35
+ import { inspectDesktopFirstParty } from "../claude/desktop-first-party";
36
+ import { isClaudeInterceptProxyUrl, type ClaudeInterceptSettingsState } from "../claude/intercept/settings";
35
37
 
36
38
  export interface ClaudeLaunchEnv {
37
39
  [key: string]: string | undefined;
@@ -52,6 +54,8 @@ export type ClaudeEnvDeps = {
52
54
  preBunAnthropicSlots?: readonly AnthropicParentEnvSlot[] | null;
53
55
  /** Explicit unsafe opt-in from a root `--dangerously-skip-permissions` launch. */
54
56
  allowRootSkipPermissions?: boolean;
57
+ ownedInterceptSettings?: ClaudeInterceptSettingsState;
58
+ warn?: (line: string) => void;
55
59
  };
56
60
 
57
61
  function deleteUntrustedAnthropicSlots(env: ClaudeLaunchEnv, deps: ClaudeEnvDeps): void {
@@ -613,6 +617,23 @@ export function buildNativeClaudeEnv(
613
617
  deps: ClaudeEnvDeps = {},
614
618
  ): ClaudeLaunchEnv {
615
619
  const env: ClaudeLaunchEnv = { ...base };
620
+ const owned = deps.ownedInterceptSettings;
621
+ // A CA-only stale entry does not override an inherited proxy.
622
+ if ((owned?.kind === "applied" || owned?.kind === "stale") && isClaudeInterceptProxyUrl(owned.env.HTTPS_PROXY)) {
623
+ const expected = owned.env.HTTPS_PROXY;
624
+ const foreignInheritedProxy = [env.HTTPS_PROXY, env.https_proxy].some(value =>
625
+ value !== undefined && value !== "" && value !== expected);
626
+ if (foreignInheritedProxy) {
627
+ deps.warn?.("⚠ Claude settings-owned intercept proxy still applies. Turn Desktop/CLI first-party off or unset the foreign HTTPS_PROXY/https_proxy to use native Claude.");
628
+ } else {
629
+ env.NO_PROXY = "*";
630
+ env.no_proxy = "*";
631
+ if (env.HTTPS_PROXY === expected) delete env.HTTPS_PROXY;
632
+ if (env.https_proxy === expected) delete env.https_proxy;
633
+ if (owned.env.NODE_EXTRA_CA_CERTS !== undefined && env.NODE_EXTRA_CA_CERTS === owned.env.NODE_EXTRA_CA_CERTS)
634
+ delete env.NODE_EXTRA_CA_CERTS;
635
+ }
636
+ }
616
637
  deleteUntrustedAnthropicSlots(env, deps);
617
638
 
618
639
  const admissionSlots = ["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_API_KEY"] as const;
@@ -822,7 +843,10 @@ async function launchNativeClaude(config: OcxConfig, args: string[], notice: str
822
843
  );
823
844
  if (override.warning) console.error(override.warning);
824
845
  const allowRootSkipPermissions = shouldAllowRootSkipPermissions(args);
825
- const env = buildNativeClaudeEnv(config, process.env, { allowRootSkipPermissions });
846
+ const env = buildNativeClaudeEnv(config, process.env, {
847
+ allowRootSkipPermissions, ownedInterceptSettings: inspectDesktopFirstParty(config).settings,
848
+ warn: line => console.error(line),
849
+ });
826
850
  if (allowRootSkipPermissions) console.error(rootSkipPermissionsNotice(env));
827
851
  return spawnClaude([...(override.flag ?? []), ...args], env);
828
852
  }
@@ -9,6 +9,8 @@
9
9
  * each external-file mutation, and use that current config for sync inputs.
10
10
  */
11
11
  import { loadConfig } from "../config";
12
+ import { cliFirstPartyDesired, firstPartyDesired, reconcileClaudeFirstPartySettings } from "../claude/first-party-settings";
13
+ import { claudeInterceptEnabled } from "../claude/intercept/runtime";
12
14
  import { removeDesktopPickerArtifacts } from "../claude/desktop-picker";
13
15
  import { findLiveProxy } from "../server/proxy-liveness";
14
16
  import { runtimeRequest } from "./runtime-api";
@@ -49,6 +51,7 @@ export interface EnsureDesiredIntegrationsDeps {
49
51
  applyDesktopFirstParty?: typeof applyDesktopFirstParty;
50
52
  inspectDesktopFirstParty?: typeof inspectDesktopFirstParty;
51
53
  observeClaudeDesktopMode?: typeof observeClaudeDesktopMode;
54
+ reconcileClaudeFirstPartySettings?: typeof reconcileClaudeFirstPartySettings;
52
55
  inspectDesktop3pConfigLibrary?: typeof inspectDesktop3pConfigLibrary;
53
56
  findLiveProxyImpl?: typeof findLiveProxy;
54
57
  runtimeRequestImpl?: typeof runtimeRequest;
@@ -143,6 +146,15 @@ export async function ensureClaudeDesktopMatchesDesired(
143
146
  ): Promise<void> {
144
147
  const config = deps.loadConfig();
145
148
  const { log, error } = io(deps);
149
+ if (cliFirstPartyDesired(config)) {
150
+ const seen = (deps.inspectDesktopFirstParty ?? inspectDesktopFirstParty)(config);
151
+ if (seen.settings.kind === "absent" || seen.stale || !claudeInterceptEnabled(config)) {
152
+ const result = (deps.reconcileClaudeFirstPartySettings ?? reconcileClaudeFirstPartySettings)(config,
153
+ firstPartyDesired(config, (deps.observeClaudeDesktopMode ?? observeClaudeDesktopMode)(config)));
154
+ if (result.ok && result.changed) log(` + Claude CLI first-party env refreshed (${result.path})`);
155
+ else if (!result.ok) error(`⚠️ Claude CLI first-party env refresh skipped: ${result.reason}.`);
156
+ }
157
+ }
146
158
  if (claudeDesktopIntegrationEnabled(config)) {
147
159
  if (resolveClaudeDesktopMode(config, (deps.observeClaudeDesktopMode ?? observeClaudeDesktopMode)(config)) !== "first-party") return;
148
160
  const library = (deps.inspectDesktop3pConfigLibrary ?? inspectDesktop3pConfigLibrary)({
@@ -179,8 +191,9 @@ export async function ensureClaudeDesktopMatchesDesired(
179
191
  error(`⚠️ Claude Desktop picker cleanup skipped: ${detail}.`);
180
192
  }
181
193
  try {
182
- const env = (deps.removeDesktopFirstParty ?? removeDesktopFirstParty)();
194
+ const env = (deps.removeDesktopFirstParty ?? removeDesktopFirstParty)(deps.loadConfig());
183
195
  if (env.ok && env.changed) log(" ↩️ Claude Desktop first-party env removed.");
196
+ else if (env.ok && env.retainedFor === "cli") log(" = Shared first-party env retained for Claude Code CLI.");
184
197
  else if (!env.ok) error(`⚠️ Claude Desktop first-party env cleanup skipped: ${env.reason} (${env.path}).`);
185
198
  } catch (err) {
186
199
  const detail = err instanceof Error ? err.message : String(err);