@iislee/opencodex 2.31.0 → 2.35.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 (261) hide show
  1. package/README.md +3 -3
  2. package/bin/ocx.mjs +99 -70
  3. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  4. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +3 -1
  7. package/src/adapters/anthropic.ts +104 -15
  8. package/src/adapters/base.ts +29 -1
  9. package/src/adapters/command-code.ts +42 -6
  10. package/src/adapters/cursor/call-id.ts +44 -0
  11. package/src/adapters/cursor/catalog.ts +541 -0
  12. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  13. package/src/adapters/cursor/cursor-errors.ts +15 -0
  14. package/src/adapters/cursor/discovery.ts +69 -18
  15. package/src/adapters/cursor/effort-map.ts +79 -1
  16. package/src/adapters/cursor/envelope-echo.ts +290 -0
  17. package/src/adapters/cursor/live-models.ts +7 -2
  18. package/src/adapters/cursor/live-transport.ts +14 -0
  19. package/src/adapters/cursor/message-mapper.ts +4 -1
  20. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  21. package/src/adapters/cursor/native-exec-fs.ts +2 -2
  22. package/src/adapters/cursor/native-exec-network.ts +1 -1
  23. package/src/adapters/cursor/native-exec-shell.ts +5 -2
  24. package/src/adapters/cursor/native-exec.ts +20 -0
  25. package/src/adapters/cursor/protobuf-request.ts +68 -14
  26. package/src/adapters/cursor/request-builder.ts +40 -10
  27. package/src/adapters/cursor/tool-definitions.ts +14 -4
  28. package/src/adapters/cursor/tool-result-normalize.ts +28 -5
  29. package/src/adapters/cursor/types.ts +20 -0
  30. package/src/adapters/cursor.ts +209 -38
  31. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  32. package/src/adapters/google-antigravity-replay.ts +173 -25
  33. package/src/adapters/google-antigravity-wire.ts +5 -0
  34. package/src/adapters/google-errors.ts +44 -12
  35. package/src/adapters/google.ts +136 -24
  36. package/src/adapters/image.ts +1 -1
  37. package/src/adapters/kiro-constants.ts +27 -0
  38. package/src/adapters/kiro-tools.ts +55 -16
  39. package/src/adapters/kiro.ts +182 -23
  40. package/src/adapters/openai-chat.ts +300 -242
  41. package/src/adapters/openai-responses.ts +353 -27
  42. package/src/adapters/run-turn-queue.ts +36 -1
  43. package/src/adapters/tool-catalog-nudge.ts +4 -3
  44. package/src/adapters/xai-tool-schema.ts +436 -0
  45. package/src/adapters/xai-web-search.ts +17 -16
  46. package/src/bridge.ts +90 -37
  47. package/src/chat/inbound.ts +14 -1
  48. package/src/chat/outbound.ts +15 -7
  49. package/src/claude/agents-inject.ts +8 -1
  50. package/src/claude/context-windows.ts +16 -9
  51. package/src/claude/outbound.ts +24 -11
  52. package/src/cli/access.ts +46 -3
  53. package/src/cli/account-api.ts +116 -19
  54. package/src/cli/account-extended.ts +262 -29
  55. package/src/cli/account-main.ts +12 -12
  56. package/src/cli/account.ts +69 -15
  57. package/src/cli/agent.ts +8 -1
  58. package/src/cli/alias.ts +66 -0
  59. package/src/cli/capabilities-command.ts +94 -0
  60. package/src/cli/capabilities.ts +496 -0
  61. package/src/cli/claude-desktop.ts +31 -11
  62. package/src/cli/claude.ts +26 -1
  63. package/src/cli/dispatch.ts +256 -28
  64. package/src/cli/doctor.ts +126 -14
  65. package/src/cli/help.ts +17 -6
  66. package/src/cli/index.ts +39 -9
  67. package/src/cli/init.ts +1 -0
  68. package/src/cli/inspect.ts +230 -0
  69. package/src/cli/models-runtime.ts +95 -0
  70. package/src/cli/models.ts +26 -10
  71. package/src/cli/observe.ts +31 -8
  72. package/src/cli/provider-runtime.ts +16 -2
  73. package/src/cli/provider.ts +2 -1
  74. package/src/cli/registry.ts +49 -10
  75. package/src/cli/runtime-api.ts +51 -7
  76. package/src/cli/status.ts +41 -1
  77. package/src/cli/storage.ts +234 -0
  78. package/src/cli/system-command.ts +16 -0
  79. package/src/cli/system-restart-client.ts +1 -1
  80. package/src/cli/usage-report.ts +184 -0
  81. package/src/cli/v2.ts +34 -10
  82. package/src/cli/version-skew.ts +46 -0
  83. package/src/clients/config-export.ts +85 -3
  84. package/src/clients/effective-status.ts +30 -2
  85. package/src/clients/probes/cline.ts +81 -0
  86. package/src/codex/account-label.ts +21 -0
  87. package/src/codex/account-pause.ts +2 -1
  88. package/src/codex/account-priority.ts +3 -2
  89. package/src/codex/app-server-processes.ts +83 -7
  90. package/src/codex/auth-api.ts +50 -10
  91. package/src/codex/auth-context.ts +21 -18
  92. package/src/codex/autostart-health.ts +16 -0
  93. package/src/codex/catalog/aggregation.ts +18 -0
  94. package/src/codex/catalog/effort.ts +18 -3
  95. package/src/codex/catalog/metadata.ts +27 -1
  96. package/src/codex/catalog/native-models.ts +5 -2
  97. package/src/codex/catalog/parsing.ts +72 -27
  98. package/src/codex/catalog/provider-fetch.ts +187 -31
  99. package/src/codex/catalog/sync.ts +131 -5
  100. package/src/codex/catalog.ts +1 -1
  101. package/src/codex/codex-write-lock.ts +3 -1
  102. package/src/codex/convergence-types.ts +1 -1
  103. package/src/codex/convergence.ts +27 -2
  104. package/src/codex/desired-state.ts +2 -2
  105. package/src/codex/desktop-app-restart.ts +355 -0
  106. package/src/codex/history-job.ts +32 -3
  107. package/src/codex/history-manifest.ts +112 -0
  108. package/src/codex/history-migration-guardian.ts +5 -5
  109. package/src/codex/history-provider.ts +825 -247
  110. package/src/codex/history-worker.ts +8 -5
  111. package/src/codex/inject-coordination.ts +83 -0
  112. package/src/codex/inject.ts +63 -22
  113. package/src/codex/injected-marker.ts +1 -1
  114. package/src/codex/internal/history-writer.ts +4 -3
  115. package/src/codex/log-guard/inspect.ts +22 -4
  116. package/src/codex/model-entitlements.ts +9 -2
  117. package/src/codex/native-profile-startup.ts +157 -27
  118. package/src/codex/native-residue.ts +26 -33
  119. package/src/codex/prompt-layers.ts +371 -25
  120. package/src/codex/prompt-text-probe.ts +238 -0
  121. package/src/codex/quota.ts +91 -10
  122. package/src/codex/routing.ts +9 -0
  123. package/src/codex/shim.ts +56 -3
  124. package/src/codex/subagent-model-fallback.ts +104 -25
  125. package/src/codex/transition-state.ts +116 -8
  126. package/src/combos/failover.ts +27 -0
  127. package/src/combos/types.ts +10 -0
  128. package/src/compatibility/index.ts +26 -0
  129. package/src/compatibility/manifest.ts +253 -0
  130. package/src/compatibility/openai-responses.ts +121 -0
  131. package/src/config/atomic-write.ts +219 -0
  132. package/src/config/paths.ts +40 -0
  133. package/src/config/process-state.ts +309 -0
  134. package/src/config/provider-validation.ts +177 -0
  135. package/src/config/rebase-provenance.ts +68 -0
  136. package/src/config.ts +206 -824
  137. package/src/generated/compatibility-version.json +373 -213
  138. package/src/images/loop.ts +5 -4
  139. package/src/images/plan.ts +5 -4
  140. package/src/integrations/ownership-policy.ts +160 -0
  141. package/src/integrations/ownership.ts +46 -2
  142. package/src/integrations/registry.ts +7 -0
  143. package/src/integrations/state.ts +79 -7
  144. package/src/integrations/writer.ts +26 -2
  145. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  146. package/src/lab/fabric/producer-child.ts +1 -1
  147. package/src/lib/admin-secrets.ts +24 -0
  148. package/src/lib/bounded-body.ts +14 -2
  149. package/src/lib/config-ownership.ts +20 -0
  150. package/src/lib/errors.ts +36 -3
  151. package/src/lib/package-tree-integrity.ts +101 -0
  152. package/src/lib/process-control.ts +2 -1
  153. package/src/lib/service-secrets.ts +15 -0
  154. package/src/lib/tool-argument-integers.ts +56 -5
  155. package/src/oauth/callback-server.ts +22 -2
  156. package/src/oauth/cline.ts +203 -0
  157. package/src/oauth/generic-account-failover.ts +231 -0
  158. package/src/oauth/health.ts +1 -1
  159. package/src/oauth/index.ts +65 -6
  160. package/src/oauth/key-providers.ts +10 -1
  161. package/src/oauth/kimi.ts +9 -1
  162. package/src/oauth/kiro.ts +45 -0
  163. package/src/oauth/local-token-detect.ts +2 -0
  164. package/src/oauth/open-browser-choice.ts +26 -0
  165. package/src/oauth/store.ts +14 -5
  166. package/src/oauth/types.ts +15 -0
  167. package/src/providers/auto-compact-budget.ts +65 -0
  168. package/src/providers/command-code-efforts.ts +38 -6
  169. package/src/providers/context-cap.ts +4 -3
  170. package/src/providers/default-aliases.ts +65 -0
  171. package/src/providers/derive.ts +28 -0
  172. package/src/providers/fastwire.ts +7 -1
  173. package/src/providers/label.ts +34 -1
  174. package/src/providers/model-presets.ts +119 -0
  175. package/src/providers/new-model-policy.ts +146 -0
  176. package/src/providers/provider-id-rewrite.ts +2 -1
  177. package/src/providers/quota.ts +65 -7
  178. package/src/providers/registry.ts +184 -69
  179. package/src/providers/slug-codec.ts +52 -0
  180. package/src/providers/xai-transport.ts +21 -0
  181. package/src/reasoning-effort.ts +19 -2
  182. package/src/responses/apply-patch-envelope.ts +63 -0
  183. package/src/responses/code-mode-helper-compat.ts +50 -0
  184. package/src/responses/custom-tool-compat.ts +157 -39
  185. package/src/responses/namespace-tool-compat.ts +84 -4
  186. package/src/responses/parser.ts +7 -2
  187. package/src/responses/reasoning-replay-cache.ts +81 -3
  188. package/src/responses/schema.ts +5 -1
  189. package/src/responses/state.ts +94 -4
  190. package/src/responses/thought-signature-replay.ts +17 -0
  191. package/src/responses/turn-termination.ts +107 -0
  192. package/src/router.ts +40 -2
  193. package/src/routing/analytics.ts +1 -0
  194. package/src/routing/quota.ts +10 -0
  195. package/src/server/auth-cors.ts +27 -7
  196. package/src/server/chat-completions.ts +24 -16
  197. package/src/server/chat-native-sse.ts +3 -3
  198. package/src/server/chat-native.ts +30 -11
  199. package/src/server/claude-messages.ts +1 -1
  200. package/src/server/index.ts +179 -32
  201. package/src/server/lifecycle.ts +52 -1
  202. package/src/server/local-management-read-client.ts +1 -1
  203. package/src/server/local-provider-reload-client.ts +1 -1
  204. package/src/server/management/agent-settings-routes.ts +45 -23
  205. package/src/server/management/codex-prompt-routes.ts +570 -0
  206. package/src/server/management/combo-routes.ts +2 -1
  207. package/src/server/management/config-routes.ts +59 -12
  208. package/src/server/management/context.ts +10 -1
  209. package/src/server/management/logs-usage-routes.ts +30 -24
  210. package/src/server/management/model-routes.ts +274 -4
  211. package/src/server/management/model-rows.ts +4 -0
  212. package/src/server/management/native-integration-routes.ts +2 -1
  213. package/src/server/management/oauth-account-routes.ts +10 -4
  214. package/src/server/management/provider-capability-config.ts +1 -1
  215. package/src/server/management/provider-routes.ts +81 -11
  216. package/src/server/management/route-registry.ts +332 -0
  217. package/src/server/management/routing-profile-routes.ts +2 -2
  218. package/src/server/management/system-restart.ts +1 -1
  219. package/src/server/management-api.ts +18 -16
  220. package/src/server/port-reclaim.ts +1 -1
  221. package/src/server/proxy-liveness.ts +29 -5
  222. package/src/server/relay-eager.ts +147 -20
  223. package/src/server/relay.ts +251 -19
  224. package/src/server/request-log-conversation.ts +51 -0
  225. package/src/server/request-log.ts +70 -16
  226. package/src/server/responses/codex-auth-error.ts +55 -0
  227. package/src/server/responses/collaboration.ts +37 -3
  228. package/src/server/responses/combo-stream-preflight.ts +178 -0
  229. package/src/server/responses/compact.ts +6 -21
  230. package/src/server/responses/core.ts +810 -208
  231. package/src/server/responses/empty-completion-guard.ts +63 -6
  232. package/src/server/responses/fetch-helpers.ts +17 -102
  233. package/src/server/responses/input-admission.ts +3 -1
  234. package/src/server/responses/passthrough-error.ts +33 -9
  235. package/src/server/responses/responses-field-backfill.ts +105 -13
  236. package/src/server/responses/ws-upstream.ts +75 -2
  237. package/src/server/responses-custom-tool-repair.ts +87 -6
  238. package/src/server/responses-terminal-repair.ts +25 -4
  239. package/src/server/responses-undeclared-tool-guard.ts +241 -18
  240. package/src/server/sse-frame-buffer.ts +31 -4
  241. package/src/server/ws-bridge.ts +14 -2
  242. package/src/service.ts +42 -4
  243. package/src/storage/policy-job.ts +14 -4
  244. package/src/storage/policy.ts +88 -23
  245. package/src/types/config.ts +66 -0
  246. package/src/types/provider.ts +69 -0
  247. package/src/types/request.ts +17 -4
  248. package/src/types/tools.ts +116 -11
  249. package/src/types.ts +3 -1
  250. package/src/update/index.ts +5 -4
  251. package/src/update/job.ts +3 -1
  252. package/src/update/transactional-install.mjs +8 -1
  253. package/src/usage/expected-prices.ts +43 -24
  254. package/src/usage/log.ts +63 -12
  255. package/src/usage/summary.ts +211 -8
  256. package/src/vision/describe.ts +18 -13
  257. package/src/web-search/exa-executor.ts +40 -9
  258. package/src/web-search/executor.ts +10 -3
  259. package/src/web-search/loop.ts +5 -4
  260. package/gui/dist/assets/index-BF38heuV.js +0 -104
  261. package/gui/dist/assets/index-DMiI18Kv.css +0 -1
