@bitkyc08/opencodex 2.41.0 → 2.43.0-preview.20260906

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 (260) hide show
  1. package/AGENTS_INSTALL.md +2 -2
  2. package/README.md +31 -0
  3. package/bin/ocx.mjs +10 -1
  4. package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
  5. package/gui/dist/assets/index-VGeQEZ_v.js +112 -0
  6. package/gui/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/adapters/anthropic-image-codec.ts +304 -0
  9. package/src/adapters/anthropic-image-normalize.ts +8 -298
  10. package/src/adapters/anthropic.ts +30 -7
  11. package/src/adapters/command-code.ts +7 -5
  12. package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
  13. package/src/adapters/cursor/images.ts +36 -6
  14. package/src/adapters/cursor/live-transport.ts +7 -2
  15. package/src/adapters/cursor/native-exec-desktop.ts +2 -15
  16. package/src/adapters/cursor/protobuf-request.ts +54 -24
  17. package/src/adapters/cursor/tool-definitions.ts +5 -670
  18. package/src/adapters/cursor/tool-guidance.ts +236 -0
  19. package/src/adapters/cursor/tool-naming.ts +252 -0
  20. package/src/adapters/cursor/tool-schemas.ts +195 -0
  21. package/src/adapters/cursor/types.ts +6 -3
  22. package/src/adapters/exec-tool-result-normalize.ts +1 -1
  23. package/src/adapters/google-errors.ts +9 -1
  24. package/src/adapters/google.ts +1 -0
  25. package/src/adapters/identity.ts +8 -2
  26. package/src/adapters/kiro-calibration.ts +181 -0
  27. package/src/adapters/kiro.ts +135 -3
  28. package/src/adapters/openai-responses.ts +259 -29
  29. package/src/adapters/responses-code-mode.ts +59 -0
  30. package/src/adapters/tool-catalog-nudge.ts +1 -1
  31. package/src/adapters/xai-schema-analysis.ts +86 -0
  32. package/src/adapters/xai-tool-schema.ts +2 -87
  33. package/src/adapters/xai-web-search.ts +1 -1
  34. package/src/bridge.ts +47 -13
  35. package/src/chat/inbound.ts +11 -3
  36. package/src/claude/inbound-content-options.ts +60 -0
  37. package/src/claude/inbound-model-options.ts +142 -0
  38. package/src/claude/inbound-records.ts +7 -0
  39. package/src/claude/inbound.ts +10 -202
  40. package/src/claude/model-info.ts +45 -0
  41. package/src/cli/account-auth.ts +49 -9
  42. package/src/cli/account-extended.ts +7 -1
  43. package/src/cli/capabilities.ts +15 -4
  44. package/src/cli/claude.ts +232 -39
  45. package/src/cli/config-command.ts +9 -1
  46. package/src/cli/dispatch.ts +5 -1
  47. package/src/cli/doctor.ts +10 -0
  48. package/src/cli/effort.ts +372 -0
  49. package/src/cli/export-command.ts +3 -9
  50. package/src/cli/help.ts +1 -0
  51. package/src/cli/index.ts +13 -0
  52. package/src/cli/init.ts +4 -0
  53. package/src/cli/model-selection-guidance.ts +30 -0
  54. package/src/cli/models-runtime.ts +3 -2
  55. package/src/cli/models.ts +8 -3
  56. package/src/cli/observe.ts +3 -1
  57. package/src/cli/opencode.ts +4 -1
  58. package/src/cli/provider-runtime.ts +65 -0
  59. package/src/cli/provider.ts +8 -0
  60. package/src/cli/registry.ts +16 -2
  61. package/src/cli/runtime-api.ts +3 -1
  62. package/src/cli/star-prompt.ts +22 -6
  63. package/src/cli/status-probes.ts +168 -0
  64. package/src/cli/status.ts +5 -168
  65. package/src/clients/config-export/constants.ts +69 -0
  66. package/src/clients/config-export/contracts.ts +154 -0
  67. package/src/clients/config-export/dsh.ts +132 -0
  68. package/src/clients/config-export/fast-models.ts +29 -0
  69. package/src/clients/config-export/mcode.ts +83 -0
  70. package/src/clients/config-export/model-metadata.ts +108 -0
  71. package/src/clients/config-export/omp.ts +104 -0
  72. package/src/clients/config-export/zcode.ts +92 -0
  73. package/src/clients/config-export.ts +18 -710
  74. package/src/codex/account-lifecycle.ts +20 -3
  75. package/src/codex/account-usability.ts +2 -0
  76. package/src/codex/auth-api.ts +272 -32
  77. package/src/codex/auth-context.ts +328 -24
  78. package/src/codex/catalog/effort.ts +44 -5
  79. package/src/codex/catalog/metadata.ts +149 -14
  80. package/src/codex/catalog/native-models.ts +116 -4
  81. package/src/codex/catalog/parsing.ts +122 -8
  82. package/src/codex/catalog/provider-fetch.ts +154 -23
  83. package/src/codex/catalog/reserve.ts +52 -0
  84. package/src/codex/catalog/sync.ts +89 -16
  85. package/src/codex/catalog.ts +1 -1
  86. package/src/codex/convergence-types.ts +1 -0
  87. package/src/codex/convergence.ts +2 -0
  88. package/src/codex/data/upstream-models.json +169 -0
  89. package/src/codex/forward-transport-headers.ts +25 -0
  90. package/src/codex/inject.ts +99 -34
  91. package/src/codex/injected-marker.ts +30 -4
  92. package/src/codex/journal.ts +14 -0
  93. package/src/codex/legacy-config-keys.ts +68 -0
  94. package/src/codex/log-guard/inspect-schema.ts +137 -0
  95. package/src/codex/log-guard/inspect.ts +2 -134
  96. package/src/codex/loopback-target.ts +54 -0
  97. package/src/codex/main-account-cache.ts +63 -1
  98. package/src/codex/main-account-hard-lock.ts +52 -0
  99. package/src/codex/main-account.ts +3 -1
  100. package/src/codex/management-convergence.ts +3 -0
  101. package/src/codex/model-entitlements.ts +54 -4
  102. package/src/codex/project-config-warnings.ts +92 -2
  103. package/src/codex/prompt-layers/encoding.ts +80 -0
  104. package/src/codex/prompt-layers/paths.ts +54 -0
  105. package/src/codex/prompt-layers/revision.ts +55 -0
  106. package/src/codex/prompt-layers/toml-edit.ts +163 -0
  107. package/src/codex/prompt-layers/toml-read.ts +181 -0
  108. package/src/codex/prompt-layers.ts +14 -520
  109. package/src/codex/quota-auto-refresh-state.ts +16 -0
  110. package/src/codex/quota-auto-refresh.ts +219 -0
  111. package/src/codex/quota-types.ts +51 -0
  112. package/src/codex/quota.ts +252 -93
  113. package/src/codex/reserve-availability.ts +177 -0
  114. package/src/codex/routing.ts +28 -9
  115. package/src/codex/shim.ts +53 -11
  116. package/src/codex/subagent-model-fallback.ts +23 -3
  117. package/src/combos/failover.ts +125 -7
  118. package/src/combos/identifiers.ts +89 -0
  119. package/src/combos/index.ts +4 -0
  120. package/src/combos/resolve.ts +80 -9
  121. package/src/combos/types.ts +20 -93
  122. package/src/config/subagent-models.ts +24 -0
  123. package/src/config.ts +156 -13
  124. package/src/generated/compatibility-version.json +474 -178
  125. package/src/generated/model-metadata.ts +1 -1
  126. package/src/integrations/journal.ts +65 -4
  127. package/src/integrations/store.ts +5 -0
  128. package/src/lab/events/limits.ts +4 -0
  129. package/src/lib/destination-policy.ts +31 -2
  130. package/src/lib/errors.ts +39 -0
  131. package/src/lib/provider-outbound.ts +69 -3
  132. package/src/lib/proxy-env.ts +22 -0
  133. package/src/lib/redact-folding.ts +176 -0
  134. package/src/lib/redact.ts +2 -175
  135. package/src/lib/state-store-sweeper.ts +20 -6
  136. package/src/lib/token-estimate.ts +94 -27
  137. package/src/lib/windows-user-principal.ts +53 -5
  138. package/src/oauth/account-quota-rank.ts +40 -1
  139. package/src/oauth/anthropic-routing.ts +99 -3
  140. package/src/oauth/chatgpt-device.ts +187 -0
  141. package/src/oauth/chatgpt.ts +31 -4
  142. package/src/oauth/generic-account-failover.ts +36 -13
  143. package/src/oauth/index.ts +140 -29
  144. package/src/oauth/log.ts +3 -0
  145. package/src/oauth/login-cli.ts +5 -0
  146. package/src/oauth/meta-muse.ts +117 -15
  147. package/src/oauth/pool-settings-capability.ts +15 -4
  148. package/src/providers/api-keys.ts +8 -10
  149. package/src/providers/default-aliases.ts +39 -0
  150. package/src/providers/derive.ts +10 -2
  151. package/src/providers/fastwire.ts +36 -7
  152. package/src/providers/initial-model-selection-runtime.ts +90 -0
  153. package/src/providers/initial-model-selection.ts +120 -0
  154. package/src/providers/key-failover.ts +134 -54
  155. package/src/providers/key-store.ts +11 -1
  156. package/src/providers/label.ts +1 -1
  157. package/src/providers/model-discovery.ts +76 -0
  158. package/src/providers/model-rename-startup.ts +72 -8
  159. package/src/providers/muse-subscription-usage.ts +95 -0
  160. package/src/providers/openai-sidecar.ts +17 -5
  161. package/src/providers/openai-tiers-destination.ts +102 -0
  162. package/src/providers/openai-tiers.ts +2 -99
  163. package/src/providers/opencode-go-transport.ts +41 -0
  164. package/src/providers/quota-key-accounts.ts +141 -0
  165. package/src/providers/quota-types.ts +9 -0
  166. package/src/providers/quota.ts +625 -98
  167. package/src/providers/registry.ts +60 -17
  168. package/src/providers/xai-responses-opt-in.ts +31 -5
  169. package/src/quota/reset-activation.ts +81 -0
  170. package/src/quota/reset-detector.ts +305 -0
  171. package/src/quota/reset-notify-config.ts +162 -0
  172. package/src/quota/reset-observer.ts +125 -0
  173. package/src/quota/reset-poller.ts +160 -0
  174. package/src/quota/reset-seen-store.ts +385 -0
  175. package/src/quota/reset-sinks.ts +199 -0
  176. package/src/quota/window-mapping.ts +106 -0
  177. package/src/responses/apply-patch-envelope.ts +46 -0
  178. package/src/responses/code-mode-helper-compat.ts +39 -1
  179. package/src/responses/custom-tool-compat.ts +10 -4
  180. package/src/responses/hosted-tool-policy.ts +12 -4
  181. package/src/responses/parser-content.ts +133 -0
  182. package/src/responses/parser-text-format.ts +24 -0
  183. package/src/responses/parser-tools.ts +188 -0
  184. package/src/responses/parser.ts +3 -326
  185. package/src/responses/state.ts +124 -28
  186. package/src/router.ts +48 -13
  187. package/src/routing/analytics.ts +1 -0
  188. package/src/routing/capability.ts +17 -4
  189. package/src/server/auth-cors.ts +7 -1
  190. package/src/server/background-lifecycle.ts +23 -1
  191. package/src/server/chat-completions.ts +25 -3
  192. package/src/server/claude-messages.ts +62 -5
  193. package/src/server/effort-row.ts +1 -1
  194. package/src/server/fast-row.ts +295 -0
  195. package/src/server/gui-static.ts +30 -4
  196. package/src/server/index.ts +122 -28
  197. package/src/server/live.ts +18 -4
  198. package/src/server/management/agent-settings-routes.ts +2 -2
  199. package/src/server/management/combo-routes.ts +37 -9
  200. package/src/server/management/config-routes.ts +93 -2
  201. package/src/server/management/integration-routes.ts +108 -0
  202. package/src/server/management/model-routes.ts +13 -3
  203. package/src/server/management/model-rows.ts +20 -1
  204. package/src/server/management/native-integration-routes.ts +4 -1
  205. package/src/server/management/oauth-account-routes.ts +45 -10
  206. package/src/server/management/provider-routes.ts +34 -3
  207. package/src/server/management/quota-reset-routes.ts +57 -0
  208. package/src/server/management/route-registry.ts +7 -4
  209. package/src/server/management/shared.ts +19 -5
  210. package/src/server/management/system-routes.ts +3 -2
  211. package/src/server/management-api.ts +14 -2
  212. package/src/server/ports.ts +12 -2
  213. package/src/server/relay-eager.ts +38 -23
  214. package/src/server/relay.ts +4 -0
  215. package/src/server/request-log.ts +6 -0
  216. package/src/server/responses/codex-ws-correlation.ts +65 -0
  217. package/src/server/responses/codex-ws-exchange.ts +261 -0
  218. package/src/server/responses/codex-ws-metadata.ts +134 -0
  219. package/src/server/responses/codex-ws-pool.ts +162 -0
  220. package/src/server/responses/codex-ws-request.ts +87 -0
  221. package/src/server/responses/codex-ws-session.ts +93 -0
  222. package/src/server/responses/codex-ws-wire.ts +144 -0
  223. package/src/server/responses/collaboration.ts +41 -1
  224. package/src/server/responses/compact.ts +105 -12
  225. package/src/server/responses/core.ts +510 -57
  226. package/src/server/responses/empty-completion-guard.ts +4 -0
  227. package/src/server/responses/fetch-helpers.ts +10 -3
  228. package/src/server/responses/input-admission.ts +16 -9
  229. package/src/server/responses/responses-field-backfill.ts +1 -1
  230. package/src/server/responses/ws-upstream.ts +34 -318
  231. package/src/server/responses-custom-tool-repair.ts +20 -4
  232. package/src/server/responses-undeclared-tool-guard.ts +100 -8
  233. package/src/server/safe-response-headers.ts +23 -0
  234. package/src/server/search.ts +9 -0
  235. package/src/server/subagent-models-startup.ts +27 -0
  236. package/src/server/system-env-shell.ts +238 -0
  237. package/src/server/system-env.ts +7 -234
  238. package/src/server/ws-bridge.ts +3 -25
  239. package/src/server/xai-responses-startup.ts +21 -0
  240. package/src/service-manager-probe.ts +1 -1
  241. package/src/service.ts +55 -16
  242. package/src/types/config.ts +108 -12
  243. package/src/types/provider.ts +36 -7
  244. package/src/types/request.ts +8 -0
  245. package/src/types/tools.ts +26 -1
  246. package/src/types.ts +2 -0
  247. package/src/update/notify.ts +8 -2
  248. package/src/usage/cost.ts +38 -28
  249. package/src/usage/expected-prices.ts +34 -15
  250. package/src/usage/log.ts +2 -0
  251. package/src/usage/model-identity.ts +26 -0
  252. package/src/usage/summary.ts +15 -1
  253. package/src/vision/describe.ts +6 -0
  254. package/src/vision/image-rewrite.ts +108 -0
  255. package/src/vision/index.ts +19 -306
  256. package/src/vision/plan.ts +205 -0
  257. package/src/web-search/executor.ts +6 -0
  258. package/src/web-search/index.ts +8 -1
  259. package/gui/dist/assets/index-B2YjLA-i.css +0 -1
  260. package/gui/dist/assets/index-aPup8CKb.js +0 -112
