@bitkyc08/opencodex 2.60.0 → 2.61.0-preview.20260922

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 (249) hide show
  1. package/AGENTS_INSTALL.md +64 -0
  2. package/README.md +28 -1
  3. package/bin/ocx.mjs +382 -209
  4. package/gui/dist/assets/App-E64Rzjap.js +50 -0
  5. package/gui/dist/assets/Tray-_nfzD8k4.js +1 -0
  6. package/gui/dist/assets/index-DpdfZWMK.js +86 -0
  7. package/gui/dist/assets/index-_bpvxJu0.css +1 -0
  8. package/gui/dist/assets/usage-companion-chart-DtoK7T6h.js +1 -0
  9. package/gui/dist/favicon.png +0 -0
  10. package/gui/dist/index.html +2 -2
  11. package/gui/dist/provider-icons/stepfun-color.svg +1 -0
  12. package/package.json +5 -1
  13. package/src/adapters/anthropic.ts +16 -0
  14. package/src/adapters/coding-agent/protocol.ts +36 -6
  15. package/src/adapters/coding-agent/turn.ts +10 -2
  16. package/src/adapters/command-code.ts +2 -1
  17. package/src/adapters/cursor/catalog.ts +51 -7
  18. package/src/adapters/cursor/protobuf-request.ts +6 -3
  19. package/src/adapters/cursor/request-builder.ts +13 -3
  20. package/src/adapters/cursor.ts +11 -2
  21. package/src/adapters/declaration-carrier.ts +45 -0
  22. package/src/adapters/devin.ts +75 -23
  23. package/src/adapters/google-antigravity-wire.ts +5 -2
  24. package/src/adapters/google-errors.ts +7 -1
  25. package/src/adapters/google.ts +29 -5
  26. package/src/adapters/image.ts +4 -1
  27. package/src/adapters/input-media-guard.ts +21 -9
  28. package/src/adapters/kiro/usage.ts +3 -2
  29. package/src/adapters/kiro-tool-fallback.ts +1 -1
  30. package/src/adapters/ollama-native.ts +6 -0
  31. package/src/adapters/openai-chat/developer-role.ts +61 -0
  32. package/src/adapters/openai-chat/messages.ts +46 -27
  33. package/src/adapters/openai-chat/parallel-tool-calls.ts +32 -0
  34. package/src/adapters/openai-chat/passthrough.ts +33 -9
  35. package/src/adapters/openai-chat/reasoning-wire.ts +89 -0
  36. package/src/adapters/openai-chat.ts +18 -57
  37. package/src/adapters/openai-responses/passthrough.ts +2 -0
  38. package/src/adapters/registry.ts +3 -2
  39. package/src/adapters/run-turn-queue.ts +178 -29
  40. package/src/adapters/xai-web-search.ts +16 -1
  41. package/src/bridge/errors.ts +8 -2
  42. package/src/bridge/response-json.ts +9 -1
  43. package/src/bridge/sse.ts +10 -0
  44. package/src/chat/inbound.ts +141 -5
  45. package/src/claude/desktop-3p.ts +7 -1
  46. package/src/claude/desktop-first-party.ts +183 -0
  47. package/src/claude/desktop-gateway-state.ts +41 -0
  48. package/src/claude/inbound-content-options.ts +6 -0
  49. package/src/claude/inbound.ts +32 -6
  50. package/src/claude/intercept/connect-proxy.ts +179 -0
  51. package/src/claude/intercept/listener.ts +122 -0
  52. package/src/claude/intercept/local-ca.ts +298 -0
  53. package/src/claude/intercept/runtime.ts +98 -0
  54. package/src/claude/intercept/settings.ts +189 -0
  55. package/src/cli/access.ts +87 -0
  56. package/src/cli/account-auth.ts +19 -0
  57. package/src/cli/capabilities.ts +31 -0
  58. package/src/cli/claude-desktop.ts +206 -16
  59. package/src/cli/codex-shim-autorestore.ts +3 -0
  60. package/src/cli/companion.ts +56 -0
  61. package/src/cli/dispatch.ts +43 -4
  62. package/src/cli/ensure-desired-integrations.ts +43 -5
  63. package/src/cli/help.ts +7 -9
  64. package/src/cli/index.ts +200 -61
  65. package/src/cli/init.ts +8 -0
  66. package/src/cli/integrations.ts +7 -1
  67. package/src/cli/registry.ts +41 -2
  68. package/src/cli/resolve.ts +230 -0
  69. package/src/cli/root.ts +24 -1
  70. package/src/cli/start-ownership-publication.ts +56 -0
  71. package/src/cli/status-probes.ts +2 -18
  72. package/src/cli/status.ts +62 -0
  73. package/src/cli/stop-report.ts +143 -0
  74. package/src/cli/uninstall-plan.ts +9 -0
  75. package/src/client/machine-listener.ts +2 -5
  76. package/src/clients/aside-profiles.ts +4 -0
  77. package/src/clients/config-export/zcode-store.ts +157 -0
  78. package/src/clients/config-export.ts +36 -0
  79. package/src/codex/app-server-processes.ts +72 -40
  80. package/src/codex/auth-api/login-flow.ts +6 -1
  81. package/src/codex/autostart-health.ts +28 -0
  82. package/src/codex/catalog/build-entries.ts +2 -2
  83. package/src/codex/catalog/effort.ts +3 -3
  84. package/src/codex/catalog/provider-models.ts +24 -15
  85. package/src/codex/catalog/retained-sync.ts +2 -2
  86. package/src/codex/convergence.ts +2 -2
  87. package/src/codex/history-provider.ts +12 -1
  88. package/src/codex/inject/config-toml.ts +41 -6
  89. package/src/codex/inject/paginated-openai-compat.ts +90 -0
  90. package/src/codex/inject.ts +18 -15
  91. package/src/codex/injected-marker.ts +18 -0
  92. package/src/codex/main-account.ts +6 -0
  93. package/src/codex/model-cache.ts +52 -6
  94. package/src/codex/model-entitlement-admission.ts +59 -0
  95. package/src/codex/model-entitlements.ts +87 -44
  96. package/src/codex/native-main-admission.ts +83 -0
  97. package/src/codex/routing/health-store.ts +39 -0
  98. package/src/codex/routing/selection.ts +37 -1
  99. package/src/codex/routing.ts +5 -41
  100. package/src/codex/shim-templates.ts +29 -3
  101. package/src/companion/settings.ts +132 -0
  102. package/src/config/atomic-write.ts +117 -5
  103. package/src/config/load-degrade.ts +34 -7
  104. package/src/config/process-state.ts +1 -1
  105. package/src/config/schema/config-schema.ts +27 -1
  106. package/src/config/schema/leaf-validators.ts +47 -0
  107. package/src/config.ts +1 -1
  108. package/src/generated/compatibility-version.json +418 -174
  109. package/src/integrations/config-io.ts +44 -10
  110. package/src/integrations/merge.ts +120 -13
  111. package/src/integrations/mutation-plan.ts +124 -18
  112. package/src/integrations/registry.ts +38 -0
  113. package/src/integrations/state.ts +78 -45
  114. package/src/integrations/target.ts +208 -0
  115. package/src/integrations/writer.ts +49 -11
  116. package/src/lab/conformance/fixture-provider.ts +5 -0
  117. package/src/lib/browser-launch-notice.ts +59 -0
  118. package/src/lib/bun-runtime.ts +6 -2
  119. package/src/lib/debug.ts +40 -0
  120. package/src/lib/open-url.ts +51 -7
  121. package/src/lib/package-tree-integrity.ts +2 -1
  122. package/src/lib/package-version.ts +8 -0
  123. package/src/lib/provider-egress.ts +310 -0
  124. package/src/lib/provider-outbound.ts +59 -14
  125. package/src/lib/proxy-env.ts +82 -7
  126. package/src/lib/request-execution-budget.ts +72 -0
  127. package/src/lib/request-failure-attribution.ts +183 -0
  128. package/src/lib/request-failure-model.ts +236 -0
  129. package/src/lib/request-resend-gate.ts +138 -0
  130. package/src/lib/standalone.ts +16 -0
  131. package/src/lib/upstream-retry.ts +167 -16
  132. package/src/lib/winsw.ts +2 -2
  133. package/src/oauth/index.ts +24 -1
  134. package/src/oauth/login-cli.ts +80 -29
  135. package/src/providers/api-key-resolve.ts +133 -0
  136. package/src/providers/api-key-selection.ts +5 -1
  137. package/src/providers/key-failover.ts +31 -1
  138. package/src/providers/key-store.ts +34 -110
  139. package/src/providers/model-rename-fields.ts +147 -0
  140. package/src/providers/model-rename-migration.ts +124 -37
  141. package/src/providers/quota/vendor-probes-key.ts +37 -22
  142. package/src/providers/reasoning-metadata.ts +43 -18
  143. package/src/providers/registry/entries-core.ts +9 -4
  144. package/src/providers/registry/entries-extended.ts +29 -4
  145. package/src/providers/registry/model-seeds.ts +47 -10
  146. package/src/providers/xai-transport.ts +12 -1
  147. package/src/reasoning-effort.ts +8 -0
  148. package/src/responses/function-call-compat.ts +38 -1
  149. package/src/responses/inline-document.ts +65 -0
  150. package/src/responses/input-media.ts +42 -8
  151. package/src/responses/muse-tool-name-alias.ts +19 -0
  152. package/src/responses/parser-content.ts +8 -2
  153. package/src/responses/parser-tools.ts +3 -0
  154. package/src/responses/parser.ts +3 -1
  155. package/src/responses/schema.ts +3 -0
  156. package/src/router.ts +17 -2
  157. package/src/server/admission-model-scope.ts +219 -0
  158. package/src/server/audio-live.ts +9 -3
  159. package/src/server/audio-upstream.ts +18 -0
  160. package/src/server/auth-cors.ts +26 -0
  161. package/src/server/chat-completions.ts +55 -2
  162. package/src/server/chat-native.ts +19 -4
  163. package/src/server/claude-messages.ts +55 -17
  164. package/src/server/grok-responses-snapshot-repair.ts +113 -11
  165. package/src/server/gui-freshness.ts +103 -0
  166. package/src/server/gui-static.ts +7 -9
  167. package/src/server/images.ts +59 -6
  168. package/src/server/index/claude-intercept-lifecycle.ts +49 -0
  169. package/src/server/index/serve-options.ts +56 -10
  170. package/src/server/index/spend-ledger-lifecycle.ts +34 -8
  171. package/src/server/index/startup-warnings.ts +24 -0
  172. package/src/server/index.ts +21 -28
  173. package/src/server/lifecycle.ts +4 -4
  174. package/src/server/live-call-bindings.ts +6 -0
  175. package/src/server/live.ts +88 -3
  176. package/src/server/management/agent-settings-routes.ts +121 -36
  177. package/src/server/management/companion-routes.ts +77 -0
  178. package/src/server/management/logs-usage-routes.ts +19 -0
  179. package/src/server/management/native-integration-routes.ts +103 -6
  180. package/src/server/management/oauth-account-routes.ts +45 -7
  181. package/src/server/management/route-registry.ts +6 -0
  182. package/src/server/management/shared.ts +18 -1
  183. package/src/server/management/usage-timeline-routes.ts +44 -0
  184. package/src/server/management-api.ts +8 -9
  185. package/src/server/proxy-liveness.ts +75 -0
  186. package/src/server/relay.ts +19 -2
  187. package/src/server/request-log-failure-attribution.ts +99 -0
  188. package/src/server/request-log.ts +114 -0
  189. package/src/server/request-metrics.ts +92 -30
  190. package/src/server/responses/codex-ws-wire.ts +34 -8
  191. package/src/server/responses/combo-stream-preflight.ts +168 -6
  192. package/src/server/responses/compact.ts +11 -0
  193. package/src/server/responses/core-opaque-recovery.ts +90 -0
  194. package/src/server/responses/fetch-helpers.ts +124 -8
  195. package/src/server/responses/input-admission.ts +10 -0
  196. package/src/server/responses/passthrough-delivery.ts +14 -1
  197. package/src/server/responses/passthrough-dispatch.ts +179 -35
  198. package/src/server/responses/passthrough-error.ts +27 -8
  199. package/src/server/responses/request-prepare.ts +42 -1
  200. package/src/server/responses/request-send-budget.ts +12 -0
  201. package/src/server/responses/request-transport.ts +24 -4
  202. package/src/server/responses/reset-replay.ts +108 -0
  203. package/src/server/responses-request-tool-scope.ts +214 -0
  204. package/src/server/responses-undeclared-tool-guard.ts +4 -1
  205. package/src/server/search.ts +25 -1
  206. package/src/server/usage-ledger-retention.ts +73 -0
  207. package/src/service/cli.ts +48 -2
  208. package/src/service/health.ts +3 -2
  209. package/src/service/install-state-contract.d.mts +27 -0
  210. package/src/service/install-state-contract.mjs +34 -0
  211. package/src/service/launchd.ts +1 -1
  212. package/src/service/orchestration.ts +2 -4
  213. package/src/service/ownership-compatibility.ts +164 -0
  214. package/src/service/ownership-mutation-lease.d.mts +32 -0
  215. package/src/service/ownership-mutation-lease.mjs +211 -0
  216. package/src/service/repair.ts +45 -1
  217. package/src/service/state-lock.ts +269 -0
  218. package/src/service/state-record.d.mts +36 -0
  219. package/src/service/state-record.mjs +138 -0
  220. package/src/service/state.ts +582 -68
  221. package/src/service/windows-taskxml.ts +11 -10
  222. package/src/service.ts +7 -3
  223. package/src/tray/windows-tray.ps1 +1 -1
  224. package/src/types/config.ts +37 -0
  225. package/src/types/provider.ts +73 -0
  226. package/src/types/request.ts +28 -2
  227. package/src/types/tools.ts +19 -0
  228. package/src/types.ts +3 -0
  229. package/src/update/index.ts +207 -63
  230. package/src/update/job.ts +9 -5
  231. package/src/update/ownership-transaction.ts +47 -0
  232. package/src/update/restart-ownership.ts +54 -0
  233. package/src/update/runtime-ownership.d.mts +40 -0
  234. package/src/update/runtime-ownership.mjs +122 -0
  235. package/src/usage/attempt-delivery.ts +198 -0
  236. package/src/usage/cache-diagnostic.ts +305 -0
  237. package/src/usage/failure-fingerprint.ts +118 -0
  238. package/src/usage/failure-projection-cache.ts +174 -0
  239. package/src/usage/failure-projection.ts +174 -0
  240. package/src/usage/ledger-retention.ts +165 -0
  241. package/src/usage/log.ts +126 -79
  242. package/src/usage/request-outcome.ts +150 -0
  243. package/src/usage/retention-contract.ts +28 -0
  244. package/src/usage/summary.ts +2 -2
  245. package/src/usage/telemetry-contract.ts +237 -0
  246. package/src/usage/timeline.ts +236 -0
  247. package/src/web-search/alpha-search.ts +21 -1
  248. package/gui/dist/assets/index-BTuCbqQd.css +0 -1
  249. package/gui/dist/assets/index-DoBVdPHP.js +0 -134
