@oh-my-pi/pi-ai 14.1.2 → 14.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +57 -2
  2. package/package.json +5 -3
  3. package/src/auth-storage.ts +18 -6
  4. package/src/model-thinking.ts +48 -2
  5. package/src/models.json +2608 -388
  6. package/src/provider-models/openai-compat.ts +97 -224
  7. package/src/providers/amazon-bedrock.ts +103 -34
  8. package/src/providers/anthropic.ts +44 -22
  9. package/src/providers/azure-openai-responses.ts +4 -4
  10. package/src/providers/cursor.ts +18 -12
  11. package/src/providers/gitlab-duo.ts +2 -21
  12. package/src/providers/kimi.ts +2 -22
  13. package/src/providers/openai-codex-responses.ts +194 -23
  14. package/src/providers/openai-completions.ts +22 -13
  15. package/src/providers/openai-responses-shared.ts +143 -18
  16. package/src/providers/openai-responses.ts +91 -15
  17. package/src/providers/shared/error-message.ts +21 -0
  18. package/src/providers/synthetic.ts +2 -22
  19. package/src/stream.ts +1 -7
  20. package/src/types.ts +34 -0
  21. package/src/usage/kimi.ts +1 -11
  22. package/src/usage/openai-codex.ts +1 -11
  23. package/src/usage/shared.ts +10 -0
  24. package/src/utils/anthropic-auth.ts +1 -7
  25. package/src/utils/foundry.ts +8 -0
  26. package/src/utils/http-inspector.ts +9 -2
  27. package/src/utils/idle-iterator.ts +29 -54
  28. package/src/utils/oauth/api-key-login.ts +87 -0
  29. package/src/utils/oauth/cerebras.ts +15 -58
  30. package/src/utils/oauth/google-antigravity.ts +11 -86
  31. package/src/utils/oauth/google-gemini-cli.ts +11 -89
  32. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  33. package/src/utils/oauth/moonshot.ts +15 -58
  34. package/src/utils/oauth/nanogpt.ts +14 -50
  35. package/src/utils/oauth/openai-codex.ts +3 -3
  36. package/src/utils/oauth/synthetic.ts +15 -59
  37. package/src/utils/oauth/together.ts +15 -58
  38. package/src/utils/oauth/zenmux.ts +14 -50
  39. package/src/utils/retry.ts +77 -0
  40. package/src/utils/schema/CONSTRAINTS.md +1 -0
  41. package/src/utils/schema/strict-mode.ts +10 -0
  42. package/src/utils/tool-choice.ts +7 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.2.1] - 2026-04-24