@@ -31,6 +31,35 @@ export function namespacedToolName(namespace: string | undefined, name: string):
31
31
  return namespace ? `${namespace}__${name}` : name;
32
32
  }
33
33
 
34
+ /**
35
+ * Codex unified-exec name normalization.
36
+ *
37
+ * Codex's code-mode shell tool is declared as `exec` (a freeform custom tool whose own
38
+ * description mentions the nested `await tools.exec_command(...)` helper). Routed models —
39
+ * DeepSeek in particular — sometimes echo that helper name as the tool-call name, emitting
40
+ * `exec_command` or `apply_patch` instead of the declared `exec`. Accept these nested helper
41
+ * names only when the request catalog actually declares `exec` and does not itself declare the
42
+ * emitted name (an MCP server may legitimately advertise one under its own namespace).
43
+ */
44
+ const LEGACY_SHELL_BRIDGE_TOOL_NAMES = ["exec_command", "shell_command"] as const;
45
+ const CODE_MODE_HELPER_TOOL_NAMES = [...LEGACY_SHELL_BRIDGE_TOOL_NAMES, "apply_patch"] as const;
46
+
47
+ export function normalizeDeclaredToolName(
48
+ name: string,
49
+ declared: ReadonlySet<string> | undefined,
50
+ ): string {
51
+ if (!declared || !declared.has("exec")) return name;
52
+ if (declared.has(name)) return name;
53
+ if (name === "apply_patch") return "exec";
54
+ // When the catalog explicitly declares any legacy shell bridge name, the environment
55
+ // genuinely exposes that tool — turn normalization off so a call is never mis-routed
56
+ // to `exec`.
57
+ if ((LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy))) {
58
+ return name;
59
+ }
60
+ return (CODE_MODE_HELPER_TOOL_NAMES as readonly string[]).includes(name) ? "exec" : name;
61
+ }
62
+
34
63
  export function toolChoiceAliases(tool: Pick<OcxTool, "namespace" | "name">): string[] {
35
64
  const wireName = namespacedToolName(tool.namespace, tool.name);
36
65
  return tool.namespace ? [wireName, `${tool.namespace}.${tool.name}`] : [wireName];
@@ -43,6 +72,74 @@ function sameToolIdentity(
43
72
  return left.namespace === right.namespace && left.name === right.name;
44
73
  }
45
74
 
75
+ type ToolIdentity = Readonly<Pick<OcxTool, "namespace" | "name">>;
76
+
77
+ function snapshotToolIdentity(tool: Pick<OcxTool, "namespace" | "name">): ToolIdentity {
78
+ return Object.freeze({
79
+ name: tool.name,
80
+ ...(tool.namespace === undefined ? {} : { namespace: tool.namespace }),
81
+ });
82
+ }
83
+
84
+ function buildToolChoiceCatalog(
85
+ tools: readonly ToolIdentity[],
86
+ ): {
87
+ candidatesByName: ReadonlyMap<string, readonly ToolIdentity[]>;
88
+ sourceCandidatesByName: ReadonlyMap<string, readonly ToolIdentity[]>;
89
+ identitiesByTool: WeakMap<object, ToolIdentity>;
90
+ } {
91
+ const index = new Map<string, ToolIdentity[]>();
92
+ const sourceIndex = new Map<string, ToolIdentity[]>();
93
+ const identities = new Map<string, Set<string>>();
94
+ const identitiesByTool = new WeakMap<object, ToolIdentity>();
95
+ for (const tool of tools) {
96
+ const snapshot = snapshotToolIdentity(tool);
97
+ identitiesByTool.set(tool, snapshot);
98
+ const identity = JSON.stringify([snapshot.namespace ?? null, snapshot.name]);
99
+ for (const selector of [...toolChoiceAliases(snapshot), snapshot.name]) {
100
+ const candidates = index.get(selector);
101
+ if (!candidates) {
102
+ index.set(selector, [snapshot]);
103
+ sourceIndex.set(selector, [tool]);
104
+ identities.set(selector, new Set([identity]));
105
+ } else if (!identities.get(selector)!.has(identity)) {
106
+ candidates.push(snapshot);
107
+ sourceIndex.get(selector)!.push(tool);
108
+ identities.get(selector)!.add(identity);
109
+ }
110
+ }
111
+ }
112
+ return { candidatesByName: index, sourceCandidatesByName: sourceIndex, identitiesByTool };
113
+ }
114
+
115
+ /** Compile one immutable view of a request's tool catalog for repeated policy checks. */
116
+ export function createToolChoiceResolver(tools: readonly ToolIdentity[] | undefined) {
117
+ const compiled = tools ? buildToolChoiceCatalog(tools) : undefined;
118
+ const candidatesByName = compiled?.candidatesByName;
119
+ const snapshotFor = (tool: ToolIdentity): ToolIdentity | undefined => {
120
+ const snapshot = compiled?.identitiesByTool.get(tool);
121
+ return snapshot && sameToolIdentity(snapshot, tool) ? snapshot : undefined;
122
+ };
123
+ return {
124
+ candidates(name: string): ToolIdentity[] {
125
+ return (candidatesByName?.get(name) ?? []).map(candidate => ({ ...candidate }));
126
+ },
127
+ candidateCount(name: string): number {
128
+ return candidatesByName?.get(name)?.length ?? 0;
129
+ },
130
+ allows(tool: ToolIdentity, allowedTools: ReadonlySet<string>): boolean {
131
+ if (!candidatesByName) return toolChoiceAliases(tool).some(name => allowedTools.has(name));
132
+ const snapshot = snapshotFor(tool);
133
+ return snapshot ? toolAllowedByChoiceFromIndex(snapshot, allowedTools, candidatesByName) : false;
134
+ },
135
+ selects(tool: ToolIdentity, name: string): boolean {
136
+ const snapshot = snapshotFor(tool);
137
+ const candidates = candidatesByName?.get(name);
138
+ return !!snapshot && candidates?.length === 1 && sameToolIdentity(candidates[0], snapshot);
139
+ },
140
+ };
141
+ }
142
+
46
143
  /**
47
144
  * All tools that could be selected by one client-facing name. Bare logical names are included
48
145
  * here because they are a compatibility selector for namespaced tools, while wire and dotted
@@ -53,12 +150,7 @@ export function toolChoiceCandidates(
53
150
  name: string,
54
151
  ): Pick<OcxTool, "namespace" | "name">[] {
55
152
  if (!tools) return [];
56
- const candidates: Pick<OcxTool, "namespace" | "name">[] = [];
57
- for (const tool of tools) {
58
- if (tool.name !== name && !toolChoiceAliases(tool).includes(name)) continue;
59
- if (!candidates.some(candidate => sameToolIdentity(candidate, tool))) candidates.push(tool);
60
- }
61
- return candidates;
153
+ return [...(buildToolChoiceCatalog(tools).sourceCandidatesByName.get(name) ?? [])];
62
154
  }
63
155
 
64
156
  /**
@@ -72,10 +164,22 @@ export function toolAllowedByChoice(
72
164
  tools?: readonly Pick<OcxTool, "namespace" | "name">[],
73
165
  ): boolean {
74
166
  if (!tools) return toolChoiceAliases(tool).some(name => allowedTools.has(name));
167
+ return toolAllowedByChoiceFromIndex(
168
+ snapshotToolIdentity(tool),
169
+ allowedTools,
170
+ buildToolChoiceCatalog(tools).candidatesByName,
171
+ );
172
+ }
173
+
174
+ function toolAllowedByChoiceFromIndex(
175
+ tool: ToolIdentity,
176
+ allowedTools: ReadonlySet<string>,
177
+ candidatesByName: ReadonlyMap<string, readonly ToolIdentity[]>,
178
+ ): boolean {
75
179
  for (const name of [...toolChoiceAliases(tool), tool.name]) {
76
180
  if (!allowedTools.has(name)) continue;
77
- const candidates = toolChoiceCandidates(tools, name);
78
- if (candidates.length === 1 && sameToolIdentity(candidates[0], tool)) return true;
181
+ const candidates = candidatesByName.get(name);
182
+ if (candidates?.length === 1 && sameToolIdentity(candidates[0], tool)) return true;
79
183
  }
80
184
  return false;
81
185
  }
@@ -123,9 +227,10 @@ export function toolChoiceToolPredicate(
123
227
  if (choice === "none") return () => false;
124
228
  if (isAllowedToolChoice(choice)) {
125
229
  const allowed = new Set(choice.allowedTools);
126
- return tool => toolAllowedByChoice(tool, allowed, tools);
230
+ const resolver = createToolChoiceResolver(tools);
231
+ return tool => resolver.allows(tool, allowed);
127
232
  }
128
233
  if (!tools) return tool => toolChoiceAliases(tool).includes(choice.name);
129
- const candidates = toolChoiceCandidates(tools, choice.name);
130
- return tool => candidates.length === 1 && sameToolIdentity(candidates[0], tool);
234
+ const resolver = createToolChoiceResolver(tools);
235
+ return tool => resolver.selects(tool, choice.name);
131
236
  }
package/src/types.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  export type { OcxTool, OcxToolChoice } from "./types/tools";
5
5
  export {
6
6
  namespacedToolName,
7
+ normalizeDeclaredToolName,
7
8
  toolChoiceAliases,
9
+ createToolChoiceResolver,
8
10
  toolChoiceCandidates,
9
11
  toolAllowedByChoice,
10
12
  resolveToolChoiceWireName,
@@ -59,6 +61,7 @@ export type {
59
61
  OcxCustomModel,
60
62
  OcxApiKeyEntry,
61
63
  OcxClientIntegrationsConfig,
64
+ OcxConfigRebaseProvenance,
62
65
  OcxConfig,
63
66
  OcxAccountPoolRotationStrategy,
64
67
  OcxComboStrategy,
@@ -102,4 +105,3 @@ export type {
102
105
  CodexAccountCredentials,
103
106
  CodexAccountCredentialRecord,
104
107
  } from "./types/accounts";
105
-
@@ -2,7 +2,8 @@ import { spawn, spawnSync } from "node:child_process";
2
2
  import { readFileSync, readdirSync } from "node:fs";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { dirname, join } from "node:path";
5
- import { getConfigDir, loadConfig, readPid, readRuntimePort } from "../config";
5
+ import { getConfigDir, loadConfig } from "../config";
6
+ import { readPid, readRuntimePort } from "../config/process-state";
6
7
  import { npmInvocation } from "./npm-invocation.mjs";
7
8
  import {
8
9
  npmCachePreflightFailureMessage,
@@ -284,9 +285,9 @@ export async function runUpdate(): Promise<void> {
284
285
  }
285
286
  if (historyRestoreIncomplete()) {
286
287
  console.warn(
287
- "⚠️ Codex resume history was NOT restored (history DB locked — Codex app/IDE open?).\n" +
288
- " Your routed threads stay hidden in the native Codex app until restored.\n" +
289
- " After the update: close the Codex app, then run 'ocx stop' once to restore.",
288
+ "⚠️ Codex resume-history metadata restore is incomplete (a backup manifest remains).\n" +
289
+ " The DB may be busy or the manifest/target may need review; untracked routed history is intentionally unchanged.\n" +
290
+ " After the update: close the Codex app, run 'ocx doctor', then run 'ocx stop' once to retry.",
290
291
  );
291
292
  }
292
293
  }
package/src/update/job.ts CHANGED
@@ -6,12 +6,14 @@ import {
6
6
  atomicWriteFile,
7
7
  getConfigDir,
8
8
  loadConfig,
9
+ } from "../config";
10
+ import {
9
11
  readPid,
10
12
  readRuntimePort,
11
13
  removePid,
12
14
  removeRuntimePort,
13
15
  verifyPidIdentity,
14
- } from "../config";
16
+ } from "../config/process-state";
15
17
  import { isProcessAlive, killProxy } from "../lib/process-control";
16
18
  import { selfLaunchArgv } from "../lib/self-launch-argv";
17
19
  import { killWindowsSchedulerWrappers } from "../lib/windows-service-wrappers";
@@ -176,7 +176,14 @@ export function transactionalNpmUpdate({
176
176
  }
177
177
  const spec = pkgName + "@" + (targetVersion || tag);
178
178
  log("Staging " + spec + " into " + stageRoot);
179
- const install = runNpm(["install", "-g", "--prefix", stageRoot, "--no-audit", "--no-fund", spec]);
179
+ // npm 12 blocks lifecycle scripts by default. Bun's postinstall copies the selected
180
+ // @oven/bun-* executable into bun/bin, so a successful npm exit without this narrow
181
+ // approval leaves the staged tree intentionally incomplete. Allow only the package
182
+ // whose executable the manifest verifies below; never broaden this to all scripts.
183
+ const install = runNpm([
184
+ "install", "-g", "--prefix", stageRoot,
185
+ "--allow-scripts=bun", "--no-audit", "--no-fund", spec,
186
+ ]);
180
187
  if (install.status !== 0) {
181
188
  try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
182
189
  return { ok: false, phase: "stage", error: "npm staging install failed (" + (install.status ?? "?") + ")" };
@@ -39,7 +39,7 @@ export interface ExpectedPriceOverlay {
39
39
  }
40
40
 
41
41
  const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
42
- const GPT56_SOL: Cost4 = { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 };
42
+ const GPT56_SOL: Cost4 = { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 };
43
43
  /**
44
44
  * Daybreak aliases. `daybreak-*-latest` never appears in the pricing table itself — only its
45
45
  * current snapshot does — so these tuples are the snapshot's published rates and carry
@@ -126,6 +126,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
126
126
  // Daybreak aliases: priced as their current snapshots (red -> gpt-5.6-cyber,
127
127
  // blue -> gpt-5.6-sol). The alias ids carry no rows of their own upstream, hence
128
128
  // verified-derived. Blue deliberately reuses GPT56_SOL rather than duplicating the tuple.
129
+ // The `gpt-` selector is native to ChatGPT/Codex accounts. The bare aliases below belong to
130
+ // the separately billed API-key catalog; keep those namespaces disjoint so pricing metadata
131
+ // cannot make an unroutable provider/model pair look supported.
132
+ { provider: "openai", modelId: "gpt-daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
129
133
  { provider: "openai-apikey", modelId: "daybreak-red-latest", cost4: DAYBREAK_RED, source: `alias of gpt-5.6-cyber ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
130
134
  { provider: "openai-apikey", modelId: "daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
131
135
  { provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
@@ -223,6 +227,8 @@ export function findExpectedPriceOverlay(
223
227
  /** OpenAI Fast price multipliers retained as a compatibility export. */
224
228
  export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
225
229
  "gpt-5.6-sol": 2,
230
+ "gpt-daybreak-blue-latest": 2,
231
+ "daybreak-blue-latest": 2,
226
232
  // Post-price-cut Fast tables (https://openai.com/api-fast-mode/, 2026-08-05):
227
233
  // Terra 4/24/0.40/5 and Luna 0.40/2.40/0.04/0.50 are both 2× the corrected
228
234
  // standard tuples; the stale bases made these look like 1.6/0.4 (#907).
@@ -257,13 +263,19 @@ const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/pr
257
263
  */
258
264
  export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
259
265
  ...["openai", "openai-apikey"].flatMap(provider =>
260
- Object.entries(PRIORITY_MULTIPLIERS).map(([modelId, multiplier]): PriorityPricingRule => ({
261
- provider,
262
- modelId,
263
- multiplier,
264
- source: OPENAI_FAST_PRICING,
265
- verifiedAt: "2026-08-05",
266
- })),
266
+ Object.entries(PRIORITY_MULTIPLIERS)
267
+ // Daybreak's `gpt-` selector belongs to ChatGPT/Codex accounts; its bare selector belongs
268
+ // to the separately billed API-key catalog. All ordinary GPT ids remain valid on both.
269
+ .filter(([modelId]) => provider === "openai"
270
+ ? modelId !== "daybreak-blue-latest"
271
+ : modelId !== "gpt-daybreak-blue-latest")
272
+ .map(([modelId, multiplier]): PriorityPricingRule => ({
273
+ provider,
274
+ modelId,
275
+ multiplier,
276
+ source: OPENAI_FAST_PRICING,
277
+ verifiedAt: "2026-08-05",
278
+ })),
267
279
  ),
268
280
  ...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
269
281
  provider: "xai",
@@ -349,6 +361,29 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
349
361
  verifiedAt: "2026-08-03",
350
362
  })),
