@bitkyc08/opencodex 2.91.0 → 2.92.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 (148) hide show
  1. package/bin/ocx.mjs +37 -3
  2. package/gui/dist/assets/{App-nZ34cR5G.js → App-sUf_8afJ.js} +18 -18
  3. package/gui/dist/assets/{Tray-BuLy7hL1.js → Tray-BSsxznDm.js} +1 -1
  4. package/gui/dist/assets/index-BlFE_VB_.js +94 -0
  5. package/gui/dist/assets/quota-summary-BRlbXgw0.js +1 -0
  6. package/gui/dist/index.html +1 -1
  7. package/gui/dist/provider-icons/workbuddy-color.svg +29 -0
  8. package/package.json +4 -3
  9. package/src/adapters/google-vertex-endpoint.ts +9 -1
  10. package/src/adapters/google-vertex-key-project.ts +223 -0
  11. package/src/adapters/google.ts +13 -3
  12. package/src/adapters/openai-chat/tool-name-registry.ts +3 -1
  13. package/src/adapters/openai-chat.ts +2 -2
  14. package/src/adapters/openai-responses/request-strips.ts +60 -0
  15. package/src/adapters/registry.ts +5 -0
  16. package/src/adapters/workbuddy-schema.ts +61 -0
  17. package/src/adapters/workbuddy.ts +68 -0
  18. package/src/bridge/response-json.ts +1 -1
  19. package/src/bridge/sse.ts +1 -1
  20. package/src/chat/outbound.ts +11 -4
  21. package/src/cli/capabilities-agents-routing.ts +3 -3
  22. package/src/cli/codex-cli-update.ts +8 -0
  23. package/src/cli/combo-result.ts +6 -0
  24. package/src/cli/combo-stats.ts +3 -0
  25. package/src/cli/combo.ts +66 -42
  26. package/src/cli/launcher-context.ts +49 -2
  27. package/src/cli/link.ts +45 -32
  28. package/src/cli/provider.ts +2 -1
  29. package/src/cli/usage-compare.ts +68 -12
  30. package/src/client/link-ingress.ts +1 -1
  31. package/src/client/link-readiness.ts +97 -0
  32. package/src/client/link-relay-transport.ts +79 -11
  33. package/src/client/link-relay.ts +74 -15
  34. package/src/client/link-tunnel.ts +45 -18
  35. package/src/client/machine-listener.ts +20 -2
  36. package/src/client/runtime.ts +20 -2
  37. package/src/codex/account-store.ts +17 -3
  38. package/src/codex/app-server-restart-service.ts +6 -5
  39. package/src/codex/catalog/effort.ts +9 -2
  40. package/src/codex/catalog/gather-capture.ts +4 -2
  41. package/src/codex/catalog/provider-models.ts +14 -8
  42. package/src/codex/chatgpt-refresh-failure.ts +6 -2
  43. package/src/codex/cli-install-provenance.ts +418 -0
  44. package/src/codex/cli-installation-targets.ts +5 -1
  45. package/src/codex/cli-update-plan.ts +43 -16
  46. package/src/codex/desktop-app/picker-freshness.ts +10 -8
  47. package/src/codex/desktop-compatibility/usage-activation.ts +2 -1
  48. package/src/codex/desktop-compatibility/usage-controller.ts +7 -1
  49. package/src/codex/pool-refresh-backoff.ts +3 -1
  50. package/src/codex/user-identity.ts +35 -6
  51. package/src/codex/windows-current-sid.ts +162 -0
  52. package/src/combos/jev-quota-activation.ts +5 -1
  53. package/src/combos/jev-quota-route.ts +1 -1
  54. package/src/combos/jev-quota.ts +9 -1
  55. package/src/combos/jev-session-pin-config.ts +51 -0
  56. package/src/combos/jev.ts +6 -0
  57. package/src/combos/types.ts +19 -0
  58. package/src/config/provider-validation.ts +7 -0
  59. package/src/config/schema/leaf-validators.ts +1 -0
  60. package/src/generated/compatibility-version.json +194 -122
  61. package/src/lib/decision-quota-sink.ts +10 -0
  62. package/src/lib/translator-budget.ts +4 -4
  63. package/src/lib/upstream-retry.ts +4 -1
  64. package/src/link/diagnostics.ts +193 -0
  65. package/src/link/relay-auth.ts +6 -0
  66. package/src/link/status-projection.ts +39 -0
  67. package/src/link/supervisor.ts +45 -12
  68. package/src/link/tunnel-history.ts +109 -0
  69. package/src/link/tunnel-state.ts +80 -17
  70. package/src/messaging/remote-files.ts +24 -1
  71. package/src/oauth/generic-account-failover.ts +91 -7
  72. package/src/oauth/index.ts +15 -0
  73. package/src/oauth/model-discovery-auth.ts +5 -0
  74. package/src/oauth/store.ts +4 -1
  75. package/src/oauth/token-guardian.ts +7 -0
  76. package/src/oauth/types.ts +9 -0
  77. package/src/oauth/workbuddy.ts +139 -0
  78. package/src/providers/antigravity-quota-identity.ts +37 -0
  79. package/src/providers/base-url-choices.ts +8 -5
  80. package/src/providers/derive.ts +23 -3
  81. package/src/providers/model-discovery.ts +2 -0
  82. package/src/providers/model-rename-fields.ts +1 -0
  83. package/src/providers/quota/account-cache.ts +3 -2
  84. package/src/providers/quota/antigravity.ts +25 -14
  85. package/src/providers/quota/report-cache.ts +13 -4
  86. package/src/providers/quota/vendor-probes-key.ts +60 -0
  87. package/src/providers/quota/workbuddy.ts +83 -0
  88. package/src/providers/quota-decision-antigravity.ts +64 -0
  89. package/src/providers/quota-decision-publication.ts +11 -0
  90. package/src/providers/quota-decision-snapshot.ts +21 -4
  91. package/src/providers/quota-types.ts +2 -0
  92. package/src/providers/quota.ts +3 -1
  93. package/src/providers/registry/entries-core.ts +9 -0
  94. package/src/providers/registry/entries-extended.ts +20 -7
  95. package/src/providers/registry/model-ids.ts +3 -0
  96. package/src/providers/registry/model-seeds.ts +27 -28
  97. package/src/providers/registry/types.ts +19 -1
  98. package/src/providers/resolved-model-policy.ts +4 -4
  99. package/src/providers/routed-config.ts +15 -0
  100. package/src/providers/stale-model-roster-migration.ts +83 -15
  101. package/src/providers/workbuddy-credit-balance.ts +81 -0
  102. package/src/providers/workbuddy-models.ts +37 -0
  103. package/src/providers/workbuddy.ts +60 -0
  104. package/src/responses/reasoning-replay-cache.ts +18 -14
  105. package/src/router.ts +2 -0
  106. package/src/routing/analytics.ts +2 -3
  107. package/src/routing/fallback-policy.ts +3 -3
  108. package/src/server/auth-cors.ts +1 -0
  109. package/src/server/chat-completions.ts +14 -1
  110. package/src/server/chat-native-reasoning.ts +10 -3
  111. package/src/server/chat-native.ts +13 -2
  112. package/src/server/claude-messages.ts +3 -0
  113. package/src/server/index/link-relay-sessions.ts +9 -5
  114. package/src/server/index/optional-listeners.ts +4 -0
  115. package/src/server/local-management-read-client.ts +3 -1
  116. package/src/server/management/combo-routes.ts +10 -4
  117. package/src/server/management/link-routes.ts +5 -2
  118. package/src/server/management/provider-routes.ts +1 -1
  119. package/src/server/port-reclaim.ts +51 -9
  120. package/src/server/request-log-conversation.ts +49 -0
  121. package/src/server/request-log.ts +2 -0
  122. package/src/server/responses/anthropic-thinking-recovery.ts +1 -0
  123. package/src/server/responses/compact.ts +10 -2
  124. package/src/server/responses/core-combo.ts +53 -31
  125. package/src/server/responses/core-lifetime.ts +2 -2
  126. package/src/server/responses/core-normalize.ts +6 -3
  127. package/src/server/responses/core-opaque-recovery.ts +1 -0
  128. package/src/server/responses/core-options.ts +5 -0
  129. package/src/server/responses/core-replay.ts +2 -1
  130. package/src/server/responses/jev-combo-choice.ts +39 -0
  131. package/src/server/responses/jev-session-pin.ts +203 -0
  132. package/src/server/responses/passthrough-delivery.ts +4 -0
  133. package/src/server/responses/request-prepare.ts +10 -5
  134. package/src/server/responses/request-transport.ts +25 -6
  135. package/src/server/responses/run-turn-execution.ts +2 -4
  136. package/src/server/responses/sidecar-execution.ts +2 -4
  137. package/src/server/responses-phase-inference.ts +267 -0
  138. package/src/server/sse-payload-rewrite.ts +14 -1
  139. package/src/service/definition.ts +20 -8
  140. package/src/types/config.ts +6 -0
  141. package/src/types/provider.ts +9 -1
  142. package/src/types/request.ts +1 -0
  143. package/src/types/wire.ts +1 -0
  144. package/src/update/codex-cli-update-launch-policy.d.mts +9 -0
  145. package/src/update/codex-cli-update-launch-policy.mjs +30 -0
  146. package/src/usage/jev-stats.ts +54 -14
  147. package/gui/dist/assets/index-DGXS51sp.js +0 -94
  148. package/gui/dist/assets/quota-summary-DO1Sx5TM.js +0 -1
