@bitkyc08/opencodex 2.62.0-preview.20260923 → 2.63.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 (80) hide show
  1. package/gui/dist/assets/App-BqrsSrIR.js +50 -0
  2. package/gui/dist/assets/{Tray-DdvPBVLi.js → Tray-CncKDBTp.js} +1 -1
  3. package/gui/dist/assets/index-C6SJrh0N.js +86 -0
  4. package/gui/dist/assets/index-DdDunwDb.css +1 -0
  5. package/gui/dist/assets/{usage-companion-chart-BIhvlbQW.js → usage-companion-chart-CzAAAB1o.js} +1 -1
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/cursor/catalog.ts +15 -0
  9. package/src/adapters/cursor/effort-map.ts +7 -0
  10. package/src/adapters/cursor/envelope-echo.ts +51 -25
  11. package/src/adapters/cursor/protobuf-request.ts +39 -13
  12. package/src/adapters/cursor.ts +52 -4
  13. package/src/adapters/devin/cloud-direct/chat.ts +50 -16
  14. package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +4 -1
  15. package/src/adapters/devin/live-models.ts +7 -0
  16. package/src/adapters/devin.ts +7 -5
  17. package/src/adapters/kiro/reasoning.ts +5 -0
  18. package/src/adapters/openai-responses/passthrough.ts +4 -4
  19. package/src/adapters/openai-responses/tool-output-recovery.ts +5 -3
  20. package/src/claude/desktop-gateway-state.ts +7 -2
  21. package/src/claude/desktop-policy.ts +105 -1
  22. package/src/cli/config-command.ts +22 -7
  23. package/src/cli/doctor.ts +14 -4
  24. package/src/codex/catalog/effort.ts +35 -4
  25. package/src/codex/catalog/metadata.ts +33 -5
  26. package/src/codex/catalog/native-models.ts +43 -2
  27. package/src/codex/catalog/pinned-models.ts +37 -0
  28. package/src/codex/catalog-auto-refresh.ts +6 -0
  29. package/src/codex/data/roster-pinned-models.json +359 -0
  30. package/src/codex/data/upstream-models.json +365 -271
  31. package/src/codex/inject/provider-table.ts +108 -0
  32. package/src/codex/inject/remove.ts +2 -114
  33. package/src/codex/model-entitlements.ts +14 -10
  34. package/src/codex/subagent-defaults.ts +2 -109
  35. package/src/codex/toml-source-lines.ts +112 -0
  36. package/src/config/live-reconcile.ts +145 -27
  37. package/src/config/load-degrade.ts +3 -4
  38. package/src/config.ts +2 -2
  39. package/src/generated/compatibility-version.json +83 -67
  40. package/src/generated/model-metadata.ts +5 -5
  41. package/src/lab/artifacts/sanitize.ts +60 -19
  42. package/src/lib/app-owned-memory-stores.ts +7 -0
  43. package/src/lib/app-owned-memory.ts +13 -2
  44. package/src/lib/errors.ts +6 -4
  45. package/src/lib/upstream-retry.ts +35 -5
  46. package/src/oauth/devin.ts +43 -37
  47. package/src/providers/codebuddy-models.ts +11 -0
  48. package/src/providers/kiro-models.ts +9 -0
  49. package/src/providers/quota/vendor-probes-oauth.ts +9 -2
  50. package/src/providers/registry/entries-core.ts +19 -8
  51. package/src/providers/registry/entries-extended.ts +4 -1
  52. package/src/providers/registry/model-seeds.ts +22 -2
  53. package/src/responses/bridge-search-replay-cache.ts +20 -10
  54. package/src/responses/plaintext-v2-agent-messages.ts +10 -1
  55. package/src/routing/identity-domains.ts +22 -15
  56. package/src/server/index/websocket-handler.ts +22 -2
  57. package/src/server/management/agent-settings-routes.ts +19 -10
  58. package/src/server/management/context.ts +4 -2
  59. package/src/server/responses/codex-ws-exchange.ts +24 -8
  60. package/src/server/responses/core-codex-account.ts +4 -0
  61. package/src/server/responses/core-combo-failure.ts +16 -9
  62. package/src/server/responses/core-combo.ts +7 -3
  63. package/src/server/responses/core-options.ts +6 -0
  64. package/src/server/responses/native-injection-replay.ts +13 -1
  65. package/src/server/responses/native-injection.ts +66 -7
  66. package/src/server/responses/native-response-control.ts +6 -2
  67. package/src/server/responses/native-steering-replay.ts +60 -0
  68. package/src/server/responses/native-steering.ts +9 -1
  69. package/src/server/responses/passthrough-delivery.ts +3 -4
  70. package/src/server/responses/passthrough-dispatch.ts +7 -0
  71. package/src/server/responses/request-prepare.ts +12 -0
  72. package/src/server/responses/request-transport.ts +5 -2
  73. package/src/server/responses/ws-upstream.ts +1 -1
  74. package/src/server/ws-bridge.ts +17 -1
  75. package/src/types/request.ts +5 -0
  76. package/src/usage/expected-prices.ts +38 -8
  77. package/src/web-search/executor.ts +38 -13
  78. package/gui/dist/assets/App-CANWata-.js +0 -50
  79. package/gui/dist/assets/index-BIS5HXEP.js +0 -86
  80. package/gui/dist/assets/index-_bpvxJu0.css +0 -1