351
363
  ),
364
+ {
365
+ // The native Codex selector aliases gpt-5.6-sol and shares its 272k long-context tier.
366
+ provider: "openai",
367
+ modelId: "gpt-daybreak-blue-latest",
368
+ thresholdInputTokens: 272_000,
369
+ inclusive: false,
370
+ multiplier: OPENAI_LONG_CONTEXT,
371
+ confirmedPriorityRelation: "exclusive",
372
+ source: OPENAI_PRICING_DOC,
373
+ verifiedAt: "2026-08-11",
374
+ },
375
+ {
376
+ // The bare selector is the separately billed API-key alias. Daybreak Red has no tier row:
377
+ // the cyber snapshot's four long-context cells are all "-" on the pricing page.
378
+ provider: "openai-apikey",
379
+ modelId: "daybreak-blue-latest",
380
+ thresholdInputTokens: 272_000,
381
+ inclusive: false,
382
+ multiplier: OPENAI_LONG_CONTEXT,
383
+ confirmedPriorityRelation: "exclusive",
384
+ source: OPENAI_PRICING_DOC,
385
+ verifiedAt: "2026-08-11",
386
+ },
352
387
  {
353
388
  provider: "xai",
354
389
  modelId: "grok-4.5",
@@ -371,22 +406,6 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
371
406
  source: "https://docs.x.ai/developers/pricing",
372
407
  verifiedAt: "2026-08-18",
373
408
  },