@@ -3,12 +3,23 @@ import { createHash } from "node:crypto";
3
3
  import { getConfigDir } from "../config/paths";
4
4
  import { captureConfigGeneration } from "../lib/state-store-sweeper";
5
5
  import type { OcxProviderConfig } from "../types";
6
+ import { invalidateAntigravityQuota, readAntigravityQuota } from "./quota-decision-antigravity";
7
+ import type { DecisionKeyObservation } from "../lib/decision-quota-sink";
6
8
 
7
9
  export interface DecisionQuotaWindow {
8
10
  window: "5h" | "weekly" | "monthly" | "fable" | "opus" | "sonnet";
9
11
  percent: number;
10
12
  observedAt: number;
11
13
  resetAt?: number;
14
+ /** Model family this window meters (Antigravity Gemini vs Claude/3P buckets); absent means every model of the provider. */
15
+ family?: DecisionQuotaFamily;
16
+ }
17
+ export type DecisionQuotaFamily = "gem" | "cla";
18
+ /** True when the provider row can carry sole-credential decision evidence under `name`. */
19
+ function keyEvidenceEligible(name: string, provider: OcxProviderConfig): boolean {
20
+ if (provider.disabled || (provider.apiKeyPool?.length ?? 0) > 1) return false;
21
+ const mode = provider.authMode ?? "key";
22
+ return mode === "key";
12
23
  }
13
24
  export interface DecisionQuotaAccount {
14
25
  id: string;
@@ -54,18 +65,24 @@ function keyPolicy(provider: OcxProviderConfig): string {
54
65
  ])).digest("hex");
55
66
  }
56
67
  /** Drop all key-based decision evidence and advance the epoch so earlier publications cannot be read. */
57
- export function invalidateDecisionKeyQuotas(): void { keyEpoch++; keys.clear(); }
68
+ export function invalidateDecisionKeyQuotas(): void { keyEpoch++; keys.clear(); invalidateAntigravityQuota(); }
69
+ /** Collector replacement does not retire live OAuth producer receipts or their original clocks. */
70
+ export function replaceDecisionKeyQuotas(rows: readonly DecisionKeyObservation[]): void {
71
+ keyEpoch++; keys.clear();
72
+ for (const row of rows) publishDecisionKeyQuota(row.name, row.provider, row.windows);
73
+ }
58
74
  /** Publish decision windows for a single-key provider; pooled, disabled or non-key providers are never published, and the table is bounded. */
