@bitkyc08/opencodex 2.65.0 → 2.66.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 (213) hide show
  1. package/README.md +14 -0
  2. package/gui/dist/assets/App-DyHlFeNB.js +51 -0
  3. package/gui/dist/assets/App-gkoJIEOV.css +1 -0
  4. package/gui/dist/assets/{Tray-D4bA1vff.js → Tray-CI8cVKWu.js} +1 -1
  5. package/gui/dist/assets/index-BiwnVAUm.js +86 -0
  6. package/gui/dist/assets/index-ComVsdSk.css +1 -0
  7. package/gui/dist/assets/{tray-data-BZXhB3qz.js → tray-data-_WyYzbMt.js} +1 -1
  8. package/gui/dist/index.html +2 -2
  9. package/package.json +1 -2
  10. package/src/adapters/anthropic/beta-allowlist.ts +80 -0
  11. package/src/adapters/anthropic/passthrough.ts +221 -0
  12. package/src/adapters/anthropic-image-codec.ts +56 -19
  13. package/src/adapters/anthropic-image-normalize.ts +20 -10
  14. package/src/adapters/anthropic.ts +55 -24
  15. package/src/adapters/coding-agent/protocol.ts +29 -19
  16. package/src/adapters/cursor/live-transport.ts +18 -3
  17. package/src/adapters/cursor/native-exec-shell.ts +18 -63
  18. package/src/adapters/cursor/native-exec.ts +5 -2
  19. package/src/adapters/cursor/native-foreground-shell.ts +44 -0
  20. package/src/adapters/devin/cloud-direct/chat.ts +7 -1
  21. package/src/adapters/exec-tool-result-normalize.ts +4 -1
  22. package/src/adapters/google.ts +12 -0
  23. package/src/adapters/ollama-native.ts +32 -1
  24. package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
  25. package/src/adapters/openai-chat.ts +6 -4
  26. package/src/adapters/openai-responses/passthrough.ts +1 -0
  27. package/src/adapters/openai-responses/reasoning.ts +45 -1
  28. package/src/bridge/response-json.ts +10 -2
  29. package/src/chat/outbound.ts +104 -52
  30. package/src/claude/claude-code-block.ts +16 -0
  31. package/src/claude/context-windows.ts +52 -4
  32. package/src/claude/desktop-first-party.ts +25 -8
  33. package/src/claude/inbound.ts +22 -0
  34. package/src/claude/intercept/connect-proxy.ts +37 -4
  35. package/src/claude/intercept/proxy-auth.ts +166 -0
  36. package/src/claude/intercept/runtime.ts +21 -0
  37. package/src/claude/intercept/settings.ts +33 -12
  38. package/src/claude/outbound.ts +45 -27
  39. package/src/cli/account-extended.ts +47 -15
  40. package/src/cli/account-target.ts +115 -0
  41. package/src/cli/account.ts +20 -28
  42. package/src/cli/agent.ts +10 -1
  43. package/src/cli/api-protocols.ts +206 -0
  44. package/src/cli/capabilities.ts +86 -0
  45. package/src/cli/combo.ts +2 -2
  46. package/src/cli/connect.ts +127 -3
  47. package/src/cli/dispatch.ts +8 -0
  48. package/src/cli/doctor.ts +33 -0
  49. package/src/cli/help.ts +2 -0
  50. package/src/cli/link.ts +288 -0
  51. package/src/cli/registry.ts +23 -0
  52. package/src/cli/runtime-api.ts +79 -0
  53. package/src/cli/system-command.ts +21 -2
  54. package/src/client/connect.ts +125 -23
  55. package/src/client/hub-relay.ts +12 -8
  56. package/src/client/link-join.ts +323 -0
  57. package/src/client/link-relay.ts +241 -0
  58. package/src/client/link-state.ts +110 -0
  59. package/src/client/link-teardown.ts +63 -0
  60. package/src/client/link-tunnel.ts +465 -0
  61. package/src/client/machine-listener.ts +14 -5
  62. package/src/client/runtime.ts +91 -42
  63. package/src/client/state.ts +4 -0
  64. package/src/codex/catalog/build-entries.ts +4 -0
  65. package/src/codex/catalog/derive-entry.ts +5 -0
  66. package/src/codex/catalog/parsing.ts +4 -0
  67. package/src/codex/catalog/retained-sync.ts +4 -1
  68. package/src/codex/desktop-switches.ts +88 -8
  69. package/src/codex/inject/plan.ts +16 -3
  70. package/src/codex/inject.ts +3 -0
  71. package/src/codex/internal/catalog-writer.ts +13 -2
  72. package/src/combos/failover.ts +3 -2
  73. package/src/combos/index.ts +12 -0
  74. package/src/combos/jev.ts +646 -0
  75. package/src/combos/request.ts +5 -0
  76. package/src/combos/resolve.ts +17 -15
  77. package/src/combos/types.ts +47 -3
  78. package/src/config/atomic-write.ts +3 -0
  79. package/src/config/live-reconcile.ts +20 -4
  80. package/src/config/persist-unlocked.ts +20 -7
  81. package/src/config/schema/config-schema.ts +16 -0
  82. package/src/config/schema/leaf-validators.ts +38 -1
  83. package/src/generated/compatibility-version.json +416 -128
  84. package/src/integrations/omp-yaml-source.ts +109 -4
  85. package/src/lab/automation/orchestrator.ts +60 -4
  86. package/src/lab/events/limits.ts +1 -1
  87. package/src/lib/bounded-body.ts +55 -38
  88. package/src/lib/config-ownership.ts +11 -22
  89. package/src/lib/local-desktop-snapshot-capability.ts +56 -0
  90. package/src/lib/local-management-capability.ts +25 -3
  91. package/src/lib/optional-shutdown-hooks.ts +17 -0
  92. package/src/lib/state-store-registrations.ts +2 -2
  93. package/src/lib/windows-elevation.ts +62 -25
  94. package/src/lib/windows-secret-acl.ts +114 -23
  95. package/src/link/admission-wait.ts +73 -0
  96. package/src/link/compensation.ts +100 -0
  97. package/src/link/fingerprint.ts +21 -0
  98. package/src/link/paths.ts +19 -0
  99. package/src/link/ports.ts +12 -0
  100. package/src/link/routes.ts +32 -0
  101. package/src/link/ssh-argv.ts +170 -0
  102. package/src/link/ssh-config.ts +152 -0
  103. package/src/link/ssh-runner.ts +161 -0
  104. package/src/link/status-projection.ts +96 -0
  105. package/src/link/store.ts +139 -0
  106. package/src/link/supervisor.ts +404 -0
  107. package/src/link/tunnel-state.ts +91 -0
  108. package/src/protocols/baseline.ts +88 -0
  109. package/src/protocols/codecs/chat.ts +17 -0
  110. package/src/protocols/codecs/messages.ts +18 -0
  111. package/src/protocols/codecs/responses.ts +17 -0
  112. package/src/protocols/contract.ts +185 -0
  113. package/src/protocols/dto.ts +267 -0
  114. package/src/protocols/encoders/adapter-events.ts +876 -0
  115. package/src/protocols/encoders/chat.ts +243 -0
  116. package/src/protocols/encoders/messages.ts +462 -0
  117. package/src/protocols/envelope.ts +43 -0
  118. package/src/protocols/features.ts +295 -0
  119. package/src/protocols/guard.ts +33 -0
  120. package/src/protocols/opaque-state.ts +132 -0
  121. package/src/protocols/path.ts +39 -0
  122. package/src/protocols/plan-snapshot.ts +226 -0
  123. package/src/protocols/plan.ts +192 -0
  124. package/src/protocols/provider-summary.ts +101 -0
  125. package/src/protocols/settings.ts +118 -0
  126. package/src/protocols/shadow-plan.ts +36 -0
  127. package/src/protocols/shadow.ts +62 -0
  128. package/src/protocols/trace.ts +335 -0
  129. package/src/providers/model-rename-migration.ts +3 -3
  130. package/src/providers/registry/entries-core.ts +6 -1
  131. package/src/providers/registry/entries-extended.ts +15 -0
  132. package/src/providers/registry/model-ids.ts +1 -0
  133. package/src/providers/registry/types.ts +6 -0
  134. package/src/providers/registry.ts +1 -1
  135. package/src/remote-control/workspace-hub.ts +5 -4
  136. package/src/responses/apply-patch-envelope.ts +6 -1
  137. package/src/responses/code-mode-shell-input.ts +4 -2
  138. package/src/responses/freeform-wrapper-scan.ts +51 -24
  139. package/src/responses/progressive-freeform-input.ts +14 -20
  140. package/src/responses/spill-store.ts +187 -33
  141. package/src/responses/state/snapshot-select.ts +49 -0
  142. package/src/responses/state/spill-inspect.ts +46 -0
  143. package/src/responses/state/spill-queue.ts +16 -0
  144. package/src/responses/state.ts +51 -27
  145. package/src/server/audio-client.ts +8 -3
  146. package/src/server/audio-upstream.ts +9 -4
  147. package/src/server/auth-cors.ts +25 -8
  148. package/src/server/chat-completions.ts +71 -11
  149. package/src/server/chat-native-eligibility.ts +74 -0
  150. package/src/server/chat-native.ts +139 -74
  151. package/src/server/claude-messages.ts +140 -22
  152. package/src/server/hub-usage.ts +4 -3
  153. package/src/server/index/link-listener.ts +195 -0
  154. package/src/server/index/optional-listeners.ts +115 -0
  155. package/src/server/index/serve-options.ts +44 -10
  156. package/src/server/index.ts +10 -9
  157. package/src/server/inference/attempt.ts +51 -0
  158. package/src/server/inference/client-encoder-delivery.ts +235 -0
  159. package/src/server/inference/client-wire-log.ts +49 -0
  160. package/src/server/inference/client-wire.ts +69 -0
  161. package/src/server/inference/context.ts +15 -0
  162. package/src/server/inference/final-log.ts +37 -0
  163. package/src/server/local-desktop-snapshot-auth.ts +57 -0
  164. package/src/server/management/agent-settings-routes.ts +15 -13
  165. package/src/server/management/api-access.ts +11 -1
  166. package/src/server/management/config-routes.ts +3 -6
  167. package/src/server/management/context.ts +25 -0
  168. package/src/server/management/link-routes.ts +535 -0
  169. package/src/server/management/logs-usage-routes.ts +31 -1
  170. package/src/server/management/native-integration-routes.ts +6 -11
  171. package/src/server/management/oauth-account-routes.ts +42 -16
  172. package/src/server/management/protocol-routes.ts +159 -0
  173. package/src/server/management/protocol-settings-patch.ts +145 -0
  174. package/src/server/management/provider-patch-transaction.ts +61 -0
  175. package/src/server/management/provider-routes.ts +39 -18
  176. package/src/server/management/route-registry.ts +12 -0
  177. package/src/server/management/sidebar-routes.ts +8 -3
  178. package/src/server/management/system-routes.ts +18 -1
  179. package/src/server/management/usage-aggregate-cache.ts +206 -6
  180. package/src/server/management-api.ts +26 -1
  181. package/src/server/management-auth.ts +19 -5
  182. package/src/server/messages-native-eligibility.ts +161 -0
  183. package/src/server/messages-native-oauth.ts +149 -0
  184. package/src/server/messages-native.ts +790 -0
  185. package/src/server/relay.ts +9 -0
  186. package/src/server/request-log-filter.ts +92 -0
  187. package/src/server/request-log.ts +31 -70
  188. package/src/server/responses/adapter-delivery.ts +43 -16
  189. package/src/server/responses/agent-task-recovery.ts +90 -22
  190. package/src/server/responses/codex-ws-exchange.ts +4 -0
  191. package/src/server/responses/codex-ws-wire.ts +10 -1
  192. package/src/server/responses/core-combo-native.ts +323 -0
  193. package/src/server/responses/core-combo.ts +224 -13
  194. package/src/server/responses/core-options.ts +22 -0
  195. package/src/server/responses/core.ts +14 -26
  196. package/src/server/responses/request-sidecar-auth.ts +8 -3
  197. package/src/server/responses/run-turn-execution.ts +257 -63
  198. package/src/server/responses/sidecar-execution.ts +7 -4
  199. package/src/server/system-env.ts +7 -0
  200. package/src/service/diagnostics.ts +7 -1
  201. package/src/service/launchd.ts +26 -37
  202. package/src/service/windows-ops.ts +24 -20
  203. package/src/types/config.ts +58 -1
  204. package/src/types.ts +2 -0
  205. package/src/update/transactional-install.d.mts +0 -1
  206. package/src/update/transactional-install.mjs +17 -17
  207. package/src/usage/jev-stats.ts +495 -0
  208. package/src/usage/log.ts +24 -3
  209. package/src/web-search/run-turn-loop.ts +568 -0
  210. package/gui/dist/assets/App-8NMiZxT0.css +0 -1
  211. package/gui/dist/assets/App-DGLte6IR.js +0 -50
  212. package/gui/dist/assets/index--EWgGQvZ.css +0 -1
  213. package/gui/dist/assets/index-Bi1K37Wl.js +0 -86