@@ -72,10 +72,10 @@ import {
72
72
  VOLCENGINE_PLAN_TEXT_ONLY_MODELS,
73
73
  ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES,
74
74
  KIMI_API_MODELS,
75
- KIMI_CODING_MODELS,
76
75
  KIMI_THINKING_MODELS,
77
76
  KIMI_CODING_NO_REASONING_MODELS,
78
77
  KIMI_API_NO_REASONING_MODELS,
78
+ KIMI_CODING_LIVE_MODELS,
79
79
  KIMI_CODING_REASONING_EFFORTS,
80
80
  KIMI_CODING_DEFAULT_REASONING_EFFORTS,
81
81
  KIMI_CODING_REASONING_EFFORT_MAPS,
@@ -102,6 +102,11 @@ import {
102
102
  OPPER_MODEL_CONTEXT_WINDOWS,
103
103
  OPPER_MODEL_MAX_OUTPUT_TOKENS,
104
104
  OPPER_MODEL_INPUT_MODALITIES,
105
+ STEPFUN_MODELS,
106
+ STEPFUN_MODEL_CONTEXT_WINDOWS,
107
+ STEPFUN_MODEL_INPUT_MODALITIES,
108
+ STEPFUN_NO_VISION_MODELS,
109
+ STEPFUN_REASONING_EFFORTS,
105
110
  } from "./model-seeds";
106
111
 
107
112
  export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
@@ -1008,13 +1013,17 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1008
1013
  },