59
75
  export function publishDecisionKeyQuota(name: string, provider: OcxProviderConfig, windows: readonly DecisionQuotaWindow[]): void {
60
76
  writesForTests++;
61
- if (name.length > 1024 || provider.disabled || (provider.authMode ?? "key") !== "key" || (provider.apiKeyPool?.length ?? 0) > 1) return;
77
+ if (name.length > 1024 || !keyEvidenceEligible(name, provider)) return;
62
78
  if (keys.size >= 256 && !keys.has(name)) keys.delete(keys.keys().next().value!);
63
79
  keys.set(name, { root: getConfigDir(), epoch: keyEpoch, configGeneration: captureConfigGeneration(), policy: keyPolicy(provider), windows: copyWindows(windows) });
64
80
  }
65
81
  /** Read key-based evidence only when the config root, epoch, generation and provider policy still match what was published. */
66
82
  export function readLoadedDecisionKeyQuota(name: string, provider: OcxProviderConfig): readonly DecisionQuotaWindow[] | undefined {
83
+ if (provider.authMode === "oauth") return readAntigravityQuota(name, provider);
67
84
  const row = keys.get(name);
68
- if (!row || provider.disabled || (provider.authMode ?? "key") !== "key" || (provider.apiKeyPool?.length ?? 0) > 1
85
+ if (!row || !keyEvidenceEligible(name, provider)
69
86
  || row.root !== getConfigDir() || row.epoch !== keyEpoch || row.configGeneration !== captureConfigGeneration()
70
87
  || row.policy !== keyPolicy(provider)) return undefined;
71
88
  return row.windows.map(row => ({ ...row }));
@@ -141,5 +158,5 @@ export function clearDecisionAccountQuotas(provider?: string, id?: string): void
141
158
  /** Copy only known window kinds into the bounded evidence table, so callers cannot retain a mutable reference. */
142
159
  function copyWindows(windows: readonly DecisionQuotaWindow[]): DecisionQuotaWindow[] {
143
160
  if (windows.length > MAX_WINDOWS) return [];
144
- return windows.filter(row => ["5h", "weekly", "monthly", "fable", "opus", "sonnet"].includes(row.window)).map(row => ({ window: row.window, percent: row.percent, observedAt: row.observedAt, ...(row.resetAt !== undefined ? { resetAt: row.resetAt } : {}) }));
161
+ return windows.filter(row => ["5h", "weekly", "monthly", "fable", "opus", "sonnet"].includes(row.window)).map(row => ({ window: row.window, percent: row.percent, observedAt: row.observedAt, ...(row.resetAt !== undefined ? { resetAt: row.resetAt } : {}), ...(row.family === "gem" || row.family === "cla" ? { family: row.family } : {}) }));
145
162
  }
@@ -53,6 +53,8 @@ export interface ProviderQuotaCreditsUsd {
53
53
  }
54
54
 
55
55
  export interface ProviderQuota {
56
+ /** Display-only subscription credits; not a routing or exhaustion window. */
57
+ workbuddyCredits?: import("./workbuddy-credit-balance").WorkBuddyCreditBalance;
56
58
  fiveHourPercent?: number;
57
59
  fiveHourResetAt?: number;
58
60
  weeklyPercent?: number;
@@ -1,5 +1,6 @@
1
1
  import { listCodexAuthAccountsSnapshot } from "../codex/auth-api";
2
2
  import { isAnthropicInstanceId, configuredAnthropicInstance } from "./anthropic-instance";
3
+ import { ANTIGRAVITY_OAUTH_STORE_KEY } from "./antigravity-quota-identity";
3
4
  import { fetchAnthropicAccountQuota } from "./quota/anthropic-account-quota";
4
5
  import { thrownQuotaFailure } from "./quota/failure-codes";
5
6
  import { resolveEnvValue } from "../config";
@@ -124,7 +125,7 @@ async function maybeFetchProviderQuota(
124
125
  }
125
126
  if (provider.authMode === "oauth" && explicitAccountReader(name)) return await fetchExplicitCurrentQuota(name, provider, config);
126
127
  if (provider.authMode === "oauth" && configuredAnthropicInstance(config, name)) return fetchAnthropicQuota(name, config);
127
- if (provider.authMode === "oauth" && name === "google-antigravity") return await fetchAntigravityQuota(name);
128
+ if (provider.authMode === "oauth" && name === ANTIGRAVITY_OAUTH_STORE_KEY) return await fetchAntigravityQuota(name, { ...provider });
128
129
  if (provider.authMode === "oauth" && name === "kiro") return fetchKiroQuota(name);
129
130
  // meta-muse: a device-logged-in account can be probed at the key endpoint; an
130
131
  // imported or pasted one cannot, and falls back to its last in-band observation.
@@ -368,6 +369,7 @@ async function readExplicitAccountQuota(provider: string, accountId: string, con
368
369
  case "kimi": result = await fetchKimiQuota(provider, config, accessToken); break;
369
370
  case "command-code": result = await fetchCommandCodeQuota(provider, config, accessToken); break;
370
371
  case "devin": result = await (await import("./quota/devin")).fetchDevinQuota(provider, accessToken, credential.apiBaseUrl, config.baseUrl); break;
372
+ case "workbuddy-ai": result = await (await import("./quota/workbuddy")).fetchWorkBuddyQuota(provider, credential); break;
371
373
  case "dsh-account": result = await (await import("./quota/dsh-account")).fetchDshAccountQuota(provider, accessToken, { accountId, credential }); break;
372
374
  default: return null;
373
375
  }
@@ -516,6 +516,15 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [
516
516
  // The proprietary generate wire has no verified per-request serialization flag.
517
517
  parallelToolCalls: false,
518
518
  },
519
+ {
520
+ id: "workbuddy-ai", label: "WorkBuddy AI", adapter: "workbuddy-ai",
521
+ baseUrl: "https://www.workbuddy.ai/v2", authKind: "oauth", oauthId: "workbuddy-ai",
522
+ featured: false, dashboardPreset: true, liveModels: true,
523
+ defaultModel: "default-model", models: ["default-model"],
524
+ modelDiscovery: { path: "/v3/config", format: "workbuddy" },
525
+ dashboardUrl: "https://www.workbuddy.ai",
526
+ note: "Use your WorkBuddy AI international subscription. Sign in in your browser; models follow your account catalog.",
527
+ },
519
528
  {
520
529
  id: "dsh-account",
521
530
  label: "DeepSeek Account (DSH)",
@@ -483,6 +483,10 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
483
483
  noPenaltyModels: KIMI_API_MODELS,
484
484
  autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
485
485
  preserveReasoningContentModels: KIMI_API_MODELS,
486
+ // api.moonshot.ai answers `400 Invalid request: role 'developer' is not allowed`. The
487
+ // translated route already folds when nothing is recorded, but the native
488
+ // /v1/chat/completions passthrough only folds on an explicit record, so record it here.
489
+ foldDeveloperRoleToSystem: true,
486
490
  note: "International default (api.moonshot.ai). China accounts: choose China (.cn) or Custom for api.moonshot.cn.",
487
491
  },
488
492
  { id: "huggingface", label: "Hugging Face", baseUrl: "https://router.huggingface.co/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://huggingface.co/settings/tokens" },
@@ -837,6 +841,8 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
837
841
  {
838
842
  id: "alibaba-token-plan",
839
843
  label: "Alibaba Token Plan (Beijing)",
844
+ // Official Personal quick start (2026-10-23) still documents this host:
845
+ // https://help.aliyun.com/en/model-studio/token-plan-personal-quick-start
840
846
  baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
841
847
  adapter: "openai-chat",
842
848
  authKind: "key",
@@ -887,19 +893,23 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
887
893
  "deepseek-v4-pro-0813": deepseekThinkingEffortsFor("deepseek-v4-pro-0813"),
888
894
  "deepseek-v4-flash-0731": deepseekThinkingEffortsFor("deepseek-v4-flash-0731"),
889
895
  "deepseek-v4.1-flash": deepseekThinkingEffortsFor("deepseek-v4.1-flash"),
896
+ "deepseek-v4.1-flash-1017": deepseekThinkingEffortsFor("deepseek-v4.1-flash-1017"),
890
897
  },
891
898
  modelReasoningEffortMap: {
892
899
  "deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro"),
893
900
  "deepseek-v4-pro-0813": deepseekReasoningMapFor("deepseek-v4-pro-0813"),
894
901
  "deepseek-v4-flash-0731": deepseekReasoningMapFor("deepseek-v4-flash-0731"),
895
902
  "deepseek-v4.1-flash": deepseekReasoningMapFor("deepseek-v4.1-flash"),
903
+ "deepseek-v4.1-flash-1017": deepseekReasoningMapFor("deepseek-v4.1-flash-1017"),
896
904
  },
897
905
  // Probed 260915 on the plan gateway: json_object returns valid JSON, strict
898
906
  // json_schema is rejected 400 ("This response_format type is unavailable now")
899
907
  // in both thinking modes, so requests downgrade to json_object rather than
900
908
  // sending a schema the gateway refuses.
901
909
  noJsonSchemaModels: ["deepseek-v4.1-flash"],
902
- modelDefaultReasoningEfforts: Object.fromEntries(QWEN38_FAMILY.map(id => [id, "xhigh"])),
910
+ // 261020 strict schema rejected; json_object unverified. Omit rather than invent a fallback.
911
+ noStructuredOutputModels: ["deepseek-v4.1-flash-1017"],
912
+ modelDefaultReasoningEfforts: { ...Object.fromEntries(QWEN38_FAMILY.map(id => [id, "xhigh"])), "deepseek-v4.1-flash-1017": "high" },
903
913
  directReasoningEffortModels: QWEN38_FAMILY,
904
914
  thinkingBudgetModels: ALIBABA_TOKEN_PLAN_QWEN_MODELS.filter(id => !QWEN38_FAMILY.includes(id)),
905
915
  preserveReasoningContentModels: ALIBABA_TOKEN_PLAN_PRESERVE_REASONING,
@@ -935,25 +945,27 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
935
945
  "glm-5.3": ZAI_GLM_53_REASONING_EFFORTS,
936
946
  "deepseek-v4-pro": deepseekThinkingEffortsFor("deepseek-v4-pro"),
937
947
  "deepseek-v4-pro-0813": deepseekThinkingEffortsFor("deepseek-v4-pro-0813"),
938
- "deepseek-v4-flash": deepseekThinkingEffortsFor("deepseek-v4-flash"),
939
948
  "deepseek-v4-flash-0731": deepseekThinkingEffortsFor("deepseek-v4-flash-0731"),
940
949
  "deepseek-v4.1-flash": deepseekThinkingEffortsFor("deepseek-v4.1-flash"),
950
+ "deepseek-v4.1-flash-1017": deepseekThinkingEffortsFor("deepseek-v4.1-flash-1017"),
941
951
  },
942
952
  modelReasoningEffortMap: {
943
953
  "deepseek-v4-pro": deepseekReasoningMapFor("deepseek-v4-pro"),
944
954
  "deepseek-v4-pro-0813": deepseekReasoningMapFor("deepseek-v4-pro-0813"),
945
- "deepseek-v4-flash": deepseekReasoningMapFor("deepseek-v4-flash"),
946
955
  "deepseek-v4-flash-0731": deepseekReasoningMapFor("deepseek-v4-flash-0731"),
947
956
  "deepseek-v4.1-flash": deepseekReasoningMapFor("deepseek-v4.1-flash"),
957
+ "deepseek-v4.1-flash-1017": deepseekReasoningMapFor("deepseek-v4.1-flash-1017"),
948
958
  },
949
959
  // Same 260915 json_schema rejection probe as the Beijing entry.
950
960
  noJsonSchemaModels: ["deepseek-v4.1-flash"],
961
+ // 261020 strict schema rejected; json_object unverified. Omit rather than invent a fallback.
962
+ noStructuredOutputModels: ["deepseek-v4.1-flash-1017"],
951
963
  directReasoningEffortModels: QWEN38_FAMILY,
952
964
  thinkingBudgetModels: ALIBABA_INTL_TOKEN_PLAN_QWEN_MODELS.filter(id => !QWEN38_FAMILY.includes(id)),
953
965
  preserveReasoningContentModels: ALIBABA_TOKEN_PLAN_PRESERVE_REASONING,
954
966
  noVisionModels: ALIBABA_TOKEN_PLAN_NO_VISION,
955
- noReasoningModels: ["kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "deepseek-v3.2", "glm-5.1", "glm-5", "MiniMax-M2.5"],
956
- modelDefaultReasoningEfforts: Object.fromEntries(QWEN38_FAMILY.map(id => [id, "xhigh"])),
967
+ noReasoningModels: ["kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "glm-5.1", "glm-5", "MiniMax-M2.5"],
968
+ modelDefaultReasoningEfforts: { ...Object.fromEntries(QWEN38_FAMILY.map(id => [id, "xhigh"])), "deepseek-v4.1-flash-1017": "high" },
957
969
  promptCacheKey: true,
958
970
  },
959
971
  // NEEDS_HUMAN 2026-07-10: kept for config compatibility, but this is a dashboard URL,
@@ -1303,14 +1315,15 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [
1303
1315
  baseUrl: "https://api.xiaomimimo.com/api/free-ai/openai/chat",
1304
1316
  authKind: "key",
1305
1317
  keyOptional: true,
1306
- featured: true,
1318
+ featured: false,
1319
+ deprecated: "Deprecated: requests through this entry fail. Bootstrap may still return a JWT, but /api/free-ai/openai/chat rejects the configured model \"mimo-auto\" with 400 \"Unsupported model\". Kept only so existing saved providers keep resolving; new setups should use the keyed xiaomi provider.",
1307
1320
  liveModels: true,
1308
1321
  dashboardUrl: "https://xiaomimimo.com",
1309
1322
  defaultModel: "mimo-auto",
1310
1323
  models: ["mimo-auto"],
1311
1324
  reasoningEfforts: ["low", "medium", "high"],
1312
1325
  reasoningEffortMap: { xhigh: "high", max: "high", ultra: "high" },
1313
- note: "No key needed — uses Xiaomi MiMo's free public tier (limited-time offer). A JWT is bootstrapped automatically with an anonymous random client id stored locally. The endpoint contract mirrors the official MiMoCode client and is not publicly documented — Xiaomi may change or restrict it at any time. Prompts may be processed/retained by Xiaomi; do not send confidential material.",
1326
+ note: "Deprecated: the anonymous MiMo free lane stopped completing requests for this entry in 2026. Bootstrap may still issue a JWT, but /api/free-ai/openai/chat rejects the configured model \"mimo-auto\" with 400 \"Unsupported model\", so turns cannot finish. This entry remains only so existing saved providers keep resolving; add the keyed Xiaomi provider at https://platform.xiaomimimo.com instead. Xiaomi may resume or change the service at any time.",
1314
1327
  },
1315
1328
  // Xiaomi MiMo paid token plan. Separate host and wire from both `xiaomi` (Anthropic) and
1316
1329
  // `mimo-free` (free tier, bespoke adapter), so it needs its own entry rather than a variant.
@@ -61,6 +61,7 @@ export const REGISTRY_FIELD_MODEL_ID_ROLES = {
61
61
  featured: NONE,
62
62
  sponsor: NONE,
63
63
  dashboardPreset: NONE,
64
+ deprecated: NONE,
64
65
  note: NONE,
65
66
  dashboardUrl: NONE,
66
67
  defaultModel: NONE,
@@ -114,10 +115,12 @@ export const REGISTRY_FIELD_MODEL_ID_ROLES = {
114
115
  noStopModels: NONE,
115
116
  noPenaltyModels: NONE,
116
117
  noJsonSchemaModels: NONE,
118
+ noStructuredOutputModels: NONE,
117
119
  parallelToolCalls: NONE,
118
120
  promptCacheKey: NONE,
119
121
  chatServiceTier: NONE,
120
122
  openaiChatEofTolerance: NONE,
123
+ foldDeveloperRoleToSystem: NONE,
121
124
  autoToolChoiceOnlyModels: NONE,
122
125
  preserveReasoningContentModels: NONE,
123
126
  requiresReasoningPlaceholderModels: NONE,
@@ -573,22 +573,22 @@ export const deepseekReasoningMapFor = (modelId: string): Record<string, string>
573
573
  // text-only input, strict json_schema accepted. glm-5.3-flash REMAINS OUT:
574
574
  // still never served by the Token Plan gateway (docs.z.ai VLM id, not plan
575
575
  // entitlement).
576
- // The Beijing preset keeps the Personal Edition subset; non-chat ids (audio/image/
577
- // video families) stay out: they answer only on async endpoints openai-chat cannot
578
- // reach. deepseek-v4-pro-0813 is callable but NOT listed by /models, which is the
579
- // reason liveModels must stay false for this provider. deepseek-v4.1-flash is the
580
- // 260910 DeepSeek rename row: listed on /models on both tiers and regions from 260915,
581
- // hybrid thinking, vision via user message and tool result, json_object but not
582
- // json_schema (see noJsonSchemaModels on the entries).
583
- // Beijing serves the Personal Edition, so this is the Personal-tier roster probed
584
- // 260909 (a strict subset of Team). deepseek-v4-pro-0813 stays out of the Beijing
585
- // entry: its callability is only proven on Team keys, and no Personal key has been
586
- // shown to reach it. The Beijing entry also shares the intl maps, so it carries a
587
- // few orphan keys (kimi/glm-5/MiniMax rows); harmless, and one map beats two
588
- // drifting ones.
576
+ // Both regions support Personal and Team subscriptions. Keep the existing Beijing
577
+ // Personal and International Team model rosters. Discovery stays disabled because
578
+ // the upstream list includes non-chat models.
579
+ // 261020: 0813 is now listed on Global Personal/Team and CN Team, and the CN Personal
580
+ // overview documents entitlement. Add it to the existing Personal curation without
581
+ // claiming new Personal inference. Retired 261011: deepseek-v3.2 and bare v4-flash.
582
+ // 1017 evidence: https://www.qianwenai.com/models/deepseek-v4.1-flash-1017 and
583
+ // https://docs.qwencloud.com/api-reference/chat/openai-chat (checked 2026-10-20).
584
+ // Global Personal probes: completion, thinking-off, low effort, tools, user/tool images
585
+ // and Responses succeed; strict json_schema rejects 400. json_object and replay are
586
+ // untested on this exact id. Published 1M context and 393K output use 393216 here,
587
+ // consistent with the existing plan budget convention, not a measured boundary;
588
+ // the lab's 384000 precision differs. Do not inherit the bare model's JSON fallback.
589
589
  export const ALIBABA_TOKEN_PLAN_MODELS = [
590
590
  "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash",
591
- "deepseek-v4-pro", "deepseek-v4-flash-0731", "deepseek-v4.1-flash", "glm-5.2", "glm-5.3",
591
+ "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash-0731", "deepseek-v4.1-flash", "deepseek-v4.1-flash-1017", "glm-5.2", "glm-5.3",
592
592
  ];
593
593
  export const ALIBABA_TOKEN_PLAN_QWEN_MODELS = [
594
594
  "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-flash",
@@ -604,12 +604,13 @@ export const ALIBABA_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
604
604
  "deepseek-v4-flash-0731": ["text"],
605
605
  // Vision probed on the plan gateway 260915 (user message and tool result, both 200).
606
606
  "deepseek-v4.1-flash": ["text", "image"],
607
+ "deepseek-v4.1-flash-1017": ["text", "image"],
607
608
  "glm-5.2": ["text"],
608
609
  "glm-5.3": ["text"],
609
610
  };
610
611
 
611
612
  // 260721 Alibaba Token Plan International (ap-southeast-1 / Singapore, hardened 260721).
612
- // Multi-vendor lineup distinct from Beijing — includes DeepSeek V4 flash, Kimi K2.7, MiniMax.
613
+ // International Team model roster; tier and region are independent.
613
614
  // Evidence: https://www.alibabacloud.com/help/en/model-studio/token-plan-overview
614
615
  // https://qwencloud.com/pricing/token-plan (qwen3.8 metadata)
615
616
  // The Team Edition roster (Singapore), verified identical to the CN Team set on 260909.
@@ -617,7 +618,7 @@ export const ALIBABA_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
617
618
  // listed on /models on both regions) after being dropped as "retired" upstream.
618
619
  export const ALIBABA_INTL_TOKEN_PLAN_MODELS = [
619
620
  "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
620
- "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash", "deepseek-v4-flash-0731", "deepseek-v4.1-flash", "deepseek-v3.2",
621
+ "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash-0731", "deepseek-v4.1-flash", "deepseek-v4.1-flash-1017",
621
622
  "kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5",
622
623
  "glm-5.2", "glm-5.3", "glm-5.1", "glm-5",
623
624
  "MiniMax-M2.5",
@@ -702,8 +703,6 @@ export const VOLCENGINE_PLAN_TEXT_ONLY_MODELS = [
702
703
  export const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]> = {
703
704
  ...ALIBABA_TOKEN_PLAN_INPUT_MODALITIES,
704
705
  "qwen3.6-plus": ["text", "image"],
705
- "deepseek-v4-flash": ["text"],
706
- "deepseek-v3.2": ["text"],
707
706
  "kimi-k2.7-code": ["text", "image"],
708
707
  "kimi-k2.6": ["text", "image"],
709
708
  "kimi-k2.5": ["text", "image"],
@@ -712,14 +711,14 @@ export const ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES: Record<string, string[]>
712
711
  "MiniMax-M2.5": ["text"],
713
712
  };
714
713
 
715
- // Shared Token Plan metadata (260909 gateway probes; output ceilings are max_tokens
716
- // boundary probes: accept at N, reject at N+1).
714
+ // Shared Token Plan metadata: legacy rows use 260909 max_tokens boundary probes.
715
+ // The 1017 row uses published card normalization, not a boundary probe (see above).
717
716
  export const QWEN38_FAMILY = ["qwen3.8-max", "qwen3.8-flash"];
718
717
  export const ALIBABA_TOKEN_PLAN_CONTEXT_WINDOWS: Record<string, number> = {
719
718
  "qwen3.8-max": 1_000_000, "qwen3.8-flash": 1_000_000, "qwen3.7-max": 1_000_000, "qwen3.7-plus": 1_000_000,
720
719
  "qwen3.6-plus": 1_000_000, "qwen3.6-flash": 1_000_000,
721
- "deepseek-v4-pro": 1_000_000, "deepseek-v4-pro-0813": 1_000_000, "deepseek-v4-flash": 1_000_000,
722
- "deepseek-v4-flash-0731": 1_000_000, "deepseek-v4.1-flash": 1_000_000, "deepseek-v3.2": 131_072,
720
+ "deepseek-v4-pro": 1_000_000, "deepseek-v4-pro-0813": 1_000_000,
721
+ "deepseek-v4-flash-0731": 1_000_000, "deepseek-v4.1-flash": 1_000_000, "deepseek-v4.1-flash-1017": 1_000_000,
723
722
  "kimi-k2.7-code": 262_144, "kimi-k2.6": 262_144, "kimi-k2.5": 262_144,
724
723
  "glm-5.2": 1_000_000, "glm-5.3": 1_000_000, "glm-5.1": 202_752, "glm-5": 202_752,
725
724
  "MiniMax-M2.5": 196_608,
@@ -727,20 +726,20 @@ export const ALIBABA_TOKEN_PLAN_CONTEXT_WINDOWS: Record<string, number> = {
727
726
  export const ALIBABA_TOKEN_PLAN_MAX_OUTPUT_TOKENS: Record<string, number> = {
728
727
  "qwen3.8-max": 131_072, "qwen3.8-flash": 131_072, "qwen3.7-max": 131_072, "qwen3.7-plus": 131_072,
729
728
  "qwen3.6-plus": 65_536, "qwen3.6-flash": 65_536,
730
- "deepseek-v4-pro": 393_216, "deepseek-v4-pro-0813": 393_216, "deepseek-v4-flash": 393_216,
731
- "deepseek-v4-flash-0731": 393_216, "deepseek-v4.1-flash": 393_216, "deepseek-v3.2": 65_536,
729
+ "deepseek-v4-pro": 393_216, "deepseek-v4-pro-0813": 393_216,
730
+ "deepseek-v4-flash-0731": 393_216, "deepseek-v4.1-flash": 393_216, "deepseek-v4.1-flash-1017": 393_216,
732
731
  "kimi-k2.7-code": 262_144, "kimi-k2.6": 262_144, "kimi-k2.5": 98_304,
733
732
  "glm-5.2": 131_072, "glm-5.3": 131_072, "glm-5.1": 128_000, "glm-5": 16_384,
734
733
  "MiniMax-M2.5": 32_768,
735
734
  };
736
735
  export const ALIBABA_TOKEN_PLAN_NO_VISION = [
737
- "qwen3.7-max", "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash",
738
- "deepseek-v4-flash-0731", "deepseek-v3.2", "glm-5.2", "glm-5.3", "glm-5.1", "glm-5", "MiniMax-M2.5",
736
+ "qwen3.7-max", "deepseek-v4-pro", "deepseek-v4-pro-0813",
737
+ "deepseek-v4-flash-0731", "glm-5.2", "glm-5.3", "glm-5.1", "glm-5", "MiniMax-M2.5",
739
738
  ];
740
739
  export const ALIBABA_TOKEN_PLAN_PRESERVE_REASONING = [
741
740
  "qwen3.8-max", "qwen3.8-flash", "qwen3.7-max", "qwen3.7-plus", "qwen3.6-plus", "qwen3.6-flash",
742
- "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash", "deepseek-v4-flash-0731",
743
- "deepseek-v4.1-flash", "glm-5.2", "glm-5.3",
741
+ "deepseek-v4-pro", "deepseek-v4-pro-0813", "deepseek-v4-flash-0731",
742
+ "deepseek-v4.1-flash", "deepseek-v4.1-flash-1017", "glm-5.2", "glm-5.3",
744
743
  ];
745
744
 
746
745
  // 260717 Kimi K3: the subscription endpoint uses one upstream id (`k3`) for both
@@ -63,6 +63,8 @@ export interface ProviderModelDiscoveryFilter {
63
63
  }
64
64
 
65
65
  interface ProviderModelDiscoverySharedSpec {
66
+ /** Product-config envelope used by WorkBuddy AI instead of an OpenAI model list. */
67
+ format?: "workbuddy";
66
68
  /** Query parameters applied to the resolved discovery URL. */
67
69
  query?: Readonly<Record<string, string>>;
68
70
  /** Top-level response key containing model rows; defaults to `data`. */
@@ -160,6 +162,14 @@ export interface ProviderRegistryEntry {
160
162
  */
161
163
  sponsor?: { tier: "main" | "standard"; url: string };
162
164
  dashboardPreset?: boolean;
165
+ /**
166
+ * The upstream route or service this entry depends on is gone. Deprecated entries stay in the
167
+ * registry so saved providers keep resolving and a pinned adapter keeps loading, but they are
168
+ * excluded from the add-provider picker, the init menu, the featured list, and new key logins.
169
+ * The value is the operator-facing reason; it is registry-only and never seeded into saved
170
+ * config.
171
+ */
172
+ deprecated?: string;
163
173
  note?: string;
164
174
  dashboardUrl?: string;
165
175
  defaultModel?: string;
@@ -334,6 +344,8 @@ export interface ProviderRegistryEntry {
334
344
  * `directReasoningEffortModels` above is registry-owned.
335
345
  */
336
346
  noJsonSchemaModels?: string[];
347
+ /** Exact models with no established structured-output wire format. Registry-owned at routing. */
348
+ noStructuredOutputModels?: string[];
337
349
  /** Opt this provider into parallel tool calls (see OcxProviderConfig.parallelToolCalls). */
338
350
  parallelToolCalls?: boolean;
339
351
  /** Opt this provider into forwarding prompt_cache_key (OpenAI-specific; strict backends reject it). */
@@ -346,6 +358,12 @@ export interface ProviderRegistryEntry {
346
358
  chatServiceTier?: boolean;
347
359
  /** OpenAI Chat EOF policy for gateways that omit terminal frames after complete tool calls. */
348
360
  openaiChatEofTolerance?: boolean;
361
+ /**
362
+ * Record that this destination rejects the Chat `developer` role (`400 role 'developer' is not
363
+ * allowed`), so both the translated route and the native `/v1/chat/completions` passthrough send
364
+ * `system` instead. Seeds `foldDeveloperRoleToSystem` on the provider config; a user value wins.
365
+ */
366
+ foldDeveloperRoleToSystem?: boolean;
349
367
  autoToolChoiceOnlyModels?: string[];
350
368
  preserveReasoningContentModels?: string[];
351
369
  requiresReasoningPlaceholderModels?: string[];
@@ -382,6 +400,6 @@ export type ProviderConfigSeed = Pick<
382
400
  | "modelMaxInputTokens" | "defaultMaxOutputTokens" | "modelMaxOutputTokens"
383
401
  | "reasoningEfforts" | "modelReasoningEfforts" | "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap" | "reasoningWireFormat"
384
402
  | "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noStopModels" | "noPenaltyModels"
385
- | "autoToolChoiceOnlyModels" | "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels" | "inlineThinkTagModels" | "reasoningDetailsModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "escapeBuiltinToolNames" | "openaiChatEofTolerance" | "showThinkingSummary"
403
+ | "autoToolChoiceOnlyModels" | "preserveReasoningContentModels" | "requiresReasoningPlaceholderModels" | "reasoningSplitModels" | "inlineThinkTagModels" | "reasoningDetailsModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "escapeBuiltinToolNames" | "openaiChatEofTolerance" | "foldDeveloperRoleToSystem" | "showThinkingSummary"
386
404
  | "googleMode" | "project" | "location" | "headers"
387
405
  >;
@@ -31,8 +31,8 @@ export type StaticProviderPolicyField =
31
31
  | "modelMaxOutputTokens" | "reasoningEfforts" | "modelReasoningEfforts" | "modelReasoningEffortsAuthoritative"
32
32
  | "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
33
33
  | "reasoningWireFormat" | "noVisionModels" | "noReasoningModels" | "noTemperatureModels"
34
- | "noTopPModels" | "noStopModels" | "noPenaltyModels" | "noJsonSchemaModels" | "parallelToolCalls"
35
- | "promptCacheKey" | "chatServiceTier" | "openaiChatEofTolerance" | "statelessResponses"
34
+ | "noTopPModels" | "noStopModels" | "noPenaltyModels" | "noJsonSchemaModels" | "noStructuredOutputModels" | "parallelToolCalls"
35
+ | "promptCacheKey" | "chatServiceTier" | "openaiChatEofTolerance" | "foldDeveloperRoleToSystem" | "statelessResponses"
36
36
  | "requiresAdjacentResponsesToolResults" | "requiresPairedResponsesToolResults" | "annotateEmptyToolOutputs"
37
37
  | "fastWire" | "supportsServiceTier" | "modelSupportsServiceTier" | "supportsOpenAiWebSearchToolFields"
38
38
  | "supportsResponsesCustomTools" | "preserveResponsesReasoningContent" | "dropResponsesReasoningItems"
@@ -173,7 +173,7 @@ export function resolveModelPolicy(input: ResolveModelPolicyInput): ResolvedMode
173
173
  "responsesPath", "chatCompletionsPath", "keyOptional", "freeTier", "modelSuffixBracketStrip",
174
174
  "defaultModel", "models", "liveModels", "contextWindow", "defaultMaxOutputTokens",
175
175
  "reasoningWireFormat", "parallelToolCalls", "promptCacheKey",
176
- "openaiChatEofTolerance", "statelessResponses",
176
+ "openaiChatEofTolerance", "foldDeveloperRoleToSystem", "statelessResponses",
177
177
  "requiresAdjacentResponsesToolResults", "requiresPairedResponsesToolResults",
178
178
  "annotateEmptyToolOutputs", "fastWire", "supportsServiceTier",
179
179
  "supportsOpenAiWebSearchToolFields", "supportsResponsesCustomTools",
@@ -231,7 +231,7 @@ export function resolveModelPolicy(input: ResolveModelPolicyInput): ResolvedMode
231
231
  for (const key of [
232
232
  "noVisionModels", "noReasoningModels", "noTemperatureModels", "noTopPModels",
233
233
  "noStopModels",
234
- "noPenaltyModels", "noJsonSchemaModels", "autoToolChoiceOnlyModels",
234
+ "noPenaltyModels", "noJsonSchemaModels", "noStructuredOutputModels", "autoToolChoiceOnlyModels",
235
235
  "preserveReasoningContentModels", "requiresReasoningPlaceholderModels",
236
236
  "reasoningSplitModels", "reasoningDetailsModels", "thinkingToggleModels", "thinkingBudgetModels",
237
237
  ] as const) putUnion(key, entry?.[key]);
@@ -21,6 +21,7 @@ import {
21
21
  providerSupportsLiveModelDiscovery,
22
22
  } from "./static-model-discovery";
23
23
  import { resolveModelPolicy } from "./resolved-model-policy";
24
+ import { mapFill, nestedMapFill, stableUnion } from "./resolved-model-policy-merge";
24
25
 
25
26
  /** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
26
27
  function isSameEndpoint(a: string, b: string): boolean {
@@ -124,6 +125,15 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
124
125
  return {
125
126
  ...provider,
126
127
  apiKey: usableResolvedApiKey(provider.apiKey),
128
+ ...(destination?.modelDefaultReasoningEfforts ? {
129
+ modelDefaultReasoningEfforts: mapFill(destination.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts)[0],
130
+ } : {}),
131
+ ...(destination?.modelReasoningEffortMap ? {
132
+ modelReasoningEffortMap: nestedMapFill(destination.modelReasoningEffortMap, provider.modelReasoningEffortMap)[0],
133
+ } : {}),
134
+ ...(destination?.noStructuredOutputModels?.length ? {
135
+ noStructuredOutputModels: stableUnion(destination.noStructuredOutputModels, provider.noStructuredOutputModels)[0],
136
+ } : {}),
127
137
  ...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
128
138
  ? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
129
139
  : {}),
@@ -172,6 +182,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
172
182
  const noStopModels = staticPolicy.noStopModels;
173
183
  const noPenaltyModels = staticPolicy.noPenaltyModels;
174
184
  const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
185
+ const noStructuredOutputModels = staticPolicy.noStructuredOutputModels;
175
186
  const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
176
187
  const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
177
188
  const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
@@ -302,6 +313,9 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
302
313
  ...(provider.openaiChatEofTolerance === undefined && registryEntry.openaiChatEofTolerance !== undefined
303
314
  ? { openaiChatEofTolerance: registryEntry.openaiChatEofTolerance }
304
315
  : {}),
316
+ ...(provider.foldDeveloperRoleToSystem === undefined && registryEntry.foldDeveloperRoleToSystem !== undefined
317
+ ? { foldDeveloperRoleToSystem: registryEntry.foldDeveloperRoleToSystem }
318
+ : {}),
305
319
  ...(provider.reasoningWireFormat === undefined && registryEntry.reasoningWireFormat !== undefined
306
320
  ? { reasoningWireFormat: registryEntry.reasoningWireFormat }
307
321
  : {}),
@@ -325,6 +339,7 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
325
339
  ...(noStopModels ? { noStopModels } : {}),
326
340
  ...(noPenaltyModels ? { noPenaltyModels } : {}),
327
341
  ...(noJsonSchemaModels ? { noJsonSchemaModels } : {}),
342
+ ...(noStructuredOutputModels ? { noStructuredOutputModels } : {}),
328
343
  ...(autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels } : {}),
329
344
  ...(preserveReasoningContentModels ? { preserveReasoningContentModels } : {}),
330
345
  ...(requiresReasoningPlaceholderModels ? { requiresReasoningPlaceholderModels } : {}),