@@ -1 +1 @@
1
- import{n as e}from"./index-BIS5HXEP.js";var t={anthropic:`claude-color.svg`,"anthropic-apikey":`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`,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`},n={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`,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`},r={"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`},i=new Set([...Object.keys(n),...Object.keys(r)]);function a(e){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:void 0}function o(e,t){let n=a(e);return n?`/provider-icons/${n}`:void 0}var s=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`]),c=new Set([`baseten.svg`,`kilo.svg`,`sambanova.svg`,`venice.svg`,`zai.svg`]),l=new Set([`bizrouter.svg`,`featherless.svg`,`hyperbolic.svg`,`nebius.svg`,`parallel.svg`,`umans.svg`]);function u(e){if(!e)return`image`;let t=e.split(`/`).pop()??``;return s.has(t)?`mask`:c.has(t)?`plate`:l.has(t)?`dark-plate`:`image`}function d(e,t){let i=e.toLowerCase(),a=Object.hasOwn(r,i)?r[i]:void 0;return a?t(a):(Object.hasOwn(n,i)?n[i]:void 0)||(e===i&&/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e)?e.split(`-`).map(e=>e&&e[0].toUpperCase()+e.slice(1)).join(` `):e)}function f(e){return i.has(e.toLowerCase())}function p(e){return e===`command-code`?`commandcode-auth`:e===`commandcode`?`commandcode-api`:e}function m(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 h={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 g(e){return e.replace(/\.0+$/,``).replace(/(\.\d*?)0+$/,`$1`)}function _(e,t){let n=h[t];if(n){for(let t of n)if(e>=t.v)return`${g((e/t.v).toFixed(1))}${t.s}`;return String(e)}return e<1e4?String(e):e<1e6?`${g((e/1e3).toFixed(1))}K`:e<1e9?`${g((e/1e6).toFixed(1))}M`:e<0xe8d4a51000?`${g((e/1e9).toFixed(1))}B`:`${g((e/0xe8d4a51000).toFixed(1))}T`}var v=1e10;function y(e){return e<v?e*1e3:e}function b(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 y(r)>t}function x(e){return{fiveHourAvailable:e?.shortWindowSeconds===18e3&&typeof e.shortResetAt==`number`,weeklyAvailable:typeof e?.weeklyResetAt==`number`}}function S(e){let t=e?.trim().toLowerCase();return t===`go`||t===`free`}function C(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 S(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 w(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=(S(t)?[e.monthlyPercent]:[e.weeklyPercent,e.monthlyPercent]).filter(r);if(a.length===0)return b({...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 T(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 E(e){return Array.isArray(e)&&e.length<=100&&e.every(e=>typeof e==`string`)?JSON.stringify([...new Set(e)].sort()):void 0}function D(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&&E(o.hiddenProviders)===E(t.hiddenProviders)&&(t.models===null?o.models===null:E(o.models)===E(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 O={6:15,24:60,72:180,168:360};function k(e){return O[e]}function A(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 j(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 M(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 N(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 P(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 F(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 I=e(),L=[`#0A84FF`,`#FF9F0A`,`#30D158`,`#BF5AF2`,`#FF453A`,`#64D2FF`],R=640,z=160,B=28;function V(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 H(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 U({timeline:e,chartStyle:t,hours:n,loading:r,error:i,onRetry:a,locale:o,t:s}){if(r)return(0,I.jsx)(`div`,{className:`usage-companion-chart-skeleton`,"aria-busy":`true`,"aria-label":s(`usage.companion.loading`)});if(i)return(0,I.jsxs)(`div`,{className:`usage-companion-chart-state`,role:`alert`,children:[(0,I.jsx)(`span`,{children:s(`usage.companion.timelineUnavailable`)}),(0,I.jsx)(`button`,{type:`button`,className:`btn btn-ghost btn-sm`,onClick:a,children:s(`common.retry`)})]});if(!e||e.series.length===0)return(0,I.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=V(e,t),l=H(e,o),u=B,d=l.map((e,t)=>(0,I.jsx)(`text`,{x:B+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,I.jsx)(`polyline`,{points:P(e.points,584,104,c,0),transform:`translate(${B},${B})`,fill:`none`,stroke:L[t%L.length],strokeWidth:`2`,strokeLinejoin:`round`,strokeLinecap:`round`},e.id)):F(e.series,584,104,c,0).map(e=>(0,I.jsx)(`rect`,{x:B+e.x,y:B+e.y,width:e.width,height:e.height,fill:L[e.seriesIndex%L.length],rx:`1`},`${e.bucketIndex}-${e.seriesIndex}`));return(0,I.jsxs)(`div`,{className:`usage-companion-chart`,children:[(0,I.jsxs)(`svg`,{viewBox:`0 0 ${R} ${z}`,role:`img`,"aria-label":s(`usage.companion.chartLabel`),children:[(0,I.jsx)(`line`,{x1:B,y1:u,x2:B,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`line`,{x1:B,y1:132,x2:612,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`text`,{x:24,y:32,textAnchor:`end`,className:`usage-companion-axis-label`,children:A(c)}),f,d]}),(0,I.jsx)(`div`,{className:`usage-companion-legend`,children:e.series.map((e,t)=>(0,I.jsxs)(`span`,{className:`usage-companion-legend-item`,children:[(0,I.jsx)(`span`,{className:`usage-companion-swatch`,style:{background:L[t%L.length]},"aria-hidden":`true`}),(0,I.jsx)(`span`,{children:e.id})]},e.id))}),e.truncated&&(0,I.jsx)(`p`,{className:`muted text-caption`,children:s(`usage.companion.olderRecordsSkipped`)})]})}export{u as _,T as a,M as c,x as d,_ as f,p as g,f as h,D as i,w as l,d as m,k as n,A as o,m as p,N as r,j as s,U as t,C as u,o as v};
1
+ import{n as e}from"./index-C6SJrh0N.js";var t={anthropic:`claude-color.svg`,"anthropic-apikey":`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`,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`},n={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`,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`},r={"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`},i=new Set([...Object.keys(n),...Object.keys(r)]);function a(e){let n=e.toLowerCase();return Object.hasOwn(t,n)?t[n]:void 0}function o(e,t){let n=a(e);return n?`/provider-icons/${n}`:void 0}var s=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`]),c=new Set([`baseten.svg`,`kilo.svg`,`sambanova.svg`,`venice.svg`,`zai.svg`]),l=new Set([`bizrouter.svg`,`featherless.svg`,`hyperbolic.svg`,`nebius.svg`,`parallel.svg`,`umans.svg`]);function u(e){if(!e)return`image`;let t=e.split(`/`).pop()??``;return s.has(t)?`mask`:c.has(t)?`plate`:l.has(t)?`dark-plate`:`image`}function d(e,t){let i=e.toLowerCase(),a=Object.hasOwn(r,i)?r[i]:void 0;return a?t(a):(Object.hasOwn(n,i)?n[i]:void 0)||(e===i&&/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(e)?e.split(`-`).map(e=>e&&e[0].toUpperCase()+e.slice(1)).join(` `):e)}function f(e){return i.has(e.toLowerCase())}function p(e){return e===`command-code`?`commandcode-auth`:e===`commandcode`?`commandcode-api`:e}function m(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 h={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 g(e){return e.replace(/\.0+$/,``).replace(/(\.\d*?)0+$/,`$1`)}function _(e,t){let n=h[t];if(n){for(let t of n)if(e>=t.v)return`${g((e/t.v).toFixed(1))}${t.s}`;return String(e)}return e<1e4?String(e):e<1e6?`${g((e/1e3).toFixed(1))}K`:e<1e9?`${g((e/1e6).toFixed(1))}M`:e<0xe8d4a51000?`${g((e/1e9).toFixed(1))}B`:`${g((e/0xe8d4a51000).toFixed(1))}T`}var v=1e10;function y(e){return e<v?e*1e3:e}function b(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 y(r)>t}function x(e){return{fiveHourAvailable:e?.shortWindowSeconds===18e3&&typeof e.shortResetAt==`number`,weeklyAvailable:typeof e?.weeklyResetAt==`number`}}function S(e){let t=e?.trim().toLowerCase();return t===`go`||t===`free`}function C(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 S(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 w(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=(S(t)?[e.monthlyPercent]:[e.weeklyPercent,e.monthlyPercent]).filter(r);if(a.length===0)return b({...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 T(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 E(e){return Array.isArray(e)&&e.length<=100&&e.every(e=>typeof e==`string`)?JSON.stringify([...new Set(e)].sort()):void 0}function D(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&&E(o.hiddenProviders)===E(t.hiddenProviders)&&(t.models===null?o.models===null:E(o.models)===E(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 O={6:15,24:60,72:180,168:360};function k(e){return O[e]}function A(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 j(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 M(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 N(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 P(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 F(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 I=e(),L=[`#0A84FF`,`#FF9F0A`,`#30D158`,`#BF5AF2`,`#FF453A`,`#64D2FF`],R=640,z=160,B=28;function V(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 H(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 U({timeline:e,chartStyle:t,hours:n,loading:r,error:i,onRetry:a,locale:o,t:s}){if(r)return(0,I.jsx)(`div`,{className:`usage-companion-chart-skeleton`,"aria-busy":`true`,"aria-label":s(`usage.companion.loading`)});if(i)return(0,I.jsxs)(`div`,{className:`usage-companion-chart-state`,role:`alert`,children:[(0,I.jsx)(`span`,{children:s(`usage.companion.timelineUnavailable`)}),(0,I.jsx)(`button`,{type:`button`,className:`btn btn-ghost btn-sm`,onClick:a,children:s(`common.retry`)})]});if(!e||e.series.length===0)return(0,I.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=V(e,t),l=H(e,o),u=B,d=l.map((e,t)=>(0,I.jsx)(`text`,{x:B+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,I.jsx)(`polyline`,{points:P(e.points,584,104,c,0),transform:`translate(${B},${B})`,fill:`none`,stroke:L[t%L.length],strokeWidth:`2`,strokeLinejoin:`round`,strokeLinecap:`round`},e.id)):F(e.series,584,104,c,0).map(e=>(0,I.jsx)(`rect`,{x:B+e.x,y:B+e.y,width:e.width,height:e.height,fill:L[e.seriesIndex%L.length],rx:`1`},`${e.bucketIndex}-${e.seriesIndex}`));return(0,I.jsxs)(`div`,{className:`usage-companion-chart`,children:[(0,I.jsxs)(`svg`,{viewBox:`0 0 ${R} ${z}`,role:`img`,"aria-label":s(`usage.companion.chartLabel`),children:[(0,I.jsx)(`line`,{x1:B,y1:u,x2:B,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`line`,{x1:B,y1:132,x2:612,y2:132,className:`usage-companion-axis`}),(0,I.jsx)(`text`,{x:24,y:32,textAnchor:`end`,className:`usage-companion-axis-label`,children:A(c)}),f,d]}),(0,I.jsx)(`div`,{className:`usage-companion-legend`,children:e.series.map((e,t)=>(0,I.jsxs)(`span`,{className:`usage-companion-legend-item`,children:[(0,I.jsx)(`span`,{className:`usage-companion-swatch`,style:{background:L[t%L.length]},"aria-hidden":`true`}),(0,I.jsx)(`span`,{children:e.id})]},e.id))}),e.truncated&&(0,I.jsx)(`p`,{className:`muted text-caption`,children:s(`usage.companion.olderRecordsSkipped`)})]})}export{u as _,T as a,M as c,x as d,_ as f,p as g,f as h,D as i,w as l,d as m,k as n,A as o,m as p,N as r,j as s,U as t,C as u,o as v};
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-BIS5HXEP.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-_bpvxJu0.css">
19
+ <script type="module" crossorigin src="/assets/index-C6SJrh0N.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-DdDunwDb.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.62.0-preview.20260923",
3
+ "version": "2.63.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",
@@ -184,6 +184,21 @@ export const CURSOR_CAPABILITIES: Record<string, CursorCapability> = {
184
184
  thinkingFast: { levels: FULL, order: T },
185
185
  },