1009
1014
  {
1010
1015
  id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
1011
- dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
1016
+ // 260921: kimi-k2.7-code was retired from the coding endpoint; the kimi-for-coding alias
1017
+ // is the stable ID and currently routes to K2.8 Preview (same as the OAuth preset).
1018
+ dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-for-coding",
1012
1019
  modelSuffixBracketStrip: true,
1013
1020
  // API-key form of the same Kimi Code Plan transport; keep cache affinity identical to OAuth.
1014
1021
  promptCacheKey: true,
1015
1022
  // Keep Responses tool-result adjacency aligned with the OAuth preset (#4726).
1016
1023
  requiresAdjacentResponsesToolResults: true,
1017
- models: KIMI_CODING_MODELS,
1024
+ // 260921: same live-id picker as the OAuth preset — the retired k2.x ids are repaired
1025
+ // in saved configs by MODEL_RENAMES, not offered on fresh installs.
1026
+ models: KIMI_CODING_LIVE_MODELS,
1018
1027
  modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
1019
1028
  modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
1020
1029
  noReasoningModels: KIMI_CODING_NO_REASONING_MODELS,
@@ -1361,5 +1370,21 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1361
1370
  noVisionModels: CODEBUDDY_CN_NO_VISION_MODELS,
1362
1371
  note: "Official CodeBuddy Code CLI (Tencent Cloud), China/internal environment. Uses the documented CODEBUDDY_API_KEY + headless CLI surface; never reads desktop sessions or private console endpoints. Region-isolated from codebuddy (Global); credentials are never exchanged across regions. v1 disables CLI tools (--tools \"\"): text/reasoning only for now. Requires `npm i -g @tencent-ai/codebuddy-code`. AUP/routing authorization flagged for maintainer security review.",
1363
1372
  },