6
+
7
+ ### Fixed
8
+
9
+ - Fixed OpenAI Codex Spark OAuth selection to require a verified ChatGPT Pro account instead of falling back to Plus or unknown-plan accounts.
10
+
11
+ ## [14.2.0] - 2026-04-23
12
+
13
+ ### Added
14
+
15
+ - Added `gpt-5.5` to the built-in model catalog for both OpenAI Responses (`openai`) and local `litellm` (`openai-completions`) providers
16
+ - Added `gpt-image-2` to the `litellm` built-in model catalog
17
+ - Added `isCopilotTransientModelError()` and `callWithCopilotModelRetry()` helpers in `utils/retry` that detect GitHub Copilot's intermittent `HTTP 400 model_not_supported` responses for preview models (`gpt-5.3-codex`, `gpt-5.4`, `gpt-5.4-mini`, ...) and retry the request up to three times with backoff. OpenAI Responses, OpenAI Completions, and Anthropic provider paths now participate in this retry when the model is served through Copilot.
18
+ - Added OpenAI Responses custom-tool grammar support for Codex-style `apply_patch` calls, including freeform streaming, history replay, and forced tool-choice mapping to the custom wire name.
19
+
20
+ ### Changed
21
+
22
+ - Updated built-in model metadata with revised `contextWindow`, `maxTokens`, and pricing values for existing entries
23
+ - Changed generated model policies to assign `applyPatchToolType: "freeform"` for first-party GPT-5 OpenAI Responses and Codex models, so regenerated `models.json` preserves the `apply_patch` custom-tool metadata.
24
+ - Renamed `rewriteCopilotAuthError` to `rewriteCopilotError` and extended it to rewrite `HTTP 400 model_not_supported` after retries are exhausted with guidance about Copilot's OAuth-client-specific rollout gap (see opencode#13313).
25
+
26
+ ### Fixed
27
+
28
+ - Fixed Amazon Bedrock proxy handling to honor lowercase `http_proxy`, `https_proxy`, and `all_proxy` environment variables when using HTTP/1 fallback
29
+ - Fixed Amazon Bedrock streaming behind corporate HTTP proxies by using a proxy-aware HTTP/1 transport when `HTTPS_PROXY`, `HTTP_PROXY`, or `ALL_PROXY` is configured, including AWS SSO credential calls.
30
+ - Fixed Amazon Bedrock requests to retry once with HTTP/1 when the AWS SDK's default HTTP/2 transport fails before streaming begins.
31
+ - Fixed OpenAI Responses streaming to display thinking tokens from local providers (llama.cpp, etc.) that send raw `reasoning_text.delta` events and empty `summary` arrays in `output_item.done`. Previously, thinking content was silently dropped during streaming while non-streaming mode worked correctly.
32
+ - Synced the bundled OpenCode Go catalog with the current docs so `kimi-k2.6`, `mimo-v2.5`, and `mimo-v2.5-pro` appear in offline/default model lists.
33
+
34
+ ## [14.1.3] - 2026-04-17
35
+
36
+ ### Fixed
37
+
38
+ - Preserved user-provided `session_id` and `x-client-request-id` headers in OpenAI Responses requests instead of overriding them with automatic session-derived values
39
+ - Stopped sending `session_id` and `x-client-request-id` headers for OpenAI Responses requests when `cacheRetention` is set to `none`
40
+ - Fixed direct OpenAI Responses requests to send `session_id` and `x-client-request-id` from the same session-derived value as `prompt_cache_key`, improving prompt cache affinity for append-only sessions
5
41
  ## [14.1.1] - 2026-04-14
6
42
 
7
43
  ### Added
@@ -21,6 +57,7 @@
21
57
  - Fixed strict tool schema enforcement to preserve `additionalProperties: false` and required keys for reused nested object schemas, preventing invalid `todo_write` function schemas in Codex/OpenAI requests
22
58
 
23
59
  ## [14.1.0] - 2026-04-11
60
+
24
61
  ### Added
25
62
 
26
63
  - Added `accountId` to usage report metadata
@@ -37,6 +74,7 @@
37
74
  ## [14.0.5] - 2026-04-11
38
75
 
39
76
  ### Changed
77
+
40
78
  - Replaced GitHub Copilot authentication from VSCode extension impersonation to the opencode OAuth flow, eliminating TOS concerns. Existing users will need to re-authenticate once with `/login github-copilot`.
41
79
  - Simplified Copilot token handling: GitHub OAuth token is used directly for all API requests (no JWT exchange or refresh cycle).
42
80
  - Changed GitHub Copilot API base URL from `api.individual.githubcopilot.com` to `api.githubcopilot.com`.
@@ -48,6 +86,7 @@
48
86
  - Fixed GitHub Copilot `/models` discovery to unwrap structured OAuth credentials before sending the bearer token, preserving dynamic catalog refresh for OAuth-backed callers.
49
87
 
50
88
  ### Removed
89
+
51
90
  - Removed Copilot JWT proxy-ep base URL resolution (no longer needed with opencode auth).
52
91
 
53
92
  ## [14.0.3] - 2026-04-09
@@ -57,6 +96,7 @@
57
96
  - Fixed Ollama discovery cache normalization so cached models upgrade to the OpenAI Responses transport after the provider change
58
97
 
59
98
  ## [14.0.0] - 2026-04-08
99
+
60
100
  ### Breaking Changes
61
101
 
62
102
  - Removed `coerceNullStrings` function and its automatic null-string coercion behavior from JSON parsing
@@ -79,6 +119,7 @@
79
119
  - Fixed Anthropic streaming to suppress transient SDK console errors for malformed SSE keep-alive frames so the TUI only shows surfaced provider errors
80
120
 
81
121
  - Added environment-based credential fallback for the OpenAI Codex provider.