374
- {
375
- // daybreak-blue-latest aliases gpt-5.6-sol, which publishes the full long-context row
376
- // ($10 / $1 / $12.50 / $45). Scoped to openai-apikey ON PURPOSE: Daybreak is not
377
- // routable on the Codex-login `openai` provider, so adding the alias to the shared
378
- // OPENAI_GPT56_CONTEXT_MODELS list (expanded across both providers above) would mint a
379
- // tier for a provider/model pair that cannot exist.
380
- // daybreak-red-latest has NO tier row: the cyber snapshot's four long-context cells are
381
- // all "-" on the pricing page (verified 2026-08-11).
382
- provider: "openai-apikey",
383
- modelId: "daybreak-blue-latest",
384
- thresholdInputTokens: 272_000,
385
- inclusive: false,
386
- multiplier: OPENAI_LONG_CONTEXT,
387
- source: OPENAI_PRICING_DOC,
388
- verifiedAt: "2026-08-11",
389
- },
390
409
  ...["minimax", "minimax-cn"].map((provider): ContextTier => ({
391
410
  provider,
392
411
  modelId: "MiniMax-M3",
package/src/usage/log.ts CHANGED
@@ -8,12 +8,33 @@ import { sanitizeLogMetadataString } from "../lib/redact";
8
8
  import { usageDisplayTotalTokens } from "./totals";
9
9
  import type { AttemptTierOutcome, OcxUsage } from "../types";
10
10
  import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
11
- import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
11
+ import { ACCOUNT_LOG_LABEL_RE, CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
12
12
 
13
13
  export type UsageStatus = "reported" | "unreported" | "unsupported" | "estimated";
14
- export type CodexUsageAccountLogLabel = "main" | `p${string}`;
14
+ /**
15
+ * A persisted account label: a Codex pool account (`main`/`p<hex6>`) or a non-Codex OAuth
16
+ * provider account (`o<hex6>`, #2699).
17
+ *
18
+ * The old name `CodexUsageAccountLogLabel` is kept as an alias because it is exported and used
19
+ * across modules; the two predicates below are what callers should choose between.
20
+ */
21
+ export type UsageAccountLogLabel = "main" | `p${string}` | `o${string}`;
22
+ export type CodexUsageAccountLogLabel = UsageAccountLogLabel;
23
+
24
+ /**
25
+ * Accepts EITHER label family. This is the predicate the persistence writers use, so widening
26
+ * it here is what stops six separate call sites from silently dropping an `o`-label -- including
27
+ * two in the live request path (`request-log.ts:972` and `:1187`).
28
+ *
29
+ * The name is unchanged deliberately: renaming it would touch every call site for no behavior,
30
+ * and the widened contract is what every one of those sites wanted.
31
+ */
32
+ export function isCodexUsageAccountLogLabel(value: unknown): value is UsageAccountLogLabel {
33
+ return value === "main" || (typeof value === "string" && ACCOUNT_LOG_LABEL_RE.test(value));
34
+ }
15
35
 
16
- export function isCodexUsageAccountLogLabel(value: unknown): value is CodexUsageAccountLogLabel {
36
+ /** Strictly a Codex pool label. Use when the Codex-only distinction actually matters. */
37
+ export function isCodexPoolAccountLogLabel(value: unknown): value is "main" | `p${string}` {
17
38
  return value === "main" || (typeof value === "string" && CODEX_ACCOUNT_LOG_LABEL_RE.test(value));
18
39
  }
19
40
 
@@ -28,6 +49,7 @@ export type AttemptRecoveryKind =
28
49
  | "key-429"
29
50
  | "rate-limit-429"
30
51
  | "anthropic-oauth-429"
52
+ | "oauth-account-429"
31
53
  | "image-413"
32
54
  | "opaque-blob-rejection"
33
55
  | "empty-completion";
@@ -50,6 +72,13 @@ export interface PersistedUsageAttempt {
50
72
  sendCount: number;
51
73
  recoveryKinds: AttemptRecoveryKind[];
52
74
  usageStatus: UsageStatus;
75
+ /**
76
+ * True when the proxy answered this turn locally and issued no upstream request. It travels on
77
+ * the attempt itself rather than as a `finishRequestAttempt` argument because that function is
78
+ * called from six places, and a new parameter would silently default to the wrong answer at any
79
+ * one of them that was missed. Absent on ordinary attempts so old rows keep their exact shape.
80
+ */
81
+ locallyAnswered?: boolean;
53
82
  /** Stable non-PII identity for the Codex pool account that served this attempt. */
54
83
  accountLogLabel?: CodexUsageAccountLogLabel;
55
84
  inputTokenEstimate?: number;
@@ -68,6 +97,7 @@ export interface PersistedUsageAttempt {
68
97
  }
69
98
 
70
99
  export interface PersistedUsageEntry {
100
+ requestedAlias?: string;
71
101
  requestId: string;
72
102
  timestamp: number;
73
103
  provider: string;
@@ -179,8 +209,20 @@ function isEstimatedUsageProvider(providerOrAdapter: string): boolean {
179
209
  || providerOrAdapter === "cursor" || providerOrAdapter.startsWith("cursor-");
180
210
  }
181
211
 
182
- export function usageForFinalLog(provider: string, usage: OcxUsage | undefined): OcxUsage | undefined {
212
+ export function usageForFinalLog(
213
+ provider: string,
214
+ usage: OcxUsage | undefined,
215
+ /**
216
+ * True when the proxy answered this turn locally and issued no upstream request. Such a turn's
217
+ * zero counts are EXACT, so the provider-wide estimated marking must not apply: Kiro and Cursor
218
+ * are marked estimated because their adapters can only guess a real inference's usage, and a
219
+ * turn with no inference has nothing to guess. Without this, a no-send turn is indistinguishable
220
+ * from a real one whose usage frame never arrived.
221
+ */
222
+ locallyAnswered = false,
223
+ ): OcxUsage | undefined {
183
224
  if (!usage) return undefined;
225
+ if (locallyAnswered) return usage;
184
226
  if (usage.estimated || isEstimatedUsageProvider(provider)) return { ...usage, estimated: true };
185
227
  return usage;
186
228
  }
@@ -218,6 +260,7 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
218
260
  "key-429",
219
261
  "rate-limit-429",
220
262
  "anthropic-oauth-429",
263
+ "oauth-account-429",
221
264
  "image-413",
222
265
  "opaque-blob-rejection",
223
266
  "empty-completion",
@@ -927,6 +970,10 @@ async function readUsageEntriesIncrementally(
927
970
  // A shrink means truncation or replacement-in-place; the retained rows may no
928
971
  // longer correspond to file contents, so refuse to extend them.
929
972
  if (size < retained.coveredThroughBytes) return null;
973
+ // Retained-state reuse is only an optimization. A burst larger than the configured
974
+ // window must re-anchor through the bounded full-tail reader instead of reading and
975
+ // parsing every byte appended since the previous poll.
976
+ if (size - retained.coveredThroughBytes > maxReadBytes) return null;
930
977
  // Verify the retained REGION is unchanged before anything is reused. Identity keeps
931
978
  // dev/ino/birthtime, and an append and an in-place rewrite both move mtime/ctime
932
979
  // forward, so only the bytes themselves settle it.
@@ -1138,6 +1185,14 @@ export async function readUsageEntriesForManagement(): Promise<PersistedUsageEnt
1138
1185
  return (await readUsageSnapshotForManagement()).entries;
1139
1186
  }
1140
1187
 
1188
+ /** Keep legacy optional fields permissive, but reject rows that cannot be safely attributed. */
1189
+ function normalizePersistedUsageRow(value: unknown): PersistedUsageEntry | undefined {
1190
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
1191
+ const row = value as Record<string, unknown>;
1192
+ if (typeof row.requestId !== "string" || typeof row.provider !== "string") return undefined;
1193
+ return normalizeUsageEntry(row as unknown as PersistedUsageEntry);
1194
+ }
1195
+
1141
1196
  export function readUsageEntries(): PersistedUsageEntry[] {
1142
1197
  const path = usageLogPath();
1143
1198
  if (!existsSync(path)) return [];
@@ -1146,10 +1201,8 @@ export function readUsageEntries(): PersistedUsageEntry[] {
1146
1201
  for (const line of lines) {
1147
1202
  if (!line.trim()) continue;
1148
1203
  try {
1149
- const parsed = JSON.parse(line) as PersistedUsageEntry;
1150
- if (parsed && typeof parsed === "object" && typeof parsed.requestId === "string") {
1151
- entries.push(normalizeUsageEntry(parsed));
1152
- }
1204
+ const parsed = normalizePersistedUsageRow(JSON.parse(line));
1205
+ if (parsed) entries.push(parsed);
1153
1206
  } catch {
1154
1207
  /* keep reading after a partially written or hand-edited line */
1155
1208
  }
@@ -1162,10 +1215,8 @@ function parseUsageLines(lines: string[]): PersistedUsageEntry[] {
1162
1215
  for (const line of lines) {
1163
1216
  if (!line.trim()) continue;
1164
1217
  try {
1165
- const parsed = JSON.parse(line) as PersistedUsageEntry;
1166
- if (parsed && typeof parsed === "object" && typeof parsed.requestId === "string") {
1167
- entries.push(normalizeUsageEntry(parsed));
1168
- }
1218
+ const parsed = normalizePersistedUsageRow(JSON.parse(line));
1219
+ if (parsed) entries.push(parsed);
1169
1220
  } catch {
1170
1221
  /* skip partial / hand-edited lines */
1171
1222
  }