codex-multi-auth 0.1.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 (327) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +162 -0
  3. package/assets/opencode-logo-ornate-dark.svg +18 -0
  4. package/assets/readme-hero.svg +31 -0
  5. package/config/README.md +87 -0
  6. package/config/minimal-opencode.json +13 -0
  7. package/config/opencode-legacy.json +571 -0
  8. package/config/opencode-modern.json +239 -0
  9. package/dist/index.d.ts +45 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3160 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/accounts/rate-limits.d.ts +22 -0
  14. package/dist/lib/accounts/rate-limits.d.ts.map +1 -0
  15. package/dist/lib/accounts/rate-limits.js +63 -0
  16. package/dist/lib/accounts/rate-limits.js.map +1 -0
  17. package/dist/lib/accounts.d.ts +95 -0
  18. package/dist/lib/accounts.d.ts.map +1 -0
  19. package/dist/lib/accounts.js +668 -0
  20. package/dist/lib/accounts.js.map +1 -0
  21. package/dist/lib/audit.d.ts +45 -0
  22. package/dist/lib/audit.d.ts.map +1 -0
  23. package/dist/lib/audit.js +131 -0
  24. package/dist/lib/audit.js.map +1 -0
  25. package/dist/lib/auth/auth.d.ts +56 -0
  26. package/dist/lib/auth/auth.d.ts.map +1 -0
  27. package/dist/lib/auth/auth.js +214 -0
  28. package/dist/lib/auth/auth.js.map +1 -0
  29. package/dist/lib/auth/browser.d.ts +34 -0
  30. package/dist/lib/auth/browser.d.ts.map +1 -0
  31. package/dist/lib/auth/browser.js +185 -0
  32. package/dist/lib/auth/browser.js.map +1 -0
  33. package/dist/lib/auth/server.d.ts +24 -0
  34. package/dist/lib/auth/server.d.ts.map +1 -0
  35. package/dist/lib/auth/server.js +116 -0
  36. package/dist/lib/auth/server.js.map +1 -0
  37. package/dist/lib/auth/token-utils.d.ts +59 -0
  38. package/dist/lib/auth/token-utils.d.ts.map +1 -0
  39. package/dist/lib/auth/token-utils.js +331 -0
  40. package/dist/lib/auth/token-utils.js.map +1 -0
  41. package/dist/lib/auth-rate-limit.d.ts +20 -0
  42. package/dist/lib/auth-rate-limit.d.ts.map +1 -0
  43. package/dist/lib/auth-rate-limit.js +91 -0
  44. package/dist/lib/auth-rate-limit.js.map +1 -0
  45. package/dist/lib/auto-update-checker.d.ts +10 -0
  46. package/dist/lib/auto-update-checker.d.ts.map +1 -0
  47. package/dist/lib/auto-update-checker.js +216 -0
  48. package/dist/lib/auto-update-checker.js.map +1 -0
  49. package/dist/lib/capability-policy.d.ts +18 -0
  50. package/dist/lib/capability-policy.d.ts.map +1 -0
  51. package/dist/lib/capability-policy.js +150 -0
  52. package/dist/lib/capability-policy.js.map +1 -0
  53. package/dist/lib/circuit-breaker.d.ts +34 -0
  54. package/dist/lib/circuit-breaker.d.ts.map +1 -0
  55. package/dist/lib/circuit-breaker.js +124 -0
  56. package/dist/lib/circuit-breaker.js.map +1 -0
  57. package/dist/lib/cli.d.ts +64 -0
  58. package/dist/lib/cli.d.ts.map +1 -0
  59. package/dist/lib/cli.js +274 -0
  60. package/dist/lib/cli.js.map +1 -0
  61. package/dist/lib/codex-cli/observability.d.ts +22 -0
  62. package/dist/lib/codex-cli/observability.d.ts.map +1 -0
  63. package/dist/lib/codex-cli/observability.js +36 -0
  64. package/dist/lib/codex-cli/observability.js.map +1 -0
  65. package/dist/lib/codex-cli/state.d.ts +86 -0
  66. package/dist/lib/codex-cli/state.d.ts.map +1 -0
  67. package/dist/lib/codex-cli/state.js +470 -0
  68. package/dist/lib/codex-cli/state.js.map +1 -0
  69. package/dist/lib/codex-cli/sync.d.ts +27 -0
  70. package/dist/lib/codex-cli/sync.d.ts.map +1 -0
  71. package/dist/lib/codex-cli/sync.js +325 -0
  72. package/dist/lib/codex-cli/sync.js.map +1 -0
  73. package/dist/lib/codex-cli/writer.d.ts +12 -0
  74. package/dist/lib/codex-cli/writer.d.ts.map +1 -0
  75. package/dist/lib/codex-cli/writer.js +388 -0
  76. package/dist/lib/codex-cli/writer.js.map +1 -0
  77. package/dist/lib/codex-manager.d.ts +2 -0
  78. package/dist/lib/codex-manager.d.ts.map +1 -0
  79. package/dist/lib/codex-manager.js +4841 -0
  80. package/dist/lib/codex-manager.js.map +1 -0
  81. package/dist/lib/config.d.ts +269 -0
  82. package/dist/lib/config.d.ts.map +1 -0
  83. package/dist/lib/config.js +789 -0
  84. package/dist/lib/config.js.map +1 -0
  85. package/dist/lib/constants.d.ts +78 -0
  86. package/dist/lib/constants.d.ts.map +1 -0
  87. package/dist/lib/constants.js +78 -0
  88. package/dist/lib/constants.js.map +1 -0
  89. package/dist/lib/context-overflow.d.ts +27 -0
  90. package/dist/lib/context-overflow.d.ts.map +1 -0
  91. package/dist/lib/context-overflow.js +124 -0
  92. package/dist/lib/context-overflow.js.map +1 -0
  93. package/dist/lib/dashboard-settings.d.ts +90 -0
  94. package/dist/lib/dashboard-settings.d.ts.map +1 -0
  95. package/dist/lib/dashboard-settings.js +327 -0
  96. package/dist/lib/dashboard-settings.js.map +1 -0
  97. package/dist/lib/entitlement-cache.d.ts +41 -0
  98. package/dist/lib/entitlement-cache.d.ts.map +1 -0
  99. package/dist/lib/entitlement-cache.js +137 -0
  100. package/dist/lib/entitlement-cache.js.map +1 -0
  101. package/dist/lib/errors.d.ts +113 -0
  102. package/dist/lib/errors.d.ts.map +1 -0
  103. package/dist/lib/errors.js +103 -0
  104. package/dist/lib/errors.js.map +1 -0
  105. package/dist/lib/forecast.d.ts +42 -0
  106. package/dist/lib/forecast.d.ts.map +1 -0
  107. package/dist/lib/forecast.js +256 -0
  108. package/dist/lib/forecast.js.map +1 -0
  109. package/dist/lib/health.d.ts +33 -0
  110. package/dist/lib/health.d.ts.map +1 -0
  111. package/dist/lib/health.js +70 -0
  112. package/dist/lib/health.js.map +1 -0
  113. package/dist/lib/index.d.ts +32 -0
  114. package/dist/lib/index.d.ts.map +1 -0
  115. package/dist/lib/index.js +32 -0
  116. package/dist/lib/index.js.map +1 -0
  117. package/dist/lib/live-account-sync.d.ts +39 -0
  118. package/dist/lib/live-account-sync.d.ts.map +1 -0
  119. package/dist/lib/live-account-sync.js +196 -0
  120. package/dist/lib/live-account-sync.js.map +1 -0
  121. package/dist/lib/logger.d.ts +40 -0
  122. package/dist/lib/logger.d.ts.map +1 -0
  123. package/dist/lib/logger.js +364 -0
  124. package/dist/lib/logger.js.map +1 -0
  125. package/dist/lib/oauth-success.html +338 -0
  126. package/dist/lib/parallel-probe.d.ts +28 -0
  127. package/dist/lib/parallel-probe.d.ts.map +1 -0
  128. package/dist/lib/parallel-probe.js +97 -0
  129. package/dist/lib/parallel-probe.js.map +1 -0
  130. package/dist/lib/preemptive-quota-scheduler.d.ts +53 -0
  131. package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -0
  132. package/dist/lib/preemptive-quota-scheduler.js +220 -0
  133. package/dist/lib/preemptive-quota-scheduler.js.map +1 -0
  134. package/dist/lib/proactive-refresh.d.ts +66 -0
  135. package/dist/lib/proactive-refresh.d.ts.map +1 -0
  136. package/dist/lib/proactive-refresh.js +143 -0
  137. package/dist/lib/proactive-refresh.js.map +1 -0
  138. package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
  139. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
  140. package/dist/lib/prompts/codex-opencode-bridge.js +169 -0
  141. package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
  142. package/dist/lib/prompts/codex.d.ts +41 -0
  143. package/dist/lib/prompts/codex.d.ts.map +1 -0
  144. package/dist/lib/prompts/codex.js +383 -0
  145. package/dist/lib/prompts/codex.js.map +1 -0
  146. package/dist/lib/prompts/opencode-codex.d.ts +25 -0
  147. package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
  148. package/dist/lib/prompts/opencode-codex.js +270 -0
  149. package/dist/lib/prompts/opencode-codex.js.map +1 -0
  150. package/dist/lib/quota-cache.d.ts +68 -0
  151. package/dist/lib/quota-cache.d.ts.map +1 -0
  152. package/dist/lib/quota-cache.js +224 -0
  153. package/dist/lib/quota-cache.js.map +1 -0
  154. package/dist/lib/quota-probe.d.ts +49 -0
  155. package/dist/lib/quota-probe.d.ts.map +1 -0
  156. package/dist/lib/quota-probe.js +368 -0
  157. package/dist/lib/quota-probe.js.map +1 -0
  158. package/dist/lib/recovery/constants.d.ts +12 -0
  159. package/dist/lib/recovery/constants.d.ts.map +1 -0
  160. package/dist/lib/recovery/constants.js +31 -0
  161. package/dist/lib/recovery/constants.js.map +1 -0
  162. package/dist/lib/recovery/index.d.ts +12 -0
  163. package/dist/lib/recovery/index.d.ts.map +1 -0
  164. package/dist/lib/recovery/index.js +12 -0
  165. package/dist/lib/recovery/index.js.map +1 -0
  166. package/dist/lib/recovery/storage.d.ts +24 -0
  167. package/dist/lib/recovery/storage.d.ts.map +1 -0
  168. package/dist/lib/recovery/storage.js +362 -0
  169. package/dist/lib/recovery/storage.js.map +1 -0
  170. package/dist/lib/recovery/types.d.ts +116 -0
  171. package/dist/lib/recovery/types.d.ts.map +1 -0
  172. package/dist/lib/recovery/types.js +7 -0
  173. package/dist/lib/recovery/types.js.map +1 -0
  174. package/dist/lib/recovery.d.ts +31 -0
  175. package/dist/lib/recovery.d.ts.map +1 -0
  176. package/dist/lib/recovery.js +313 -0
  177. package/dist/lib/recovery.js.map +1 -0
  178. package/dist/lib/refresh-guardian.d.ts +31 -0
  179. package/dist/lib/refresh-guardian.d.ts.map +1 -0
  180. package/dist/lib/refresh-guardian.js +151 -0
  181. package/dist/lib/refresh-guardian.js.map +1 -0
  182. package/dist/lib/refresh-lease.d.ts +37 -0
  183. package/dist/lib/refresh-lease.d.ts.map +1 -0
  184. package/dist/lib/refresh-lease.js +335 -0
  185. package/dist/lib/refresh-lease.js.map +1 -0
  186. package/dist/lib/refresh-queue.d.ts +117 -0
  187. package/dist/lib/refresh-queue.d.ts.map +1 -0
  188. package/dist/lib/refresh-queue.js +297 -0
  189. package/dist/lib/refresh-queue.js.map +1 -0
  190. package/dist/lib/request/failure-policy.d.ts +42 -0
  191. package/dist/lib/request/failure-policy.d.ts.map +1 -0
  192. package/dist/lib/request/failure-policy.js +133 -0
  193. package/dist/lib/request/failure-policy.js.map +1 -0
  194. package/dist/lib/request/fetch-helpers.d.ts +152 -0
  195. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  196. package/dist/lib/request/fetch-helpers.js +704 -0
  197. package/dist/lib/request/fetch-helpers.js.map +1 -0
  198. package/dist/lib/request/helpers/input-utils.d.ts +7 -0
  199. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
  200. package/dist/lib/request/helpers/input-utils.js +214 -0
  201. package/dist/lib/request/helpers/input-utils.js.map +1 -0
  202. package/dist/lib/request/helpers/model-map.d.ts +28 -0
  203. package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
  204. package/dist/lib/request/helpers/model-map.js +133 -0
  205. package/dist/lib/request/helpers/model-map.js.map +1 -0
  206. package/dist/lib/request/helpers/tool-utils.d.ts +29 -0
  207. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -0
  208. package/dist/lib/request/helpers/tool-utils.js +117 -0
  209. package/dist/lib/request/helpers/tool-utils.js.map +1 -0
  210. package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
  211. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  212. package/dist/lib/request/rate-limit-backoff.js +83 -0
  213. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  214. package/dist/lib/request/request-transformer.d.ts +107 -0
  215. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  216. package/dist/lib/request/request-transformer.js +814 -0
  217. package/dist/lib/request/request-transformer.js.map +1 -0
  218. package/dist/lib/request/response-handler.d.ts +23 -0
  219. package/dist/lib/request/response-handler.d.ts.map +1 -0
  220. package/dist/lib/request/response-handler.js +155 -0
  221. package/dist/lib/request/response-handler.js.map +1 -0
  222. package/dist/lib/request/stream-failover.d.ts +21 -0
  223. package/dist/lib/request/stream-failover.d.ts.map +1 -0
  224. package/dist/lib/request/stream-failover.js +204 -0
  225. package/dist/lib/request/stream-failover.js.map +1 -0
  226. package/dist/lib/rotation.d.ts +146 -0
  227. package/dist/lib/rotation.d.ts.map +1 -0
  228. package/dist/lib/rotation.js +321 -0
  229. package/dist/lib/rotation.js.map +1 -0
  230. package/dist/lib/runtime-paths.d.ts +58 -0
  231. package/dist/lib/runtime-paths.d.ts.map +1 -0
  232. package/dist/lib/runtime-paths.js +164 -0
  233. package/dist/lib/runtime-paths.js.map +1 -0
  234. package/dist/lib/schemas.d.ts +435 -0
  235. package/dist/lib/schemas.d.ts.map +1 -0
  236. package/dist/lib/schemas.js +268 -0
  237. package/dist/lib/schemas.js.map +1 -0
  238. package/dist/lib/session-affinity.d.ts +23 -0
  239. package/dist/lib/session-affinity.d.ts.map +1 -0
  240. package/dist/lib/session-affinity.js +127 -0
  241. package/dist/lib/session-affinity.js.map +1 -0
  242. package/dist/lib/shutdown.d.ts +7 -0
  243. package/dist/lib/shutdown.d.ts.map +1 -0
  244. package/dist/lib/shutdown.js +43 -0
  245. package/dist/lib/shutdown.js.map +1 -0
  246. package/dist/lib/storage/migrations.d.ts +59 -0
  247. package/dist/lib/storage/migrations.d.ts.map +1 -0
  248. package/dist/lib/storage/migrations.js +41 -0
  249. package/dist/lib/storage/migrations.js.map +1 -0
  250. package/dist/lib/storage/paths.d.ts +51 -0
  251. package/dist/lib/storage/paths.d.ts.map +1 -0
  252. package/dist/lib/storage/paths.js +152 -0
  253. package/dist/lib/storage/paths.js.map +1 -0
  254. package/dist/lib/storage.d.ts +106 -0
  255. package/dist/lib/storage.d.ts.map +1 -0
  256. package/dist/lib/storage.js +896 -0
  257. package/dist/lib/storage.js.map +1 -0
  258. package/dist/lib/table-formatter.d.ts +32 -0
  259. package/dist/lib/table-formatter.d.ts.map +1 -0
  260. package/dist/lib/table-formatter.js +44 -0
  261. package/dist/lib/table-formatter.js.map +1 -0
  262. package/dist/lib/tools/hashline-tools.d.ts +51 -0
  263. package/dist/lib/tools/hashline-tools.d.ts.map +1 -0
  264. package/dist/lib/tools/hashline-tools.js +456 -0
  265. package/dist/lib/tools/hashline-tools.js.map +1 -0
  266. package/dist/lib/types.d.ts +130 -0
  267. package/dist/lib/types.d.ts.map +1 -0
  268. package/dist/lib/types.js +2 -0
  269. package/dist/lib/types.js.map +1 -0
  270. package/dist/lib/ui/ansi.d.ts +40 -0
  271. package/dist/lib/ui/ansi.d.ts.map +1 -0
  272. package/dist/lib/ui/ansi.js +68 -0
  273. package/dist/lib/ui/ansi.js.map +1 -0
  274. package/dist/lib/ui/auth-menu.d.ts +76 -0
  275. package/dist/lib/ui/auth-menu.d.ts.map +1 -0
  276. package/dist/lib/ui/auth-menu.js +590 -0
  277. package/dist/lib/ui/auth-menu.js.map +1 -0
  278. package/dist/lib/ui/confirm.d.ts +11 -0
  279. package/dist/lib/ui/confirm.d.ts.map +1 -0
  280. package/dist/lib/ui/confirm.js +29 -0
  281. package/dist/lib/ui/confirm.js.map +1 -0
  282. package/dist/lib/ui/copy.d.ts +123 -0
  283. package/dist/lib/ui/copy.d.ts.map +1 -0
  284. package/dist/lib/ui/copy.js +127 -0
  285. package/dist/lib/ui/copy.js.map +1 -0
  286. package/dist/lib/ui/format.d.ts +62 -0
  287. package/dist/lib/ui/format.d.ts.map +1 -0
  288. package/dist/lib/ui/format.js +205 -0
  289. package/dist/lib/ui/format.js.map +1 -0
  290. package/dist/lib/ui/runtime.d.ts +43 -0
  291. package/dist/lib/ui/runtime.d.ts.map +1 -0
  292. package/dist/lib/ui/runtime.js +69 -0
  293. package/dist/lib/ui/runtime.js.map +1 -0
  294. package/dist/lib/ui/select.d.ts +60 -0
  295. package/dist/lib/ui/select.d.ts.map +1 -0
  296. package/dist/lib/ui/select.js +467 -0
  297. package/dist/lib/ui/select.js.map +1 -0
  298. package/dist/lib/ui/theme.d.ts +56 -0
  299. package/dist/lib/ui/theme.d.ts.map +1 -0
  300. package/dist/lib/ui/theme.js +186 -0
  301. package/dist/lib/ui/theme.js.map +1 -0
  302. package/dist/lib/unified-settings.d.ts +71 -0
  303. package/dist/lib/unified-settings.d.ts.map +1 -0
  304. package/dist/lib/unified-settings.js +299 -0
  305. package/dist/lib/unified-settings.js.map +1 -0
  306. package/dist/lib/utils.d.ts +29 -0
  307. package/dist/lib/utils.d.ts.map +1 -0
  308. package/dist/lib/utils.js +54 -0
  309. package/dist/lib/utils.js.map +1 -0
  310. package/package.json +115 -0
  311. package/scripts/audit-dev-allowlist.js +128 -0
  312. package/scripts/bench-format/hashline-v2.mjs +642 -0
  313. package/scripts/bench-format/models.mjs +105 -0
  314. package/scripts/bench-format/opencode.mjs +205 -0
  315. package/scripts/bench-format/render.mjs +496 -0
  316. package/scripts/bench-format/stats.mjs +54 -0
  317. package/scripts/bench-format/tasks.mjs +151 -0
  318. package/scripts/benchmark-edit-formats.mjs +1161 -0
  319. package/scripts/benchmark-render-dashboard.mjs +49 -0
  320. package/scripts/codex-multi-auth.js +6 -0
  321. package/scripts/codex-routing.js +34 -0
  322. package/scripts/codex.js +122 -0
  323. package/scripts/copy-oauth-success.js +37 -0
  324. package/scripts/install-opencode-codex-auth.js +193 -0
  325. package/scripts/test-all-models.sh +7 -0
  326. package/scripts/test-model-matrix.js +424 -0
  327. package/scripts/validate-model-map.sh +7 -0