1373
+ {
1374
+ id: "stepfun",
1375
+ label: "StepFun",
1376
+ baseUrl: "https://api.stepfun.com/v1",
1377
+ adapter: "openai-chat",
1378
+ authKind: "key",
1379
+ dashboardUrl: "https://platform.stepfun.com",
1380
+ defaultModel: "step-5-preview",
1381
+ models: STEPFUN_MODELS,
1382
+ liveModels: true,
1383
+ preserveCustomDestination: true,
1384
+ modelContextWindows: STEPFUN_MODEL_CONTEXT_WINDOWS,
1385
+ modelInputModalities: STEPFUN_MODEL_INPUT_MODALITIES,
1386
+ noVisionModels: STEPFUN_NO_VISION_MODELS,
1387
+ reasoningEfforts: STEPFUN_REASONING_EFFORTS,
1388
+ note: "StepFun (阶跃星辰) official OpenAI-compatible API.",
1389
+ },
1364
1390
  ];
1365
-
@@ -642,11 +642,26 @@ export const ALIBABA_TOKEN_PLAN_PRESERVE_REASONING = [
642
642
  export const KIMI_K3_STANDARD_CONTEXT_WINDOW = 262_144;
643
643
  export const KIMI_K3_1M_CONTEXT_WINDOW = 1_048_576;
644
644
  export const KIMI_CODING_K3_MODELS = ["k3", "k3[1m]"];
645
+ // 260921 Kimi K2.8: `kimi-for-coding` is the stable subscription alias Moonshot re-points
646
+ // at each coding release. Live GET /coding/v1/models lists only kimi-for-coding[-highspeed],
647
+ // k3, k3-256k — the k2.x ids are retired from the subscription endpoint. Since K2.8 Preview
648
+ // the alias serves an adjustable low/high/max thinking ladder (same wire map as k3) and a
649
+ // 1M context ceiling. Verified live 260921: 350K-token request accepted; upstream rejects
650
+ // with "model token limit: 1048576" beyond that.
651
+ // Evidence: https://www.kimi.com/code/docs/en/kimi-code/models.html
652
+ export const KIMI_CODING_K28_MODELS = ["kimi-for-coding"];
653
+ export const KIMI_CODING_LIVE_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_CODING_K28_MODELS];
654
+ export const KIMI_CODING_ADJUSTABLE_THINKING_MODELS = [...KIMI_CODING_LIVE_MODELS];
645
655
  export const KIMI_LEGACY_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
646
656
  export const KIMI_API_MODELS = ["kimi-k3", ...KIMI_LEGACY_API_MODELS];
647
- export const KIMI_CODING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_LEGACY_API_MODELS, "kimi-for-coding"];
648
- export const KIMI_THINKING_MODELS = KIMI_CODING_MODELS;
649
- export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_K3_MODELS.includes(id));
657
+ // Every kimi coding preset record - picker, context windows, locked-parameter lists -
658
+ // derives from the live ids only. seeding a retired id in a metadata list would re-arm
659
+ // the model-rename migration on every boot (#5066): the list holds the retired id but
660
+ // not the live alias, so the residue guard cannot skip it. The retired ids survive only
661
+ // in KIMI_LEGACY_API_MODELS (moonshot platform API records); model-rename-migration
662
+ // repairs saved rows still naming them.
663
+ export const KIMI_THINKING_MODELS = KIMI_CODING_LIVE_MODELS;
664
+ export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_LIVE_MODELS.filter(id => !KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id));
650
665
  export const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => id !== "kimi-k3");
651
666
  export const KIMI_CODING_K3_REASONING_EFFORTS = ["low", "high", "max"];
652
667
  export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record<string, string> = {
@@ -658,19 +673,19 @@ export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record<string, string> = {
658
673
  max: "max",
659
674
  };
660
675
  export const KIMI_CODING_REASONING_EFFORTS = Object.fromEntries(
661
- KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_K3_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]),
676
+ KIMI_CODING_LIVE_MODELS.map(id => [id, KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]),
662
677
  );
663
678
  export const KIMI_CODING_DEFAULT_REASONING_EFFORTS = Object.fromEntries(
664
- KIMI_CODING_K3_MODELS.map(id => [id, "max"]),
679
+ KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, "max"]),
665
680
  );
666
681
  export const KIMI_CODING_REASONING_EFFORT_MAPS = Object.fromEntries(
667
- KIMI_CODING_K3_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]),
682
+ KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]),
668
683
  );
