@bitkyc08/opencodex 2.59.0 → 2.60.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 (194) hide show
  1. package/gui/dist/assets/index-BTuCbqQd.css +1 -0
  2. package/gui/dist/assets/index-DoBVdPHP.js +134 -0
  3. package/gui/dist/index.html +2 -2
  4. package/package.json +4 -1
  5. package/src/adapters/anthropic-image-codec.ts +16 -2
  6. package/src/adapters/anthropic-image-normalize.ts +49 -2
  7. package/src/adapters/anthropic.ts +4 -1
  8. package/src/adapters/base.ts +23 -0
  9. package/src/adapters/coding-agent/turn.ts +22 -2
  10. package/src/adapters/command-code.ts +50 -3
  11. package/src/adapters/cursor/checkpoint-store.ts +3 -0
  12. package/src/adapters/cursor/discovery.ts +11 -8
  13. package/src/adapters/cursor/live-transport.ts +26 -9
  14. package/src/adapters/cursor/request-builder.ts +7 -1
  15. package/src/adapters/cursor/transport.ts +19 -0
  16. package/src/adapters/cursor.ts +3 -1
  17. package/src/adapters/devin/cloud-direct/chat.ts +3 -1
  18. package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +42 -5
  19. package/src/adapters/devin.ts +50 -13
  20. package/src/adapters/google-antigravity-replay.ts +1 -1
  21. package/src/adapters/google-antigravity-wire.ts +29 -5
  22. package/src/adapters/google-http.ts +49 -10
  23. package/src/adapters/google-tool-schema.ts +595 -31
  24. package/src/adapters/google-wire-compiler.ts +93 -10
  25. package/src/adapters/google-wire-shape.ts +461 -0
  26. package/src/adapters/google.ts +37 -6
  27. package/src/adapters/openai-chat-images.ts +3 -1
  28. package/src/adapters/openai-chat.ts +5 -1
  29. package/src/adapters/openai-responses/image-gen.ts +8 -6
  30. package/src/adapters/openai-responses/passthrough.ts +15 -3
  31. package/src/adapters/openai-responses/reasoning.ts +7 -0
  32. package/src/adapters/opencode-go-additional-tools.ts +12 -2
  33. package/src/bridge/sse.ts +3 -151
  34. package/src/cli/account-extended.ts +4 -4
  35. package/src/cli/dispatch.ts +3 -3
  36. package/src/cli/doctor.ts +28 -9
  37. package/src/cli/hub.ts +3 -2
  38. package/src/cli/index.ts +7 -2
  39. package/src/cli/opencode.ts +2 -2
  40. package/src/cli/provider.ts +13 -1
  41. package/src/client/machine-api.ts +2 -2
  42. package/src/client/machine-listener.ts +2 -2
  43. package/src/client/runtime.ts +26 -2
  44. package/src/codex/account-store.ts +65 -0
  45. package/src/codex/auth-api/account-list.ts +19 -11
  46. package/src/codex/auth-api/pool-quota-probe.ts +30 -7
  47. package/src/codex/catalog/gather-capture.ts +21 -2
  48. package/src/codex/catalog/model-hints.ts +29 -28
  49. package/src/codex/catalog/parsing.ts +7 -0
  50. package/src/codex/catalog/provider-models.ts +19 -2
  51. package/src/codex/catalog/retained-sync.ts +22 -26
  52. package/src/codex/catalog/routed-gather.ts +19 -0
  53. package/src/codex/context-compat.ts +5 -2
  54. package/src/codex/desired-state.ts +4 -1
  55. package/src/codex/history-job.ts +6 -6
  56. package/src/codex/history-provider.ts +20 -166
  57. package/src/codex/history-rollout-read.ts +174 -0
  58. package/src/codex/internal/catalog-writer.ts +33 -1
  59. package/src/codex/model-cache.ts +47 -0
  60. package/src/codex/model-entitlements.ts +29 -10
  61. package/src/codex/observed-model-denials.ts +101 -8
  62. package/src/codex/prompt-text-probe.ts +9 -6
  63. package/src/codex/routing.ts +7 -1
  64. package/src/codex/shim.ts +1 -1
  65. package/src/codex/subagent-model-fallback.ts +22 -4
  66. package/src/combos/failover.ts +3 -0
  67. package/src/config/admitted-identity.ts +222 -0
  68. package/src/config/diagnostics.ts +22 -1
  69. package/src/config/feature-flags.ts +5 -0
  70. package/src/config/load-degrade.ts +18 -0
  71. package/src/config/proxy-env.ts +8 -2
  72. package/src/config/schema/compaction-triggers.ts +11 -0
  73. package/src/config/schema/config-schema.ts +4 -0
  74. package/src/config/schema/leaf-validators.ts +12 -0
  75. package/src/config.ts +2 -2
  76. package/src/generated/compatibility-version.json +249 -169
  77. package/src/grok/reset-coupons.ts +38 -19
  78. package/src/images/loop.ts +6 -1
  79. package/src/integrations/aside-profile-context.ts +37 -3
  80. package/src/integrations/aside-profile-journal.ts +68 -3
  81. package/src/integrations/aside-profiles.ts +128 -3
  82. package/src/integrations/mutation-plan.ts +815 -0
  83. package/src/integrations/writer.ts +85 -99
  84. package/src/lab/live/transport.ts +4 -0
  85. package/src/lab/live/types.ts +5 -0
  86. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  87. package/src/lib/admin-secrets.ts +9 -1
  88. package/src/lib/debug-log-buffer.ts +6 -1
  89. package/src/lib/debug.ts +23 -0
  90. package/src/lib/errors.ts +79 -0
  91. package/src/lib/http-response-semantics.ts +57 -0
  92. package/src/lib/lab-live-pinned-sender.ts +26 -12
  93. package/src/lib/pinned-http.ts +142 -2
  94. package/src/lib/plain-data.ts +103 -0
  95. package/src/lib/process-control.ts +13 -5
  96. package/src/lib/provider-outbound.ts +50 -2
  97. package/src/lib/socks5-fetch.ts +136 -26
  98. package/src/lib/spend-ledger-owner.ts +364 -0
  99. package/src/lib/spend-reservation-ledger.ts +218 -27
  100. package/src/lib/windows-system-proxy.ts +16 -11
  101. package/src/oauth/callback-server.ts +4 -3
  102. package/src/oauth/generic-account-failover.ts +1 -0
  103. package/src/oauth/health.ts +12 -1
  104. package/src/oauth/index.ts +3 -107
  105. package/src/oauth/login-flow-state.ts +127 -0
  106. package/src/providers/derive.ts +34 -17
  107. package/src/providers/devin-cli-authmode-migration.ts +14 -10
  108. package/src/providers/key-failover.ts +66 -18
  109. package/src/providers/model-rename-migration.ts +55 -1
  110. package/src/providers/model-rename-startup.ts +7 -5
  111. package/src/providers/openai-virtual-models.ts +42 -2
  112. package/src/providers/quota/antigravity.ts +22 -2
  113. package/src/providers/quota/vendor-probes-key.ts +1 -1
  114. package/src/providers/registry/entries-core.ts +39 -17
  115. package/src/providers/registry/entries-extended.ts +35 -1
  116. package/src/providers/registry/model-ids.ts +168 -0
  117. package/src/providers/registry/model-seeds.ts +9 -0
  118. package/src/providers/registry/types.ts +2 -0
  119. package/src/providers/resolved-model-policy-merge.ts +167 -0
  120. package/src/providers/resolved-model-policy.ts +406 -0
  121. package/src/providers/stale-vision-classification-migration.ts +137 -0
  122. package/src/responses/apply-patch-envelope.ts +0 -12
  123. package/src/responses/freeform-wrapper-scan.ts +279 -0
  124. package/src/responses/legacy-dotted-tool-name-repair.ts +134 -0
  125. package/src/responses/progressive-freeform-input.ts +130 -0
  126. package/src/responses/reasoning-envelope.ts +30 -0
  127. package/src/responses/state.ts +5 -12
  128. package/src/responses/tool-name-aliases.ts +15 -1
  129. package/src/router.ts +91 -115
  130. package/src/routing/compatibility/behavior.ts +9 -0
  131. package/src/routing/compatibility/subject.ts +16 -1
  132. package/src/server/adapter-resolve.ts +9 -0
  133. package/src/server/auth-cors.ts +3 -0
  134. package/src/server/chat-completions.ts +5 -2
  135. package/src/server/claude-messages.ts +6 -3
  136. package/src/server/effort-row.ts +11 -3
  137. package/src/server/grok-responses-control-frame.ts +160 -1
  138. package/src/server/index/serve-options.ts +86 -29
  139. package/src/server/index/spend-ledger-lifecycle.ts +66 -0
  140. package/src/server/index/websocket-handler.ts +6 -1
  141. package/src/server/index.ts +16 -13
  142. package/src/server/management/aside-profile-routes.ts +266 -7
  143. package/src/server/management/config-routes.ts +18 -2
  144. package/src/server/management/context.ts +3 -0
  145. package/src/server/management/integration-routes.ts +287 -5
  146. package/src/server/management/metrics-routes.ts +20 -0
  147. package/src/server/management/model-rows.ts +224 -12
  148. package/src/server/management/route-registry.ts +14 -0
  149. package/src/server/management/shared.ts +10 -3
  150. package/src/server/management/system-restart.ts +7 -2
  151. package/src/server/management/system-routes.ts +2 -0
  152. package/src/server/management/usage-aggregate-cache.ts +4 -0
  153. package/src/server/management-api.ts +2 -0
  154. package/src/server/management-auth.ts +15 -1
  155. package/src/server/readiness.ts +29 -10
  156. package/src/server/relay-eager.ts +24 -2
  157. package/src/server/relay.ts +119 -10
  158. package/src/server/request-log.ts +55 -2
  159. package/src/server/request-metrics.ts +236 -0
  160. package/src/server/responses/adapter-continuation.ts +3 -3
  161. package/src/server/responses/adapter-dispatch.ts +11 -6
  162. package/src/server/responses/compact.ts +32 -10
  163. package/src/server/responses/compaction-routing.ts +111 -0
  164. package/src/server/responses/core-codex-account.ts +8 -3
  165. package/src/server/responses/core-combo.ts +7 -7
  166. package/src/server/responses/core-normalize.ts +6 -12
  167. package/src/server/responses/core-opaque-recovery.ts +1 -0
  168. package/src/server/responses/core-options.ts +4 -0
  169. package/src/server/responses/encrypted-payload.ts +20 -2
  170. package/src/server/responses/passthrough-delivery.ts +31 -14
  171. package/src/server/responses/passthrough-dispatch.ts +36 -9
  172. package/src/server/responses/policy-fallback.ts +5 -13
  173. package/src/server/responses/request-prepare.ts +67 -17
  174. package/src/server/responses/request-send-budget.ts +5 -1
  175. package/src/server/responses/request-sidecar-auth.ts +1 -1
  176. package/src/server/responses/request-transport.ts +2 -2
  177. package/src/server/responses/run-turn-execution.ts +25 -3
  178. package/src/server/responses/sidecar-execution.ts +17 -2
  179. package/src/server/responses/ws-upstream.ts +14 -27
  180. package/src/server/responses-custom-tool-repair.ts +27 -54
  181. package/src/server/responses-undeclared-tool-guard.ts +31 -1
  182. package/src/server/sse-payload-rewrite.ts +1 -1
  183. package/src/tray/windows-tray.ps1 +155 -3
  184. package/src/types/config.ts +11 -3
  185. package/src/types/provider.ts +18 -0
  186. package/src/types/request.ts +2 -0
  187. package/src/types/tools.ts +14 -0
  188. package/src/types.ts +1 -0
  189. package/src/vision/eligibility.ts +88 -9
  190. package/src/vision/plan.ts +34 -10
  191. package/src/web-search/executor.ts +41 -2
  192. package/src/web-search/loop.ts +6 -1
  193. package/gui/dist/assets/index-C5IebErG.js +0 -136
  194. package/gui/dist/assets/index-OESInAjC.css +0 -1