@@ -0,0 +1,814 @@
1
+ import { logDebug, logWarn } from "../logger.js";
2
+ import { TOOL_REMAP_MESSAGE } from "../prompts/codex.js";
3
+ import { CODEX_OPENCODE_BRIDGE } from "../prompts/codex-opencode-bridge.js";
4
+ import { getOpenCodeCodexPrompt } from "../prompts/opencode-codex.js";
5
+ import { getNormalizedModel } from "./helpers/model-map.js";
6
+ import { filterOpenCodeSystemPromptsWithCachedPrompt, normalizeOrphanedToolOutputs, injectMissingToolOutputs, } from "./helpers/input-utils.js";
7
+ import { cleanupToolDefinitions } from "./helpers/tool-utils.js";
8
+ const PLAN_MODE_ONLY_TOOLS = new Set(["request_user_input"]);
9
+ export { isOpenCodeSystemPrompt, filterOpenCodeSystemPromptsWithCachedPrompt, } from "./helpers/input-utils.js";
10
+ /**
11
+ * Normalize model name to Codex-supported variants
12
+ *
13
+ * Uses explicit model map for known models, with fallback pattern matching
14
+ * for unknown/custom model names.
15
+ *
16
+ * @param model - Original model name (e.g., "gpt-5-codex-low", "openai/gpt-5-codex")
17
+ * @returns Normalized model name (e.g., "gpt-5-codex", "gpt-5.1-codex-max")
18
+ */
19
+ export function normalizeModel(model) {
20
+ if (!model)
21
+ return "gpt-5.1";
22
+ // Strip provider prefix if present (e.g., "openai/gpt-5-codex" → "gpt-5-codex")
23
+ const modelId = model.includes("/") ? model.split("/").pop() ?? model : model;
24
+ // Try explicit model map first (handles all known model variants)
25
+ const mappedModel = getNormalizedModel(modelId);
26
+ if (mappedModel) {
27
+ return mappedModel;
28
+ }
29
+ // Fallback: Pattern-based matching for unknown/custom model names
30
+ // This preserves backwards compatibility with old verbose names
31
+ // like "GPT 5 Codex Low (ChatGPT Subscription)"
32
+ const normalized = modelId.toLowerCase();
33
+ // Priority order for pattern matching (most specific first):
34
+ // 1. GPT-5.3 Codex Spark (legacy alias -> canonical gpt-5-codex)
35
+ if (normalized.includes("gpt-5.3-codex-spark") ||
36
+ normalized.includes("gpt 5.3 codex spark")) {
37
+ return "gpt-5-codex";
38
+ }
39
+ // 2. GPT-5.3 Codex (legacy alias -> canonical gpt-5-codex)
40
+ if (normalized.includes("gpt-5.3-codex") ||
41
+ normalized.includes("gpt 5.3 codex")) {
42
+ return "gpt-5-codex";
43
+ }
44
+ // 3. GPT-5.2 Codex (legacy alias -> canonical gpt-5-codex)
45
+ if (normalized.includes("gpt-5.2-codex") ||
46
+ normalized.includes("gpt 5.2 codex")) {
47
+ return "gpt-5-codex";
48
+ }
49
+ // 4. GPT-5.2 (general purpose)
50
+ if (normalized.includes("gpt-5.2") || normalized.includes("gpt 5.2")) {
51
+ return "gpt-5.2";
52
+ }
53
+ // 5. GPT-5.1 Codex Max
54
+ if (normalized.includes("gpt-5.1-codex-max") ||
55
+ normalized.includes("gpt 5.1 codex max")) {
56
+ return "gpt-5.1-codex-max";
57
+ }
58
+ // 6. GPT-5.1 Codex Mini
59
+ if (normalized.includes("gpt-5.1-codex-mini") ||
60
+ normalized.includes("gpt 5.1 codex mini")) {
61
+ return "gpt-5.1-codex-mini";
62
+ }
63
+ // 7. Legacy Codex Mini
64
+ if (normalized.includes("codex-mini-latest") ||
65
+ normalized.includes("gpt-5-codex-mini") ||
66
+ normalized.includes("gpt 5 codex mini")) {
67
+ return "gpt-5.1-codex-mini";
68
+ }
69
+ // 8. GPT-5 Codex canonical + GPT-5.1 Codex legacy alias
70
+ if (normalized.includes("gpt-5-codex") ||
71
+ normalized.includes("gpt 5 codex")) {
72
+ return "gpt-5-codex";
73
+ }
74
+ // 9. GPT-5.1 Codex (legacy alias)
75
+ if (normalized.includes("gpt-5.1-codex") ||
76
+ normalized.includes("gpt 5.1 codex")) {
77
+ return "gpt-5-codex";
78
+ }
79
+ // 10. GPT-5.1 (general-purpose)
80
+ if (normalized.includes("gpt-5.1") || normalized.includes("gpt 5.1")) {
81
+ return "gpt-5.1";
82
+ }
83
+ // 11. GPT-5 Codex family (any other variant with "codex")
84
+ if (normalized.includes("codex")) {
85
+ return "gpt-5-codex";
86
+ }
87
+ // 12. GPT-5 family (any variant) - default to 5.1
88
+ if (normalized.includes("gpt-5") || normalized.includes("gpt 5")) {
89
+ return "gpt-5.1";
90
+ }
91
+ // Default fallback
92
+ return "gpt-5.1";
93
+ }
94
+ /**
95
+ * Extract configuration for a specific model
96
+ * Merges global options with model-specific options (model-specific takes precedence)
97
+ * @param modelName - Model name (e.g., "gpt-5-codex")
98
+ * @param userConfig - Full user configuration object
99
+ * @returns Merged configuration for this model
100
+ */
101
+ export function getModelConfig(modelName, userConfig = { global: {}, models: {} }) {
102
+ const globalOptions = userConfig.global ?? {};
103
+ const modelMap = userConfig.models ?? {};
104
+ const stripProviderPrefix = (name) => name.includes("/") ? (name.split("/").pop() ?? name) : name;
105
+ const getVariantFromModelName = (name) => {
106
+ const stripped = stripProviderPrefix(name).toLowerCase();
107
+ const match = stripped.match(/-(none|minimal|low|medium|high|xhigh)$/);
108
+ if (!match)
109
+ return undefined;
110
+ const variant = match[1];
111
+ if (variant === "none" ||
112
+ variant === "minimal" ||
113
+ variant === "low" ||
114
+ variant === "medium" ||
115
+ variant === "high" ||
116
+ variant === "xhigh") {
117
+ return variant;
118
+ }
119
+ return undefined;
120
+ };
121
+ const removeVariantSuffix = (name) => stripProviderPrefix(name).replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
122
+ const findModelEntry = (candidates) => {
123
+ for (const key of candidates) {
124
+ const entry = modelMap[key];
125
+ if (entry)
126
+ return { key, entry };
127
+ }
128
+ return undefined;
129
+ };
130
+ const strippedModelName = stripProviderPrefix(modelName);
131
+ const normalizedModelName = normalizeModel(strippedModelName);
132
+ const normalizedBaseModelName = normalizeModel(removeVariantSuffix(strippedModelName));
133
+ const baseModelName = removeVariantSuffix(strippedModelName);
134
+ const requestedVariant = getVariantFromModelName(strippedModelName);
135
+ // 1) Honor exact per-model keys first (including variant-specific keys)
136
+ const directMatch = findModelEntry([modelName, strippedModelName]);
137
+ if (directMatch?.entry?.options) {
138
+ return { ...globalOptions, ...directMatch.entry.options };
139
+ }
140
+ // 2) Resolve to base model config (supports provider-prefixed names + aliases)
141
+ const baseMatch = findModelEntry([
142
+ baseModelName,
143
+ normalizedBaseModelName,
144
+ normalizedModelName,
145
+ ]);
146
+ const baseOptions = baseMatch?.entry?.options ?? {};
147
+ // 3) If model requested a variant, merge variant options from base model config
148
+ const variantConfig = requestedVariant && baseMatch?.entry?.variants
149
+ ? baseMatch.entry.variants[requestedVariant]
150
+ : undefined;
151
+ let variantOptions = {};
152
+ if (variantConfig) {
153
+ const { disabled: _disabled, ...rest } = variantConfig;
154
+ void _disabled;
155
+ variantOptions = rest;
156
+ }
157
+ // Model-specific options override global options
158
+ return { ...globalOptions, ...baseOptions, ...variantOptions };
159
+ }
160
+ /**
161
+ * Apply fast-session defaults to reduce latency/cost for interactive sessions.
162
+ * Explicit user/model overrides still take precedence.
163
+ */
164
+ export function applyFastSessionDefaults(userConfig = { global: {}, models: {} }) {
165
+ const global = userConfig.global ?? {};
166
+ return {
167
+ ...userConfig,
168
+ global: {
169
+ ...global,
170
+ reasoningEffort: global.reasoningEffort ?? "low",
171
+ textVerbosity: global.textVerbosity ?? "low",
172
+ },
173
+ };
174
+ }
175
+ function resolveReasoningConfig(modelName, modelConfig, body) {
176
+ const providerOpenAI = body.providerOptions?.openai;
177
+ const existingEffort = body.reasoning?.effort ?? providerOpenAI?.reasoningEffort;
178
+ const existingSummary = body.reasoning?.summary ?? providerOpenAI?.reasoningSummary;
179
+ const mergedConfig = {
180
+ ...modelConfig,
181
+ ...(existingEffort ? { reasoningEffort: existingEffort } : {}),
182
+ ...(existingSummary ? { reasoningSummary: existingSummary } : {}),
183
+ };
184
+ return getReasoningConfig(modelName, mergedConfig);
185
+ }
186
+ function resolveTextVerbosity(modelConfig, body) {
187
+ const providerOpenAI = body.providerOptions?.openai;
188
+ return (body.text?.verbosity ??
189
+ providerOpenAI?.textVerbosity ??
190
+ modelConfig.textVerbosity ??
191
+ "medium");
192
+ }
193
+ function resolveInclude(modelConfig, body) {
194
+ const providerOpenAI = body.providerOptions?.openai;
195
+ const base = body.include ??
196
+ providerOpenAI?.include ??
197
+ modelConfig.include ??
198
+ ["reasoning.encrypted_content"];
199
+ const include = Array.from(new Set(base.filter(Boolean)));
200
+ if (!include.includes("reasoning.encrypted_content")) {
201
+ include.push("reasoning.encrypted_content");
202
+ }
203
+ return include;
204
+ }
205
+ function parseCollaborationMode(value) {
206
+ if (!value)
207
+ return undefined;
208
+ const normalized = value.trim().toLowerCase();
209
+ if (normalized === "plan")
210
+ return "plan";
211
+ if (normalized === "default")
212
+ return "default";
213
+ return undefined;
214
+ }
215
+ function extractMessageText(content) {
216
+ if (typeof content === "string")
217
+ return content;
218
+ if (!Array.isArray(content))
219
+ return "";
220
+ return content
221
+ .map((item) => {
222
+ if (typeof item === "string")
223
+ return item;
224
+ if (!item || typeof item !== "object")
225
+ return "";
226
+ const typedItem = item;
227
+ return typeof typedItem.text === "string" ? typedItem.text : "";
228
+ })
229
+ .filter(Boolean)
230
+ .join("\n");
231
+ }
232
+ function detectCollaborationMode(body) {
233
+ const envMode = parseCollaborationMode(process.env.CODEX_COLLABORATION_MODE) ??
234
+ parseCollaborationMode(process.env.OPENCODE_COLLABORATION_MODE);
235
+ if (envMode)
236
+ return envMode;
237
+ if (!Array.isArray(body.input))
238
+ return "unknown";
239
+ let sawPlan = false;
240
+ let sawDefault = false;
241
+ for (const item of body.input) {
242
+ if (!item || typeof item !== "object")
243
+ continue;
244
+ const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
245
+ if (role !== "developer" && role !== "system")
246
+ continue;
247
+ const text = extractMessageText(item.content);
248
+ if (!text)
249
+ continue;
250
+ if (/collaboration mode:\s*plan/i.test(text) || /in Plan mode/i.test(text)) {
251
+ sawPlan = true;
252
+ }
253
+ if (/collaboration mode:\s*default/i.test(text) || /in Default mode/i.test(text)) {
254
+ sawDefault = true;
255
+ }
256
+ }
257
+ if (sawPlan && !sawDefault)
258
+ return "plan";
259
+ if (sawDefault)
260
+ return "default";
261
+ return "unknown";
262
+ }
263
+ function sanitizePlanOnlyTools(tools, mode) {
264
+ if (!Array.isArray(tools) || mode === "plan")
265
+ return tools;
266
+ let removed = 0;
267
+ const filtered = tools.filter((entry) => {
268
+ if (!entry || typeof entry !== "object")
269
+ return true;
270
+ const functionDef = entry.function;
271
+ if (!functionDef || typeof functionDef !== "object")
272
+ return true;
273
+ const name = functionDef.name;
274
+ if (typeof name !== "string")
275
+ return true;
276
+ if (!PLAN_MODE_ONLY_TOOLS.has(name))
277
+ return true;
278
+ removed++;
279
+ return false;
280
+ });
281
+ if (removed > 0) {
282
+ logWarn(`Removed ${removed} plan-mode-only tool definition(s) because collaboration mode is ${mode}`);
283
+ }
284
+ return filtered;
285
+ }
286
+ /**
287
+ * Configure reasoning parameters based on model variant and user config
288
+ *
289
+ * NOTE: This plugin follows Codex CLI defaults instead of opencode defaults because:
290
+ * - We're accessing the ChatGPT backend API (not OpenAI Platform API)
291
+ * - opencode explicitly excludes gpt-5-codex from automatic reasoning configuration
292
+ * - Codex CLI has been thoroughly tested against this backend
293
+ *
294
+ * @param originalModel - Original model name before normalization
295
+ * @param userConfig - User configuration object
296
+ * @returns Reasoning configuration
297
+ */
298
+ export function getReasoningConfig(modelName, userConfig = {}) {
299
+ const normalizedName = modelName?.toLowerCase() ?? "";
300
+ // Canonical GPT-5 Codex (stable) defaults to high and does not support "none".
301
+ const isGpt5Codex = normalizedName.includes("gpt-5-codex") ||
302
+ normalizedName.includes("gpt 5 codex");
303
+ // Legacy GPT-5.3 Codex alias behavior (supports xhigh, but not "none")
304
+ const isGpt53Codex = normalizedName.includes("gpt-5.3-codex") ||
305
+ normalizedName.includes("gpt 5.3 codex");
306
+ // Legacy GPT-5.2 Codex alias behavior (supports xhigh, but not "none")
307
+ const isGpt52Codex = normalizedName.includes("gpt-5.2-codex") ||
308
+ normalizedName.includes("gpt 5.2 codex");
309
+ // GPT-5.2 general purpose (not codex variant)
310
+ const isGpt52General = (normalizedName.includes("gpt-5.2") || normalizedName.includes("gpt 5.2")) &&
311
+ !isGpt52Codex;
312
+ const isCodexMax = normalizedName.includes("codex-max") ||
313
+ normalizedName.includes("codex max");
314
+ const isCodexMini = normalizedName.includes("codex-mini") ||
315
+ normalizedName.includes("codex mini") ||
316
+ normalizedName.includes("codex_mini") ||
317
+ normalizedName.includes("codex-mini-latest");
318
+ const isCodex = normalizedName.includes("codex") && !isCodexMini;
319
+ const isLightweight = !isCodexMini &&
320
+ (normalizedName.includes("nano") ||
321
+ normalizedName.includes("mini"));
322
+ // GPT-5.1 general purpose (not codex variants) - supports "none" per OpenAI API docs
323
+ const isGpt51General = (normalizedName.includes("gpt-5.1") ||
324
+ normalizedName.includes("gpt 5.1") ||
325
+ normalizedName === "gpt-5" ||
326
+ normalizedName.startsWith("gpt-5-")) &&
327
+ !isCodex &&
328
+ !isGpt52General &&
329
+ !isCodexMax &&
330
+ !isCodexMini;
331
+ // GPT-5.2 general, legacy GPT-5.2/5.3 Codex aliases, and Codex Max support xhigh reasoning
332
+ const supportsXhigh = isGpt52General || isGpt53Codex || isGpt52Codex || isCodexMax;
333
+ // GPT 5.1 general and GPT 5.2 general support "none" reasoning per:
334
+ // - OpenAI API docs: "gpt-5.1 defaults to none, supports: none, low, medium, high"
335
+ // - Codex CLI: ReasoningEffort enum includes None variant (codex-rs/protocol/src/openai_models.rs)
336
+ // - Codex CLI: docs/config.md lists "none" as valid for model_reasoning_effort
337
+ // - gpt-5.2 (being newer) also supports: none, low, medium, high, xhigh
338
+ // - Codex models (including GPT-5 Codex and legacy GPT-5.3/5.2 Codex aliases) do NOT support "none"
339
+ const supportsNone = isGpt52General || isGpt51General;
340
+ // Default based on model type (Codex CLI defaults + plugin opinionated tuning)
341
+ // Note: OpenAI docs say gpt-5.1 defaults to "none", but we default to "medium"
342
+ // for better coding assistance unless user explicitly requests "none".
343
+ // - Canonical GPT-5 Codex defaults to high in stable Codex.
344
+ // - Legacy GPT-5.3/5.2 Codex aliases default to xhigh for backward compatibility.
345
+ const defaultEffort = isCodexMini
346
+ ? "medium"
347
+ : isGpt5Codex
348
+ ? "high"
349
+ : isGpt53Codex || isGpt52Codex
350
+ ? "xhigh"
351
+ : supportsXhigh
352
+ ? "high"
353
+ : isLightweight
354
+ ? "minimal"
355
+ : "medium";
356
+ // Get user-requested effort
357
+ let effort = userConfig.reasoningEffort || defaultEffort;
358
+ if (isCodexMini) {
359
+ if (effort === "minimal" || effort === "low" || effort === "none") {
360
+ effort = "medium";
361
+ }
362
+ if (effort === "xhigh") {
363
+ effort = "high";
364
+ }
365
+ if (effort !== "high" && effort !== "medium") {
366
+ effort = "medium";
367
+ }
368
+ }
369
+ // For models that don't support xhigh, downgrade to high
370
+ if (!supportsXhigh && effort === "xhigh") {
371
+ effort = "high";
372
+ }
373
+ // For models that don't support "none", upgrade to "low"
374
+ // (Codex models don't support "none" - only GPT-5.1 and GPT-5.2 general purpose do)
375
+ if (!supportsNone && effort === "none") {
376
+ effort = "low";
377
+ }
378
+ // Normalize "minimal" to "low" for Codex families
379
+ // Codex CLI presets are low/medium/high (or xhigh for Codex Max / GPT-5.3/5.2 Codex)
380
+ if (isCodex && effort === "minimal") {
381
+ effort = "low";
382
+ }
383
+ const summary = sanitizeReasoningSummary(userConfig.reasoningSummary);
384
+ return {
385
+ effort,
386
+ summary,
387
+ };
388
+ }
389
+ function sanitizeReasoningSummary(summary) {
390
+ if (!summary)
391
+ return "auto";
392
+ const normalized = summary.toLowerCase();
393
+ if (normalized === "concise" || normalized === "detailed" || normalized === "auto") {
394
+ return normalized;
395
+ }
396
+ return "auto";
397
+ }
398
+ /**
399
+ * Filter input array for stateless Codex API (store: false)
400
+ *
401
+ * Two transformations needed:
402
+ * 1. Remove AI SDK-specific items (not supported by Codex API)
403
+ * 2. Strip IDs from all remaining items (stateless mode)
404
+ *
405
+ * AI SDK constructs to REMOVE (not in OpenAI Responses API spec):
406
+ * - type: "item_reference" - AI SDK uses this for server-side state lookup
407
+ *
408
+ * Items to KEEP (strip IDs):
409
+ * - type: "message" - Conversation messages (provides context to LLM)
410
+ * - type: "function_call" - Tool calls from conversation
411
+ * - type: "function_call_output" - Tool results from conversation
412
+ *
413
+ * Context is maintained through:
414
+ * - Full message history (without IDs)
415
+ * - reasoning.encrypted_content (for reasoning continuity)
416
+ *
417
+ * @param input - Original input array from OpenCode/AI SDK
418
+ * @returns Filtered input array compatible with Codex API
419
+ */
420
+ export function filterInput(input) {
421
+ if (!Array.isArray(input))
422
+ return input;
423
+ return input
424
+ .filter((item) => {
425
+ // Remove AI SDK constructs not supported by Codex API
426
+ if (item.type === "item_reference") {
427
+ return false; // AI SDK only - references server state
428
+ }
429
+ return true; // Keep all other items
430
+ })
431
+ .map((item) => {
432
+ // Strip IDs from all items (Codex API stateless mode)
433
+ if (item.id) {
434
+ const { id: _omit, ...itemWithoutId } = item;
435
+ void _omit;
436
+ return itemWithoutId;
437
+ }
438
+ return item;
439
+ });
440
+ }
441
+ /**
442
+ * Trim long stateless histories for low-latency sessions.
443
+ * Keeps a small leading developer/system context plus the most recent items.
444
+ */
445
+ export function trimInputForFastSession(input, maxItems, options) {
446
+ if (!Array.isArray(input))
447
+ return input;
448
+ const MAX_HEAD_INSTRUCTION_CHARS = 1200;
449
+ const MAX_HEAD_INSTRUCTION_CHARS_TRIVIAL = 400;
450
+ if (options?.preferLatestUserOnly) {
451
+ const keepIndexes = new Set();
452
+ for (let i = 0; i < input.length; i++) {
453
+ const item = input[i];
454
+ if (!item || typeof item !== "object")
455
+ continue;
456
+ const role = typeof item?.role === "string" ? item.role : "";
457
+ if (role === "developer" || role === "system") {
458
+ const headText = extractMessageText(item.content);
459
+ if (headText.length <= MAX_HEAD_INSTRUCTION_CHARS_TRIVIAL) {
460
+ keepIndexes.add(i);
461
+ }
462
+ break;
463
+ }
464
+ }
465
+ for (let i = input.length - 1; i >= 0; i--) {
466
+ const item = input[i];
467
+ const role = typeof item?.role === "string" ? item.role.toLowerCase() : "";
468
+ if (role === "user") {
469
+ keepIndexes.add(i);
470
+ break;
471
+ }
472
+ }
473
+ const compacted = input.filter((_item, index) => keepIndexes.has(index));
474
+ if (compacted.length > 0)
475
+ return compacted;
476
+ }
477
+ const safeMax = Math.max(8, Math.floor(maxItems));
478
+ const keepIndexes = new Set();
479
+ const excludedHeadIndexes = new Set();
480
+ let keptHead = 0;
481
+ for (let i = 0; i < input.length && keptHead < 2; i++) {
482
+ const item = input[i];
483
+ if (!item || typeof item !== "object")
484
+ break;
485
+ const role = typeof item?.role === "string" ? item.role : "";
486
+ if (role === "developer" || role === "system") {
487
+ const headText = extractMessageText(item.content);
488
+ if (headText.length <= MAX_HEAD_INSTRUCTION_CHARS) {
489
+ keepIndexes.add(i);
490
+ keptHead++;
491
+ }
492
+ else {
493
+ excludedHeadIndexes.add(i);
494
+ }
495
+ continue;
496
+ }
497
+ break;
498
+ }
499
+ for (let i = Math.max(0, input.length - safeMax); i < input.length; i++) {
500
+ if (excludedHeadIndexes.has(i))
501
+ continue;
502
+ keepIndexes.add(i);
503
+ }
504
+ const trimmed = input.filter((_item, index) => keepIndexes.has(index));
505
+ if (trimmed.length === 0)
506
+ return input;
507
+ if (input.length <= maxItems && excludedHeadIndexes.size === 0)
508
+ return input;
509
+ if (trimmed.length <= safeMax)
510
+ return trimmed;
511
+ return trimmed.slice(trimmed.length - safeMax);
512
+ }
513
+ function isTrivialLatestPrompt(text) {
514
+ const normalized = text.trim();
515
+ if (!normalized)
516
+ return false;
517
+ if (normalized.length > 220)
518
+ return false;
519
+ if (normalized.includes("\n"))
520
+ return false;
521
+ if (normalized.includes("```"))
522
+ return false;
523
+ if (/(^|\n)\s*(?:[-*]|\d+\.)\s+\S/m.test(normalized))
524
+ return false;
525
+ if (/https?:\/\//i.test(normalized))
526
+ return false;
527
+ if (/\|.+\|/.test(normalized))
528
+ return false;
529
+ return true;
530
+ }
531
+ function isStructurallyComplexPrompt(text) {
532
+ const normalized = text.trim();
533
+ if (!normalized)
534
+ return false;
535
+ if (normalized.includes("```"))
536
+ return true;
537
+ const lineCount = normalized.split(/\r?\n/).filter(Boolean).length;
538
+ if (lineCount >= 3)
539
+ return true;
540
+ if (/(^|\n)\s*(?:[-*]|\d+\.)\s+\S/m.test(normalized))
541
+ return true;
542
+ if (/\|.+\|/.test(normalized))
543
+ return true;
544
+ return false;
545
+ }
546
+ function isComplexFastSessionRequest(body, maxItems) {
547
+ const input = Array.isArray(body.input) ? body.input : [];
548
+ const lookbackWindow = Math.max(12, Math.floor(maxItems / 2));
549
+ const recentItems = input.slice(-lookbackWindow);
550
+ const userTexts = [];
551
+ for (const item of recentItems) {
552
+ if (!item || typeof item !== "object")
553
+ continue;
554
+ if (item.type === "function_call" || item.type === "function_call_output") {
555
+ return true;
556
+ }
557
+ const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
558
+ if (role !== "user")
559
+ continue;
560
+ const text = extractMessageText(item.content);
561
+ if (!text)
562
+ continue;
563
+ userTexts.push(text);
564
+ }
565
+ if (userTexts.length === 0)
566
+ return false;
567
+ const latestUserText = userTexts[userTexts.length - 1];
568
+ if (latestUserText && isTrivialLatestPrompt(latestUserText)) {
569
+ return false;
570
+ }
571
+ const recentUserTexts = userTexts.slice(-3);
572
+ if (recentUserTexts.some(isStructurallyComplexPrompt))
573
+ return true;
574
+ return false;
575
+ }
576
+ function getLatestUserText(input) {
577
+ if (!Array.isArray(input))
578
+ return undefined;
579
+ for (let i = input.length - 1; i >= 0; i--) {
580
+ const item = input[i];
581
+ if (!item || typeof item !== "object")
582
+ continue;
583
+ const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
584
+ if (role !== "user")
585
+ continue;
586
+ const text = extractMessageText(item.content);
587
+ if (text)
588
+ return text;
589
+ }
590
+ return undefined;
591
+ }
592
+ function compactInstructionsForFastSession(instructions, isTrivialTurn = false) {
593
+ const normalized = instructions.trim();
594
+ if (!normalized)
595
+ return instructions;
596
+ const MAX_FAST_INSTRUCTION_CHARS = isTrivialTurn ? 320 : 900;
597
+ if (normalized.length <= MAX_FAST_INSTRUCTION_CHARS) {
598
+ return instructions;
599
+ }
600
+ const splitIndex = normalized.lastIndexOf("\n", MAX_FAST_INSTRUCTION_CHARS);
601
+ const safeCutoff = splitIndex >= 180 ? splitIndex : MAX_FAST_INSTRUCTION_CHARS;
602
+ const compacted = normalized.slice(0, safeCutoff).trimEnd();
603
+ return `${compacted}\n\n[Fast session mode: keep answers concise, direct, and action-oriented. Do not output internal planning labels such as "Thinking:".]`;
604
+ }
605
+ /**
606
+ * Filter out OpenCode system prompts from input
607
+ * Used in CODEX_MODE to replace OpenCode prompts with Codex-OpenCode bridge
608
+ * @param input - Input array
609
+ * @returns Input array without OpenCode system prompts
610
+ */
611
+ export async function filterOpenCodeSystemPrompts(input) {
612
+ if (!Array.isArray(input))
613
+ return input;
614
+ // Fetch cached OpenCode prompt for verification
615
+ let cachedPrompt = null;
616
+ try {
617
+ cachedPrompt = await getOpenCodeCodexPrompt();
618
+ }
619
+ catch {
620
+ // If fetch fails, fallback to text-based detection only
621
+ // This is safe because we still have the "starts with" check
622
+ }
623
+ return filterOpenCodeSystemPromptsWithCachedPrompt(input, cachedPrompt);
624
+ }
625
+ /**
626
+ * Add Codex-OpenCode bridge message to input if tools are present
627
+ * @param input - Input array
628
+ * @param hasTools - Whether tools are present in request
629
+ * @returns Input array with bridge message prepended if needed
630
+ */
631
+ export function addCodexBridgeMessage(input, hasTools) {
632
+ if (!hasTools || !Array.isArray(input))
633
+ return input;
634
+ const bridgeMessage = {
635
+ type: "message",
636
+ role: "developer",
637
+ content: [
638
+ {
639
+ type: "input_text",
640
+ text: CODEX_OPENCODE_BRIDGE,
641
+ },
642
+ ],
643
+ };
644
+ return [bridgeMessage, ...input];
645
+ }
646
+ /**
647
+ * Add tool remapping message to input if tools are present
648
+ * @param input - Input array
649
+ * @param hasTools - Whether tools are present in request
650
+ * @returns Input array with tool remap message prepended if needed
651
+ */
652
+ export function addToolRemapMessage(input, hasTools) {
653
+ if (!hasTools || !Array.isArray(input))
654
+ return input;
655
+ const toolRemapMessage = {
656
+ type: "message",
657
+ role: "developer",
658
+ content: [
659
+ {
660
+ type: "input_text",
661
+ text: TOOL_REMAP_MESSAGE,
662
+ },
663
+ ],
664
+ };
665
+ return [toolRemapMessage, ...input];
666
+ }
667
+ /**
668
+ * Transform request body for Codex API
669
+ *
670
+ * NOTE: Configuration follows Codex CLI patterns instead of opencode defaults:
671
+ * - opencode sets textVerbosity="low" for gpt-5, but Codex CLI uses "medium"
672
+ * - opencode excludes gpt-5-codex from reasoning configuration
673
+ * - This plugin uses store=false (stateless), requiring encrypted reasoning content
674
+ *
675
+ * @param body - Original request body
676
+ * @param codexInstructions - Codex system instructions
677
+ * @param userConfig - User configuration from loader
678
+ * @param codexMode - Enable CODEX_MODE (bridge prompt instead of tool remap) - defaults to true
679
+ * @param fastSession - Force low-latency output settings for faster responses
680
+ * @returns Transformed request body
681
+ */
682
+ export async function transformRequestBody(body, codexInstructions, userConfig = { global: {}, models: {} }, codexMode = true, fastSession = false, fastSessionStrategy = "hybrid", fastSessionMaxInputItems = 30) {
683
+ const originalModel = body.model;
684
+ const normalizedModel = normalizeModel(body.model);
685
+ // Get model-specific configuration using ORIGINAL model name (config key)
686
+ // This allows per-model options like "gpt-5-codex-low" to work correctly
687
+ const lookupModel = originalModel || normalizedModel;
688
+ const modelConfig = getModelConfig(lookupModel, userConfig);
689
+ // Debug: Log which config was resolved
690
+ logDebug(`Model config lookup: "${lookupModel}" → normalized to "${normalizedModel}" for API`, {
691
+ hasModelSpecificConfig: !!userConfig.models?.[lookupModel],
692
+ resolvedConfig: modelConfig,
693
+ });
694
+ // Normalize model name for API call
695
+ body.model = normalizedModel;
696
+ const shouldUseNormalizedReasoningModel = normalizedModel === "gpt-5-codex" &&
697
+ lookupModel.toLowerCase().includes("codex");
698
+ const reasoningModel = shouldUseNormalizedReasoningModel
699
+ ? normalizedModel
700
+ : lookupModel;
701
+ const shouldApplyFastSessionTuning = fastSession &&
702
+ (fastSessionStrategy === "always" ||
703
+ !isComplexFastSessionRequest(body, fastSessionMaxInputItems));
704
+ const latestUserText = getLatestUserText(body.input);
705
+ const isTrivialTurn = isTrivialLatestPrompt(latestUserText ?? "");
706
+ const shouldDisableToolsForTrivialTurn = shouldApplyFastSessionTuning &&
707
+ isTrivialTurn;
708
+ const shouldPreferLatestUserOnly = shouldApplyFastSessionTuning && isTrivialTurn;
709
+ // Codex required fields
710
+ // ChatGPT backend REQUIRES store=false (confirmed via testing)
711
+ body.store = false;
712
+ // Always set stream=true for API - response handling detects original intent
713
+ body.stream = true;
714
+ // Clean up tool definitions (implement strict "require" logic)
715
+ // Filters invalid required fields and ensures empty objects have placeholders
716
+ const collaborationMode = detectCollaborationMode(body);
717
+ if (body.tools) {
718
+ if (shouldDisableToolsForTrivialTurn) {
719
+ body.tools = undefined;
720
+ }
721
+ }
722
+ if (body.tools) {
723
+ body.tools = cleanupToolDefinitions(body.tools);
724
+ body.tools = sanitizePlanOnlyTools(body.tools, collaborationMode);
725
+ }
726
+ body.instructions = shouldApplyFastSessionTuning
727
+ ? compactInstructionsForFastSession(codexInstructions, isTrivialTurn)
728
+ : codexInstructions;
729
+ // Prompt caching relies on the host providing a stable prompt_cache_key
730
+ // (OpenCode passes its session identifier). We no longer synthesize one here.
731
+ // Filter and transform input
732
+ if (body.input && Array.isArray(body.input)) {
733
+ let inputItems = body.input;
734
+ if (shouldApplyFastSessionTuning) {
735
+ inputItems =
736
+ trimInputForFastSession(inputItems, fastSessionMaxInputItems, {
737
+ preferLatestUserOnly: shouldPreferLatestUserOnly,
738
+ }) ?? inputItems;
739
+ }
740
+ // Debug: Log original input message IDs before filtering
741
+ const originalIds = inputItems
742
+ .filter((item) => item.id)
743
+ .map((item) => item.id);
744
+ if (originalIds.length > 0) {
745
+ logDebug(`Filtering ${originalIds.length} message IDs from input:`, originalIds);
746
+ }
747
+ inputItems = filterInput(inputItems) ?? inputItems;
748
+ body.input = inputItems;
749
+ // istanbul ignore next -- filterInput always removes IDs; this is defensive debug code
750
+ const remainingIds = (body.input || [])
751
+ .filter((item) => item.id)
752
+ .map((item) => item.id);
753
+ // istanbul ignore if -- filterInput always removes IDs; defensive debug warning
754
+ if (remainingIds.length > 0) {
755
+ logWarn(`WARNING: ${remainingIds.length} IDs still present after filtering:`, remainingIds);
756
+ }
757
+ else if (originalIds.length > 0) {
758
+ logDebug(`Successfully removed all ${originalIds.length} message IDs`);
759
+ }
760
+ if (codexMode) {
761
+ // CODEX_MODE: Remove OpenCode system prompt, add bridge prompt
762
+ body.input = await filterOpenCodeSystemPrompts(body.input);
763
+ body.input = addCodexBridgeMessage(body.input, !!body.tools);
764
+ }
765
+ else {
766
+ // DEFAULT MODE: Keep original behavior with tool remap message
767
+ body.input = addToolRemapMessage(body.input, !!body.tools);
768
+ }
769
+ // Handle orphaned function_call_output items (where function_call was an item_reference that got filtered)
770
+ // Instead of removing orphans (which causes infinite loops as LLM loses tool results),
771
+ // convert them to messages to preserve context while avoiding API errors
772
+ if (body.input) {
773
+ body.input = normalizeOrphanedToolOutputs(body.input);
774
+ body.input = injectMissingToolOutputs(body.input);
775
+ }
776
+ }
777
+ // Configure reasoning (prefer existing body/provider options, then config defaults)
778
+ const reasoningConfig = resolveReasoningConfig(reasoningModel, modelConfig, body);
779
+ body.reasoning = {
780
+ ...body.reasoning,
781
+ ...reasoningConfig,
782
+ };
783
+ // Configure text verbosity (support user config)
784
+ // Default: "medium" (matches Codex CLI default for all GPT-5 models)
785
+ body.text = {
786
+ ...body.text,
787
+ verbosity: resolveTextVerbosity(modelConfig, body),
788
+ };
789
+ if (shouldApplyFastSessionTuning) {
790
+ // In fast-session mode, prioritize speed by clamping to minimum reasoning + verbosity.
791
+ // getReasoningConfig normalizes unsupported values per model family.
792
+ const fastReasoning = getReasoningConfig(reasoningModel, {
793
+ reasoningEffort: "none",
794
+ reasoningSummary: "auto",
795
+ });
796
+ body.reasoning = {
797
+ ...body.reasoning,
798
+ ...fastReasoning,
799
+ };
800
+ body.text = {
801
+ ...body.text,
802
+ verbosity: "low",
803
+ };
804
+ }
805
+ // Add include for encrypted reasoning content
806
+ // Default: ["reasoning.encrypted_content"] (required for stateless operation with store=false)
807
+ // This allows reasoning context to persist across turns without server-side storage
808
+ body.include = resolveInclude(modelConfig, body);
809
+ // Remove unsupported parameters
810
+ body.max_output_tokens = undefined;
811
+ body.max_completion_tokens = undefined;
812
+ return body;
813
+ }
814
+ //# sourceMappingURL=request-transformer.js.map