@bitkyc08/opencodex 2.10.0 → 2.10.1

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 (274) hide show
  1. package/AGENTS_INSTALL.md +77 -0
  2. package/README.md +4 -10
  3. package/bin/ocx.mjs +71 -18
  4. package/gui/dist/assets/index-Cd6_PBKn.css +1 -0
  5. package/gui/dist/assets/index-ChZQsmBY.js +70 -0
  6. package/gui/dist/index.html +2 -2
  7. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  8. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  9. package/gui/dist/provider-icons/claude-color.svg +1 -1
  10. package/gui/dist/provider-icons/cline-color.svg +16 -0
  11. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  12. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  13. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  14. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  15. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  16. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  17. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  18. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  19. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  20. package/gui/dist/provider-icons/grok.svg +1 -1
  21. package/gui/dist/provider-icons/groq-color.svg +1 -1
  22. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  23. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  24. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  25. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  26. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  27. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  28. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  29. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  30. package/gui/dist/provider-icons/openai.svg +1 -1
  31. package/gui/dist/provider-icons/opencode.svg +2 -1
  32. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  33. package/gui/dist/provider-icons/pi.svg +2 -2
  34. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  35. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  36. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  37. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  38. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  39. package/package.json +8 -4
  40. package/src/adapters/anthropic.ts +208 -14
  41. package/src/adapters/base.ts +16 -5
  42. package/src/adapters/cursor/effort-map.ts +3 -2
  43. package/src/adapters/cursor/framing.ts +39 -0
  44. package/src/adapters/cursor/live-transport.ts +105 -95
  45. package/src/adapters/cursor/native-exec.ts +32 -6
  46. package/src/adapters/cursor/protobuf-request.ts +20 -15
  47. package/src/adapters/cursor/request-builder.ts +21 -7
  48. package/src/adapters/cursor/types.ts +7 -0
  49. package/src/adapters/google-antigravity-replay.ts +237 -21
  50. package/src/adapters/google-truncation.ts +11 -0
  51. package/src/adapters/google.ts +50 -9
  52. package/src/adapters/identity.ts +39 -6
  53. package/src/adapters/kiro-errors.ts +11 -0
  54. package/src/adapters/kiro-events.ts +19 -1
  55. package/src/adapters/kiro-thinking.ts +10 -2
  56. package/src/adapters/kiro-tools.ts +10 -1
  57. package/src/adapters/kiro.ts +37 -11
  58. package/src/adapters/openai-chat.ts +284 -83
  59. package/src/adapters/openai-responses.ts +182 -24
  60. package/src/bridge.ts +177 -7
  61. package/src/chat/outbound.ts +78 -23
  62. package/src/claude/agents-inject.ts +27 -5
  63. package/src/claude/inbound.ts +11 -1
  64. package/src/claude/model-info.ts +13 -10
  65. package/src/claude/outbound.ts +17 -0
  66. package/src/cli/account-api.ts +24 -0
  67. package/src/cli/account-auth.ts +31 -6
  68. package/src/cli/account-main.ts +317 -0
  69. package/src/cli/account.ts +5 -0
  70. package/src/cli/claude.ts +2 -1
  71. package/src/cli/doctor.ts +93 -22
  72. package/src/cli/export-command.ts +26 -12
  73. package/src/cli/help.ts +8 -6
  74. package/src/cli/index.ts +56 -22
  75. package/src/cli/integrations.ts +84 -1
  76. package/src/cli/observe.ts +54 -1
  77. package/src/cli/opencode.ts +2 -1
  78. package/src/cli/provider-runtime.ts +18 -1
  79. package/src/cli/route-policy.ts +92 -0
  80. package/src/cli/runtime-api.ts +6 -3
  81. package/src/cli/star-prompt.ts +71 -15
  82. package/src/cli/status.ts +1 -1
  83. package/src/cli/v2.ts +36 -9
  84. package/src/clients/config-export.ts +687 -10
  85. package/src/codex/account-lifecycle.ts +30 -5
  86. package/src/codex/account-usability.ts +22 -2
  87. package/src/codex/admission.ts +255 -0
  88. package/src/codex/auth-api.ts +427 -140
  89. package/src/codex/auth-context.ts +155 -30
  90. package/src/codex/autostart-health.ts +8 -1
  91. package/src/codex/catalog/account-models.ts +62 -0
  92. package/src/codex/catalog/aggregation.ts +14 -1
  93. package/src/codex/catalog/bundled.ts +282 -32
  94. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  95. package/src/codex/catalog/metadata.ts +51 -6
  96. package/src/codex/catalog/parsing.ts +6 -3
  97. package/src/codex/catalog/provider-fetch.ts +576 -41
  98. package/src/codex/catalog/sync.ts +505 -66
  99. package/src/codex/catalog-admission.ts +197 -0
  100. package/src/codex/catalog-write-serialization.ts +241 -0
  101. package/src/codex/catalog.ts +2 -1
  102. package/src/codex/codex-write-lock.ts +372 -0
  103. package/src/codex/convergence-types.ts +593 -0
  104. package/src/codex/convergence.ts +441 -0
  105. package/src/codex/desired-state.ts +177 -0
  106. package/src/codex/features.ts +52 -8
  107. package/src/codex/generation.ts +202 -0
  108. package/src/codex/history-job.ts +257 -0
  109. package/src/codex/history-lock.ts +241 -0
  110. package/src/codex/history-migration-guardian.ts +18 -5
  111. package/src/codex/history-provider.ts +9 -2
  112. package/src/codex/history-transition.ts +105 -0
  113. package/src/codex/history-worker.ts +176 -0
  114. package/src/codex/inject-coordination.ts +245 -0
  115. package/src/codex/inject.ts +605 -124
  116. package/src/codex/integration-record.ts +266 -0
  117. package/src/codex/internal/catalog-writer.ts +203 -0
  118. package/src/codex/internal/history-writer.ts +80 -0
  119. package/src/codex/journal.ts +10 -1
  120. package/src/codex/main-account-cache.ts +24 -0
  121. package/src/codex/management-convergence.ts +114 -0
  122. package/src/codex/native-main-admission.ts +47 -0
  123. package/src/codex/native-main-auth-temp.ts +187 -0
  124. package/src/codex/native-main-claim.ts +167 -0
  125. package/src/codex/native-main-lock-file.ts +158 -0
  126. package/src/codex/native-main-owner.ts +315 -0
  127. package/src/codex/native-profile-api.ts +247 -0
  128. package/src/codex/native-profile-manager.ts +1512 -0
  129. package/src/codex/native-profile-processes.ts +121 -0
  130. package/src/codex/native-profile-recovery.ts +99 -0
  131. package/src/codex/native-profile-stage-store.ts +387 -0
  132. package/src/codex/native-profile-startup.ts +340 -0
  133. package/src/codex/native-profile-store.ts +855 -0
  134. package/src/codex/native-profile-types.ts +120 -0
  135. package/src/codex/native-residue.ts +557 -0
  136. package/src/codex/project-config-warnings.ts +18 -4
  137. package/src/codex/prompt-journal.ts +311 -0
  138. package/src/codex/prompt-layers.ts +967 -0
  139. package/src/codex/prompt-lock.ts +143 -0
  140. package/src/codex/quota-rejection.ts +224 -0
  141. package/src/codex/quota.ts +86 -3
  142. package/src/codex/routing.ts +299 -62
  143. package/src/codex/runtime.ts +159 -38
  144. package/src/codex/shim.ts +39 -13
  145. package/src/codex/subagent-model-fallback.ts +73 -12
  146. package/src/codex/transition-state.ts +604 -0
  147. package/src/codex/upstream-host-health.ts +70 -0
  148. package/src/codex/user-identity.ts +266 -0
  149. package/src/codex/write-coordination.ts +114 -0
  150. package/src/config.ts +562 -26
  151. package/src/generated/jawcode-model-metadata.ts +2 -2
  152. package/src/grok/inject.ts +15 -4
  153. package/src/grok/inspect.ts +45 -0
  154. package/src/images/loop.ts +113 -20
  155. package/src/integrations/config-io.ts +151 -0
  156. package/src/integrations/journal.ts +315 -0
  157. package/src/integrations/merge.ts +135 -0
  158. package/src/integrations/native/ownership-preflight.ts +165 -0
  159. package/src/integrations/ownership.ts +111 -0
  160. package/src/integrations/registry.ts +101 -0
  161. package/src/integrations/serialize.ts +235 -0
  162. package/src/integrations/state.ts +290 -0
  163. package/src/integrations/store.ts +103 -0
  164. package/src/integrations/writer.ts +492 -0
  165. package/src/lib/bounded-body.ts +46 -8
  166. package/src/lib/bun-runtime.ts +110 -1
  167. package/src/lib/bun-stream-caps.ts +2 -1
  168. package/src/lib/redact.ts +407 -2
  169. package/src/lib/shadow-call.ts +24 -0
  170. package/src/lib/translator-budget.ts +10 -0
  171. package/src/lib/upstream-reachability.ts +91 -0
  172. package/src/lib/upstream-retry.ts +154 -2
  173. package/src/lib/windows-secret-acl.ts +212 -11
  174. package/src/lib/winsw.ts +9 -3
  175. package/src/oauth/index.ts +61 -3
  176. package/src/oauth/key-providers.ts +4 -0
  177. package/src/oauth/kiro.ts +50 -6
  178. package/src/oauth/store.ts +31 -6
  179. package/src/oauth/token-guardian.ts +9 -3
  180. package/src/providers/codex-capacity.ts +288 -0
  181. package/src/providers/derive.ts +33 -1
  182. package/src/providers/free-directory.ts +3 -1
  183. package/src/providers/key-failover.ts +71 -3
  184. package/src/providers/openai-sidecar.ts +64 -4
  185. package/src/providers/openai-virtual-models.ts +1 -0
  186. package/src/providers/quota.ts +334 -26
  187. package/src/providers/registry.ts +284 -16
  188. package/src/providers/xai-transport.ts +11 -4
  189. package/src/responses/compaction.ts +8 -1
  190. package/src/responses/hosted-tool-policy.ts +9 -0
  191. package/src/responses/parser.ts +44 -2
  192. package/src/responses/reasoning-envelope.ts +9 -1
  193. package/src/responses/reasoning-replay-cache.ts +105 -0
  194. package/src/responses/spill-store.ts +45 -8
  195. package/src/responses/state.ts +161 -17
  196. package/src/router.ts +302 -16
  197. package/src/routing/analytics.ts +377 -0
  198. package/src/routing/capability.ts +204 -0
  199. package/src/routing/cost.ts +77 -0
  200. package/src/routing/evaluator.ts +444 -0
  201. package/src/routing/health.ts +401 -0
  202. package/src/routing/history/cursor.ts +43 -0
  203. package/src/routing/history/indexer.ts +590 -0
  204. package/src/routing/history/schema.ts +72 -0
  205. package/src/routing/profile.ts +423 -0
  206. package/src/routing/quota.ts +124 -0
  207. package/src/routing/request-evidence.ts +45 -0
  208. package/src/routing/trace.ts +686 -0
  209. package/src/server/auth-cors.ts +46 -6
  210. package/src/server/chat-completions.ts +28 -13
  211. package/src/server/claude-messages.ts +23 -15
  212. package/src/server/gui-static.ts +39 -10
  213. package/src/server/images.ts +10 -1
  214. package/src/server/index.ts +238 -52
  215. package/src/server/lifecycle.ts +228 -9
  216. package/src/server/live.ts +14 -3
  217. package/src/server/management/agent-settings-routes.ts +64 -14
  218. package/src/server/management/combo-routes.ts +5 -5
  219. package/src/server/management/config-routes.ts +1 -1
  220. package/src/server/management/context.ts +42 -2
  221. package/src/server/management/integration-routes.ts +538 -0
  222. package/src/server/management/logs-usage-routes.ts +1 -1
  223. package/src/server/management/model-routes.ts +32 -113
  224. package/src/server/management/model-rows.ts +117 -0
  225. package/src/server/management/native-integration-routes.ts +587 -0
  226. package/src/server/management/oauth-account-routes.ts +1 -1
  227. package/src/server/management/provider-routes.ts +218 -117
  228. package/src/server/management/request-history-routes.ts +191 -0
  229. package/src/server/management/routing-analytics-routes.ts +74 -0
  230. package/src/server/management/routing-profile-routes.ts +177 -0
  231. package/src/server/management/shared.ts +2 -2
  232. package/src/server/management/sidebar-routes.ts +47 -31
  233. package/src/server/management/sync-response.ts +69 -0
  234. package/src/server/management/system-restart.ts +276 -24
  235. package/src/server/management/system-routes.ts +4 -0
  236. package/src/server/management-api.ts +84 -9
  237. package/src/server/management-auth.ts +43 -5
  238. package/src/server/relay-eager.ts +82 -42
  239. package/src/server/relay.ts +120 -6
  240. package/src/server/request-log.ts +26 -6
  241. package/src/server/responses/collaboration.ts +63 -8
  242. package/src/server/responses/compact.ts +272 -41
  243. package/src/server/responses/core.ts +730 -132
  244. package/src/server/responses/fetch-helpers.ts +15 -1
  245. package/src/server/responses-item-id-repair.ts +32 -3
  246. package/src/server/responses-json-events.ts +52 -0
  247. package/src/server/responses-snapshot-repair.ts +621 -0
  248. package/src/server/search.ts +51 -6
  249. package/src/server/sse-payload-rewrite.ts +89 -12
  250. package/src/server/startup-health-cache.ts +7 -1
  251. package/src/server/ws-bridge.ts +11 -17
  252. package/src/service-manager-probe.ts +297 -0
  253. package/src/service.ts +222 -32
  254. package/src/tray/windows-tray.ps1 +9 -0
  255. package/src/tray/windows.ts +15 -7
  256. package/src/types.ts +194 -14
  257. package/src/update/index.ts +13 -13
  258. package/src/update/job.ts +24 -21
  259. package/src/update/notify.ts +7 -3
  260. package/src/usage/cost.ts +0 -0
  261. package/src/usage/expected-prices.ts +129 -10
  262. package/src/usage/log.ts +50 -15
  263. package/src/usage/summary.ts +4 -4
  264. package/src/vision/index.ts +6 -1
  265. package/src/web-search/loop.ts +161 -34
  266. package/gui/dist/assets/index-OY43ubAq.css +0 -1
  267. package/gui/dist/assets/index-YwNnKZcL.js +0 -67
  268. package/gui/dist/provider-icons/antigravity.svg +0 -1
  269. package/gui/dist/provider-icons/claude.svg +0 -1
  270. package/gui/dist/provider-icons/copilot.svg +0 -1
  271. package/gui/dist/provider-icons/cursor.svg +0 -2
  272. package/gui/dist/provider-icons/gemini.svg +0 -1
  273. package/gui/dist/provider-icons/grok-color.svg +0 -1
  274. package/gui/dist/provider-icons/kiro.svg +0 -14