186
186
  },
187
+ // 260923 Claude Opus 5.5: cursor.com/docs/models/claude-opus-5-5 publishes the id
188
+ // `claude-opus-5-5`, a thinking variant, a `claude-opus-5-5-fast` tier and a 1M max context.
189
+ // The ladders mirror the measured claude-opus-5 rows (fast stops at high) until the live
190
+ // GetUsableModels roster is dumped; the live filter drops any id the account cannot use.
191
+ "claude-opus-5-5": {
192
+ displayName: "Claude Opus 5.5",
193
+ window: CONTEXT_1M,
194
+ defaultVariant: "thinking",
195
+ variants: {
196
+ regular: { levels: FULL },
197
+ thinking: { levels: FULL, order: T },
198
+ fast: { levels: ["low", "medium", "high"] },
199
+ thinkingFast: { levels: FULL, order: T },
200
+ },
201
+ },
187
202
  "glm-5.2": {
188
203
  displayName: "GLM 5.2",
189
204
  window: CONTEXT_1M,
@@ -36,6 +36,9 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
36
36
  "claude-opus-4-8-fast": ["low", "medium", "high", "xhigh", "max"],
37
37
  "claude-opus-5": ["low", "medium", "high", "xhigh", "max"],
38
38
  "claude-opus-5-fast": ["low", "medium", "high"],
39
+ // 260923 Opus 5.5 (cursor.com/docs/models/claude-opus-5-5), mirroring the measured opus-5 rows.
40
+ "claude-opus-5-5": ["low", "medium", "high", "xhigh", "max"],
41
+ "claude-opus-5-5-fast": ["low", "medium", "high"],
39
42
  "claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
40
43
  "glm-5.2": ["high", "max"],
41
44
  // 260825 live GetUsableModels. gemini-3.6-flash was the first Cursor model exposing
@@ -53,6 +56,8 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
53
56
  // 4.6-opus thinks only at high/max, 4.5-opus only at high, 4.6-sonnet only at medium.
54
57
  "claude-opus-5-thinking": ["low", "medium", "high", "xhigh", "max"],
55
58
  "claude-opus-5-thinking-fast": ["low", "medium", "high", "xhigh", "max"],
59
+ "claude-opus-5-5-thinking": ["low", "medium", "high", "xhigh", "max"],
60
+ "claude-opus-5-5-thinking-fast": ["low", "medium", "high", "xhigh", "max"],
56
61
  "claude-opus-4-8-thinking": ["low", "medium", "high", "xhigh", "max"],
57
62
  "claude-opus-4-8-thinking-fast": ["low", "medium", "high", "xhigh", "max"],
58
63
  "claude-opus-4-7-thinking": ["low", "medium", "high", "xhigh", "max"],
@@ -130,6 +135,8 @@ const CANONICAL_CODEX_EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"] a
130
135
  const CURSOR_THINKING_FAMILIES: Readonly<Record<string, { source: string; order: "thinking-then-effort" | "effort-then-thinking" | "bare" }>> = {
131
136
  "claude-opus-5-thinking": { source: "claude-opus-5", order: "thinking-then-effort" },
132
137
  "claude-opus-5-thinking-fast": { source: "claude-opus-5-fast", order: "thinking-then-effort" },
138
+ "claude-opus-5-5-thinking": { source: "claude-opus-5-5", order: "thinking-then-effort" },
139
+ "claude-opus-5-5-thinking-fast": { source: "claude-opus-5-5-fast", order: "thinking-then-effort" },
133
140
  "claude-opus-4-8-thinking": { source: "claude-opus-4-8", order: "thinking-then-effort" },
134
141
  "claude-opus-4-8-thinking-fast": { source: "claude-opus-4-8-fast", order: "thinking-then-effort" },
135
142
  "claude-opus-4-7-thinking": { source: "claude-opus-4-7", order: "thinking-then-effort" },
@@ -76,7 +76,7 @@ export const MAX_MIDSTREAM_SCAN_LENGTH = 512 * 1024;
76
76
  const MAX_MIDSTREAM_FINDINGS = 8;
77
77
  const MAX_ROUTING_COMMENTARY_BYTES = 512;
78
78
  /** Aggregate quarantine cap: past this, flush and disarm. */
79
- const MAX_HOLD_BYTES = 8 * 1024;
79
+ export const CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES = 8 * 1024;
80
80
  const encoder = new TextEncoder();
81
81
 
82
82
  export class CursorToolResultEchoError extends Error {
@@ -130,16 +130,19 @@ export class CursorMidstreamEchoObserver {
130
130
  private totalLength = 0;
131
131
  private disarmed = false;
132
132
  private lineDisarmed = false;
133
- private corruptionWatch: { finding: MidstreamEchoFinding; remaining: number; window: string } | undefined;
133
+ private readonly corruptionWatches: Array<{
134
+ finding: MidstreamEchoFinding;
135
+ remaining: number;
136
+ window: string;
137
+ }> = [];
134
138
  private readonly recorded: MidstreamEchoFinding[] = [];
135
139
 
136
140
  feed(textDelta: string): void {
137
- if (this.disarmed && !this.corruptionWatch) return;
141
+ if (this.disarmed && this.corruptionWatches.length === 0) return;
138
142
  let index = 0;
139
143
  while (index < textDelta.length) {
140
144
  const newline = textDelta.indexOf("\n", index);
141
145
  const segment = newline === -1 ? textDelta.slice(index) : textDelta.slice(index, newline);
142
- if (this.corruptionWatch) this.watchCorruption(segment + (newline === -1 ? "" : "\n"));
143
146
  if (!this.disarmed && !this.lineDisarmed && segment.length > 0) {
144
147
  this.lineBuffer += segment;
145
148
  if (this.lineBuffer.length > MAX_MIDSTREAM_LINE_INDENT + 32) {
@@ -149,6 +152,11 @@ export class CursorMidstreamEchoObserver {
149
152
  }
150
153
  this.checkLine();
151
154
  }
155
+ // Recognize the next marker before charging its line to a corruption window.
156
+ // A call-id on the marker's own line belongs to the new finding as well.
157
+ if (this.corruptionWatches.length > 0) {
158
+ this.watchCorruption(segment + (newline === -1 ? "" : "\n"));
159
+ }
152
160
  if (newline === -1) break;
153
161
  this.lineBuffer = "";
154
162
  this.lineDisarmed = false;
@@ -160,9 +168,7 @@ export class CursorMidstreamEchoObserver {
160
168
  }
161
169
 
162
170
  findings(): readonly MidstreamEchoFinding[] {
163
- if (this.corruptionWatch) {
164
- this.settleCorruption();
165
- }
171
+ while (this.corruptionWatches.length > 0) this.settleCorruption(0);
166
172
  return this.recorded;
167
173
  }
168
174
 
@@ -187,12 +193,24 @@ export class CursorMidstreamEchoObserver {
187
193
  this.lineDisarmed = true;
188
194
  return;
189
195
  }
190
- const finding: MidstreamEchoFinding = {
191
- marker,
192
- offset: this.lineStartOffset,
193
- callIdCorrupt: false,
194
- };
195
- this.corruptionWatch = { finding, remaining: MIDSTREAM_CORRUPTION_WINDOW, window: "" };
196
+ // A new marker ends the previous marker's corruption window: the text
197
+ // between two markers belongs to the earlier finding only. Without this,
198
+ // every open watch consumed the same following text, so one corrupt
199
+ // call-id after a second marker also marked the first, clean finding
200
+ // corrupt (clean-then-corrupt cross-contamination).
201
+ while (this.corruptionWatches.length > 0) this.settleCorruption(0);
202
+ if (this.recorded.length + this.corruptionWatches.length < MAX_MIDSTREAM_FINDINGS) {
203
+ const finding: MidstreamEchoFinding = {
204
+ marker,
205
+ offset: this.lineStartOffset,
206
+ callIdCorrupt: false,
207
+ };
208
+ this.corruptionWatches.push({
209
+ finding,
210
+ remaining: MIDSTREAM_CORRUPTION_WINDOW,
211
+ window: "",
212
+ });
213
+ }
196
214
  this.lineDisarmed = true;
197
215
  return;
198
216
  }
@@ -203,24 +221,28 @@ export class CursorMidstreamEchoObserver {
203
221
  }
204
222
 
205
223
  private watchCorruption(text: string): void {
206
- const watch = this.corruptionWatch;
207
- if (!watch) return;
208
- const take = Math.min(watch.remaining, text.length);
209
- watch.window += text.slice(0, take);
210
- watch.remaining -= take;
211
- if (watch.remaining <= 0) this.settleCorruption();
224
+ for (const watch of this.corruptionWatches) {
225
+ const take = Math.min(watch.remaining, text.length);
226
+ watch.window += text.slice(0, take);
227
+ watch.remaining -= take;
228
+ }
229
+ let index = 0;
230
+ while (index < this.corruptionWatches.length) {
231
+ if (this.corruptionWatches[index]!.remaining <= 0) this.settleCorruption(index);
232
+ else index += 1;
233
+ }
212
234
  }
213
235
 
214
- private settleCorruption(): void {
215
- const watch = this.corruptionWatch;
236
+ private settleCorruption(index: number): void {
237
+ const watch = this.corruptionWatches[index];
216
238
  if (!watch) return;
217
239
  const window = watch.window;
218
240
  watch.finding.callIdCorrupt =
219
241
  /fc_[0-9a-f]+[ \t]+mar-/.test(window)
220
242
  || /call_id: \S+[ \t]+\S+_0\b/.test(window);
221
- if (this.recorded.length < MAX_MIDSTREAM_FINDINGS) this.recorded.push(watch.finding);
243
+ this.recorded.push(watch.finding);
222
244
  // Window text is discarded here; only booleans/offsets survive.
223
- this.corruptionWatch = undefined;
245
+ this.corruptionWatches.splice(index, 1);
224
246
  }
225
247
  }
226
248
 
@@ -251,7 +273,11 @@ export class CursorEnvelopeEchoSniffer {
251
273
  const stillPrefix = ECHO_MARKERS.some(marker =>
252
274
  probe.length < marker.length && marker.startsWith(probe),
253
275
  );
254
- if (stillPrefix && this.byteCount <= MAX_SNIFF_BYTES && this.buffered.length < MAX_HOLD_BYTES) {
276
+ if (
277
+ stillPrefix
278
+ && this.byteCount <= MAX_SNIFF_BYTES
279
+ && this.buffered.length < CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES
280
+ ) {
255
281
  return { kind: "hold" };
256
282
  }
257
283
  this.done = true;
@@ -316,7 +342,7 @@ export class CursorRoutingCommentarySniffer {
316
342
  && lineBreakCount < 2;
317
343
  if (
318
344
  this.byteCount < MAX_ROUTING_COMMENTARY_BYTES
319
- && this.buffered.length < MAX_HOLD_BYTES
345
+ && this.buffered.length < CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES
320
346
  && (lineBreakCount === 0 || pendingFailureClaim)
321
347
  && (hasRoutingHint || this.byteCount < 64)
322
348
  ) {
@@ -311,6 +311,7 @@ function rootPromptMessages(
311
311
  const replayRuns = new Map<RootBlobCandidate["role"], {
312
312
  text: string;
313
313
  entry: RootBlobCandidate;
314
+ entryIndex: number;
314
315
  length: number;
315
316
  }>();
316
317
  const toolCallCounts = new Map<string, number>();
@@ -337,13 +338,13 @@ function rootPromptMessages(
337
338
  // half, so losing it re-primes the self-reinforcing loop the breaker exists to end.
338
339
  { ...opts, text: marked, messageIndex: previous.entry.messageIndex ?? opts.messageIndex },
339
340
  );
340
- entries[entries.indexOf(previous.entry)] = replacement;
341
- replayRuns.set(role, { text: normalized, entry: replacement, length: runLength });
341
+ entries[previous.entryIndex] = replacement;
342
+ replayRuns.set(role, { text: normalized, entry: replacement, entryIndex: previous.entryIndex, length: runLength });
342
343
  return;
343
344
  }
344
345
  const entry = rootBlobCandidate(payload, role, opts);
345
346
  entries.push(entry);
346
- replayRuns.set(role, { text: normalized, entry, length: 1 });
347
+ replayRuns.set(role, { text: normalized, entry, entryIndex: entries.length - 1, length: 1 });
347
348
  };
348
349
 
349
350
  for (let i = 0; i < messages.length; i++) {
@@ -949,11 +950,26 @@ function serializeToolCallArguments(args: Record<string, unknown>): string | und
949
950
 
950
951
  /** Truncate to a byte budget without splitting a UTF-8 sequence. */
951
952
  function truncateUtf8(text: string, maxBytes: number): string {
952
- const encoded = encoder.encode(text);
953
- if (encoded.byteLength <= maxBytes) return text;
954
- let end = Math.max(0, maxBytes);
955
- while (end > 0 && (encoded[end]! & 0xc0) === 0x80) end -= 1;
956
- return decoder.decode(encoded.subarray(0, end));
953
+ const encoded = new Uint8Array(Math.max(0, maxBytes));
954
+ const { read, written } = encoder.encodeInto(text, encoded);
955
+ return read === text.length ? text : decoder.decode(encoded.subarray(0, written));
956
+ }
957
+
958
+ /** Return the UTF-8 length only when it fits the bound, without allocating an input-sized buffer. */
959
+ function boundedUtf8ByteLength(text: string, maxBytes: number): number | undefined {
960
+ let bytes = 0;
961
+ for (let i = 0; i < text.length; i++) {
962
+ const code = text.charCodeAt(i);
963
+ if (code < 0x80) bytes += 1;
964
+ else if (code < 0x800) bytes += 2;
965
+ else if (code >= 0xd800 && code <= 0xdbff && i + 1 < text.length
966
+ && text.charCodeAt(i + 1) >= 0xdc00 && text.charCodeAt(i + 1) <= 0xdfff) {
967
+ bytes += 4;
968
+ i += 1;
969
+ } else bytes += 3;
970
+ if (bytes > maxBytes) return undefined;
971
+ }
972
+ return bytes;
957
973
  }
958
974
 
959
975
  /**
@@ -966,10 +982,8 @@ function truncateUtf8(text: string, maxBytes: number): string {
966
982
  * exists to prevent. A bounded prefix still identifies the call (tool name plus the head of its
967
983
  * arguments) while leaving the output room to survive.
968
984
  */
969
- function toolCallArgumentsText(args: Record<string, unknown>): string {
970
- const serialized = serializeToolCallArguments(args);
971
- if (serialized === undefined) return "[unserializable arguments]";
972
- if (encoder.encode(serialized).byteLength <= CURSOR_INVOCATION_ARGUMENTS_BYTE_LIMIT) return serialized;
985
+ function serializedToolCallArgumentsText(serialized: string): string {
986
+ if (boundedUtf8ByteLength(serialized, CURSOR_INVOCATION_ARGUMENTS_BYTE_LIMIT) !== undefined) return serialized;
973
987
  // The budget is the size of the RENDERED line, so the marker has to come out of it rather than be
974
988
  // added on top: otherwise every truncated invocation exceeds the declared limit by the marker.
975
989
  const marker = "…[arguments truncated]";
@@ -978,6 +992,11 @@ function toolCallArgumentsText(args: Record<string, unknown>): string {
978
992
  return `${truncateUtf8(serialized, keep)}${marker}`;
979
993
  }
980
994
 
995
+ function toolCallArgumentsText(args: Record<string, unknown>): string {
996
+ const serialized = serializeToolCallArguments(args);
997
+ return serialized === undefined ? "[unserializable arguments]" : serializedToolCallArgumentsText(serialized);
998
+ }
999
+
981
1000
  /**
982
1001
  * The invocation that produced a replayed tool result, rendered as ONE descriptive line inside the
983
1002
  * result envelope.
@@ -1052,8 +1071,15 @@ function restoreClippedInvocationArguments(
1052
1071
  if (!call) continue;
1053
1072
  const full = serializeToolCallArguments(call.arguments);
1054
1073
  if (full === undefined) continue;
1055
- const clipped = toolCallArgumentsText(call.arguments);
1074
+ const clipped = serializedToolCallArgumentsText(full);
1056
1075
  if (clipped === full) continue;
1076
+ // The replacement must add at least the raw UTF-8 argument-byte delta. Reject an impossible
1077
+ // restoration with a bounded scan before building the widened string, JSON, and byte array.
1078
+ const clippedBytes = encoder.encode(clipped).byteLength;
1079
+ // boundedUtf8ByteLength already gives up past clippedBytes + spare, so a returned number
1080
+ // always fits; a second size comparison here can never fire.
1081
+ const fullBytes = boundedUtf8ByteLength(full, clippedBytes + spare);
1082
+ if (fullBytes === undefined) continue;
1057
1083
  const name = namespacedToolName(call.namespace, call.name);
1058
1084
  // Anchored on the preceding newline. `toolResultToText` always emits the invocation after the
1059
1085
  // `[tool_result]`, `call_id:` and `name:` lines, so the real line is never first — and
@@ -48,6 +48,7 @@ import {
48
48
  import { runCursorTurnWithRetry } from "./cursor/transport-retry";
49
49
  import { cursorRequestHasShellAlias, cursorRequestUsesCodeMode } from "./cursor/tool-definitions";
50
50
  import {
51
+ CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES,
51
52
  CURSOR_ECHO_RETRY_CONTINUATION_TEXT,
52
53
  CURSOR_ROUTING_COMMENTARY_RETRY_TEXT,
53
54
  CursorEnvelopeEchoSniffer,
@@ -315,6 +316,8 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
315
316
  ? new CursorRoutingCommentarySniffer()
316
317
  : undefined;
317
318
  let guardHeld: AdapterEvent[] = [];
319
+ let guardHeldBytes = 0;
320
+ const guardEncoder = new TextEncoder();
318
321
  // Exactly-once observation: every client-bound text delta passes through here
319
322
  // exactly once — held deltas only on release, ordinary deltas at emit time.
320
323
  const emitTextObserved = (event: AdapterEvent): void => {
@@ -327,7 +330,49 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
327
330
  emitTextObserved(held);
328
331
  }
329
332
  guardHeld = [];
333
+ guardHeldBytes = 0;
330
334
  };
335
+ // A single frame can carry a multi-megabyte payload (the transport accepts up to the
336
+ // 16 MiB Cursor message bound), so the serialized size is projected — object overhead
337
+ // plus raw payload length — BEFORE any encoded copy exists. Escapes only inflate the
338
+ // exact figure, making the raw length a safe lower bound for the overflow decision.
339
+ const GUARD_EVENT_OVERHEAD_BYTES = 64;
340
+ const projectedGuardEventBytes = (event: AdapterEvent): number =>
341
+ GUARD_EVENT_OVERHEAD_BYTES
342
+ + (event.type === "text_delta"
343
+ ? Buffer.byteLength(event.text, "utf8")
344
+ : event.type === "thinking_delta"
345
+ ? Buffer.byteLength(event.thinking, "utf8")
346
+ : 0);
347
+ const holdGuardEvent = (event: AdapterEvent) => {
348
+ if (guardHeldBytes + projectedGuardEventBytes(event) > CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES) {
349
+ // Too large to retain even unescaped: settle the sniffers, release what was held,
350
+ // and pass this event through without ever encoding it.
351
+ echoSniffer?.finish();
352
+ routingCommentarySniffer?.finish();
353
+ releaseGuardHeld();
354
+ if (event.type !== "heartbeat") emittedOutput = true;
355
+ emitTextObserved(event);
356
+ return false;
357
+ }
358
+ guardHeld.push(event);
359
+ // Count the complete retained representation, including per-event overhead, so an
360
+ // upstream cannot evade the cap with empty or non-text reasoning frames.
361
+ guardHeldBytes += guardEncoder.encode(JSON.stringify(event)).byteLength;
362
+ if (guardHeldBytes <= CURSOR_OUTPUT_GUARD_MAX_HOLD_BYTES) return true;
363
+ echoSniffer?.finish();
364
+ routingCommentarySniffer?.finish();
365
+ releaseGuardHeld();
366
+ return false;
367
+ };
368
+ // Bound each feed before a sniffer copies or encodes it. Their normal 40 B / 512 B
369
+ // hold thresholds are checked after classification, so one large frame previously
370
+ // let a late match inspect an arbitrary tail. Only these leading UTF-16 prefixes
371
+ // now participate in corrective retry; later text remains ordinary output.
372
+ const ECHO_SNIFF_FEED_MAX_CHARS = 512;
373
+ const ROUTING_SNIFF_FEED_MAX_CHARS = 2048;
374
+ const boundedSniffText = (text: string, maxChars: number): string =>
375
+ text.length > maxChars ? text.slice(0, maxChars) : text;
331
376
  const guardsSettled = () =>
332
377
  (!echoSniffer || echoSniffer.settled)
333
378
  && (!routingCommentarySniffer || routingCommentarySniffer.settled);
@@ -368,27 +413,30 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
368
413
  }
369
414
  if (!guardsSettled()) {
370
415
  if (event.type === "text_delta") {
371
- guardHeld.push(event);
416
+ // Classify the delta before the aggregate-cap check: an oversized first
417
+ // delta must still pass the armed sniffers (echo/hallucination detection is
418
+ // prefix-based), so the cap cannot disarm them before they see the text.
372
419
  if (echoSniffer && !echoSniffer.settled) {
373
- const decision = echoSniffer.feed(event.text);
420
+ const decision = echoSniffer.feed(boundedSniffText(event.text, ECHO_SNIFF_FEED_MAX_CHARS));
374
421
  if (decision.kind === "echo") {
375
422
  guardHeld = [];
376
423
  throw new CursorToolResultEchoError(decision.marker);
377
424
  }
378
425
  }
379
426
  if (routingCommentarySniffer && !routingCommentarySniffer.settled) {
380
- const decision = routingCommentarySniffer.feed(event.text);
427
+ const decision = routingCommentarySniffer.feed(boundedSniffText(event.text, ROUTING_SNIFF_FEED_MAX_CHARS));
381
428
  if (decision.kind === "hallucination") {
382
429
  guardHeld = [];
383
430
  throw new CursorRoutingCommentaryError();
384
431
  }
385
432
  }
433
+ if (!holdGuardEvent(event)) continue;
386
434
  if (guardsSettled()) releaseGuardHeld();
387
435
  continue;
388
436
  } else if (event.type === "thinking_delta" || event.type === "heartbeat") {
389
437
  // Reasoning before first text stays ordered; liveness still passes through.
390
438
  if (event.type === "thinking_delta") {
391
- guardHeld.push(event);
439
+ holdGuardEvent(event);
392
440
  continue;
393
441
  }
394
442
  } else {
@@ -37,6 +37,7 @@ import { buildMetadata } from './metadata.js';
37
37
  import { getCachedUserJwt } from './auth.js';
38
38
  import { getCachedCatalog, ModelNotAvailableError, type CacheEntry } from './catalog.js';
39
39
  import { anySignal, cancelBodyOnAbort } from '../../../lib/abort.js';
40
+ import { parseRetryAfterFromMessage } from '../../../lib/retry-delay.js';
40
41
  import { resolveDevinApiBaseUrl } from '../../../oauth/devin/api-base.js';
41
42
 
42
43
  /**
@@ -1071,6 +1072,13 @@ export class CloudChatError extends Error {
1071
1072
  * a live rate limit was classified 502 and core's failover never rotated.
1072
1073
  */
1073
1074
  public readonly status?: number,
1075
+ /**
1076
+ * Provider-stated recovery delay in seconds. The trailer parser extracts
1077
+ * it while the raw upstream text is still available, because the thrown
1078
+ * message is content-free: the stated-reset retry reads this typed field
1079
+ * instead of scraping untrusted trailer text out of error.message.
1080
+ */
1081
+ public readonly retryAfterSeconds?: number,
1074
1082
  ) {
1075
1083
  super(message);
1076
1084
  this.name = 'CloudChatError';
@@ -1078,6 +1086,15 @@ export class CloudChatError extends Error {
1078
1086
  }
1079
1087
 
1080
1088
  const TRACE_ID_RE = /\(trace ID: ([0-9a-f]+)\)/i;
1089
+ const SAFE_CONNECT_CODES = new Set([
1090
+ 'canceled', 'unknown', 'invalid_argument', 'deadline_exceeded', 'not_found', 'already_exists',
1091
+ 'permission_denied', 'resource_exhausted', 'failed_precondition', 'aborted', 'out_of_range',
1092
+ 'unimplemented', 'internal', 'unavailable', 'data_loss', 'unauthenticated',
1093
+ ]);
1094
+
1095
+ function safeConnectCode(value: unknown): string | undefined {
1096
+ return typeof value === 'string' && SAFE_CONNECT_CODES.has(value) ? value : undefined;
1097
+ }
1081
1098
 
1082
1099
  /**
1083
1100
  * A quota refusal Cognition delivers as `permission_denied`.
@@ -1296,7 +1313,7 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator<C
1296
1313
  // Bun + Node ReadableStream readers diverge on the type-level shape
1297
1314
  // (Bun's includes a `readMany` method); both work the same at runtime.
1298
1315
  const reader = resp.body.getReader() as ReadableStreamDefaultReader<Uint8Array>;
1299
- let trailerError: { code?: string; message: string; traceId?: string } | null = null;
1316
+ let trailerError: { code?: string; message: string; opaqueDenial: boolean; retryAfterSeconds?: number; traceId?: string } | null = null;
1300
1317
  let sawEos = false;
1301
1318
 
1302
1319
  /**
@@ -1441,14 +1458,23 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator<C
1441
1458
  const text = payload.toString('utf8');
1442
1459
  if (text && text.includes('"error"')) {
1443
1460
  let code: string | undefined;
1444
- let message = text;
1461
+ let message = '';
1445
1462
  try {
1446
1463
  const j = JSON.parse(text) as { error?: { code?: string; message?: string } };
1447
- code = j.error?.code;
1448
- if (j.error?.message) message = j.error.message;
1449
- } catch { /* keep raw */ }
1464
+ code = safeConnectCode(j.error?.code);
1465
+ if (typeof j.error?.message === 'string') message = j.error.message;
1466
+ } catch { /* malformed trailers still become a content-free error */ }
1450
1467
  const traceMatch = message.match(TRACE_ID_RE);
1451
- trailerError = { code, message, traceId: traceMatch?.[1] };
1468
+ trailerError = {
1469
+ code,
1470
+ message,
1471
+ opaqueDenial: /an internal error occurred/i.test(message),
1472
+ // Parsed here, where the raw text still exists: the thrown error
1473
+ // is content-free, so the stated-reset retry consumes this typed
1474
+ // field rather than the upstream sentence.
1475
+ retryAfterSeconds: parseRetryAfterFromMessage(message),
1476
+ traceId: traceMatch?.[1],
1477
+ };
1452
1478
  }
1453
1479
  continue;
1454
1480
  }
@@ -1477,16 +1503,16 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator<C
1477
1503
  // the catalog disagrees with the call, but the catalog can lag (a model
1478
1504
  // that was enabled at fetch time may have been gated between then and
1479
1505
  // now) or be missing (network failure caused a fall-through). When the
1480
- // raw trailer is this exact shape, swap in a message that names the
1506
+ // trailer is this exact shape, swap in a message that names the
1481
1507
  // model and explains the likely cause rather than re-passing
1482
- // Cognition's opaque text. The cloud's original message is appended in
1483
- // parens so users (and bug reports) still have it verbatim.
1508
+ // Cognition's opaque text. Raw trailer messages never leave this
1509
+ // parser: they can reflect the credential carried by the request.
1484
1510
  // Both codes carry this shape. Cognition uses `invalid_argument` for a
1485
1511
  // request it could not accept and `permission_denied` for one it would not,
1486
1512
  // and the message body is the same opaque sentence either way.
1487
1513
  const isOpaqueDenial =
1488
1514
  (trailerError.code === 'permission_denied' || trailerError.code === 'invalid_argument') &&
1489
- /an internal error occurred/i.test(trailerError.message);
1515
+ trailerError.opaqueDenial;
1490
1516
  if (isOpaqueDenial) {
1491
1517
  const enriched =
1492
1518
  `Cognition denied this request for model "${req.modelUid}" with the opaque ` +
@@ -1504,6 +1530,7 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator<C
1504
1530
  trailerError.code,
1505
1531
  trailerError.traceId,
1506
1532
  connectTrailerHttpStatus(trailerError.code, trailerError.message),
1533
+ trailerError.retryAfterSeconds,
1507
1534
  );
1508
1535
  }
1509
1536
  // Cognition also returns `permission_denied` when a tool description
@@ -1518,24 +1545,31 @@ export async function* streamChatEvents(req: CloudChatRequest): AsyncGenerator<C
1518
1545
  `Cognition denied this request (permission_denied). If tool descriptions ` +
1519
1546
  `are present, a blocklisted phrase may have triggered this — see the ` +
1520
1547
  `COGNITION_BLOCKLIST_REWRITES table in cloud-direct/chat.ts. ` +
1521
- // Keep the cloud's own sentence. Replacing it outright is what made the
1522
- // two Codex entries in that table expensive to find: the message named
1523
- // the table but dropped the only text that could have said whether this
1524
- // was a phrase match at all.
1525
- `(cloud message: ${trailerError.message}) ` +
1526
1548
  `(cloud trace ID: ${trailerError.traceId ?? 'n/a'})`;
1527
1549
  throw new CloudChatError(
1528
1550
  enriched,
1529
1551
  trailerError.code,
1530
1552
  trailerError.traceId,
1531
1553
  connectTrailerHttpStatus(trailerError.code, trailerError.message),
1554
+ trailerError.retryAfterSeconds,
1532
1555
  );
1533
1556
  }
1557
+ // Raw trailer messages never leave this parser: they can reflect the
1558
+ // credential carried by the request. The allowlisted code and the hex
1559
+ // trace id are the only upstream-controlled fields that reach the error.
1560
+ // The stated delay rides along in our own words so a client can still
1561
+ // tell how long to wait when local retry gives up, exceeds its cap, or
1562
+ // is disabled; the `~` keeps it from re-parsing as a downstream hint.
1534
1563
  throw new CloudChatError(
1535
- trailerError.message,
1564
+ `Cognition chat failed${trailerError.code ? ` (${trailerError.code})` : ''} ` +
1565
+ `(cloud trace ID: ${trailerError.traceId ?? 'n/a'})` +
1566
+ (trailerError.retryAfterSeconds === undefined
1567
+ ? ''
1568
+ : `; retry after ~${trailerError.retryAfterSeconds}s`),
1536
1569
  trailerError.code,
1537
1570
  trailerError.traceId,
1538
1571
  connectTrailerHttpStatus(trailerError.code, trailerError.message),
1572
+ trailerError.retryAfterSeconds,
1539
1573
  );
1540
1574
  }
1541
1575
  // Truncation detection: the cloud always terminates a successful stream
@@ -112,8 +112,11 @@ export async function* streamChatEventsWithResetRetry(
112
112
  && error.status === 429
113
113
  ? error
114
114
  : undefined;
115
+ // Prefer the typed field: trailer-derived errors are content-free now,
116
+ // so the parser preserved the stated delay there. The message scrape
117
+ // remains for errors whose text still carries a hint.
115
118
  const waitSec = retryableError
116
- ? parseRetryAfterFromMessage(retryableError.message)
119
+ ? retryableError.retryAfterSeconds ?? parseRetryAfterFromMessage(retryableError.message)
117
120
  : undefined;
118
121
  const waitMs = waitSec === undefined ? undefined : waitSec * 1000;
119
122
  if (