669
684
  export const KIMI_API_REASONING_EFFORTS = Object.fromEntries(
670
685
  KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? ["max"] : []]),
671
686
  );
672
- export const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_MODELS;
673
- export const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-for-coding"];
687
+ export const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_LIVE_MODELS;
688
+ export const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-for-coding"];
674
689
  export const KIMI_API_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
675
690
  KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? KIMI_K3_1M_CONTEXT_WINDOW : 262_144]),
676
691
  );
@@ -758,10 +773,10 @@ export const NVIDIA_NIM_NO_VISION_MODELS = [
758
773
  "poolside/laguna-xs-2.1", "z-ai/glm-5.3", "z-ai/glm-5.2",
759
774
  ];
760
775
  export const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
761
- KIMI_CODING_MODELS.map(id => [id, id === "k3[1m]" ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]),
776
+ KIMI_CODING_LIVE_MODELS.map(id => [id, (id === "k3[1m]" || KIMI_CODING_K28_MODELS.includes(id)) ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]),
762
777
  );
763
778
  export const KIMI_CODING_MODEL_INPUT_MODALITIES = Object.fromEntries(
764
- KIMI_CODING_K3_MODELS.map(id => [id, ["text", "image"]]),
779
+ KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, ["text", "image"]]),
765
780
  );
766
781
  export const NEURALWATT_REASONING_HISTORY_MODELS = [
767
782
  "glm-5.3", "glm-5.3-short", "glm-5.3-flash",
@@ -1026,3 +1041,25 @@ export const OPPER_TEXT_ONLY_MODELS = ["deepseek-v4-pro", "kimi-k3"];
1026
1041
  export const OPPER_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntries(
1027
1042
  OPPER_MODELS.map(id => [id, OPPER_TEXT_ONLY_MODELS.includes(id) ? ["text"] : ["text", "image"]]),
1028
1043
  );
1044
+
1045
+ export const STEPFUN_MODELS = [
1046
+ "step-5-preview",
1047
+ "step-3.5-flash",
1048
+ "step-3.7-flash",
1049
+ ];
1050
+
1051
+ export const STEPFUN_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
1052
+ "step-5-preview": 1_000_000,
1053
+ "step-3.5-flash": 256_000,
1054
+ "step-3.7-flash": 256_000,
1055
+ };
1056
+
1057
+ export const STEPFUN_MODEL_INPUT_MODALITIES: Record<string, string[]> = {
1058
+ "step-5-preview": ["text", "image"],
1059
+ "step-3.5-flash": ["text"],
1060
+ "step-3.7-flash": ["text", "image"],
1061
+ };
1062
+
1063
+ export const STEPFUN_NO_VISION_MODELS = ["step-3.5-flash"];
1064
+
1065
+ export const STEPFUN_REASONING_EFFORTS = ["low", "medium", "high"];
@@ -1,5 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import type { OcxProviderConfig } from "../types";
3
+ import { isEgressTransparentExecutor, markEgressTransparentExecutor } from "../lib/provider-egress";
4
+ import { configuredOutboundFetch } from "../lib/proxy-env";
3
5
  import { resolveGithubCopilotTransport } from "./github-copilot-transport";
4
6
 
5
7
  export const XAI_GROK_CLI_BASE_URL = "https://cli-chat-proxy.grok.com/v1";
@@ -156,9 +158,18 @@ export function resolveProviderTransport(
156
158
  // transient retries reuse one id so the upstream can dedupe them. A rotated key resolves a
157
159
  // fresh transport, which gets its own id.
158
160
  const requestId = configuredRequestId ?? randomUUID();
159
- const baseFetch = provider.fetch ?? globalThis.fetch;
161
+ // Without a configured executor the default routes through `configuredOutboundFetch` rather
162
+ // than the bare global fetch, so a per-provider SOCKS5 route reaches the SOCKS transport.
163
+ // Bare Bun fetch ignores a socks5 value, which would have sent the request unproxied while
164
+ // the configuration named a proxy.
165
+ const baseFetch = provider.fetch
166
+ ?? markEgressTransparentExecutor(((input, init) => configuredOutboundFetch(input, init)) as typeof globalThis.fetch);
160
167
  const attemptFetch = ((input, init) =>
161
168
  baseFetch(input, withGeneratedRequestId(init, requestId, stableHeaders))) as typeof globalThis.fetch;
169
+ // This wrapper only adds a header and forwards the init, so it carries a request-scoped proxy
170
+ // option through to whatever it wraps — but only if what it wraps carries it too. A
171
+ // configured executor owns its own routing and is not assumed to.
172
+ if (isEgressTransparentExecutor(baseFetch)) markEgressTransparentExecutor(attemptFetch);
162
173
 
163
174
  return {
164
175
  ...provider,
@@ -12,6 +12,14 @@ export const CODEX_REASONING_LEVELS: { effort: string; description: string }[] =
12
12
  { effort: "ultra", description: "Maximum reasoning with automatic task delegation" },
13
13
  ];
14
14
 
15
+ /**
16
+ * One canonical rung. Catalog writers that hold a partially populated ladder derive their
17
+ * element type from this instead of restating `{ effort?: string }`: those restatements
18
+ * silently excluded `description`, so pushing a canonical rung into them is a type error
19
+ * that only surfaced once an unrelated import cycle through this module was broken.
20
+ */
21
+ export type CodexReasoningLevel = (typeof CODEX_REASONING_LEVELS)[number];
22
+
15
23
  const CODEX_REASONING_ORDER = CODEX_REASONING_LEVELS.map(l => l.effort);
16
24
  const CODEX_REASONING_SET = new Set(CODEX_REASONING_ORDER);
17
25
 
@@ -33,6 +33,38 @@ function namespaceOf(value: unknown): string | undefined {
33
33
  return typeof value === "string" && value !== "functions" ? value : undefined;
34
34
  }
35
35
 
36
+ /**
37
+ * Repair one provider spelling drift for Codex's flat shell bridge.
38
+ *
39
+ * This is schema-bound rather than a general alias: `input` is also a legitimate
40
+ * argument name for arbitrary caller and MCP tools. Only the exact bare
41
+ * `exec_command` declaration can establish that its sole string input has one faithful
42
+ * reading as the required string `cmd` member.
43
+ */
44
+ function repairExecCommandInput(
45
+ argumentsText: string,
46
+ schema: FunctionCallRepairSchema,
47
+ ): string {
48
+ if (schema.namespace !== undefined || schema.name !== "exec_command") return argumentsText;
49
+ const parameters = schema.parameters;
50
+ if (!isObject(parameters) || parameters.type !== "object"
51
+ || !Array.isArray(parameters.required) || !parameters.required.includes("cmd")
52
+ || !isObject(parameters.properties) || !isObject(parameters.properties.cmd)
53
+ || parameters.properties.cmd.type !== "string") return argumentsText;
54
+ let parsed: unknown;
55
+ try {
56
+ parsed = JSON.parse(argumentsText);
57
+ } catch {
58
+ return argumentsText;
59
+ }
60
+ if (!isObject(parsed)) return argumentsText;
61
+ const keys = Object.keys(parsed);
62
+ if (keys.length !== 1 || keys[0] !== "input" || typeof parsed.input !== "string") {
63
+ return argumentsText;
64
+ }
65
+ return JSON.stringify({ cmd: parsed.input });
66
+ }
67
+
36
68
  function selectorAllows(
37
69
  selector: unknown,
38
70
  lowered: unknown,
@@ -131,7 +163,12 @@ function repairItem(item: unknown, schemas: FunctionCallRepairSchemas, completed
131
163
  if (unsafe) return item;
132
164
  } catch { return item; }
133
165
  }
134
- const argumentsText = coerceIntegerToolArguments(raw || "{}", schema.parameters, schema.namespace ? undefined : schema.name);
166
+ const integerRepaired = coerceIntegerToolArguments(
167
+ raw || "{}",
168
+ schema.parameters,
169
+ schema.namespace ? undefined : schema.name,
170
+ );
171
+ const argumentsText = repairExecCommandInput(integerRepaired, schema);
135
172
  return argumentsText === raw ? item : { ...item, arguments: argumentsText };
136
173
  }