122
+
82
123
  ## [13.17.6] - 2026-04-01
83
124
 
84
125
  ### Fixed
@@ -86,6 +127,7 @@
86
127
  - Fixed Anthropic first-event timeouts to exclude stream connection setup from the watchdog, preserve timeout-specific retry classification after local aborts, and reset retry state cleanly between attempts
87
128
 
88
129
  ## [13.17.5] - 2026-04-01
130
+
89
131
  ### Changed
90
132
 
91
133
  - Increased default first-event timeout from 15s to 45s to better accommodate longer request setup times
@@ -124,6 +166,7 @@
124
166
  - Added Vercel AI Gateway to `/login` providers for interactive API key setup
125
167
 
126
168
  ### Fixed
169
+
127
170
  - Fixed `omp commit` failing with HTTP 400 errors when using reasoning-enabled models on OpenAI-compatible endpoints that don't support the `developer` role (e.g., GitHub Copilot, custom proxies). Now falls back to `system` role when `developer` is unsupported.
128
171
 
129
172
  ## [13.17.0] - 2026-03-30
@@ -148,6 +191,7 @@
148
191
  - Fixed normalizeAnthropicBaseUrl returning empty string instead of undefined when baseUrl is empty
149
192
 
150
193
  ## [13.16.4] - 2026-03-28
194
+
151
195
  ### Added
152
196
 
153
197
  - Added support for Groq Compound and Compound Mini models with extended context window (131K tokens) and configurable thinking levels
@@ -168,6 +212,7 @@
168
212
  - Updated OpenRouter Claude 3.5 Sonnet pricing: input from 0.45 to 0.42, cache read from 0.225 to 0.21
169
213
 
170
214
  ## [13.16.3] - 2026-03-28
215
+
171
216
  ### Changed
172
217
 
173
218
  - Modified OAuth credential saving to preserve unrelated identities instead of replacing all credentials for a provider
@@ -193,6 +238,7 @@
193
238
  - Fixed `parseRateLimitReason` not recognizing "usage limit" in Codex error messages, causing incorrect fallback to `UNKNOWN` classification instead of `QUOTA_EXHAUSTED`
194
239
 
195
240
  ## [13.14.2] - 2026-03-21
241
+
196
242
  ### Changed
197
243
 
198
244
  - Updated thinking configuration format from `levels` array to `minLevel` and `maxLevel` properties for improved clarity
@@ -215,13 +261,14 @@
215
261
  - Added bundled GPT-5.4 mini model metadata for OpenAI, OpenAI Codex, and GitHub Copilot, including low-to-xhigh thinking support and GitHub Copilot premium multiplier metadata
216
262
  - Added bundled GPT-5.4 nano model metadata for OpenAI and OpenAI Codex, including low-to-xhigh thinking support
217
263
 
218
-
219
264
  ## [13.13.2] - 2026-03-18
265
+
220
266
  ### Changed
221
267
 
222
268
  - Modified tool result handling for aborted assistant messages to preserve existing tool results when already recorded, instead of always replacing them with synthetic 'aborted' results
223
269
 
224
270
  ## [13.13.0] - 2026-03-18
271
+
225
272
  ### Changed
226
273
 
227
274
  - Changed tool argument validation to always normalize optional null values before type coercion, ensuring consistent handling of LLM-generated 'null' strings
@@ -232,6 +279,7 @@
232
279
  - Improved type safety of `validateToolCall` and `validateToolArguments` functions by returning properly typed `ToolCall["arguments"]` instead of `any`
233
280
 
234
281
  ## [13.12.9] - 2026-03-17
282
+
235
283
  ### Changed
236
284
 
237
285
  - Extracted OpenAI compatibility detection and resolution logic into dedicated `openai-completions-compat` module for improved maintainability and reusability
@@ -281,6 +329,7 @@
281
329
  - Fixed auth schema V0-to-V1 migration crash when the V0 table lacks a `disabled` column
282
330
 
283
331
  ## [13.11.0] - 2026-03-12
332
+
284
333
  ### Added
285
334
 
286
335
  - Added support for Parallel AI provider with API key authentication