@@ -1 +1 @@
1
- import{m as e,n as t}from"./index-Bi1K37Wl.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-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};
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-Bi1K37Wl.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index--EWgGQvZ.css">
19
+ <script type="module" crossorigin src="/assets/index-BiwnVAUm.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-ComVsdSk.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.65.0",
3
+ "version": "2.66.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",
@@ -70,7 +70,6 @@
70
70
  "prepush": "bun run typecheck && bun run lint:gui:if-changed && bun run test && bun run privacy:scan && bun run doctor:gui:if-changed",
71
71
  "lint:gui": "cd gui && bun run lint",
72
72
  "lint:gui:if-changed": "bun scripts/lint-gui-if-changed.ts",
73
- "postmerge": "bun scripts/build-gui-if-changed.ts",
74
73
  "doctor:gui": "cd gui && bun run doctor",
75
74
  "doctor:gui:full": "cd gui && bun run doctor:full",
76
75
  "doctor:gui:if-changed": "bun scripts/doctor-gui-if-changed.ts",
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The `anthropic-beta` values a managed native Messages request may carry from its caller (PF-10).
3
+ *
4
+ * A beta header changes what the provider accepts and bills, so the native lane never forwards
5
+ * the caller's header as written. Each value is compared, case-insensitively, against a fixed
6
+ * list for the destination's provider class; a match is re-emitted in this file's own spelling,
7
+ * so no caller byte ever reaches the wire, and everything else is dropped. The caller learns
8
+ * nothing about which value was dropped, and the trace records only that something was
9
+ * (`anthropic-beta-dropped`), never the value.
10
+ *
11
+ * Proxy-owned betas (the OAuth pair, the fast-mode beta) are not listed here: the lane sets them
12
+ * itself from the provider's own configuration, exactly as the adapter does.
13
+ *
14
+ * LEAF MODULE: no runtime import, so the builder, the lane and tests can read it freely.
15
+ */
16
+
17
+ /** Where the native lane sends: Anthropic's own API, or an Anthropic-compatible third party. */
18
+ export type AnthropicProviderClass = "first-party" | "compatible";
19
+
20
+ /**
21
+ * Betas a caller may forward to `api.anthropic.com`. Deliberately short: only a value whose
22
+ * effect is limited to how the model uses the body it already sent, with no new body field,
23
+ * no billing tier and no retained server state, belongs here.
24
+ */
25
+ const FIRST_PARTY_BETAS: readonly string[] = [
26
+ // Lets thinking blocks appear between tool calls in one assistant turn. No request field;
27
+ // the thinking and tool blocks it affects are already on the lane's field allowlist, and the
28
+ // proxy's own OAuth quota probe sends it (`src/providers/quota/vendor-probes-oauth.ts`).
29
+ "interleaved-thinking-2025-05-14",
30
+ ];
31
+
32
+ /**
33
+ * Betas a caller may forward to an Anthropic-compatible third party. Empty: a third party's
34
+ * beta semantics are its own, an unknown value can fail the request outright, and none is
35
+ * needed for the fields the lane forwards.
36
+ */
37
+ const COMPATIBLE_BETAS: readonly string[] = [];
38
+
39
+ const ALLOWLISTS: Readonly<Record<AnthropicProviderClass, ReadonlyMap<string, string>>> = {
40
+ "first-party": new Map(FIRST_PARTY_BETAS.map(beta => [beta.toLowerCase(), beta])),
41
+ compatible: new Map(COMPATIBLE_BETAS.map(beta => [beta.toLowerCase(), beta])),
42
+ };
43
+
44
+ /** A caller header longer than this is not parsed at all and counts as dropped. */
45
+ const MAX_CALLER_BETA_HEADER_CHARS = 2048;
46
+
47
+ export interface AllowlistedAnthropicBetas {
48
+ /** Allowlisted values in this file's spelling, first-seen order, no duplicates. */
49
+ betas: string[];
50
+ /** Whether any non-empty caller value was left out. Never says which. */
51
+ dropped: boolean;
52
+ }
53
+
54
+ /**
55
+ * Filter the caller's comma-separated `anthropic-beta` header for one provider class. An absent
56
+ * or blank header yields nothing and drops nothing.
57
+ */
58
+ export function allowlistAnthropicBetas(
59
+ callerHeader: string | null | undefined,
60
+ providerClass: AnthropicProviderClass,
61
+ ): AllowlistedAnthropicBetas {
62
+ if (typeof callerHeader !== "string" || callerHeader.trim() === "") return { betas: [], dropped: false };
63
+ if (callerHeader.length > MAX_CALLER_BETA_HEADER_CHARS) return { betas: [], dropped: true };
64
+ const allowed = ALLOWLISTS[providerClass];
65
+ const betas: string[] = [];
66
+ let dropped = false;
67
+ for (const part of callerHeader.split(",")) {
68
+ const token = part.trim().toLowerCase();
69
+ if (!token) continue;
70
+ const canonical = allowed.get(token);
71
+ if (canonical === undefined) dropped = true;
72
+ else if (!betas.includes(canonical)) betas.push(canonical);
73
+ }
74
+ return { betas, dropped };
75
+ }
76
+
77
+ /** The allowlist for one class, for documentation and tests. */
78
+ export function anthropicBetaAllowlist(providerClass: AnthropicProviderClass): readonly string[] {
79
+ return [...ALLOWLISTS[providerClass].values()];
80
+ }
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Managed native Messages request builder (PF-08, PF-10).
3
+ *
4
+ * The request a proxy-managed Anthropic credential sends when the client already spoke Messages:
5
+ * the caller's own body, cut to a fixed field allowlist, with the wire model and the provider's
6
+ * credential. URL, `anthropic-version`, client identity and credential placement come from the
7
+ * same helpers the Anthropic adapter uses, so the two lanes cannot drift apart.
8
+ *
9
+ * Authority: only the provider's own credential is ever placed on the request — a configured
10
+ * key, or (PF-10) the access token of the OAuth account the lane resolved, which is sent only to
11
+ * `api.anthropic.com`. The one caller header this builder sees is `anthropic-beta`, handed over
12
+ * explicitly and reduced to `beta-allowlist.ts`; no other caller header is read here at all. The
13
+ * caller-forward passthrough in `src/server/claude-messages.ts` is the only place a caller's
14
+ * Anthropic credential may travel, and it does not come through here.
15
+ *
16
+ * Opaque state: thinking signatures and `redacted_thinking` blocks reach only first-party
17
+ * Anthropic (`src/protocols/opaque-state.ts`). The source body is never mutated, so every build
18
+ * for every destination decides from the full source again.
19
+ */
20
+ import { mergeAnthropicBetaHeader } from "../../providers/anthropic-fast";
21
+ import { applyClaudeToolPrefix, CLAUDE_CODE_SYSTEM_INSTRUCTION } from "../../oauth/anthropic";
22
+ import { credentialDomainFor, opaqueStateForDestination } from "../../protocols/opaque-state";
23
+ import type { OcxConfig, OcxProviderConfig } from "../../types";
24
+ import {
25
+ anthropicBaseRequestHeaders,
26
+ applyAnthropicKeyAuth,
27
+ applyAnthropicOAuthAuth,
28
+ resolveAnthropicMessagesUrl,
29
+ } from "../anthropic";
30
+ import { allowlistAnthropicBetas } from "./beta-allowlist";
31
+
32
+ /**
33
+ * Top-level Messages fields the native lane forwards. Everything else is dropped: an unknown or
34
+ * beta-gated field would otherwise reach the provider unchecked. None of the dropped fields has
35
+ * a name in the protocol feature vocabulary, so no feature effect is recorded for them.
36
+ */
37
+ export const ANTHROPIC_MESSAGES_PASSTHROUGH_FIELDS = [
38
+ "model",
39
+ "messages",
40
+ "system",
41
+ "max_tokens",
42
+ "metadata",
43
+ "stop_sequences",
44
+ "stream",
45
+ "temperature",
46
+ "top_p",
47
+ "top_k",
48
+ "tools",
49
+ "tool_choice",
50
+ "thinking",
51
+ "output_config",
52
+ "service_tier",
53
+ ] as const;
54
+
55
+ const PASSTHROUGH_FIELD_SET: ReadonlySet<string> = new Set(ANTHROPIC_MESSAGES_PASSTHROUGH_FIELDS);
56
+
57
+ export interface AnthropicMessagesPassthroughRequest {
58
+ url: string;
59
+ headers: Record<string, string>;
60
+ /** The serialized wire body. */
61
+ body: string;
62
+ /** The same body before serialization, for callers that count or inspect what is sent. */
63
+ wireBody: Record<string, unknown>;
64
+ /** Caller `anthropic-beta` values were left out. Which ones is never recorded. */
65
+ droppedBetas: boolean;
66
+ /** Thinking signatures or `redacted_thinking` blocks were removed for this destination. */
67
+ strippedOpaqueState: boolean;
68
+ /**
69
+ * OAuth only: wire tool name to the caller's name, for every tool the OAuth prefix renamed.
70
+ * The lane maps `tool_use` names in the answer back through it.
71
+ */
72
+ oauthToolNames?: ReadonlyMap<string, string>;
73
+ }
74
+
75
+ export interface AnthropicMessagesPassthroughOptions {
76
+ /** The caller's `anthropic-beta` header, handed over by the ingress. */
77
+ callerAnthropicBeta?: string | null;
78
+ }
79
+
80
+ /** The allowlisted copy of `body` with `model` set to the wire model. Shallow: nothing is cloned. */
81
+ export function anthropicMessagesPassthroughBody(
82
+ body: Readonly<Record<string, unknown>>,
83
+ modelId: string,
84
+ ): Record<string, unknown> {
85
+ const out: Record<string, unknown> = {};
86
+ for (const [key, value] of Object.entries(body)) {
87
+ if (PASSTHROUGH_FIELD_SET.has(key) && value !== undefined) out[key] = value;
88
+ }
89
+ out.model = modelId;
90
+ return out;
91
+ }
92
+
93
+ type Rec = Record<string, unknown>;
94
+ function isRec(value: unknown): value is Rec {
95
+ return value !== null && typeof value === "object" && !Array.isArray(value);
96
+ }
97
+
98
+ /** A tool the caller executes (no `type`, or `custom`), as opposed to a typed server tool. */
99
+ function isClientTool(tool: unknown): tool is Rec & { name: string } {
100
+ return isRec(tool) && typeof tool.name === "string" && (tool.type === undefined || tool.type === "custom");
101
+ }
102
+
103
+ /**
104
+ * The Claude OAuth request shape the adapter produces, applied to a Messages body: the Claude
105
+ * Code identity as the first system block, and declared client tool names under the OAuth
106
+ * prefix — in `tools`, a named `tool_choice` and the history's `tool_use` blocks. Copy-on-write;
107
+ * the input is not mutated. Two caller names that meet under the prefix are refused rather than
108
+ * guessed at.
109
+ */
110
+ export function anthropicOAuthWireBody(body: Rec): { body: Rec; toolNames: Map<string, string> } {
111
+ const out: Rec = { ...body };
112
+ const toolNames = new Map<string, string>();
113
+ const owners = new Map<string, string>();
114
+ const wireName = (name: string): string => {
115
+ const wire = applyClaudeToolPrefix(name);
116
+ const owner = owners.get(wire);
117
+ if (owner !== undefined && owner !== name) throw new Error("tool names collide under the Claude OAuth tool prefix");
118
+ owners.set(wire, name);
119
+ if (wire !== name) toolNames.set(wire, name);
120
+ return wire;
121
+ };
122
+ const identity = { type: "text", text: CLAUDE_CODE_SYSTEM_INSTRUCTION };
123
+ if (typeof body.system === "string" && body.system.length > 0) {
124
+ out.system = [identity, { type: "text", text: body.system }];
125
+ } else if (Array.isArray(body.system)) {
126
+ const first = body.system[0];
127
+ const present = isRec(first) && first.type === "text" && first.text === CLAUDE_CODE_SYSTEM_INSTRUCTION;
128
+ out.system = present ? body.system : [identity, ...body.system];
129
+ } else {
130
+ out.system = [identity];
131
+ }
132
+ // Only names the caller declared as its own tools are renamed. A typed tool (a server tool,
133
+ // or a client-executed builtin such as `bash_*`) keeps the name its type fixes, and so do
134
+ // its calls in the history.
135
+ const declared = new Set<string>();
136
+ if (Array.isArray(body.tools)) {
137
+ out.tools = body.tools.map(tool => {
138
+ if (!isClientTool(tool)) return tool;
139
+ declared.add(tool.name);
140
+ return { ...tool, name: wireName(tool.name) };
141
+ });
142
+ }
143
+ const renames = (name: unknown): name is string => typeof name === "string" && declared.has(name);
144
+ if (isRec(body.tool_choice) && body.tool_choice.type === "tool" && renames(body.tool_choice.name)) {
145
+ out.tool_choice = { ...body.tool_choice, name: wireName(body.tool_choice.name) };
146
+ }
147
+ const isRenamedUse = (block: unknown): block is Rec & { name: string } => isRec(block) && block.type === "tool_use" && renames(block.name);
148
+ if (Array.isArray(body.messages)) {
149
+ out.messages = body.messages.map(message => {
150
+ if (!isRec(message) || !Array.isArray(message.content) || !message.content.some(isRenamedUse)) return message;
151
+ return {
152
+ ...message,
153
+ content: message.content.map(block => isRenamedUse(block) ? { ...block, name: wireName(block.name) } : block),
154
+ };
155
+ });
156
+ }
157
+ return { body: out, toolNames };
158
+ }
159
+
160
+ /**
161
+ * What the native lane sends for this provider, without a credential: the allowlisted body,
162
+ * opaque state kept only for first-party Anthropic, and the OAuth request shape for an OAuth
163
+ * provider. `count_tokens` counts this; the builder below sends it.
164
+ */
165
+ export function anthropicMessagesNativeWireBody(
166
+ provider: Pick<OcxProviderConfig, "baseUrl" | "authMode">,
167
+ modelId: string,
168
+ body: Readonly<Record<string, unknown>>,
169
+ ): { wireBody: Rec; strippedOpaqueState: boolean; oauthToolNames?: Map<string, string> } {
170
+ const allowlisted = anthropicMessagesPassthroughBody(body, modelId);
171
+ const opaque = opaqueStateForDestination(allowlisted, credentialDomainFor(provider));
172
+ if (provider.authMode !== "oauth") return { wireBody: opaque.body, strippedOpaqueState: opaque.stripped };
173
+ const oauth = anthropicOAuthWireBody(opaque.body);
174
+ return { wireBody: oauth.body, strippedOpaqueState: opaque.stripped, oauthToolNames: oauth.toolNames };
175
+ }
176
+
177
+ /**
178
+ * Build the upstream request. Throws the adapter's own errors for a missing credential or a
179
+ * malformed or unresolved base URL, and refuses an OAuth credential for any destination other
180
+ * than first-party Anthropic. `config` is accepted for parity with the other passthrough
181
+ * builders; no config key changes the wire today.
182
+ */
183
+ export function buildAnthropicMessagesPassthroughRequest(
184
+ provider: OcxProviderConfig,
185
+ modelId: string,
186
+ body: Readonly<Record<string, unknown>>,
187
+ _config?: OcxConfig,
188
+ options: AnthropicMessagesPassthroughOptions = {},
189
+ ): AnthropicMessagesPassthroughRequest {
190
+ const oauth = provider.authMode === "oauth";
191
+ if (provider.authMode !== undefined && provider.authMode !== "key" && !oauth) {
192
+ throw new Error("managed native Messages requires a key-auth or OAuth anthropic provider");
193
+ }
194
+ const domain = credentialDomainFor(provider);
195
+ if (oauth && !domain?.firstPartyAnthropic) {
196
+ throw new Error("managed native Messages sends Anthropic OAuth credentials only to api.anthropic.com");
197
+ }
198
+ if (typeof provider.apiKey !== "string" || provider.apiKey.trim() === "") {
199
+ throw new Error(oauth
200
+ ? "anthropic oauth token missing — run ocx login anthropic"
201
+ : "anthropic provider requires a non-empty apiKey (authMode: key)");
202
+ }
203
+ const url = resolveAnthropicMessagesUrl(provider);
204
+ const { wireBody, strippedOpaqueState, oauthToolNames } = anthropicMessagesNativeWireBody(provider, modelId, body);
205
+ const headers = anthropicBaseRequestHeaders(wireBody.stream === true);
206
+ if (oauth) applyAnthropicOAuthAuth(headers, provider.apiKey);
207
+ else applyAnthropicKeyAuth(headers, provider);
208
+ // Operator-configured provider headers apply exactly as the adapter applies them.
209
+ if (provider.headers) Object.assign(headers, provider.headers);
210
+ const betas = allowlistAnthropicBetas(options.callerAnthropicBeta, domain?.firstPartyAnthropic ? "first-party" : "compatible");
211
+ mergeAnthropicBetaHeader(headers, betas.betas);
212
+ return {
213
+ url,
214
+ headers,
215
+ body: JSON.stringify(wireBody),
216
+ wireBody,
217
+ droppedBetas: betas.dropped,
218
+ strippedOpaqueState,
219
+ ...(oauthToolNames ? { oauthToolNames } : {}),
220
+ };
221
+ }
@@ -116,16 +116,40 @@ let encodeCalls = 0;
116
116
  * rank by one and can push it across a tier boundary — re-encoding it to different