@@ -0,0 +1,195 @@
1
+ import type { OcxTool } from "../../types";
2
+ import { CODEX_SHELL_COMMAND_TOOL, isBareCodexExecCommandTool, isBareCodexShellBridgeTool, isCodexShellBridgeToolName } from "./tool-naming";
3
+
4
+ export const CURSOR_EXEC_COMMAND_INPUT_SCHEMA = {
5
+ type: "object",
6
+ properties: {
7
+ cmd: { type: "string", description: "Shell command to execute." },
8
+ workdir: { type: "string", description: "Working directory for the command. Defaults to the turn cwd." },
9
+ shell: { type: "string", description: "Shell binary to launch. Defaults to the user's default shell." },
10
+ tty: { type: "boolean", description: "True allocates a PTY for the command; false or omitted uses plain pipes." },
11
+ yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms." },
12
+ max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy." },
13
+ },
14
+ required: ["cmd"],
15
+ additionalProperties: false,
16
+ } as const;
17
+
18
+ /**
19
+ * Structured single-replacement schema advertised to Cursor models in addition to the freeform
20
+ * `apply_patch` tool. Cursor-trained models reliably emit exact-match replacements (the native
21
+ * Edit shape) but cannot produce Codex's freeform patch grammar, so every file edit attempt on the
22
+ * Cursor route produced malformed `apply_patch` payloads that the Codex client rejected locally
23
+ * (#1017). Calls to this tool are converted server-side into a valid apply_patch payload.
24
+ */
25
+ export const CURSOR_EDIT_FILE_INPUT_SCHEMA = {
26
+ type: "object",
27
+ properties: {
28
+ file_path: { type: "string", description: "Path of the file to edit, relative to the workspace root." },
29
+ old_string: { type: "string", description: "Exact text to replace. Must match the current file content, including line breaks." },
30
+ new_string: { type: "string", description: "Replacement text. Empty removes the matched text." },
31
+ },
32
+ required: ["file_path", "old_string", "new_string"],
33
+ additionalProperties: false,
34
+ } as const;
35
+
36
+ /** Structured multi-replacement schema; mirrors Cursor's native MultiEdit shape. */
37
+ export const CURSOR_MULTI_EDIT_INPUT_SCHEMA = {
38
+ type: "object",
39
+ properties: {
40
+ file_path: { type: "string", description: "Path of the file to edit, relative to the workspace root." },
41
+ edits: {
42
+ type: "array",
43
+ items: {
44
+ type: "object",
45
+ properties: {
46
+ old_string: { type: "string", description: "Exact text to replace. Must match the current file content, including line breaks." },
47
+ new_string: { type: "string", description: "Replacement text. Empty removes the matched text." },
48
+ },
49
+ required: ["old_string", "new_string"],
50
+ additionalProperties: false,
51
+ },
52
+ description: "Ordered replacement edits for this file. Each old_string must match the current file content.",
53
+ },
54
+ },
55
+ required: ["file_path", "edits"],
56
+ additionalProperties: false,
57
+ } as const;
58
+
59
+ /**
60
+ * Responses/Codex-side schema used ONLY for arg-key normalization after Cursor returns a call.
61
+ * Cursor models are trained to emit `cmd`; Codex `shell_command` / `exec_command` validate
62
+ * `command`. Keeping `cmd` out of this schema lets `normalizeArgKeys` rewrite `cmd` → `command`.
63
+ */
64
+ export const CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA = {
65
+ type: "object",
66
+ properties: {
67
+ command: { type: "string", description: "Shell command to execute." },
68
+ workdir: { type: "string", description: "Working directory for the command. Defaults to the turn cwd." },
69
+ shell: { type: "string", description: "Shell binary to launch. Defaults to the user's default shell." },
70
+ tty: { type: "boolean", description: "True allocates a PTY for the command; false or omitted uses plain pipes." },
71
+ yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms." },
72
+ max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy." },
73
+ max_output_chars: { type: "number", description: "Output character budget when the Responses tool uses chars instead of tokens." },
74
+ },
75
+ required: ["command"],
76
+ } as const;
77
+
78
+
79
+ /** Schema advertised to Cursor for this tool (may use Cursor-preferred field names like `cmd`). */
80
+ export function cursorToolInputSchema(tool: OcxTool): unknown {
81
+ return isBareCodexExecCommandTool(tool) ? CURSOR_EXEC_COMMAND_INPUT_SCHEMA : (tool.parameters ?? {});
82
+ }
83
+
84
+ /**
85
+ * Schema used to normalize completed Cursor tool args back to Responses/Codex field names.
86
+ * Must NOT reuse `cursorToolInputSchema` for the shell bridge: advertising `cmd` while also
87
+ * treating `cmd` as canonical prevents the `cmd` → `command` rewrite Codex requires (#399).
88
+ */
89
+ export function cursorToolArgNormalizeSchema(tool: OcxTool): unknown {
90
+ if (isBareCodexShellBridgeTool(tool)) {
91
+ return shellBridgeArgNormalizeSchema(tool);
92
+ }
93
+ return tool.parameters ?? {};
94
+ }
95
+
96
+ function shellBridgeArgNormalizeSchema(tool: OcxTool): unknown {
97
+ const parameters = tool.parameters;
98
+ if (!parameters || typeof parameters !== "object") return CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA;
99
+ const base = parameters as Record<string, unknown>;
100
+ const rawProps = base.properties && typeof base.properties === "object"
101
+ ? { ...(base.properties as Record<string, unknown>) }
102
+ : {};
103
+ const required = Array.isArray(base.required) ? [...base.required as unknown[]] : [];
104
+ const requiresCommand = required.includes("command") || "command" in rawProps;
105
+ const requiresCmd = required.includes("cmd") || "cmd" in rawProps;
106
+ const shouldRewriteCmdToCommand = tool.name === CODEX_SHELL_COMMAND_TOOL || requiresCommand;
107
+
108
+ if (!shouldRewriteCmdToCommand && requiresCmd) {
109
+ return parameters;
110
+ }
111
+
112
+ // Drop Cursor-preferred aliases so normalizeArgKeys can rewrite them to Responses keys.
113
+ delete rawProps.cmd;
114
+ const properties = {
115
+ ...CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA.properties,
116
+ ...rawProps,
117
+ command: rawProps.command ?? CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA.properties.command,
118
+ };
119
+ return {
120
+ ...base,
121
+ type: "object",
122
+ properties,
123
+ required: requiresCommand ? required : ["command"],
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Required command payload keys for a shell bridge tool, derived from the advertised schema when present.
129
+ */
130
+ export function shellBridgeRequiredCommandKeys(
131
+ toolName: string,
132
+ schema?: unknown,
133
+ ): readonly ("cmd" | "command")[] {
134
+ if (schema && typeof schema === "object") {
135
+ const required = (schema as Record<string, unknown>).required;
136
+ if (Array.isArray(required)) {
137
+ const keys = required.filter((key): key is "cmd" | "command" => key === "cmd" || key === "command");
138
+ if (keys.length > 0) return keys;
139
+ }
140
+ }
141
+ return toolName === CODEX_SHELL_COMMAND_TOOL ? ["command"] : ["cmd"];
142
+ }
143
+
144
+ /** Normalize-schema defaults used when validating stateless synthetic shell-bridge calls. */
145
+ export function defaultShellBridgeArgNormalizeSchema(toolName: string): unknown {
146
+ return toolName === CODEX_SHELL_COMMAND_TOOL
147
+ ? CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA
148
+ : {
149
+ type: "object",
150
+ properties: CURSOR_EXEC_COMMAND_INPUT_SCHEMA.properties,
151
+ required: ["cmd"],
152
+ };
153
+ }
154
+
155
+ export function cursorShellBridgeDropError(toolName: string): string {
156
+ return `Cursor emitted ${toolName} without a non-empty command; the tool call was dropped.`;
157
+ }
158
+
159
+ /**
160
+ * Extract a non-empty shell command from completed Cursor bridge args using the schema's required
161
+ * command key (`cmd` for bare exec_command, `command` for shell_command).
162
+ */
163
+ export function nonEmptyShellBridgeCommandFromArgs(
164
+ finalArgs: string,
165
+ toolName: string,
166
+ schema?: unknown,
167
+ ): string | undefined {
168
+ let parsed: unknown;
169
+ try {
170
+ parsed = finalArgs.length > 0 ? JSON.parse(finalArgs) : {};
171
+ } catch {
172
+ return undefined;
173
+ }
174
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return undefined;
175
+ const record = parsed as Record<string, unknown>;
176
+ const requiredKeys = shellBridgeRequiredCommandKeys(toolName, schema);
177
+ const candidateKeys = new Set<"cmd" | "command">([
178
+ ...requiredKeys,
179
+ requiredKeys.includes("cmd") ? "command" : "cmd",
180
+ ]);
181
+ for (const key of candidateKeys) {
182
+ const value = record[key];
183
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
184
+ }
185
+ return undefined;
186
+ }
187
+
188
+ export function cursorShellBridgeArgsValid(
189
+ finalArgs: string,
190
+ toolName: string,
191
+ schema?: unknown,
192
+ ): boolean {
193
+ return !isCodexShellBridgeToolName(toolName)
194
+ || nonEmptyShellBridgeCommandFromArgs(finalArgs, toolName, schema) !== undefined;
195
+ }
@@ -31,7 +31,7 @@ export interface CursorRunRequest {
31
31
  /**
32
32
  * Corrective active-turn text for the single envelope-echo retry (devlog 260826 gap-10).
33
33
  * When set on an external tool-result continuation, buildPreparedCursorRunRequest uses it as
34
- * the userMessageAction text instead of the standard continuation text; rawMessages stay
34
+ * the userMessageAction prefix instead of the standard continuation text; rawMessages stay
35
35
  * untouched so history replay is unchanged.
36
36
  */
37
37
  echoRetryContinuationText?: string;
@@ -40,9 +40,12 @@ export interface CursorRunRequest {
40
40
  messages: CursorRequestMessage[];
41
41
  rawMessages?: readonly OcxMessage[];
42
42
  /**
43
- * Images for the active user/developer turn. Encoded as SelectedImage blobIdWithData refs under
43
+ * Images for the active user/developer turn or an external model's trailing tool-result run.
44
+ * Encoded as SelectedImage blobIdWithData refs under
44
45
  * UserMessage.selected_context (bytes live in the request-scoped KV store for getBlobArgs
45
- * hydration). History stays text-only. data: URLs only in this slice.
46
+ * hydration). Bounded sourceLabel metadata from tool images is appended to the active action,
47
+ * outside prunable history; it is not a new image wire field. Native Composer selection stays
48
+ * unchanged. History stays text-only. data: URLs only in this slice.
46
49
  */
47
50
  selectedImages?: readonly ResolvedCursorImage[];
48
51
  tools?: OcxTool[];
@@ -113,7 +113,7 @@ export const EMPTY_EXEC_OUTPUT_MESSAGE =
113
113
  * that drifts apart is how a model gets told two different things about the same isolate.
114
114
  */
115
115
  export const CODE_MODE_RESULT_ECHO_SENTENCE =
116
- "Nothing in the isolate is echoed automatically: a bare trailing `await tools.<name>(...)` or final expression value is DISCARDED, and the cell reports empty output. Pass anything you need to read to `text(...)` (or `notify(...)`) in the same cell — for example `text(JSON.stringify(await tools.exec_command({cmd: \"ls\"})))` — and treat an empty result as your own missing `text(...)` call rather than a failed command or lost context.";
116
+ "Nothing in the isolate is echoed automatically: a bare trailing `await tools.<name>(...)` or final expression value is DISCARDED, and the cell reports empty output. Pass anything you need to read to `text(...)` (or `notify(...)`) in the same cell — for example `text(JSON.stringify(await tools.exec_command({cmd: 'ls'})))` — and treat an empty result as your own missing `text(...)` call rather than a failed command or lost context.";
117
117
 
118
118
  /**
119
119
  * Codex exec / shell-bridge tool names (flat and MCP-prefixed display aliases). An empty result
@@ -1,4 +1,5 @@
1
1
  import { parseUpstreamJsonPayload, safeUpstreamErrorString, sanitizeUpstreamErrorText } from "./upstream-http-error";
2
+ import { isLocationUnsupportedMessage } from "../lib/errors";
2
3
 
3
4
  /** Pull the human detail out of the Google API error envelope `{error:{message,status,code}}`. */
4
5
  function googleErrorDetail(payloadText: string): { message?: string; status?: string } {
@@ -65,9 +66,16 @@ function classifyGoogle(label: string, status: number | undefined, enumStatus: s
65
66
  if (status === 401 || enumStatus === "UNAUTHENTICATED" || lower.includes("unauthenticated") || lower.includes("invalid authentication") || lower.includes("expired")) {
66
67
  return `${label} authentication failed`;
67
68
  }
68
- if (status === 403 || enumStatus === "PERMISSION_DENIED" || lower.includes("permission denied") || lower.includes("access denied")) {
69
+ if (status === 403 || enumStatus === "PERMISSION_DENIED" || lower.includes("permission_denied") || lower.includes("permission denied") || lower.includes("access denied")) {
69
70
  return `${label} access denied`;
70
71
  }
72
+ // Google rejects unsupported geographic / datacenter locations with HTTP 400
73
+ // FAILED_PRECONDITION. The payload is not malformed, so it must not fall through to
74
+ // "invalid request" (#3467). Only the observed 400/precondition envelope permits
75
+ // this inference; other explicit enums and server statuses remain authoritative.
76
+ if (status === 400 && (!enumStatus || enumStatus === "FAILED_PRECONDITION") && isLocationUnsupportedMessage(lower)) {
77
+ return `${label} location not supported`;
78
+ }
71
79
  if (status === 503 || enumStatus === "UNAVAILABLE" || lower.includes("overloaded") || lower.includes("unavailable")) {
72
80
  return `${label} server overloaded`;
73
81
  }
@@ -52,6 +52,7 @@ const GOOGLE_BREVITY_INSTRUCTION = [
52
52
  "- Do detailed reasoning internally, not as visible intermediate output.",
53
53
  "- Prefer taking the next tool action over explaining; keep calling tools until the task is complete.",
54
54
  "- This applies only to intermediate progress text. Your final answer after the work is done is exempt: write it in full and at whatever length the task requires.",
55
+ "- Formatting: The client environment renders standard Markdown and does not support LaTeX math delimiters ($...$, $$...$$, \\(...\\), \\[...\\]). Do not use LaTeX math delimiters or LaTeX markup (such as \\text{}, \\times, \\le, \\ge, etc.) for variables, formulas, dimensions, or units. Use clean plain text, Markdown, and Unicode symbols (e.g. 180°, 2560 × 1920 px, ≤, ≥, Δ, ±) instead.",
55
56
  ].join("\n");
56
57
 
57
58
  const ANTIGRAVITY_REJECTED_CLAUDE_SDK_PARAGRAPH =
@@ -22,11 +22,17 @@ export const CODEX_GPT5_IDENTITY_LINE = "You are Codex, a coding agent based on
22
22
  export const CODEX_GPT5_IDENTITY_LINE_AGENT = "You are Codex, an agent based on GPT-5.";
23
23
 
24
24
  /**
25
- * Known Codex GPT-5 identity sentences. Narrow: only "coding agent" / "an agent" + GPT-5(.x)?
25
+ * Known Codex identity sentences. Narrow: only "coding agent" / "an agent" + GPT-<major>(.minor)*.
26
26
  * Avoid a broad `You are Codex.*` rewrite that could touch unrelated content.
27
+ *
28
+ * The major version is a wildcard because Codex writes the CURRENT generation into this line and
29
+ * bumps it: `gpt-6-astra` (upstream #42607) ships "You are Codex, an agent based on GPT-6.".
30
+ * Pinning `GPT-5` meant a GPT-6-era prompt routed to a third-party provider kept telling that
31
+ * model it was Codex-on-GPT-6 — the exact misattribution this chokepoint exists to remove, silently
32
+ * reintroduced by a version bump.
27
33
  */
28
34
  const CODEX_GPT5_IDENTITY_RE =
29
- /You are Codex, (?:a coding agent|an agent) based on GPT-5(?:\.[0-9]+)*\./g;
35
+ /You are Codex, (?:a coding agent|an agent) based on GPT-[0-9]+(?:\.[0-9]+)*\./g;
30
36
 
31
37
  /** Proxy-neutral replacement: no "opencodex proxy" mention, just the GPT-5/OpenAI disclaimer. */
32
38
  export const NEUTRAL_IDENTITY_LINE = "You are a coding agent. Do not claim to be GPT-5 or to be made by OpenAI.";
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Per-conversation calibration of the Kiro input estimate.
3
+ *
4
+ * The estimator in `lib/token-estimate` is a fixed heuristic: constants derived from recorded
5
+ * traffic, applied to every account and every conversation identically. It is close on average
6
+ * and necessarily wrong in the particular, because how densely a prompt tokenizes depends on
7
+ * what is in it — a Korean design discussion and a repository of minified JSON do not share a
8
+ * ratio, and no constant can serve both.
9
+ *
10
+ * Kiro, however, tells us the answer. Mid-stream it reports `contextUsagePercentage`, which
11
+ * against a known window is an authoritative token count for the exact payload just sent. Today
12
+ * that number is used once, as a floor under the current turn's total, and then discarded. The
13
+ * conversation's own measured tokens-per-character is therefore recomputed from scratch, and
14
+ * mispredicted the same way, on every single turn.
15
+ *
16
+ * This module keeps it. After a TERMINAL attempt that produced both an estimate and a reported
17
+ * percentage, the realised ratio `charged / estimated` is folded into a per-conversation
18
+ * correction and applied to the next turn of that conversation, so a long conversation converges
19
+ * on the rate it actually exhibits rather than the rate the average conversation exhibits.
20
+ *
21
+ * "Terminal" matters: the adapter's bounded completion retry streams a second time for the same
22
+ * user turn against a rebuilt payload, so the caller commits an observation only once that turn
23
+ * is genuinely over.
24
+ *
25
+ * Four properties keep this from being able to make the gauge worse:
26
+ *
27
+ * - **Bounded.** The factor is clamped, so a single anomalous reading cannot distort the
28
+ * estimate by an unbounded amount, and a malformed or hostile percentage cannot either.
29
+ * - **Smoothed.** Each observation moves the factor part of the way rather than replacing it, so
30
+ * one cache-affected or truncated turn cannot swing the gauge.
31
+ * - **Bounded in memory.** Conversation-scoped with an eviction cap and no persistence. This is
32
+ * a hint that improves with use, not state anything depends on.
33
+ * - **Subordinate to the truth.** The existing upstream floor is untouched. Calibration only
34
+ * sharpens the estimate BEFORE upstream reports; it can never lower a value upstream has
35
+ * already justified.
36
+ *
37
+ * Deliberately not in `lib/token-estimate`: that module is pure, shared by every provider, and
38
+ * must stay so. This is Kiro-specific state and lives with the Kiro adapter.
39
+ */
40
+
41
+ /**
42
+ * Clamp for the correction factor.
43
+ *
44
+ * Wide enough to absorb the genuine spread between conversation kinds, narrow enough that a
45
+ * wrong reading cannot produce a nonsensical gauge. A factor below 1 shrinks the estimate, so
46
+ * the lower bound is the more dangerous side and is kept nearer 1.
47
+ */
48
+ const MIN_FACTOR = 0.7;
49
+ const MAX_FACTOR = 3;
50
+
51
+ /**
52
+ * Weight given to a new observation. 0.35 reaches most of the way to a persistent new rate
53
+ * within a few turns while still requiring more than one reading to move far.
54
+ */
55
+ const SMOOTHING = 0.35;
56
+
57
+ /**
58
+ * Maximum conversations tracked. Entries are small, but the map must not grow with uptime.
59
+ *
60
+ * Eviction is least-recently-USED, not oldest-inserted: `touch` re-inserts on every estimate and
61
+ * every observation, so an active long conversation survives an arbitrary number of short ones
62
+ * started after it. Insertion order would have evicted exactly the conversation most worth
63
+ * keeping.
64
+ */
65
+ const MAX_TRACKED_CONVERSATIONS = 256;
66
+
67
+ /**
68
+ * A ratio this far from 1 is not a mis-calibrated estimate, it is a different measurement:
69
+ * a compaction, a cache boundary, or a percentage reported against a window we did not expect.
70
+ * Learning from it would teach the wrong lesson, so it is ignored entirely.
71
+ */
72
+ const MAX_PLAUSIBLE_OBSERVATION = 6;
73
+
74
+ /**
75
+ * One conversation's calibration state.
76
+ *
77
+ * `factor` is the learned correction. `rawEstimate` is the uncorrected estimate most recently
78
+ * applied: the stream records what upstream charged, but by then the value it holds has already
79
+ * been corrected, and learning from that would make the factor measure its own residual error
80
+ * instead of the heuristic's — each round would close only the part it had not yet fixed and the
81
+ * factor would converge short of the true ratio.
82
+ *
83
+ * Both live in ONE entry so they cannot desynchronise. Held as two maps, a conversation could be
84
+ * refreshed in one and evicted from the other, and the pair could retain twice the documented
85
+ * bound in distinct ids.
86
+ */
87
+ interface Calibration {
88
+ factor?: number;
89
+ rawEstimate?: number;
90
+ }
91
+
92
+ /** Insertion-ordered, so the first key is the least recently touched entry. */
93
+ const calibrations = new Map<string, Calibration>();
94
+
95
+ /** Re-insert to move an entry to the most-recently-used end, then evict from the old end. */
96
+ function touch(conversationId: string, update: (current: Calibration) => Calibration): void {
97
+ const current = calibrations.get(conversationId) ?? {};
98
+ calibrations.delete(conversationId);
99
+ calibrations.set(conversationId, update(current));
100
+ while (calibrations.size > MAX_TRACKED_CONVERSATIONS) {
101
+ const oldest = calibrations.keys().next();
102
+ if (oldest.done) break;
103
+ calibrations.delete(oldest.value);
104
+ }
105
+ }
106
+
107
+ function clamp(value: number): number {
108
+ return Math.min(MAX_FACTOR, Math.max(MIN_FACTOR, value));
109
+ }
110
+
111
+ /**
112
+ * Record what a conversation was actually charged against what we estimated.
113
+ *
114
+ * `estimated` must be the RAW estimate — what the fixed heuristic produced before any correction
115
+ * was applied — and `charged` the token count implied by the upstream percentage. Passing the
116
+ * already-corrected value instead would make the factor measure its own residual error rather
117
+ * than the heuristic's, so each round would learn only the part it had not yet fixed and the
118
+ * factor would settle short of the true ratio, permanently under-correcting.
119
+ *
120
+ * Non-finite, non-positive, or implausible inputs are ignored rather than clamped, because a bad
121
+ * reading carries no information worth smoothing in.
122
+ */
123
+ export function recordKiroCalibration(
124
+ conversationId: string | undefined,
125
+ estimated: number,
126
+ charged: number,
127
+ ): void {
128
+ if (!conversationId) return;
129
+ if (!Number.isFinite(estimated) || !Number.isFinite(charged)) return;
130
+ if (estimated <= 0 || charged <= 0) return;
131
+ const entry = calibrations.get(conversationId);
132
+ // Prefer the raw estimate recorded at build time; fall back to the caller's value when this
133
+ // conversation was never seen by `calibrateKiroEstimate` (a first turn, or a rebuilt retry).
134
+ const baseline = entry?.rawEstimate ?? estimated;
135
+ if (!Number.isFinite(baseline) || baseline <= 0) return;
136
+ const observed = charged / baseline;
137
+ if (!Number.isFinite(observed) || observed <= 0 || observed > MAX_PLAUSIBLE_OBSERVATION) return;
138
+
139
+ // Smooth from 1 (the "no correction" prior) on the FIRST observation too. Jumping straight to
140
+ // `clamp(observed)` would let a single cache-affected or truncated turn set the factor outright,
141
+ // which is exactly the swing the smoothing exists to prevent.
142
+ const previous = entry?.factor ?? 1;
143
+ const next = clamp(previous + (observed - previous) * SMOOTHING);
144
+
145
+ // The observation for this turn is consumed: dropping the raw estimate keeps a later record for
146
+ // the same conversation from being scored a second time against a stale baseline, which would
147
+ // re-learn the same correction from a payload that has since grown.
148
+ touch(conversationId, () => ({ factor: next }));
149
+ }
150
+
151
+ /**
152
+ * Apply a conversation's learned correction to an estimate. Unknown conversations are returned
153
+ * unchanged, so the first turn behaves exactly as it did before calibration existed.
154
+ */
155
+ export function calibrateKiroEstimate(conversationId: string | undefined, estimate: number): number {
156
+ if (!conversationId || !Number.isFinite(estimate) || estimate <= 0) return estimate;
157
+ touch(conversationId, current => ({ ...current, rawEstimate: estimate }));
158
+ const factor = calibrations.get(conversationId)?.factor;
159
+ return factor === undefined ? estimate : Math.ceil(estimate * factor);
160
+ }
161
+
162
+ /**
163
+ * Carry a conversation's state to the id upstream actually returned.
164
+ *
165
+ * The estimate is stored under the id the request was BUILT with, but Kiro can answer with a
166
+ * different conversation id, and the stream then records under that one. Without this the record
167
+ * would miss its own raw estimate and silently fall back to the corrected value — the exact
168
+ * feedback bug the raw estimate exists to avoid.
169
+ */
170
+ export function rekeyKiroCalibration(fromConversationId: string | undefined, toConversationId: string | undefined): void {
171
+ if (!fromConversationId || !toConversationId || fromConversationId === toConversationId) return;
172
+ const entry = calibrations.get(fromConversationId);
173
+ if (!entry) return;
174
+ calibrations.delete(fromConversationId);
175
+ touch(toConversationId, current => ({ ...current, ...entry }));
176
+ }
177
+
178
+ /** Test seam: drop all learned state. */
179
+ export function resetKiroCalibration(): void {
180
+ calibrations.clear();
181
+ }