@@ -296,6 +345,7 @@
296
345
  - Improved retry logic to handle HTTP/2 stream errors and internal_error responses from Anthropic API
297
346
 
298
347
  ## [13.9.16] - 2026-03-10
348
+
299
349
  ### Added
300
350
 
301
351
  - Support for `onPayload` callback to replace provider request payloads before sending, enabling request interception and modification
@@ -318,11 +368,13 @@
318
368
  - Fixed handling of malformed JSON messages in websocket streams to trigger immediate fallback to SSE without retry attempts
319
369
 
320
370
  ## [13.9.13] - 2026-03-10
371
+
321
372
  ### Added
322
373
 
323
374
  - Added `isSpecialServiceTier` utility function to validate OpenAI service tier values
324
375
 
325
376
  ## [13.9.12] - 2026-03-09
377
+
326
378
  ### Added
327
379
 
328
380
  - Added Tavily web search provider support with API key authentication
@@ -355,11 +407,13 @@
355
407
  - Fixed auth storage to preserve newer recorded schema versions when opened by older binaries
356
408
 
357
409
  ## [13.9.8] - 2026-03-08
410
+
358
411
  ### Fixed
359
412
 
360
413
  - Fixed WebSocket stream fallback logic to safely replay buffered output over SSE when WebSocket fails after partial content has been streamed
361
414
 
362
415
  ## [13.9.4] - 2026-03-07
416
+
363
417
  ### Changed
364
418
 
365
419
  - Simplified API key credential storage to always replace existing credentials on re-login instead of accumulating multiple keys
@@ -372,6 +426,7 @@
372
426
  - Fixed Cerebras model compatibility by preventing `stream_options` usage requests in chat completions
373
427
 
374
428
  ## [13.9.3] - 2026-03-07
429
+
375
430
  ### Breaking Changes
376
431
 
377
432
  - Changed `reasoning` parameter from `ThinkingLevel | undefined` to `Effort | undefined` in `SimpleStreamOptions`; 'off' is no longer valid (omit the field instead)
@@ -2042,4 +2097,4 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
2042
2097
 
2043
2098
  ## [0.9.4] - 2025-11-26
2044
2099
 
2045
- Initial release with multi-provider LLM support.
2100
+ Initial release with multi-provider LLM support.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "14.1.2",
4
+ "version": "14.2.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -43,16 +43,18 @@
43
43
  "dependencies": {
44
44
  "@anthropic-ai/sdk": "^0.78",
45
45
  "@aws-sdk/client-bedrock-runtime": "^3",
46
+ "@aws-sdk/credential-provider-node": "^3",
46
47
  "@bufbuild/protobuf": "^2.11",
47
48
  "@google/genai": "^1.43",
48
- "@oh-my-pi/pi-natives": "14.1.2",
49
- "@oh-my-pi/pi-utils": "14.1.2",
49
+ "@oh-my-pi/pi-natives": "14.2.1",
50
+ "@oh-my-pi/pi-utils": "14.2.1",
50
51
  "@sinclair/typebox": "^0.34",
51
52
  "@smithy/node-http-handler": "^4.4",
52
53
  "ajv": "^8.18",
53
54
  "ajv-formats": "^3.0",
54
55
  "openai": "^6.25",
55
56
  "partial-json": "^0.1",
57
+ "proxy-agent": "^6.5",
56
58
  "zod": "4.3.6"
57
59
  },