117
117
  * bytes and busting Anthropic's prompt prefix cache for the whole history. Pinning the
118
118
  * start position to the image's own identity keeps already-emitted bytes stable across
119
- * appends. Keys are the encode cache's identity minus the position suffix
120
- * (`${hash}:${mediaType}`, see processAt). Entry-count cap with LRU eviction: a
121
- * value is one small number, so a count bound is a byte bound (~4096 * ~50B worst
122
- * case, far under the app-owned memory budget's headroom).
119
+ * appends. Keys are fixed-size digests of the bytes and canonical media type, so
120
+ * caller-controlled metadata cannot make the retained identity arbitrarily large.
121
+ * The bytes count toward the shared retained-memory budget but are pinned: the
122
+ * shared evictor clears normalization cache slots first and never drops position
123
+ * memory, because a request reads this store before it finishes and a mid-request
124
+ * eviction would make a repeated image lose its pinned position and fall back to
125
+ * an age-derived tier. Positions shrink only through this store's own entry-count
126
+ * cap, applied when positions are committed after the request settles.
123
127
  */
124
128
  const POSITION_STORE_MAX_ENTRIES = 4_096;
125
- const emittedPositions = new Map<string, number>();
129
+ const MAX_CANONICAL_MEDIA_TYPE_LENGTH = 127;
130
+ const MEDIA_TYPE_PATTERN = /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/;
131
+ interface PositionEntry { position: number; sizeBytes: number; storedAt: number }
132
+ const emittedPositions = new Map<string, PositionEntry>();
133
+ let positionBytes = 0;
126
134
 
127
135
  function positionKey(b64: string, mediaType: string): string {
128
- return `${Bun.hash(b64).toString(36)}:${mediaType}`;
136
+ const normalized = mediaType.trim().toLowerCase();
137
+ const canonical = normalized.length <= MAX_CANONICAL_MEDIA_TYPE_LENGTH && MEDIA_TYPE_PATTERN.test(normalized)
138
+ ? normalized
139
+ // Keep invalid/overlong types distinct under an "invalid:" namespace: folding
140
+ // them all onto application/octet-stream let a different invalid type reuse a
141
+ // prior emitted position and demote the second image to a smaller tier. The
142
+ // prefix cannot collide with a valid canonical type (':' fails the pattern).
143
+ : `invalid:${normalized}`;
144
+ return new Bun.CryptoHasher("sha256").update(b64).update("\0").update(canonical).digest("hex");
145
+ }
146
+
147
+ function deletePositionEntry(key: string): number {
148
+ const entry = emittedPositions.get(key);
149
+ if (!entry) return 0;
150
+ emittedPositions.delete(key);
151
+ positionBytes -= entry.sizeBytes;
152
+ return entry.sizeBytes;
129
153
  }
130
154
 
131
155
  /**
@@ -134,12 +158,13 @@ function positionKey(b64: string, mediaType: string): string {
134
158
  */
135
159
  export function recordedEmittedPosition(b64: string, mediaType: string): number | undefined {
136
160
  const key = positionKey(b64, mediaType);
137
- const pos = emittedPositions.get(key);
138
- if (pos !== undefined) {
161
+ const entry = emittedPositions.get(key);
162
+ if (entry !== undefined) {
139
163
  emittedPositions.delete(key);
140
- emittedPositions.set(key, pos);
164
+ entry.storedAt = Date.now();
165
+ emittedPositions.set(key, entry);
141
166
  }
142
- return pos;
167
+ return entry?.position;
143
168
  }
144
169
 
145
170
  /**
@@ -153,15 +178,17 @@ export function recordEmittedPosition(b64: string, mediaType: string, pos: numbe
153
178
  const key = positionKey(b64, mediaType);
154
179
  const existing = emittedPositions.get(key);
155
180
  if (existing !== undefined) {
156
- emittedPositions.delete(key);
157
- pos = Math.max(existing, pos);
181
+ deletePositionEntry(key);
182
+ pos = Math.max(existing.position, pos);
158
183
  }
159
184
  while (emittedPositions.size + 1 > POSITION_STORE_MAX_ENTRIES) {
160
185
  const oldest = emittedPositions.keys().next().value;
161
186
  if (oldest === undefined) break;
162
- emittedPositions.delete(oldest);
187
+ deletePositionEntry(oldest);
163
188
  }
164
- emittedPositions.set(key, pos);
189
+ const sizeBytes = cacheEncoder.encode(key).byteLength + 16;
190
+ emittedPositions.set(key, { position: pos, sizeBytes, storedAt: Date.now() });
191
+ positionBytes += sizeBytes;
165
192
  enforceAppOwnedMemoryBudget();
166
193
  }
167
194
 
@@ -227,6 +254,8 @@ export function getNormalizeStatsForTests(): {
227
254
  cacheBytes: number;
228
255
  sentinelEntries: number;
229
256
  metadataBytes: number;
257
+ positionEntries: number;
258
+ positionBytes: number;
230
259
  oldestAt: number | null;
231
260
  } {
232
261
  return {
@@ -235,12 +264,15 @@ export function getNormalizeStatsForTests(): {
235
264
  cacheBytes,
236
265
  sentinelEntries: cacheSentinelEntries,
237
266
  metadataBytes: cacheMetadataBytes,
267
+ positionEntries: emittedPositions.size,
268
+ positionBytes,
238
269
  oldestAt: cache.values().next().value?.storedAt ?? null,
239
270
  };
240
271
  }
241
272
  export function resetNormalizeStateForTests(): void {
242
273
  cache.clear();
243
274
  emittedPositions.clear();
275
+ positionBytes = 0;
244
276
  cacheBytes = 0;
245
277
  cacheMetadataBytes = 0;
246
278
  cacheSentinelEntries = 0;
@@ -260,17 +292,22 @@ export function anthropicImageNormalizeRetainedStoreSnapshot(): {
260
292
  oldestAt: number | null;
261
293
  } {
262
294
  return {
263
- count: cache.size,
264
- bytes: cacheBytes,
295
+ count: cache.size + emittedPositions.size,
296
+ bytes: cacheBytes + positionBytes,
265
297
  evictableBytes: cacheBytes,
266
- pinnedBytes: 0,
298
+ pinnedBytes: positionBytes,
267
299
  oldestAt: cache.values().next().value?.storedAt ?? null,
268
300
  };
269
301
  }
270
302
 
271
303
  export function evictOldestAnthropicImageNormalizeForBudget(): number {
272
- const oldest = cache.keys().next().value;
273
- return oldest === undefined ? 0 : deleteCacheEntry(oldest);
304
+ // Position memory is pinned for the life of a request (see the store comment):
305
+ // the shared budget reclaims normalization cache slots first, and a position
306
+ // entry is only released by recordEmittedPosition's own entry-count cap after
307
+ // the request settles. A cache miss is cheaper than losing an image's pinned
308
+ // ladder position mid-request.
309
+ const cacheOldest = cache.entries().next().value as [string, CacheEntry] | undefined;
310
+ return cacheOldest ? deleteCacheEntry(cacheOldest[0]) : 0;
274
311
  }
275
312
 
276
313
  /** Default encoder: Bun.Image resize-to-fit + JPEG at the given quality. */
@@ -165,6 +165,15 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
165
165
  // peak memory, not throughput (native encode parallelism lives below this layer).
166
166
  // entries[] stays index-addressed, so completion order never affects output order
167
167
  // or the sequential demotion loop below.
168
+ // #4532: snapshot every target's recorded emitted position BEFORE any worker
169
+ // starts. The read used to happen inside the worker loop, so a concurrent request
170
+ // (or this request's own earlier encodes) could fill the position store's
171
+ // entry-count cap and evict an entry between collection and processing — a later
172
+ // occurrence then fell back to an age-derived tier and busted the prompt prefix
173
+ // cache. Reading a fixed pre-pass snapshot keeps every occurrence on the position
174
+ // it had when this request started.
175
+ const recordedPositions = targets.map(t =>
176
+ !t.base64 ? undefined : recordedEmittedPosition(t.base64, t.mediaType.toLowerCase()));
168
177
  let nextIndex = 0;
169
178
  let firstError: unknown;
170
179
  let failed = false;
@@ -203,16 +212,17 @@ export async function normalizeImageTargets(targets: NormalizeTarget[], options:
203
212
  // Anthropic's prompt prefix cache. tierBias (413 retry) applies on top of
204
213
  // either base and still clamps to TERMINAL_POS.
205
214
  //
206
- // Every read in this pass sees the store as it was BEFORE this request,
207
- // because nothing is written until the whole request settles (see the
208
- // record loop at the end). That is load-bearing, not incidental: an image
209
- // can appear more than once in one history, and identity keying collapses
210
- // those occurrences onto one entry. Writing during the pass let the OLDEST
211
- // occurrence's tier win a race against the newest one and drag it down —
212
- // 30 copies of a screenshot all landed on the oldest copy's tier instead of
213
- // the age pyramid. Reading a fixed snapshot gives each occurrence its own
214
- // age tier on a cold store, which is the pre-#4532 behaviour.
215
- const recorded = recordedEmittedPosition(b64, sourceMedia);
215
+ // Every read sees the pre-request snapshot taken above: nothing is written
216
+ // until the whole request settles (see the record loop at the end), and no
217
+ // mid-pass eviction can change what an occurrence sees. That is
218
+ // load-bearing, not incidental: an image can appear more than once in one
219
+ // history, and identity keying collapses those occurrences onto one entry.
220
+ // Writing during the pass let the OLDEST occurrence's tier win a race
221
+ // against the newest one and drag it down — 30 copies of a screenshot all
222
+ // landed on the oldest copy's tier instead of the age pyramid. Reading a
223
+ // fixed snapshot gives each occurrence its own age tier on a cold store,
224
+ // which is the pre-#4532 behaviour.
225
+ const recorded = recordedPositions[i];
216
226
  const pos = Math.min((recorded ?? initialPosition(newestFirstIndex, 0)) + Math.max(0, bias), TERMINAL_POS);
217
227
  const result = await process(b64, pos, sourceMedia);
218
228
  if (result.kind === "failed") {