137
174
 
@@ -0,0 +1,65 @@
1
+ import type { OcxDocumentContent } from "../types";
2
+
3
+ const DATA_URL = /^data:([^;,]+)((?:;[^;,]*)*),(.*)$/s;
4
+ const BASE64_PAYLOAD = /^[A-Za-z0-9+/]+={0,2}$/;
5
+
6
+ function usableBase64(payload: string): boolean {
7
+ if (!BASE64_PAYLOAD.test(payload)) return false;
8
+ // Preserve valid unpadded encodings, but a single sextet cannot represent any byte.
9
+ // Padding, when present, must complete a four-character quantum. No payload allocation.
10
+ return payload.endsWith("=") ? payload.length % 4 === 0 : payload.length % 4 !== 1;
11
+ }
12
+
13
+ /** The one marker vocabulary for an attached document, derived from what the part knows. */
14
+ export function inlineDocumentMarker(filename: string | undefined): string {
15
+ return filename !== undefined && filename.length > 0 ? `[document: ${filename}]` : "[document]";
16
+ }
17
+
18
+ /**
19
+ * An inline document part from a `data:` URL, or `undefined` when there are no usable bytes.
20
+ *
21
+ * A reference with no payload — a `file_id`, a bare filename — is deliberately not a document:
22
+ * there is nothing to carry, and minting a part for it would claim an attachment the request
23
+ * never contained. Those keep the marker path they already had.
24
+ */
25
+ export function inlineDocumentFromDataUrl(
26
+ fileData: string | undefined,
27
+ filename: string | undefined,
28
+ ): OcxDocumentContent | undefined {
29
+ if (typeof fileData !== "string" || fileData.length === 0) return undefined;
30
+ const match = DATA_URL.exec(fileData);
31
+ if (!match) return undefined;
32
+ const mediaType = match[1]!;
33
+ const payload = match[3]!;
34
+ if (!hasBase64Parameter(match[2] ?? "") || !usableBase64(payload)) return undefined;
35
+ return {
36
+ type: "document",
37
+ text: inlineDocumentMarker(filename),
38
+ mediaType,
39
+ data: payload,
40
+ ...(filename !== undefined && filename.length > 0 ? { filename } : {}),
41
+ };
42
+ }
43
+
44
+ /** The `data:` URL spelling of a document part, for wires whose counterpart takes one. */
45
+ export function inlineDocumentDataUrl(part: OcxDocumentContent): string {
46
+ return `data:${part.mediaType};base64,${part.data}`;
47
+ }
48
+
49
+ /**
50
+ * Whether this string is a `data:` URL this module would actually decode.
51
+ *
52
+ * The media-type scanner and this parser have to agree exactly. A looser scanner would exempt an
53
+ * attachment from the untranslated-media refusal that the parser then reduces to a marker, which
54
+ * is the silent drop the refusal exists to prevent. `;base64` must be a whole parameter token,
55
+ * not a substring of one: `;notbase64,` and `;x=base64,` are not base64 payloads.
56
+ */
57
+ export function isInlineDocumentDataUrl(value: unknown): boolean {
58
+ if (typeof value !== "string" || value.length === 0) return false;
59
+ const match = DATA_URL.exec(value);
60
+ return match !== null && hasBase64Parameter(match[2] ?? "") && usableBase64(match[3]!);
61
+ }
62
+
63
+ function hasBase64Parameter(parameters: string): boolean {
64
+ return parameters.split(";").includes("base64");
65
+ }
@@ -1,3 +1,5 @@
1
+ import { isInlineDocumentDataUrl } from "./inline-document";
2
+
1
3
  /** Input kinds for which the normalized request has no lossless content carrier. */