58
60
  "devDependencies": {
@@ -208,6 +208,10 @@ function getOpenAICodexPlanPriority(report: UsageReport | null): number {
208
208
  return planType.includes("pro") ? 0 : 2;
209
209
  }
210
210
 
211
+ function hasOpenAICodexProPlan(report: UsageReport | null): boolean {
212
+ return getUsagePlanType(report)?.includes("pro") === true;
213
+ }
214
+
211
215
  function resolveDefaultUsageProvider(provider: Provider): UsageProvider | undefined {
212
216
  return DEFAULT_USAGE_PROVIDER_MAP.get(provider);
213
217
  }
@@ -1656,14 +1660,14 @@ export class AuthStorage {
1656
1660
  const providerKey = this.#getProviderTypeKey(provider, "oauth");
1657
1661
  const order = this.#getCredentialOrder(providerKey, sessionId, credentials.length);
1658
1662
  const strategy = this.#rankingStrategyResolver?.(provider);
1659
- const checkUsage = strategy !== undefined && credentials.length > 1;
1663
+ const requiresProModel = requiresOpenAICodexProModel(provider, options?.modelId);
1664
+ const checkUsage = strategy !== undefined && (credentials.length > 1 || requiresProModel);
1660
1665
  const sessionCredential = this.#getSessionCredential(provider, sessionId);
1661
1666
  const sessionPreferredIndex = sessionCredential?.type === "oauth" ? sessionCredential.index : undefined;
1662
1667
  // Skip ranking only when the session already has a working preferred credential — re-ranking
1663
1668
  // mid-session causes account switches that cold-start the server-side prompt cache. New sessions
1664
1669
  // (no preference) and sessions whose preferred is blocked still rank, so we pick the account
1665
1670
  // with the most headroom proactively and fall back intelligently when rate-limited.
1666
- const requiresProModel = requiresOpenAICodexProModel(provider, options?.modelId);
1667
1671
  const sessionPreferredIsAvailable =
1668
1672
  sessionPreferredIndex !== undefined && !this.#isCredentialBlocked(providerKey, sessionPreferredIndex);
1669
1673
  const shouldRank = checkUsage && (!sessionPreferredIsAvailable || requiresProModel);
@@ -1777,10 +1781,11 @@ export class AuthStorage {
1777
1781
  return undefined;
1778
1782
  }
1779
1783
 
1784
+ const requiresProModel = requiresOpenAICodexProModel(provider, options?.modelId);
1780
1785
  let usage: UsageReport | null = null;
1781
1786
  let usageChecked = false;
1782
1787
 
1783
- if (checkUsage && !allowBlocked) {
1788
+ if ((checkUsage && !allowBlocked) || requiresProModel) {
1784
1789
  if (usagePrechecked) {
1785
1790
  usage = prefetchedUsage;
1786
1791
  usageChecked = true;
@@ -1791,7 +1796,10 @@ export class AuthStorage {
1791
1796
  });
1792
1797
  usageChecked = true;
1793
1798
  }
1794
- if (usage && this.#isUsageLimitReached(usage)) {
1799
+ if (requiresProModel && !hasOpenAICodexProPlan(usage)) {
1800
+ return undefined;
1801
+ }
1802
+ if (checkUsage && !allowBlocked && usage && this.#isUsageLimitReached(usage)) {
1795
1803
  const resetAtMs = this.#getUsageResetAtMs(usage, Date.now());
1796
1804
  this.#markCredentialBlocked(
1797
1805
  providerKey,
@@ -1829,15 +1837,19 @@ export class AuthStorage {
1829
1837
  enterpriseUrl: result.newCredentials.enterpriseUrl ?? selection.credential.enterpriseUrl,
1830
1838
  };
1831
1839
  this.#replaceCredentialAt(provider, selection.index, updated);
1832
- if (checkUsage && !allowBlocked) {
1840
+ if ((checkUsage && !allowBlocked) || requiresProModel) {
1833
1841
  const sameAccount = selection.credential.accountId === updated.accountId;
1834
1842
  if (!usageChecked || !sameAccount) {
1835
1843
  usage = await this.#getUsageReport(provider, updated, {
1836
1844
  ...options,
1837
1845
  timeoutMs: this.#usageRequestTimeoutMs,
1838
1846
  });
1847
+ usageChecked = true;
1848
+ }
1849
+ if (requiresProModel && !hasOpenAICodexProPlan(usage)) {
1850
+ return undefined;
1839
1851
  }
1840
- if (usage && this.#isUsageLimitReached(usage)) {
1852
+ if (checkUsage && !allowBlocked && usage && this.#isUsageLimitReached(usage)) {
1841
1853
  const resetAtMs = this.#getUsageResetAtMs(usage, Date.now());
1842
1854
  this.#markCredentialBlocked(
1843
1855
  providerKey,
@@ -48,6 +48,14 @@ const CODEX_GPT_5_4_PRIORITY_BY_VARIANT: Partial<Record<OpenAIVariant, number>>
48
48
  nano: 2,
49
49
  };
50
50
 
51
+ const COPILOT_GENERATED_LIMITS: Record<string, { contextWindow: number; maxTokens: number }> = {
52
+ "claude-opus-4.6": { contextWindow: 168000, maxTokens: 32000 },
53
+ "gpt-5.2": { contextWindow: 272000, maxTokens: 128000 },
54
+ "gpt-5.4": { contextWindow: 272000, maxTokens: 128000 },
55
+ "gpt-5.4-mini": { contextWindow: 272000, maxTokens: 128000 },
56
+ "grok-code-fast-1": { contextWindow: 192000, maxTokens: 64000 },
57
+ };
58
+
51
59
  interface GeminiModel {
52
60
  family: "gemini";
53
61
  kind: GeminiKind;
@@ -258,7 +266,7 @@ export function mapEffortToGoogleThinkingLevel<TApi extends Api>(
258
266
  export function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(
259
267
  model: ApiModel<TApi>,
260
268
  effort: Effort,
261
- ): "low" | "medium" | "high" | "max" {
269
+ ): "low" | "medium" | "high" | "xhigh" | "max" {
262
270
  switch (requireSupportedEffort(model, effort)) {
263
271
  case Effort.Minimal:
264
272
  case Effort.Low:
@@ -268,12 +276,34 @@ export function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(
268
276
  case Effort.High:
269
277
  return "high";
270
278
  case Effort.XHigh:
271
- return "max";
279
+ // Opus 4.7+ introduced a distinct "xhigh" effort level (between "high" and "max").
280
+ // The Anthropic docs scope this to the Messages API only, so Bedrock Converse and
281
+ // older adaptive-thinking Opus 4.6 models keep the legacy "max" alias.
282
+ return anthropicModelHasRealXHighEffort(model) ? "xhigh" : "max";
272
283
  }
273
284
  }
274
285
 
286
+ function anthropicModelHasRealXHighEffort<TApi extends Api>(model: ApiModel<TApi>): boolean {
287
+ if (model.api !== "anthropic-messages") return false;
288
+ const parsedModel = parseKnownModel(model.id);
289
+ if (parsedModel.family !== "anthropic" || parsedModel.kind !== "opus") return false;
290
+ return semverGte(parsedModel.version, "4.7");
291
+ }
292
+
275
293
  function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
294
+ const copilotLimits = model.provider === "github-copilot" ? COPILOT_GENERATED_LIMITS[model.id] : undefined;
295
+ if (copilotLimits) {
296
+ model.contextWindow = copilotLimits.contextWindow;
297
+ model.maxTokens = copilotLimits.maxTokens;
298
+ }
299
+
276
300
  const parsedModel = parseKnownModel(model.id);
301
+ const applyPatchToolType = inferGeneratedApplyPatchToolType(model, parsedModel);
302
+ if (applyPatchToolType) {
303
+ model.applyPatchToolType = applyPatchToolType;
304
+ } else {
305
+ delete model.applyPatchToolType;
306
+ }
277
307
  if (parsedModel.family === "anthropic") {
278
308
  applyAnthropicCatalogPolicy(model, parsedModel);
279
309
  }
@@ -298,6 +328,22 @@ function applyAnthropicCatalogPolicy(model: ApiModel<Api>, parsedModel: Anthropi
298
328
  }
299
329
  }
300
330
 
331
+ function inferGeneratedApplyPatchToolType(
332
+ model: ApiModel<Api>,
333
+ parsedModel: ParsedModel,
334
+ ): ApiModel<Api>["applyPatchToolType"] {
335
+ if (parsedModel.family !== "openai" || parsedModel.version.major !== 5) {
336
+ return undefined;
337
+ }
338
+ if (model.provider === "openai" && model.api === "openai-responses") {
339
+ return "freeform";
340
+ }
341
+ if (model.provider === "openai-codex" && model.api === "openai-codex-responses") {
342
+ return "freeform";
343
+ }
344
+ return undefined;
345
+ }
346
+
301
347
  function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel): void {
302
348
  // Codex models: 400K figure includes output budget; input window is 272K.
303
349
  if (parsedModel.variant.startsWith("codex") && parsedModel.variant !== "codex-spark") {