package/src/router.ts CHANGED
@@ -15,10 +15,12 @@ import { assertProviderDestinationAllowed } from "./lib/destination-policy";
15
15
  import { redactSecretString, redactUrlForLog } from "./lib/redact";
16
16
  import {
17
17
  PROVIDER_REGISTRY,
18
- mergeRegistryStaticHeaders,
19
18
  providerCodexAccountMode,
20
- registryModelServiceTierCapabilityApplies,
19
+ registryEntryForProviderDestination,
21
20
  } from "./providers/registry";
21
+ // Imported from the module directly rather than through the registry facade, which is at its
22
+ // file-size cap.
23
+ import { registryModelIdKeys } from "./providers/registry/model-ids";
22
24
  import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive";
23
25
  import { cloneFastWire } from "./providers/fastwire";
24
26
  import {
@@ -29,11 +31,10 @@ import {
29
31
  isCanonicalOpenAiForwardProvider,
30
32
  LEGACY_CHATGPT_PROVIDER_ID,
31
33
  LEGACY_OPENAI_MULTI_PROVIDER_ID,
32
- OPENAI_API_PROVIDER_ID,
33
34
  OPENAI_CODEX_PROVIDER_ID,
34
35
  } from "./providers/openai-tiers";
35
36
  import { decodeRoutedModelIdOrThrow, encodeRoutedModelId } from "./providers/slug-codec";
36
- import { resolveModelAlias } from "./providers/default-aliases";
37
+ import { effectiveProviderAliasDecision, resolveModelAlias } from "./providers/default-aliases";
37
38
  import { resolveBlockedModelRedirect } from "./lib/shadow-call";
38
39
  import { getStaleCached } from "./codex/model-cache";
39
40
  import { codexAccountNamespaceEntries } from "./codex/account-namespaces";
@@ -46,6 +47,7 @@ import {
46
47
  import { getRoutingProfile, resolvePolicyProfileId, POLICY_NAMESPACE } from "./routing/profile";
47
48
  import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/evaluator";
48
49
  import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
50
+ import { resolveModelPolicy, type ResolvedModelPolicy } from "./providers/resolved-model-policy";
49
51
 
50
52
  export class UnknownRoutingPolicyError extends Error {
51
53
  constructor(readonly profileId: string) {
@@ -69,6 +71,8 @@ export interface RouteResult {
69
71
  providerName: string;
70
72
  provider: OcxProviderConfig;
71
73
  modelId: string;
74
+ /** Immutable static policy for the current final wire model. */
75
+ staticPolicy: ResolvedModelPolicy;
72
76
  /** Which deterministic routing path produced this route (RI-01). */
73
77
  routeKind: RouteDecisionKind;
74
78
  /** Stable wire reason code for the selected route (RI-01). */
@@ -83,6 +87,29 @@ export interface RouteResult {
83
87
  routeDecision?: RouteDecisionTraceV1;
84
88
  }
85
89
 
90
+ export function captureRouteStaticPolicy(
91
+ providerName: string,
92
+ modelId: string,
93
+ provider: OcxProviderConfig,
94
+ effectiveAlias?: string | null,
95
+ inboundWire: "responses" | "chat" | "anthropic" = "responses",
96
+ ): ResolvedModelPolicy {
97
+ const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
98
+ const transportMatchedRegistry = !!registryEntry
99
+ && providerMatchesRegistryTransportWithStaticGuards(providerName, provider);
100
+ return resolveModelPolicy({
101
+ providerName,
102
+ modelId,
103
+ provider,
104
+ registryEntry,
105
+ transportMatchedRegistry,
106
+ inboundWire,
107
+ modelCapabilities: provider.modelCapabilities?.[modelId],
108
+ ...(provider.authMode ? { effectiveAuth: { authMode: provider.authMode } } : {}),
109
+ ...(effectiveAlias !== undefined ? { effectiveAlias } : {}),
110
+ });
111
+ }
112
+
86
113
  const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
87
114
  {
88
115
  providerNames: ["anthropic"],
@@ -117,19 +144,11 @@ export function knownModelIdsForProvider(
117
144
  : undefined;
118
145
  for (const id of registry?.models ?? []) ids.add(id);
119
146
  // Registry model-keyed hint maps double as known native ids (e.g. NVIDIA carries no
120
- // static models list but names `moonshotai/kimi-k2.6` in its effort/window maps).
121
- for (const map of [
122
- registry?.modelContextWindows,
123
- registry?.modelInputModalities,
124
- registry?.modelReasoningEfforts,
125
- registry?.modelDefaultReasoningEfforts,
126
- registry?.modelReasoningEffortMap,
127
- registry?.modelMaxOutputTokens,
128
- registry?.modelSupportsServiceTier,
129
- registry?.modelSupportsVerbosity,
130
- ]) {
131
- for (const id of Object.keys(map ?? {})) ids.add(id);
132
- }
147
+ // static models list but names `moonshotai/kimi-k2.6` in its effort/window maps). Which
148
+ // maps count is classified by the registry itself rather than listed here, so an id declared
149
+ // only in a map this function forgot is no longer undecodable, and a new model-keyed field
150
+ // fails typecheck until its keys are given a meaning.
151
+ for (const id of registry ? registryModelIdKeys(registry) : []) ids.add(id);
133
152
  for (const cached of getStaleCached(provName) ?? []) ids.add(cached.id);
134
153
  for (const model of config?.customModels ?? []) {
135
154
  if (model.provider === provName && model.modelId) ids.add(model.modelId);
@@ -137,66 +156,6 @@ export function knownModelIdsForProvider(
137
156
  return [...ids];
138
157
  }
139
158
 
140
- // Merge registry-default effort maps under user values so built-in provider configs can
141
- // carry real upstream aliases without a disk migration. User overrides win per-key.
142
- function mergeRecord(
143
- seed: Record<string, string> | undefined,
144
- user: Record<string, string> | undefined,
145
- ): Record<string, string> | undefined {
146
- if (!seed && !user) return undefined;
147
- return { ...(seed ?? {}), ...(user ?? {}) };
148
- }
149
-
150
- function mergeNestedRecord(
151
- seed: Record<string, Record<string, string>> | undefined,
152
- user: Record<string, Record<string, string>> | undefined,
153
- ): Record<string, Record<string, string>> | undefined {
154
- if (!seed && !user) return undefined;
155
- const out: Record<string, Record<string, string>> = {};
156
- for (const [key, value] of Object.entries(seed ?? {})) out[key] = { ...value };
157
- for (const [key, value] of Object.entries(user ?? {})) out[key] = { ...(out[key] ?? {}), ...value };
158
- return out;
159
- }
160
-
161
- function mergeStringArray(
162
- seed: string[] | undefined,
163
- user: string[] | undefined,
164
- ): string[] | undefined {
165
- if (!seed && !user) return undefined;
166
- return [...new Set([...(seed ?? []), ...(user ?? [])])];
167
- }
168
-
169
- function mergeRecordFill<T>(
170
- seed: Record<string, T> | undefined,
171
- user: Record<string, T> | undefined,
172
- ): Record<string, T> | undefined {
173
- if (!seed && !user) return undefined;
174
- return { ...(seed ?? {}), ...(user ?? {}) };
175
- }
176
-
177
- function mergePositiveNumberCaps(
178
- seed: Record<string, number> | undefined,
179
- user: Record<string, number> | undefined,
180
- ): Record<string, number> | undefined {
181
- if (!seed && !user) return undefined;
182
- const out = { ...(seed ?? {}) };
183
- for (const [key, value] of Object.entries(user ?? {})) {
184
- out[key] = typeof out[key] === "number" ? Math.min(out[key]!, value) : value;
185
- }
186
- return out;
187
- }
188
-
189
- function mergeStringArrayRecord(
190
- seed: Record<string, string[]> | undefined,
191
- user: Record<string, string[]> | undefined,
192
- ): Record<string, string[]> | undefined {
193
- if (!seed && !user) return undefined;
194
- const out: Record<string, string[]> = {};
195
- for (const [key, value] of Object.entries(seed ?? {})) out[key] = [...value];
196
- for (const [key, value] of Object.entries(user ?? {})) out[key] = [...value];
197
- return out;
198
- }
199
-
200
159
  /** Same endpoint modulo surrounding space and trailing slashes — matches `matchBaseUrlChoice`. */
201
160
  function isSameEndpoint(a: string, b: string): boolean {
202
161
  return a.trim().replace(/\/+$/, "") === b.trim().replace(/\/+$/, "");
@@ -302,7 +261,20 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
302
261
  const registryEntry = PROVIDER_REGISTRY.find(entry => entry.id === providerName);
303
262
  if (!registryEntry || !providerMatchesRegistryTransportWithStaticGuards(providerName, provider)) {
304
263
  assertProviderDestinationAllowed(providerName, provider);
305
- return { ...provider, apiKey: usableResolvedApiKey(provider.apiKey) };
264
+ // A row whose adapter no longer matches its registry entry still reaches the Responses
265
+ // adapter when one model opts in through `modelAdapters` — a Volcengine Coding Plan config
266
+ // saved on Chat, for instance. The replay-drop flag belongs to the DESTINATION rather than
267
+ // to the provider-wide wire, so it is filled here too; without it that continuation forwards
268
+ // the reasoning item the upstream answers 400 to. Destination matching refuses templated and
269
+ // overridable base URLs, so this cannot follow a retargeted row, and an explicit value wins.
270
+ const destination = registryEntryForProviderDestination(provider);
271
+ return {
272
+ ...provider,
273
+ apiKey: usableResolvedApiKey(provider.apiKey),
274
+ ...(provider.dropResponsesReasoningItems === undefined && destination?.dropResponsesReasoningItems !== undefined
275
+ ? { dropResponsesReasoningItems: destination.dropResponsesReasoningItems }
276
+ : {}),
277
+ };
306
278
  }
307
279
  const resolvedApiKey = usableResolvedApiKey(provider.apiKey);
308
280
  const staticModelCatalog = !providerSupportsLiveModelDiscovery(providerName, provider);
@@ -317,46 +289,42 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
317
289
  : registryEntry.authKind === "forward" || registryEntry.authKind === "oauth"
318
290
  ? registryEntry.authKind
319
291
  : provider.authMode === "forward" ? undefined : provider.authMode;
320
- const reasoningEffortMap = mergeRecord(registryEntry.reasoningEffortMap, provider.reasoningEffortMap);
321
- const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
322
- const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
323
- const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
324
- const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID
325
- ? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
326
- : mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
327
- const modelInputModalities = mergeRecordFill(registryEntry.modelInputModalities, provider.modelInputModalities);
292
+ const staticPolicy = resolveModelPolicy({
293
+ providerName,
294
+ modelId: "__provider_static__",
295
+ provider,
296
+ registryEntry,
297
+ transportMatchedRegistry: true,
298
+ ...(canonicalAuthMode ? { effectiveAuth: { authMode: canonicalAuthMode } } : {}),
299
+ }).provider;
300
+ const reasoningEffortMap = staticPolicy.reasoningEffortMap;
301
+ const modelReasoningEffortMap = staticPolicy.modelReasoningEffortMap;
302
+ const modelReasoningEfforts = staticPolicy.modelReasoningEfforts;
303
+ const modelDefaultReasoningEfforts = staticPolicy.modelDefaultReasoningEfforts;
304
+ const modelContextWindows = staticPolicy.modelContextWindows;
305
+ const modelInputModalities = staticPolicy.modelInputModalities;
328
306
  // Registry static headers are documented as applying to every upstream request, so they are
329
307
  // filled at resolve time rather than only at seed time: a config written before a header
330
308
  // existed, or one carrying any header of its own, would otherwise never receive it. User
331
309
  // headers win, matched case-insensitively so an override replaces rather than duplicates.
332
- const headers = mergeRegistryStaticHeaders(registryEntry.staticHeaders, provider.headers);
333
- const modelMaxInputTokens = providerName === OPENAI_API_PROVIDER_ID
334
- ? mergePositiveNumberCaps(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens)
335
- : mergeRecordFill(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens);
336
- const modelMaxOutputTokens = mergeRecordFill(registryEntry.modelMaxOutputTokens, provider.modelMaxOutputTokens);
337
- const modelSupportsServiceTier = mergeRecordFill(
338
- registryModelServiceTierCapabilityApplies(registryEntry, provider)
339
- ? registryEntry.modelSupportsServiceTier
340
- : undefined,
341
- provider.modelSupportsServiceTier,
342
- );
343
- const modelSupportsVerbosity = mergeRecordFill(
344
- registryEntry.modelSupportsVerbosity,
345
- provider.modelSupportsVerbosity,
346
- );
347
- const noVisionModels = mergeStringArray(registryEntry.noVisionModels, provider.noVisionModels);
348
- const noReasoningModels = mergeStringArray(registryEntry.noReasoningModels, provider.noReasoningModels);
349
- const noTemperatureModels = mergeStringArray(registryEntry.noTemperatureModels, provider.noTemperatureModels);
350
- const noTopPModels = mergeStringArray(registryEntry.noTopPModels, provider.noTopPModels);
351
- const noPenaltyModels = mergeStringArray(registryEntry.noPenaltyModels, provider.noPenaltyModels);
352
- const noJsonSchemaModels = mergeStringArray(registryEntry.noJsonSchemaModels, provider.noJsonSchemaModels);
353
- const autoToolChoiceOnlyModels = mergeStringArray(registryEntry.autoToolChoiceOnlyModels, provider.autoToolChoiceOnlyModels);
354
- const preserveReasoningContentModels = mergeStringArray(registryEntry.preserveReasoningContentModels, provider.preserveReasoningContentModels);
355
- const requiresReasoningPlaceholderModels = mergeStringArray(registryEntry.requiresReasoningPlaceholderModels, provider.requiresReasoningPlaceholderModels);
356
- const reasoningSplitModels = mergeStringArray(registryEntry.reasoningSplitModels, provider.reasoningSplitModels);
357
- const reasoningDetailsModels = mergeStringArray(registryEntry.reasoningDetailsModels, provider.reasoningDetailsModels);
358
- const thinkingToggleModels = mergeStringArray(registryEntry.thinkingToggleModels, provider.thinkingToggleModels);
359
- const thinkingBudgetModels = mergeStringArray(registryEntry.thinkingBudgetModels, provider.thinkingBudgetModels);
310
+ const headers = staticPolicy.headers;
311
+ const modelMaxInputTokens = staticPolicy.modelMaxInputTokens;
312
+ const modelMaxOutputTokens = staticPolicy.modelMaxOutputTokens;
313
+ const modelSupportsServiceTier = staticPolicy.modelSupportsServiceTier;
314
+ const modelSupportsVerbosity = staticPolicy.modelSupportsVerbosity;
315
+ const noVisionModels = staticPolicy.noVisionModels;
316
+ const noReasoningModels = staticPolicy.noReasoningModels;
317
+ const noTemperatureModels = staticPolicy.noTemperatureModels;
318
+ const noTopPModels = staticPolicy.noTopPModels;
319
+ const noPenaltyModels = staticPolicy.noPenaltyModels;
320
+ const noJsonSchemaModels = staticPolicy.noJsonSchemaModels;
321
+ const autoToolChoiceOnlyModels = staticPolicy.autoToolChoiceOnlyModels;
322
+ const preserveReasoningContentModels = staticPolicy.preserveReasoningContentModels;
323
+ const requiresReasoningPlaceholderModels = staticPolicy.requiresReasoningPlaceholderModels;
324
+ const reasoningSplitModels = staticPolicy.reasoningSplitModels;
325
+ const reasoningDetailsModels = staticPolicy.reasoningDetailsModels;
326
+ const thinkingToggleModels = staticPolicy.thinkingToggleModels;
327
+ const thinkingBudgetModels = staticPolicy.thinkingBudgetModels;
360
328
  const registryBaseUrlIsTemplate = /\{[^}]*\}/.test(registryEntry.baseUrl);
361
329
  const userBaseUrl = typeof provider.baseUrl === "string" ? provider.baseUrl.trim() : "";
362
330
  const userBaseUrlIsResolved = userBaseUrl.length > 0 && !/\{[^}]*\}/.test(userBaseUrl);
@@ -417,6 +385,9 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
417
385
  ...(provider.preserveResponsesReasoningContent === undefined && registryEntry.preserveResponsesReasoningContent !== undefined
418
386
  ? { preserveResponsesReasoningContent: registryEntry.preserveResponsesReasoningContent }
419
387
  : {}),
388
+ ...(provider.dropResponsesReasoningItems === undefined && registryEntry.dropResponsesReasoningItems !== undefined
389
+ ? { dropResponsesReasoningItems: registryEntry.dropResponsesReasoningItems }
390
+ : {}),
420
391
  // The request path resolves through routedProviderConfig() and never calls
421
392
  // enrichProviderFromRegistry(), so a saved provider row written before the
422
393
  // registry learned this flag must be backfilled here or route.provider never
@@ -565,10 +536,15 @@ function routeResult(
565
536
  const effectiveModelId = redirected ?? modelId;
566
537
  const effectiveRouteReason = redirected ? "blocked-model-redirect" : routeReason;
567
538
  const codexAccountMode = providerCodexAccountMode(providerName, provider);
539
+ const routedProvider = routedProviderConfig(providerName, provider);
540
+ const effectiveAlias = config
541
+ ? effectiveProviderAliasDecision(providerName, provider, config)
542
+ : undefined;
568
543
  return {
569
544
  providerName,
570
- provider: routedProviderConfig(providerName, provider),
545
+ provider: routedProvider,
571
546
  modelId: effectiveModelId,
547
+ staticPolicy: captureRouteStaticPolicy(providerName, effectiveModelId, routedProvider, effectiveAlias),
572
548
  routeKind,
573
549
  routeReason: effectiveRouteReason,
574
550
  ...(codexAccountMode ? { codexAccountMode } : {}),
@@ -200,6 +200,12 @@ export function resolveProductionBehaviorValues(
200
200
  : reasoningEfforts.length > 0 || Boolean(registryEntry?.reasoningEfforts),
201
201
  ),
202
202
  "reasoning.efforts": behaviorRow("provider_config", reasoningEfforts),
203
+ // Whether the configured ladder above is what the WIRE uses, or only what the catalog
204
+ // advertises. An adapter shipping its own per-model table (command-code) otherwise wins for
205
+ // models it knows, and it also repairs a refused rung by replaying without the effort. Two
206
+ // routes that disagree about this send different bytes and recover differently, so they are
207
+ // not the same compatibility subject and must not share evidence.
208
+ "reasoning.effortsAuthoritative": behaviorRow("provider_config", effective.modelReasoningEffortsAuthoritative === true),
203
209
  "reasoning.defaultEffort": behaviorRow("provider_config", defaultReasoningEffort),
204
210
  "reasoning.effortMap": behaviorRow("provider_config", reasoningEffortMap),
205
211
  "reasoning.wireFormat": behaviorRow("provider_config", effective.reasoningWireFormat ?? null),
@@ -209,6 +215,9 @@ export function resolveProductionBehaviorValues(
209
215
  }),
210
216
  "reasoning.replayMode": behaviorRow("provider_config", {
211
217
  preserveResponses: effective.preserveResponsesReasoningContent === true,
218
+ // Dropping replayed reasoning items changes the continuation body, so two routes that
219
+ // disagree about it are not the same compatibility subject and must not share evidence.
220
+ dropResponses: effective.dropResponsesReasoningItems === true,
212
221
  preserveContent: includesModel(effective.preserveReasoningContentModels, modelId),
213
222
  placeholder: includesModel(effective.requiresReasoningPlaceholderModels, modelId),
214
223
  }),
@@ -1,5 +1,8 @@
1
1
  import type { OcxConfig, OcxProviderConfig } from "../../types";
2
2
  import type { InboundWire } from "../../providers/registry";
3
+ import { getProviderRegistryEntry } from "../../providers/registry";
4
+ import { providerMatchesRegistryTransportWithStaticGuards } from "../../providers/static-model-discovery";
5
+ import { resolveModelPolicy } from "../../providers/resolved-model-policy";
3
6
  import { subjectIdForSubject } from "../../lab/digest";
4
7
  import { buildRouteSubjectV1 } from "../../lab/subject/route-subject";
5
8
  import { buildProtocolSubjectV1 } from "../../lab/subject/protocol-subject";
@@ -65,7 +68,19 @@ export function resolveCompatibilitySubjectsForInboundWire(
65
68
  inboundWire: InboundWire,
66
69
  configDir?: string,
67
70
  ): ResolvedPolicyCompatibilitySubjects {
68
- const effective = resolveWireProtocolOverride(providerName, modelId, routed, inboundWire);
71
+ const registryEntry = getProviderRegistryEntry(providerName);
72
+ const staticPolicy = resolveModelPolicy({
73
+ providerName,
74
+ modelId,
75
+ provider: routed,
76
+ registryEntry,
77
+ transportMatchedRegistry: !!registryEntry
78
+ && providerMatchesRegistryTransportWithStaticGuards(providerName, routed),
79
+ inboundWire,
80
+ modelCapabilities: routed.modelCapabilities?.[modelId],
81
+ ...(routed.authMode ? { effectiveAuth: { authMode: routed.authMode } } : {}),
82
+ });
83
+ const effective = resolveWireProtocolOverride(providerName, modelId, routed, inboundWire, staticPolicy);
69
84
  const baseUrl = typeof effective.baseUrl === "string" ? effective.baseUrl.trim() : "";
70
85
  const adapter = effective.adapter ?? "openai-responses";
71
86
  const inboundProtocol = inboundProtocolForWire(inboundWire);
@@ -3,6 +3,7 @@ import type { OcxProviderConfig } from "../types";
3
3
  import { isWirePinnedModel, MODEL_ADAPTER_OVERRIDE_ALLOWED, pinnedWireAdapter } from "../types";
4
4
  import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
5
5
  import { type InboundWire, providerModelWireDefault } from "../providers/registry";
6
+ import type { ResolvedModelPolicy } from "../providers/resolved-model-policy";
6
7
 
7
8
  /**
8
9
  * Resolve the wire a single model should use: a hard pin first, then a configured
@@ -22,7 +23,15 @@ export function resolveWireProtocolOverride(
22
23
  modelId: string,
23
24
  providerConfig: OcxProviderConfig,
24
25
  inbound: InboundWire = "responses",
26
+ staticPolicy?: ResolvedModelPolicy,
25
27
  ): OcxProviderConfig {
28
+ // RouteResult policy is recaptured for the original inbound protocol whenever routing selects
29
+ // or replaces a destination, so every downstream rebuild consumes the same adapter authority.
30
+ if (staticPolicy) {
31
+ return staticPolicy.model.adapter !== providerConfig.adapter
32
+ ? { ...providerConfig, adapter: staticPolicy.model.adapter }
33
+ : providerConfig;
34
+ }
26
35
  const pinned = pinnedWireAdapter(providerName, modelId);
27
36
  if (pinned && providerConfig.adapter !== pinned) {
28
37
  return { ...providerConfig, adapter: pinned };
@@ -942,12 +942,15 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
942
942
  supportsServiceTier: "editor",
943
943
  modelSupportsServiceTier: "editor",
944
944
  preserveResponsesReasoningContent: "editor",
945
+ dropResponsesReasoningItems: "editor",
946
+ modelReasoningEffortsAuthoritative: "editor",
945
947
  decodesNativeCompactionBlobs: "editor",
946
948
  allowEncryptedV2AgentTasks: "editor",
947
949
  allowPrivateNetwork: "editor",
948
950
  upstreamHttpVersion: "editor",
949
951
  upstreamWebsocket: "editor",
950
952
  directGeminiWireRenames: "editor",
953
+ googleToolSchemaPolicy: "editor",
951
954
  disabled: "editor",
952
955
  codexAccountMode: "editor",
953
956
  apiKey: "redacted",
@@ -23,7 +23,7 @@ import { classifyError, cyberPolicyErrorType, CYBER_POLICY_ERROR_CODE, isCyberPo
23
23
  import { redactSecretString } from "../lib/redact";
24
24
  import { resolveClientRetryAfter } from "../lib/retry-after";
25
25
  import { estimateTokens } from "../lib/token-estimate";
26
- import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
26
+ import { captureRouteStaticPolicy, NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
27
27
  import { evidenceFromBody } from "../routing/request-evidence";
28
28
  import { resolveWireProtocolOverride } from "./adapter-resolve";
29
29
  import { resolveOpenCodeGoTransport } from "../providers/opencode-go-transport";
@@ -153,7 +153,10 @@ async function handleChatCompletionsWithBudget(
153
153
  // Preserve the routed destination for Go recognition, then settle the wire before
154
154
  // deriving protocol-scoped affinity. Recognition must not inspect the flipped adapter.
155
155
  const routedProvider = route.provider;
156
- const wireProvider = resolveWireProtocolOverride(route.providerName, route.modelId, routedProvider, "chat");
156
+ route.staticPolicy = captureRouteStaticPolicy(
157
+ route.providerName, route.modelId, routedProvider, route.staticPolicy.effectiveAlias, "chat",
158
+ );
159
+ const wireProvider = resolveWireProtocolOverride(route.providerName, route.modelId, routedProvider, "chat", route.staticPolicy);
157
160
  route.provider = resolveOpenCodeGoTransport(
158
161
  wireProvider,
159
162
  getOrAllocateRequestSessionLane(req),
@@ -29,7 +29,7 @@ import {
29
29
  } from "../claude/outbound";
30
30
  import { clearableDeadline, idleDeadline } from "../lib/abort";
31
31
  import { estimateTokens } from "../lib/token-estimate";
32
- import { NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
32
+ import { captureRouteStaticPolicy, NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
33
33
  import { evidenceFromBody } from "../routing/request-evidence";
34
34
  import { resolveWireProtocolOverride } from "./adapter-resolve";
35
35
  import type { OcxConfig } from "../types";
@@ -420,7 +420,7 @@ async function anthropicNativePassthrough(
420
420
  // count_tokens too — counts must match what the real send will contain, and the 32MB
421
421
  // body cap applies to it equally. Non-message bodies pass through untouched.
422
422
  if (Array.isArray(body.messages)) {
423
- await normalizeAnthropicImages(body.messages);
423
+ await normalizeAnthropicImages(body.messages, { abortSignal: req.signal });
424
424
  enforceAnthropicImageLimits(body.messages);
425
425
  }
426
426
  const headers = new Headers();
@@ -817,7 +817,10 @@ async function handleClaudeMessagesWithBudget(
817
817
  const route = routeModel(config, internalBody.model as string, evidenceFromBody(internalBody));
818
818
  // Settle the wire once so the sampling decision below reads the effective
819
819
  // adapter rather than the provider-wide default (#404).
820
- route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic");
820
+ route.staticPolicy = captureRouteStaticPolicy(
821
+ route.providerName, route.modelId, route.provider, route.staticPolicy.effectiveAlias, "anthropic",
822
+ );
823
+ route.provider = resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, "anthropic", route.staticPolicy);
821
824
  logCtx.routeDecision = route.routeDecision;
822
825
  if (route.provider.adapter === "openai-responses") {
823
826
  delete internalBody.max_output_tokens;
@@ -30,7 +30,9 @@ export interface EffortRowOptions {
30
30
  }
31
31
 
32
32
  export function isKnownId(knownIds: EffortRowKnownIds | undefined, id: string): boolean {
33
- return typeof knownIds === "function" ? knownIds(id) : knownIds?.has(id) === true;
33
+ return typeof knownIds === "function"
34
+ ? knownIds(id)
35
+ : knownIds?.has(id) === true || knownIds?.has(id.toLowerCase()) === true;
34
36
  }
35
37
 
36
38
  export function effortRowId(baseId: string, effort: string): string {
@@ -43,6 +45,12 @@ export function effortRowId(baseId: string, effort: string): string {
43
45
  */
44
46
  export function knownEffortRowIds(config: OcxConfig): Set<string> {
45
47
  const ids = new Set<string>();
48
+ const addAlias = (id: string): void => {
49
+ ids.add(id);
50
+ // Model aliases are resolved case-insensitively. Preserve that contract here so an
51
+ // alias cannot be mistaken for a synthetic effort row before ordinary routing runs.
52
+ ids.add(id.toLowerCase());
53
+ };
46
54
  for (const [providerName, provider] of Object.entries(config.providers)) {
47
55
  const known = knownModelIdsForProvider(providerName, provider, config);
48
56
  const namespaces = [providerName, provider.alias].filter((value): value is string => (
@@ -54,8 +62,8 @@ export function knownEffortRowIds(config: OcxConfig): Set<string> {
54
62
  for (const namespace of namespaces) ids.add(`${namespace}/${id}`);
55
63
  }
56
64
  for (const alias of Object.values(provider.modelAliases ?? {})) {
57
- ids.add(alias);
58
- for (const namespace of namespaces) ids.add(`${namespace}/${alias}`);
65
+ addAlias(alias);
66
+ for (const namespace of namespaces) addAlias(`${namespace}/${alias}`);
59
67
  }
60
68
  }
61
69
  for (const [id, combo] of Object.entries(config.combos ?? {})) {
@@ -1,10 +1,137 @@
1
- import { sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
1
+ import { replaceSseDataPayload, sseDataPayload, type SseBlockRewrite } from "./sse-payload-rewrite";
2
2
 
3
3
  const GROK_CONTROL_FRAME_TYPES: Record<string, true> = {
4
4
  "codex.rate_limits": true,
5
5
  "codex.response.metadata": true,
6
6
  };
7
7
 
8
+ const GROK_RESPONSE_INTEGER_TIMESTAMP_FIELDS = ["created_at", "completed_at"] as const;
9
+
10
+ type JsonRange = { start: number; end: number };
11
+
12
+ function skipJsonWhitespace(payload: string, index: number): number {
13
+ while (index < payload.length && /[\t\n\r ]/.test(payload[index] ?? "")) index += 1;
14
+ return index;
15
+ }
16
+
17
+ function findJsonStringEnd(payload: string, start: number): number | undefined {
18
+ for (let index = start + 1; index < payload.length; index += 1) {
19
+ if (payload[index] === "\\") index += 1;
20
+ else if (payload[index] === '"') return index + 1;
21
+ }
22
+ return undefined;
23
+ }
24
+
25
+ function findJsonValueEnd(payload: string, start: number): number | undefined {
26
+ if (payload[start] === '"') return findJsonStringEnd(payload, start);
27
+ if (payload[start] !== "{" && payload[start] !== "[") {
28
+ let index = start;
29
+ while (index < payload.length && !",}]".includes(payload[index] ?? "")) index += 1;
30
+ return index;
31
+ }
32
+
33
+ let depth = 0;
34
+ let inString = false;
35
+ for (let index = start; index < payload.length; index += 1) {
36
+ const char = payload[index];
37
+ if (inString) {
38
+ if (char === "\\") index += 1;
39
+ else if (char === '"') inString = false;
40
+ continue;
41
+ }
42
+ if (char === '"') inString = true;
43
+ else if (char === "{" || char === "[") depth += 1;
44
+ else if (char === "}" || char === "]") {
45
+ depth -= 1;
46
+ if (depth === 0) return index + 1;
47
+ }
48
+ }
49
+ return undefined;
50
+ }
51
+
52
+ function findResponseObjectRange(payload: string): JsonRange | null {
53
+ let cursor = skipJsonWhitespace(payload, 0);
54
+ if (payload[cursor] !== "{") return null;
55
+ cursor += 1;
56
+ let responseRange: JsonRange | null = null;
57
+
58
+ while (cursor < payload.length) {
59
+ cursor = skipJsonWhitespace(payload, cursor);
60
+ if (payload[cursor] === "}") return responseRange;
61
+ if (payload[cursor] !== '"') return null;
62
+ const keyEnd = findJsonStringEnd(payload, cursor);
63
+ if (keyEnd === undefined) return null;
64
+ let key: unknown;
65
+ try {
66
+ key = JSON.parse(payload.slice(cursor, keyEnd));
67
+ } catch {
68
+ return null;
69
+ }
70
+
71
+ cursor = skipJsonWhitespace(payload, keyEnd);
72
+ if (payload[cursor] !== ":") return null;
73
+ const valueStart = skipJsonWhitespace(payload, cursor + 1);
74
+ const valueEnd = findJsonValueEnd(payload, valueStart);
75
+ if (valueEnd === undefined) return null;
76
+ if (key === "response" && payload[valueStart] === "{") {
77
+ responseRange = { start: valueStart, end: valueEnd };
78
+ }
79
+
80
+ cursor = skipJsonWhitespace(payload, valueEnd);
81
+ if (payload[cursor] === "}") return responseRange;
82
+ if (payload[cursor] !== ",") return null;
83
+ cursor += 1;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function rewriteIntegralTimestamp(payload: string, field: string, value: number, event: unknown): string | null {
89
+ const responseRange = findResponseObjectRange(payload);
90
+ if (responseRange === null) return null;
91
+ let cursor = responseRange.start + 1;
92
+ let candidate: { start: number; length: number } | null = null;
93
+ while (cursor < responseRange.end) {
94
+ cursor = skipJsonWhitespace(payload, cursor);
95
+ if (payload[cursor] === "}") break;
96
+ if (payload[cursor] !== '"') return null;
97
+ const keyEnd = findJsonStringEnd(payload, cursor);
98
+ if (keyEnd === undefined) return null;
99
+ let key: unknown;
100
+ try {
101
+ key = JSON.parse(payload.slice(cursor, keyEnd));
102
+ } catch {
103
+ return null;
104
+ }
105
+ cursor = skipJsonWhitespace(payload, keyEnd);
106
+ if (payload[cursor] !== ":") return null;
107
+ const valueStart = skipJsonWhitespace(payload, cursor + 1);
108
+ const valueEnd = findJsonValueEnd(payload, valueStart);
109
+ if (valueEnd === undefined || valueEnd > responseRange.end) return null;
110
+ const valueToken = payload.slice(valueStart, valueEnd).trimEnd();
111
+ if (
112
+ key === field &&
113
+ /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(valueToken) &&
114
+ valueToken !== String(value) &&
115
+ Number(valueToken) === value
116
+ ) {
117
+ if (candidate !== null) return null;
118
+ candidate = { start: valueStart, length: valueToken.length };
119
+ }
120
+ cursor = skipJsonWhitespace(payload, valueEnd);
121
+ if (payload[cursor] === "}") break;
122
+ if (payload[cursor] !== ",") return null;
123
+ cursor += 1;
124
+ }
125
+ if (candidate === null) return payload;
126
+
127
+ const rewritten = `${payload.slice(0, candidate.start)}${value}${payload.slice(candidate.start + candidate.length)}`;
128
+ try {
129
+ return JSON.stringify(JSON.parse(rewritten)) === JSON.stringify(event) ? rewritten : null;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+
8
135
  /**
9
136
  * Hide Codex-only control frames from Grok's strict Responses decoder.
10
137
  *
@@ -41,3 +168,35 @@ export function createGrokResponsesControlFrameBlockRewrite(): SseBlockRewrite {
41
168
  : [block];
42
169
  };
43
170
  }
171
+
172
+ /** Normalize safe integer response timestamps for Grok's strict Responses decoder. */
173
+ export function createGrokResponsesTimestampBlockRewrite(): SseBlockRewrite {
174
+ return (block) => {
175
+ const payload = sseDataPayload(block);
176
+ if (payload === null || payload === "[DONE]") return [block];
177
+
178
+ let event: unknown;
179
+ try {
180
+ event = JSON.parse(payload);
181
+ } catch {
182
+ return [block];
183
+ }
184
+ if (!event || typeof event !== "object" || Array.isArray(event)) return [block];
185
+ const eventRecord = event as Record<string, unknown>;
186
+ if (typeof eventRecord.type !== "string" || !eventRecord.type.startsWith("response.")) return [block];
187
+ if (!eventRecord.response || typeof eventRecord.response !== "object" || Array.isArray(eventRecord.response)) {
188
+ return [block];
189
+ }
190
+ const response = eventRecord.response as Record<string, unknown>;
191
+ let rewrittenPayload = payload;
192
+ for (const field of GROK_RESPONSE_INTEGER_TIMESTAMP_FIELDS) {
193
+ const value = response[field];
194
+ if (value === undefined) continue;
195
+ if (typeof value !== "number" || value < 0 || !Number.isSafeInteger(value)) return [block];
196
+ const rewritten = rewriteIntegralTimestamp(rewrittenPayload, field, value, event);
197
+ if (rewritten === null) return [block];
198
+ rewrittenPayload = rewritten;
199
+ }
200
+ return rewrittenPayload === payload ? [block] : [replaceSseDataPayload(block, rewrittenPayload)];
201
+ };
202
+ }