2
4
  export type UntranslatedInputMedia = "audio" | "file";
3
5
 
@@ -7,20 +9,47 @@ function isRecord(value: unknown): value is RecordValue {
7
9
  return value !== null && typeof value === "object" && !Array.isArray(value);
8
10
  }
9
11
 
10
- function mediaKind(value: unknown): UntranslatedInputMedia | undefined {
12
+ /**
13
+ * Whether the position being scanned has a converter that builds a document part.
14
+ *
15
+ * Only user content does. A tool output, a system or assistant message, and a Chat `developer`
16
+ * message are all flattened to text by their converters, so exempting an attachment there would
17
+ * turn today's explicit refusal into the silent drop this scanner exists to prevent.
18
+ */
19
+ type CarrierPosition = "user-content" | "flattened";
20
+
21
+ function mediaKind(value: unknown, position: CarrierPosition): UntranslatedInputMedia | undefined {
11
22
  if (!isRecord(value)) return undefined;
12
23
  if (value.type === "input_audio" || value.type === "audio") return "audio";
13
- if (value.type === "input_file" || value.type === "file" || value.type === "document") return "file";
24
+ if (value.type === "input_file" || value.type === "file" || value.type === "document") {
25
+ // A document that carries its own bytes has a lossless carrier in user content, so refusing
26
+ // it there would reject the very request #5212 exists to preserve. A reference with no
27
+ // payload has none anywhere: translated adapters cannot dereference a file_id or a remote
28
+ // source.
29
+ return position === "user-content" && carriesInlineDocumentBytes(value) ? undefined : "file";
30
+ }
14
31
  // A file-id-only image is not pixels: translated adapters cannot dereference it.
15
32
  if (value.type === "input_image" && typeof value.file_id === "string" && value.file_id.length > 0
16
33
  && !(typeof value.image_url === "string" && value.image_url.length > 0)) return "file";
17
34
  return undefined;
18
35
  }
19
36
 
20
- function contentMedia(content: unknown): UntranslatedInputMedia | undefined {
37
+ /** Presence of a base64 payload only. No payload is read, decoded, copied or returned. */
38
+ function carriesInlineDocumentBytes(value: RecordValue): boolean {
39
+ if (isInlineDocumentDataUrl(value.file_data)) return true;
40
+ // Chat Completions nests the payload under `file`.
41
+ if (isRecord(value.file) && isInlineDocumentDataUrl(value.file.file_data)) return true;
42
+ // Anthropic nests it under a base64 `source`.
43
+ return isRecord(value.source)
44
+ && value.source.type === "base64"
45
+ && typeof value.source.data === "string"
46
+ && value.source.data.length > 0;
47
+ }
48
+
49
+ function contentMedia(content: unknown, position: CarrierPosition): UntranslatedInputMedia | undefined {
21
50
  if (!Array.isArray(content)) return undefined;
22
51
  for (const part of content) {
23
- const kind = mediaKind(part);
52
+ const kind = mediaKind(part, position);
24
53
  if (kind) return kind;
25
54
  }
26
55
  return undefined;
@@ -35,13 +64,16 @@ export function untranslatedResponsesInputMedia(body: unknown): UntranslatedInpu
35
64
  if (!isRecord(body) || !Array.isArray(body.input)) return undefined;
36
65
  for (const item of body.input) {
37
66
  if (!isRecord(item)) continue;
38
- const direct = mediaKind(item);
67
+ const direct = mediaKind(item, "flattened");
39
68
  if (direct) return direct;
40
69
  if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
41
- const kind = contentMedia(item.output);
70
+ const kind = contentMedia(item.output, "flattened");
42
71
  if (kind) return kind;
43
72
  } else if (item.type === "message" || item.type === undefined) {
44
- const kind = contentMedia(item.content);
73
+ // `inputContentParts` runs for user and developer messages only; a system message is
74
+ // flattened to text by the parser.
75
+ const role = item.role;
76
+ const kind = contentMedia(item.content, role === "user" || role === "developer" ? "user-content" : "flattened");
45
77
  if (kind) return kind;
46
78
  }
47
79
  }
@@ -53,7 +85,9 @@ export function untranslatedChatInputMedia(body: unknown): UntranslatedInputMedi
53
85
  if (!isRecord(body) || !Array.isArray(body.messages)) return undefined;
54
86
  for (const message of body.messages) {
55
87
  if (!isRecord(message)) continue;
56
- const kind = contentMedia(message.content);
88
+ // Only the `user` branch of the Chat projection builds content blocks. `system`,
89
+ // `developer`, `assistant` and `tool` all reduce their content to a string.
90
+ const kind = contentMedia(message.content, message.role === "user" ? "user-content" : "flattened");
57
91
  if (kind) return kind;
58
92
  }
59
93
  return undefined;
@@ -8,6 +8,25 @@ const MUSE_SAFE_NAME = /^[a-zA-Z0-9_-]+$/;
8
8
 
9
9
  export type MuseToolNameAliases = ReadonlyMap<string, string>;
10
10
 
11
+ /**
12
+ * Final wire name for an identity before the Muse length rewrite.
13
+ *
14
+ * The alias map is request-local and already narrowed by `tool_choice`. Returning undefined for
15
+ * a contradictory map keeps downstream identity checks fail-closed instead of choosing one alias.
16
+ */
17
+ export function museWireNameForOriginal(
18
+ originalName: string,
19
+ aliases: MuseToolNameAliases,
20
+ ): string | undefined {
21
+ let wireName: string | undefined;
22
+ for (const [candidate, original] of aliases) {
23
+ if (original !== originalName) continue;
24
+ if (wireName !== undefined && wireName !== candidate) return undefined;
25
+ wireName = candidate;
26
+ }
27
+ return wireName ?? originalName;
28
+ }
29
+
11
30
  function isPlainObject(value: unknown): value is Record<string, unknown> {
12
31
  return !!value && typeof value === "object" && !Array.isArray(value);
13
32
  }
@@ -1,4 +1,5 @@
1
1
  import type { OcxContentPart, OcxTextContent } from "../types";
2
+ import { inlineDocumentFromDataUrl } from "./inline-document";
2
3
 
3
4
  export function isObj(v: unknown): v is Record<string, unknown> {
4
5
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -90,8 +91,13 @@ export function inputContentParts(blocks: unknown): string | OcxContentPart[] {
90
91
  if (fileId) {
91
92
  parts.push({ type: "text", text: `[file: ${fileId}]` });
92
93
  } else if (fileData) {
93
- // Inline file_data is often large base64. Preserve only its presence and name, never bytes.
94
- parts.push({ type: "text", text: filename ? `[file: ${filename}]` : "[file: inline data]" });
94
+ // Inline bytes used to be reduced to a name here, which meant no adapter could forward
95
+ // the attachment even to a target that has a representation for it, and a title-only
96
+ // forward came back as a confident answer about a document the model never saw (#5212).
97
+ // The marker survives on the part for every wire that still cannot carry one. Bytes with
98
+ // no declared media type are not decodable into a document and keep the marker they had.
99
+ const document = inlineDocumentFromDataUrl(fileData, filename);
100
+ parts.push(document ?? { type: "text", text: filename ? `[file: ${filename}]` : "[file: inline data]" });
95
101
  }
96
102
  // A bare filename is not a file resource in the Responses schema, so omit it rather than
97
103
  // fabricating a "[file: ...]" marker for an attachment that was never sent.
@@ -61,6 +61,9 @@ export function buildTools(tools: unknown[] | undefined): OcxTool[] | undefined
61
61
  parameters: normalizeParameters(t.parameters),
62
62
  };
63
63
  if (t.strict !== undefined) tool.strict = t.strict as boolean;
64
+ if (Array.isArray(t.allowed_callers)) {
65
+ tool.allowedCallers = (t.allowed_callers as unknown[]).filter((c): c is string => typeof c === "string");
66
+ }
64
67
  if (namespace) tool.namespace = namespace;
65
68
  out.push(tool);
66
69
  };
@@ -245,7 +245,9 @@ export function parseRequest(
245
245
  case "system": {
246
246
  pendingReasoning.length = 0;
247
247
  const text = inputContentParts(msg.content);
248
- const flat = typeof text === "string" ? text : text.map(p => (p.type === "text" ? p.text : "")).join("");
248
+ const flat = typeof text === "string"
249
+ ? text
250
+ : text.map(p => (p.type === "text" || p.type === "document" ? p.text : "")).join("");
249
251
  if (flat.length > 0) systemPrompt.push(flat);
250
252
  break;
251
253
  }
@@ -122,6 +122,9 @@ export const toolSchema = z.object({
122
122
  description: z.string().optional(),
123
123
  parameters: z.record(z.string(), z.unknown()).optional(),
124
124
  strict: z.boolean().optional(),
125
+ // Unknown keys are stripped here, so a field the parser is expected to read has to be
126
+ // declared: an undeclared allowed_callers never reached buildTools at all (#5210).
127
+ allowed_callers: z.array(z.string()).optional(),
125
128
  });
126
129
 
127
130
  const builtinToolSchema = z.object({ type: z.string() }).loose();
package/src/router.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
2
+ import { createHash } from "node:crypto";
3
+ import { peekAuthStore } from "./oauth/store";
2
4
  import {
3
5
  getCombo,
4
6
  isComboTargetInCooldown,
@@ -36,7 +38,7 @@ import {
36
38
  import { decodeRoutedModelIdOrThrow, encodeRoutedModelId } from "./providers/slug-codec";
37
39
  import { effectiveProviderAliasDecision, resolveModelAlias } from "./providers/default-aliases";
38
40
  import { resolveBlockedModelRedirect } from "./lib/shadow-call";
39
- import { getStaleCached } from "./codex/model-cache";
41
+ import { getRoutingCached } from "./codex/model-cache";
40
42
  import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
41
43
  import {
42
44
  buildRouteDecisionTrace,
@@ -149,7 +151,20 @@ export function knownModelIdsForProvider(
149
151
  // only in a map this function forgot is no longer undecodable, and a new model-keyed field
150
152
  // fails typecheck until its keys are given a meaning.
151
153
  for (const id of registry ? registryModelIdKeys(registry) : []) ids.add(id);
152
- for (const cached of getStaleCached(provName) ?? []) ids.add(cached.id);
154
+ const cachedModels = getRoutingCached(provName, () => {
155
+ // This callback runs only for a scoped entry, not for each provider in an alias scan.
156
+ const routed = routedProviderConfig(provName, prov);
157
+ let key = routed.apiKey;
158
+ if (routed.authMode === "oauth") {
159
+ const set = peekAuthStore()[provName];
160
+ const account = set?.accounts.find(row => row.id === set.activeAccountId);
161
+ if (!account || account.needsReauth || !Number.isFinite(account.credential.expires)
162
+ || account.credential.expires <= Date.now()) return undefined;
163
+ key = account.credential.access;
164
+ }
165
+ return key ? createHash("sha256").update(key).digest("hex") : undefined;
166
+ });
167
+ for (const cached of cachedModels ?? []) ids.add(cached.id);
153
168
  for (const model of config?.customModels ?? []) {
154
169
  if (model.provider === provName && model.modelId) ids.add(model.modelId);
155
170
  }