package/src/types.ts CHANGED
@@ -2,6 +2,8 @@ import type { KiroOAuthMetadata } from "./oauth/types";
2
2
 
3
3
  export interface OcxParsedRequest {
4
4
  modelId: string;
5
+ /** Selected OpenAI API virtual-model id retained after it rewrites the upstream wire model. */
6
+ _openAiVirtualSelectedModelId?: string;
5
7
  previousResponseId?: string;
6
8
  context: OcxContext;
7
9
  stream: boolean;
@@ -83,6 +85,12 @@ export interface OcxAssistantMessage {
83
85
  phase?: OcxMessagePhase;
84
86
  model?: string;
85
87
  timestamp: number;
88
+ /**
89
+ * Kiro `reasoningContent.redactedContent` for THIS assistant turn — an opaque encrypted blob
90
+ * Kiro replays to preserve model reasoning across turns. Provider-specific and unrenderable, so
91
+ * it rides the message rather than a content part: any other adapter simply ignores it.
92
+ */
93
+ kiroRedactedReasoning?: string;
86
94
  }
87
95
 
88
96
  export interface OcxDeveloperMessage {
@@ -252,6 +260,9 @@ export type AdapterEvent =
252
260
  // opaque redacted_thinking blocks. Both must be replayed verbatim or tool-use turns 400.
253
261
  | { type: "thinking_signature"; signature: string }
254
262
  | { type: "redacted_thinking"; data: string }
263
+ // Kiro reasoning round-trip: the encrypted `redactedContent` blob for the CURRENT assistant turn.
264
+ // Never rendered — it only rides the reasoning item's envelope so the next request can replay it.
265
+ | { type: "kiro_redacted_reasoning"; data: string }
255
266
  | { type: "reasoning_raw_delta"; text: string }
256
267
  | { type: "tool_call_start"; id: string; name: string }
257
268
  | { type: "tool_call_delta"; arguments: string }
@@ -528,6 +539,22 @@ export interface OcxApiKeyEntry {
528
539
  createdAt: string;
529
540
  }
530
541
 
542
+ /**
543
+ * Durable per-client intent. One key today, deliberately.
544
+ *
545
+ * A top-level `codexEnabled` would force every later client to invent an
546
+ * unrelated name and its own helpers; a ten-key union recreated the coupling
547
+ * that failed two audits, because every phase then had to touch every client's
548
+ * write path. A one-key object keeps the extension point without letting this
549
+ * phase claim ownership over a client it does not implement.
550
+ */
551
+ export interface OcxClientIntegrationsConfig {
552
+ /** Durable desired state for native Codex. MISSING MEANS ON. */
553
+ codex?: boolean;
554
+ /** Durable desired state for Grok Build. MISSING MEANS ON. */
555
+ grok?: boolean;
556
+ }
557
+
531
558
  export interface OcxConfig {
532
559
  port: number;
533
560
  /** Maximum usage-log bytes read for one management snapshot. */
@@ -541,8 +568,15 @@ export interface OcxConfig {
541
568
  /** Claude Code inbound + launcher settings. */
542
569
  claudeCode?: OcxClaudeCodeConfig;
543
570
  /**
544
- * Up to 5 routed model ids ("<provider>/<model>") to feature FIRST in the injected Codex catalog.
545
- * Codex's spawn_agent only advertises the first 5 routed models, so this picks which 5 appear.
571
+ * Per-client durable intent. This phase owns only `codex`; later phases extend
572
+ * one key at a time rather than widening a shared union.
573
+ */
574
+ clientIntegrations?: OcxClientIntegrationsConfig;
575
+ /**
576
+ * Up to 5 Codex-facing catalog ids to feature first. Values may be bare catalog ids,
577
+ * exact account-qualified "<selector>/<native-openai-model>" ids, or routed
578
+ * "<provider>/<model>" ids. With account selectors, one bare native choice can expand
579
+ * into a selector-qualified group; Codex still advertises only the first 5 visible rows.
546
580
  */
547
581
  subagentModels?: string[];
548
582
  /**
@@ -605,12 +639,17 @@ export interface OcxConfig {
605
639
  */
606
640
  streamMode?: "auto" | "legacy-tee" | "eager-relay";
607
641
  /**
608
- * Custom override for the injected multi-agent guidance body (the text inside the
609
- * <multi_agent_mode> tags). When set, it replaces the built-in prompt on whichever
610
- * collab surface would have fired; firing gates are unchanged. Placeholders:
611
- * `{{model}}` -> injectionModel, `{{effort}}` -> injectionEffort, `{{roster}}` ->
612
- * the resolved sub-agent roster block ("" when nothing resolves), `{{fallback}}` ->
613
- * the configured subagent model fallback guidance block ("" when unset).
642
+ * Custom override for the injected v2 multi-agent guidance body (the text inside
643
+ * the <multi_agent_mode> tags). After guidance is enabled and the v2 surface and
644
+ * catalog-state gates pass, a configured injectionModel is sufficient to render it;
645
+ * otherwise an eligible roster or fallback is required. Placeholders: `{{model}}` -> the
646
+ * effective preferred model for the request (a bare native model is account-qualified
647
+ * only when the request targets an explicit account selector; unresolved or ambiguous
648
+ * bare values become "", while unresolved explicit routed or account-qualified values
649
+ * remain unchanged),
650
+ * `{{effort}}` -> injectionEffort, `{{roster}}` -> the resolved sub-agent roster
651
+ * block ("" when nothing resolves), `{{fallback}}` -> the configured subagent
652
+ * model fallback guidance block ("" when unset).
614
653
  */
615
654
  injectionPrompt?: string;
616
655
  /**
@@ -633,10 +672,10 @@ export interface OcxConfig {
633
672
  */
634
673
  subagentEffortCap?: string;
635
674
  /**
636
- * Models hidden from Codex. Routed ids are namespaced ("<provider>/<model>") and are excluded
637
- * from the catalog + /v1/models entirely. BARE ids (no "/") are native GPT passthrough slugs:
638
- * their catalog entries flip to visibility "hide" (entry preserved, picker-hidden) and they
639
- * are omitted from the bare /v1/models list.
675
+ * Models hidden from Codex discovery without blocking direct proxy calls. Routed provider ids
676
+ * are excluded from the catalog + /v1/models entirely. Account-qualified native ids hide only
677
+ * their generated selector row and are omitted from raw /v1/models. BARE native GPT ids hide
678
+ * the bare row plus every generated selector row and omit that model family from raw discovery.
640
679
  */
641
680
  disabledModels?: string[];
642
681
  /** 사용자가 대시보드에서 직접 추가한 커스텀 모델 목록. */
@@ -645,7 +684,8 @@ export interface OcxConfig {
645
684
  * Shadow call intercept: redirect Codex's hard-coded helper calls (title generation,
646
685
  * commit messages, skill orchestration) to a user-chosen model. Default intercepted
647
686
  * source models: gpt-5.4-mini (older clients) and gpt-5.6-luna (Codex 0.145.0+).
648
- * Opt-in; disabled by default. When enabled, effort is forced to low.
687
+ * Opt-in; disabled by default. Matching maintenance/helper requests are forced to low.
688
+ * Normal Codex turns identified by request_kind=turn are never rewritten.
649
689
  */
650
690
  shadowCallIntercept?: {
651
691
  /** When true, requests for known shadow/helper source models are rewritten to the configured model. */
@@ -754,9 +794,16 @@ export interface OcxConfig {
754
794
  };
755
795
  /** Virtual `combo/<id>` models spanning concrete provider/model targets (issue #133). */
756
796
  combos?: Record<string, OcxComboConfig>;
797
+ /**
798
+ * Routing policy profiles (Router Intelligence, RI-04+): explicitly requested
799
+ * `policy/<id>` (or configured alias) models select among an explicit
800
+ * candidate allowlist using hard capability requirements and deterministic
801
+ * scoring. Existing model ids are never routed through profiles implicitly.
802
+ */
803
+ routingProfiles?: Record<string, OcxRoutingProfileConfig>;
757
804
  /** Background proactive token refresh ("Token Guardian"). Off by default; see OcxTokenGuardianConfig. */
758
805
  tokenGuardian?: OcxTokenGuardianConfig;
759
- /** Additional origins allowed for CORS (e.g. ["https://clisu-oracle.tail19a2d7.ts.net"]). Loopback origins are always allowed. */
806
+ /** Additional exact origins allowed for CORS (e.g. HTTPS or chrome-extension://<id>). Loopback origins are always allowed. */
760
807
  corsAllowOrigins?: string[];
761
808
  }
762
809
 
@@ -788,6 +835,65 @@ export interface OcxComboConfig {
788
835
  alias?: string;
789
836
  }
790
837
 
838
+ export type OcxRoutingUnknownEvidenceMode = "allow" | "penalize" | "exclude";
839
+
840
+ export interface OcxRoutingProfileCandidate {
841
+ provider: string;
842
+ model: string;
843
+ }
844
+
845
+ export interface OcxRoutingProfileRequirements {
846
+ /** Minimum model context window in tokens. */
847
+ minContextWindow?: number;
848
+ /** Minimum remaining quota headroom fraction (0..1). */
849
+ minQuotaHeadroom?: number;
850
+ tools?: boolean;
851
+ imageInput?: boolean;
852
+ structuredOutput?: boolean;
853
+ reasoningEffort?: string;
854
+ serviceTier?: string;
855
+ localOnly?: boolean;
856
+ remoteAllowed?: boolean;
857
+ /** Special encrypted Codex task readability (ChatGPT forward pool). */
858
+ encryptedCodexTasks?: boolean;
859
+ }
860
+
861
+ export interface OcxRoutingProfileOptimize {
862
+ latency?: number;
863
+ health?: number;
864
+ cost?: number;
865
+ quota?: number;
866
+ }
867
+
868
+ export interface OcxRoutingProfileLimits {
869
+ /** Hard per-request estimated-cost ceiling in USD. */
870
+ maxEstimatedCostUsd?: number;
871
+ }
872
+
873
+ export interface OcxRoutingProfileUnknownEvidence {
874
+ capability?: OcxRoutingUnknownEvidenceMode;
875
+ health?: OcxRoutingUnknownEvidenceMode;
876
+ quota?: OcxRoutingUnknownEvidenceMode;
877
+ cost?: OcxRoutingUnknownEvidenceMode;
878
+ }
879
+
880
+ export interface OcxRoutingProfileConfig {
881
+ /**
882
+ * Explicit candidate allowlist (`provider/model` refs). No implicit
883
+ * expansion in v1.
884
+ */
885
+ candidates: OcxRoutingProfileCandidate[];
886
+ /** Optional public model name replacing the default `policy/<id>` slug. */
887
+ alias?: string;
888
+ /** Hard requirements evaluated before scoring. */
889
+ require?: OcxRoutingProfileRequirements;
890
+ /** Optimization weights; normalized deterministically. */
891
+ optimize?: OcxRoutingProfileOptimize;
892
+ limits?: OcxRoutingProfileLimits;
893
+ /** How unknown evidence is handled per dimension. */
894
+ unknownEvidence?: OcxRoutingProfileUnknownEvidence;
895
+ }
896
+
791
897
  /**
792
898
  * Per-provider proactive-refresh policy. The guardian only ever touches a provider whose EFFECTIVE
793
899
  * policy is "proactive"; "lazy-only" keeps today's on-demand refresh, "disabled" forbids the
@@ -905,8 +1011,37 @@ export interface ResponsesItemIdRepairConfig {
905
1011
  reasoning?: string[];
906
1012
  /** Backfill missing `output_item.done` / terminal snapshot ids from the matching output_index. */
907
1013
  repairMissingTerminalIds?: boolean;
1014
+ /**
1015
+ * Treat existing message/reasoning ids without the canonical `msg_`/`rs_` prefix (e.g. bare
1016
+ * UUIDs from DeepSeek's Responses route) as invalid and mint canonical replacements (#938).
1017
+ * function_call ids and call_id pairing are never rewritten.
1018
+ */
1019
+ repairInvalidIds?: boolean;
908
1020
  }
909
1021
 
1022
+ /**
1023
+ * Same-target 429 wait-and-retry policy (`providers.<name>.retryOn429`). When present and not
1024
+ * explicitly disabled, the proxy waits and replays the identical request on the same key before
1025
+ * any key failover. All fields optional; the runtime applies defaults (attempts=3,
1026
+ * intervalMs=5000, maxIntervalMs=60000, respectRetryAfter=true, enabled=true).
1027
+ */
1028
+ export interface RateLimitRetryPolicy {
1029
+ /** Master switch. The presence of the object also enables the policy (default true). */
1030
+ enabled?: boolean;
1031
+ /** Extra replay attempts after the first 429 (1..20, default 3). */
1032
+ attempts?: number;
1033
+ /** Fixed wait between attempts when the upstream sends no usable Retry-After (default 5000). */
1034
+ intervalMs?: number;
1035
+ /** Cap for any single wait, including an upstream Retry-After (default 60000). */
1036
+ maxIntervalMs?: number;
1037
+ /** Prefer the upstream Retry-After header when present and parseable (default true). */
1038
+ respectRetryAfter?: boolean;
1039
+ }
1040
+
1041
+ /**
1042
+ * One configured provider entry. `authMode` (default `"key"`) decides whether same-target 429
1043
+ * retries are allowed; OAuth/forward credentials and local runtimes are never replayed.
1044
+ */
910
1045
  export interface OcxProviderConfig {
911
1046
  adapter: string;
912
1047
  /** Cursor MCP compatibility bounds; positive integers when configured. */
@@ -938,6 +1073,24 @@ export interface OcxProviderConfig {
938
1073
  * forwarded to an upstream that cannot resolve their pair.
939
1074
  */
940
1075
  statelessResponses?: boolean;
1076
+ /**
1077
+ * Whether this provider's Responses route honours the OpenAI `service_tier`
1078
+ * parameter. Tri-state: `true` lets fast mode inject/remove the field (an unset
1079
+ * fast mode preserves a caller-supplied value); `false` strips the field and
1080
+ * never injects, because an upstream documented as not supporting the parameter
1081
+ * must not receive it; absent (`undefined`) leaves the provider unclassified —
1082
+ * caller-supplied values are preserved untouched, and fast mode never injects.
1083
+ * An explicit config value always wins over the registry default.
1084
+ */
1085
+ supportsServiceTier?: boolean;
1086
+ /**
1087
+ * Responses upstream whose native contract accepts plaintext reasoning replay
1088
+ * (DeepSeek documents reasoning items with plaintext content). When set, the
1089
+ * passthrough serializer keeps `reasoning_text` content on replayed reasoning
1090
+ * items instead of blanking it the way the ChatGPT backend requires; proxy-minted
1091
+ * `ocxr1` envelopes are still stripped because no upstream can decrypt them.
1092
+ */
1093
+ preserveResponsesReasoningContent?: boolean;
941
1094
  /**
942
1095
  * Explicit opt-in for non-registry private-network destinations such as localhost, RFC1918,
943
1096
  * link-local, or unique-local upstreams. Metadata endpoints remain blocked.
@@ -1046,10 +1199,23 @@ export interface OcxProviderConfig {
1046
1199
  * Presence also advertises reasoning-summary support for that routed model.
1047
1200
  */
1048
1201
  modelReasoningSummaryDelivery?: Record<string, ReasoningSummaryDelivery>;
1202
+ /**
1203
+ * Exact-model hosted tools that win collisions with Codex client tool declarations.
1204
+ * Use for non-forward Responses gateways that reserve a hosted tool namespace server-side.
1205
+ */
1206
+ modelPreferHostedTools?: Record<string, string[]>;
1207
+ /**
1208
+ * Provider-local repair for Responses gateways whose lifecycle snapshots omit canonical
1209
+ * fields or closing events (#893). Disabled by default and applied only to client-facing
1210
+ * SSE/JSON; raw inspection state remains authoritative.
1211
+ */
1212
+ responsesSnapshotRepair?: boolean;
1049
1213
  /** Provider-wide mapping from Codex effort labels to upstream `reasoning_effort` values. */
1050
1214
  reasoningEffortMap?: Record<string, string>;
1051
1215
  /** Model-specific mapping from Codex effort labels to upstream `reasoning_effort` values. */
1052
1216
  modelReasoningEffortMap?: Record<string, Record<string, string>>;
1217
+ /** OpenAI-compatible gateway reasoning wire shape. Default sends `reasoning_effort`. */
1218
+ reasoningWireFormat?: "gateway-object";
1053
1219
  /**
1054
1220
  * Model ids that do NOT support a reasoning/thinking parameter. The openai-chat adapter drops
1055
1221
  * reasoning_effort for these even when Codex selects a reasoning level (e.g. xAI grok-build-0.1).
@@ -1085,6 +1251,13 @@ export interface OcxProviderConfig {
1085
1251
  autoToolChoiceOnlyModels?: string[];
1086
1252
  /** Model ids that expect prior assistant `reasoning_content` to be preserved in chat history. */
1087
1253
  preserveReasoningContentModels?: string[];
1254
+ /**
1255
+ * Opt-in same-target 429 retry policy. Codex itself never retries 429 (it retries 5xx only,
1256
+ * openai/codex#30471), and single-key pools have no failover, so the proxy waits and replays
1257
+ * the identical request on the same key before any failover. Pre-stream only: a 429 arrives
1258
+ * before any response bytes are relayed, so the replay is lossless.
1259
+ */
1260
+ retryOn429?: RateLimitRetryPolicy;
1088
1261
  /**
1089
1262
  * Model ids whose OpenAI-compatible chat endpoint accepts `reasoning_split: true` and returns
1090
1263
  * thinking separately in `reasoning_content` / `reasoning_details` instead of visible content.
@@ -1104,6 +1277,13 @@ export interface OcxProviderConfig {
1104
1277
  thinkingBudgetModels?: string[];
1105
1278
  /** Anthropic-compatible gateways that need custom tool names escaped on the wire. */
1106
1279
  escapeBuiltinToolNames?: boolean;
1280
+ /**
1281
+ * Anthropic-compatible gateways (e.g. AgentRouter) that may close the stream before
1282
+ * `message_stop`. With this enabled the adapter completes an otherwise-clean EOF only when
1283
+ * visible text was received or an open tool call has complete JSON-object arguments; all
1284
+ * other EOFs remain truncation errors. Absent = strict default behavior.
1285
+ */
1286
+ anthropicEofTolerance?: boolean;
1107
1287
  /**
1108
1288
  * Model ids that do NOT accept image inputs. The proxy gives them "eyes" via the vision sidecar:
1109
1289
  * attached images are described by a gpt vision model and replaced with text before the call.
@@ -5,6 +5,7 @@ import { dirname, join } from "node:path";
5
5
  import { getConfigDir, loadConfig, readPid, readRuntimePort } from "../config";
6
6
  import { npmInvocation } from "./npm-invocation.mjs";
7
7
  import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "./tray-update-plan.mjs";
8
+ import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
8
9
 
9
10
  /**
10
11
  * A `codex-history-backup-*.json` surviving a stop means the native-history restore was
@@ -185,7 +186,7 @@ export async function runUpdate(): Promise<void> {
185
186
  }
186
187
 
187
188
  // Remember whether a background service manages the proxy BEFORE stopping — `ocx stop`
188
- // unloads it permanently, so a successful update must reinstall/restart it afterwards.
189
+ // unloads it, so a successful update must repair/restart it afterwards.
189
190
  let serviceWasInstalled = false;
190
191
  try {
191
192
  const { isServiceInstalled } = await import("../service");
@@ -294,11 +295,11 @@ export async function runUpdate(): Promise<void> {
294
295
  if (trayWasRunning) spawnSync(process.execPath, [process.argv[1], "tray", "start"], { stdio: "ignore", windowsHide: true });
295
296
  }
296
297
  }
297
- // The stop above unloaded any managed service; reinstall it with the NEW files
298
+ // The stop above unloaded any managed service; repair it with the NEW files
298
299
  // (spawn the fresh cli.ts so updated code writes the baked paths) so a
299
300
  // launchd/schtasks/systemd user isn't left with the background proxy down.
300
301
  if (serviceWasInstalled) {
301
- console.log("🔁 Reinstalling the background service with the updated files...");
302
+ console.log("🔁 Refreshing the background service with the updated files...");
302
303
  const { serviceReinstallArgs } = await import("../service");
303
304
  const { reclaimListenPort } = await import("../server/port-reclaim");
304
305
  const freed = await reclaimListenPort(capturedListen.port, capturedListen.hostname, {
@@ -309,7 +310,7 @@ export async function runUpdate(): Promise<void> {
309
310
  onlyKillPids: capturedListen.oldPid != null ? [capturedListen.oldPid] : [],
310
311
  });
311
312
  if (!freed) {
312
- console.warn(`⚠️ Port ${capturedListen.port} still busy after 30s; reinstalling service with pinned --port ${capturedListen.port} anyway (refusing to hop).`);
313
+ console.warn(`⚠️ Port ${capturedListen.port} still busy after 30s; repairing service with pinned --port ${capturedListen.port} anyway (refusing to hop).`);
313
314
  }
314
315
  const prevBake = process.env.OCX_BAKE_PORT;
315
316
  process.env.OCX_BAKE_PORT = String(capturedListen.port);
@@ -332,9 +333,8 @@ export async function runUpdate(): Promise<void> {
332
333
  }
333
334
  }
334
335
  if (!serviceRefreshed || !serviceViable) {
335
- // On Windows, schtasks /create requires elevation. The CLI inherits the
336
- // user's (non-admin) token, so the service reinstall can fail with access
337
- // denied — or exit 0 while leaving stale/missing assets that never start
336
+ // A repair needs no elevation (it never calls `schtasks /create`), but it can
337
+ // still fail or exit 0 while leaving stale/missing assets that never start
338
338
  // the proxy. Fall back to a direct detached proxy start so the update
339
339
  // never leaves the user without a running proxy — but only when the port is free.
340
340
  if (!freed) {
@@ -344,8 +344,8 @@ export async function runUpdate(): Promise<void> {
344
344
  : "⚠️ Service refresh failed and the captured port is still busy; not starting on another port.",
345
345
  );
346
346
  console.warn(process.platform === "win32"
347
- ? ` Run 'ocx service install' as administrator, then 'ocx start --port ${capturedListen.port}'.`
348
- : ` Run 'ocx service install' to see the reason, then 'ocx start --port ${capturedListen.port}'.`);
347
+ ? ` Run 'ocx service repair', then 'ocx start --port ${capturedListen.port}'.`
348
+ : ` Run 'ocx service repair' to see the reason, then 'ocx start --port ${capturedListen.port}'.`);
349
349
  } else {
350
350
  console.warn(
351
351
  serviceRefreshed
@@ -353,18 +353,18 @@ export async function runUpdate(): Promise<void> {
353
353
  : "⚠️ Service refresh failed — starting the proxy directly instead.",
354
354
  );
355
355
  // Elevation is a Windows-only remedy; elsewhere the refresh fails for
356
- // reasons `ocx service install` reports directly (since it now verifies
356
+ // reasons `ocx service repair` reports directly (since it now verifies
357
357
  // the service actually serves).
358
358
  console.warn(process.platform === "win32"
359
- ? " Run 'ocx service install' as administrator to refresh the background service."
360
- : " Run 'ocx service install' to refresh the background service and see why it failed.");
359
+ ? " Run 'ocx service repair' to refresh the background service."
360
+ : " Run 'ocx service repair' to refresh the background service and see why it failed.");
361
361
  const env = { ...process.env };
362
362
  delete env.OCX_SERVICE;
363
363
  const child = spawn(process.execPath, [process.argv[1], "start", "--port", String(capturedListen.port)], {
364
364
  detached: true,
365
365
  stdio: "ignore",
366
366
  windowsHide: true,
367
- env,
367
+ env: withProcessRuntimeProvenance(env),
368
368
  });
369
369
  child.unref();
370
370
  console.log(`✅ Proxy starting on port ${capturedListen.port}.`);
package/src/update/job.ts CHANGED
@@ -300,7 +300,9 @@ export function restartCommand(
300
300
  const startArgs = pinPort
301
301
  ? [launcher, "start", "--port", String(Math.trunc(port))]
302
302
  : [launcher, "start"];
303
- const svcArgs = serviceInstalled ? [launcher, ...(serviceArgs ?? ["service", "install"])] : startArgs;
303
+ // Default to the non-registering refresh: an update path reaching here has an already
304
+ // installed service, and `install` would demand elevation on Windows scheduler backends.
305
+ const svcArgs = serviceInstalled ? [launcher, ...(serviceArgs ?? ["service", "repair"])] : startArgs;
304
306
  if (installer === "npm") {
305
307
  const bin = nodeBin();
306
308
  const args = svcArgs;
@@ -648,6 +650,8 @@ export interface RestartIo {
648
650
  serviceHealthTimeoutMs?: number;
649
651
  sleepMs?: (ms: number) => Promise<void>;
650
652
  now?: () => number;
653
+ /** Test seam — defaults to process.platform so the Windows-only branch is reachable off Windows. */
654
+ platform?: NodeJS.Platform;
651
655
  /** Service-mode install/reinstall command (defaults to spawnSync via runLoggedCommand). */
652
656
  runService?: (
653
657
  job: UpdateJobState,
@@ -782,11 +786,17 @@ async function restartAfterUpdate(
782
786
  const preServiceAllow = reclaimKillAllowlist();
783
787
  const freed = await waitFn(port, hostname, reclaimOptsFor(preServiceAllow));
784
788
  let skipServiceInstall = false;
785
- // Windows GUI update worker sets OCX_SERVICE=1 and is never elevated.
786
- // `schtasks /create` will UAC-fail and can race the subsequent direct start.
787
- // Keep systemd/launchd reinstall on non-Windows supervisors.
788
- if (process.platform === "win32" && process.env.OCX_SERVICE === "1") {
789
- updateJob(job, {}, "Skipping service reinstall from the non-elevated update worker; falling back to a direct proxy start.");
789
+ // This skip existed because the refresh ran `ocx service install`, whose Windows
790
+ // scheduler path always reaches `schtasks /create` elevation the GUI update worker
791
+ // (OCX_SERVICE=1) never has. `service repair` rewrites the wrapper assets and
792
+ // restarts the EXISTING task with no `/create`, so the reason no longer applies and
793
+ // skipping would leave the dashboard-triggered update the most common Windows
794
+ // path — with a stale service it could have refreshed.
795
+ //
796
+ // Only a caller that still passes install argv keeps the old behavior.
797
+ const refreshRegisters = (svcArgs ?? []).includes("install");
798
+ if ((io.platform ?? process.platform) === "win32" && process.env.OCX_SERVICE === "1" && refreshRegisters) {
799
+ updateJob(job, {}, "Skipping service re-registration from the non-elevated update worker; falling back to a direct proxy start.");
790
800
  skipServiceInstall = true;
791
801
  }
792
802
  if (!freed && !skipServiceInstall) {
@@ -813,24 +823,17 @@ async function restartAfterUpdate(
813
823
  const result = run(job, cmd.bin, cmd.args);
814
824
  serviceOk = result.status === 0;
815
825
  if (!serviceOk) {
816
- // On Windows, `schtasks /create` requires an elevated token. The update worker
817
- // inherits the (non-admin) proxy's privileges, so a service-managed install
818
- // updated from the GUI or a normal terminal fails here with access denied.
819
- // Falling back to a direct proxy start keeps the update from leaving the proxy
820
- // stopped; the stale service manager can be refreshed later with an admin
821
- // `ocx service install`.
822
- //
823
- // That advice is Windows-only, and on macOS/Linux it now actively misleads:
824
- // `ocx service install` gained a non-zero exit for a service that registers
825
- // but does not serve, so this branch fires there for a reason elevation
826
- // cannot fix. Point at the command that prints the real reason instead.
826
+ // The refresh that just failed was `ocx service repair` (serviceReinstallArgs),
827
+ // which needs no elevation because it never calls `schtasks /create`. Advising
828
+ // `install` here would send the user to re-registration a UAC prompt on
829
+ // Windows and a possible WinSW-to-scheduler backend switch to fix a service
830
+ // that is already registered. Point at the same command that failed so its
831
+ // output explains why, on every platform.
827
832
  updateJob(
828
833
  job,
829
834
  {},
830
- `Service reinstall failed (exit ${result.status ?? "?"}); falling back to a direct proxy start.`
831
- + (process.platform === "win32"
832
- ? " Run 'ocx service install' as administrator to refresh the background service manager."
833
- : " Run 'ocx service install' by hand to see the reason, then 'ocx service status'."),
835
+ `Service refresh failed (exit ${result.status ?? "?"}); falling back to a direct proxy start.`
836
+ + " Run 'ocx service repair' by hand to see the reason, then 'ocx service status'.",
834
837
  );
835
838
  }
836
839
  } finally {
@@ -83,8 +83,12 @@ function gt(a: number[], b: number[]): boolean {
83
83
 
84
84
  /**
85
85
  * Channel-aware "is latest newer than current?".
86
- * - latest channel: compare maj.min.pat only; prereleases are never "newer"
87
- * (parity with codex-rs), so stable users are not pushed onto previews.
86
+ * - latest channel: compare maj.min.pat only; prerelease TARGETS are never
87
+ * "newer" (parity with codex-rs), so stable users are not pushed onto
88
+ * previews. An installed preview CURRENT compares by its maj.min.pat core,
89
+ * so a stable release with a strictly higher base is offered (same base is
90
+ * content-lateral promotion and stays not-newer, mirroring the preview
91
+ * channel's O3 rule).
88
92
  * - preview channel: preview-vs-preview compares the trailing -preview.N; a
89
93
  * stable release with a strictly higher base counts as newer (O3), while a
90
94
  * stable release with the same base as the current preview does not.
@@ -92,7 +96,7 @@ function gt(a: number[], b: number[]): boolean {
92
96
  export function isNewer(latest: string, current: string, channel: Channel): boolean {
93
97
  if (channel === "latest") {
94
98
  const l = parseStable(latest);
95
- const c = parseStable(current);
99
+ const c = parseStable(current) ?? parsePreview(current)?.slice(0, 3);
96
100
  if (!l || !c) return false;
97
101
  return gt(l, c);
98
102
  }
package/src/usage/cost.ts CHANGED
Binary file