@gajae-code/ai 0.16.3 → 0.16.6

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 (50) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/types/auth-gateway/server.d.ts +4 -2
  3. package/dist/types/bedrock-claude-cache-policy.d.ts +21 -0
  4. package/dist/types/openai-completions-compat.d.ts +22 -0
  5. package/dist/types/providers/amazon-bedrock.d.ts +17 -2
  6. package/dist/types/providers/anthropic.d.ts +1 -0
  7. package/dist/types/providers/openai-completions-compat.d.ts +1 -1
  8. package/dist/types/providers/opencode-go-session.d.ts +5 -0
  9. package/dist/types/types.d.ts +9 -2
  10. package/dist/types/utils/codex-entitlement.d.ts +10 -0
  11. package/dist/types/utils/provider-response.d.ts +1 -0
  12. package/package.json +3 -3
  13. package/src/auth-gateway/server.ts +5 -0
  14. package/src/auth-storage.ts +17 -8
  15. package/src/bedrock-claude-cache-policy.d.ts +21 -0
  16. package/src/bedrock-claude-cache-policy.ts +68 -0
  17. package/src/model-pricing.ts +11 -0
  18. package/src/model-thinking.ts +43 -8
  19. package/src/models.json +37 -0
  20. package/src/openai-completions-compat.d.ts +22 -0
  21. package/src/openai-completions-compat.ts +77 -3
  22. package/src/providers/amazon-bedrock.d.ts +17 -2
  23. package/src/providers/amazon-bedrock.ts +19 -11
  24. package/src/providers/anthropic.d.ts +1 -0
  25. package/src/providers/anthropic.ts +44 -22
  26. package/src/providers/azure-openai-responses.ts +5 -2
  27. package/src/providers/cursor.ts +12 -6
  28. package/src/providers/google-gemini-cli.ts +6 -1
  29. package/src/providers/google-shared.ts +1 -1
  30. package/src/providers/kiro-api-key.ts +5 -2
  31. package/src/providers/kiro-codewhisperer.ts +10 -2
  32. package/src/providers/mock.ts +1 -0
  33. package/src/providers/ollama.ts +1 -1
  34. package/src/providers/openai-codex/response-handler.ts +1 -1
  35. package/src/providers/openai-codex-responses.ts +10 -2
  36. package/src/providers/openai-completions-compat.d.ts +1 -1
  37. package/src/providers/openai-completions-compat.ts +8 -1
  38. package/src/providers/openai-completions.ts +25 -3
  39. package/src/providers/openai-responses.ts +21 -3
  40. package/src/providers/opencode-go-session.d.ts +5 -0
  41. package/src/providers/opencode-go-session.ts +57 -0
  42. package/src/stream.ts +1 -0
  43. package/src/types.d.ts +9 -2
  44. package/src/types.ts +9 -0
  45. package/src/utils/codex-entitlement.d.ts +10 -0
  46. package/src/utils/codex-entitlement.ts +21 -0
  47. package/src/utils/discovery/antigravity.ts +2 -1
  48. package/src/utils/discovery/gemini.ts +2 -1
  49. package/src/utils/provider-response.d.ts +1 -0
  50. package/src/utils/provider-response.ts +9 -2
@@ -48,10 +48,57 @@ function parseHostname(baseUrl: string): string | undefined {
48
48
  }
49
49
  }
50
50
 
51
+ function isProductionXaiApiUrl(baseUrl: string): boolean {
52
+ if (
53
+ /[\u0000-\u0020\u007f]/u.test(baseUrl) ||
54
+ baseUrl.includes("?") ||
55
+ baseUrl.includes("#") ||
56
+ baseUrl.includes("\\")
57
+ ) {
58
+ return false;
59
+ }
60
+ const authorityTail = baseUrl.slice("https://".length);
61
+ const authorityEnd = authorityTail.search(/[/?#]/u);
62
+ const authority = authorityTail.slice(0, authorityEnd === -1 ? undefined : authorityEnd);
63
+ if (!/^api\.x\.ai(?::443)?$/iu.test(authority)) return false;
64
+ try {
65
+ const url = new URL(baseUrl);
66
+ return (
67
+ url.protocol === "https:" &&
68
+ url.hostname.toLowerCase() === "api.x.ai" &&
69
+ url.port === "" &&
70
+ url.username === "" &&
71
+ url.password === "" &&
72
+ url.search === "" &&
73
+ url.hash === ""
74
+ );
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
51
80
  function hostnameMatches(hostname: string | undefined, suffix: string): boolean {
52
81
  return hostname !== undefined && (hostname === suffix || hostname.endsWith(`.${suffix}`));
53
82
  }
54
83
 
84
+ export type GrokGeneration = {
85
+ major: number;
86
+ minor: number;
87
+ };
88
+
89
+ function parseGrokGeneration(modelId: string): GrokGeneration | undefined {
90
+ const canonicalId = modelId.startsWith("x-ai/") ? modelId.slice("x-ai/".length) : modelId;
91
+ const match =
92
+ /^grok-([1-9]\d?)(?:\.(0|[1-9]\d?))?(?:-(latest|preview|\d{8}|reasoning|\d{4}-reasoning|beta-latest-reasoning|multi-agent-beta-latest))?$/.exec(
93
+ canonicalId,
94
+ );
95
+ if (!match) return undefined;
96
+ return {
97
+ major: Number(match[1]),
98
+ minor: match[2] === undefined ? 0 : Number(match[2]),
99
+ };
100
+ }
101
+
55
102
  /** Returns whether the request endpoint is an audited reasoning-control transport. */
56
103
  export function isAuditedOpenAIReasoningTransport(
57
104
  model: { provider: string; baseUrl?: string },
@@ -63,6 +110,32 @@ export function isAuditedOpenAIReasoningTransport(
63
110
  return AUDITED_REASONING_EFFORT_HOST_SUFFIXES.some(suffix => hostnameMatches(hostname, suffix));
64
111
  }
65
112
 
113
+ /**
114
+ * xAI's first-party API accepts `reasoning_effort` on Grok 4.5 and later.
115
+ * Provider labels are user-configurable, so both the provider and the official
116
+ * API origin must match. Unknown variants fail closed instead of inheriting a
117
+ * capability from a loose model-id prefix.
118
+ */
119
+ export function parseDirectXaiReasoningEffortGeneration(
120
+ model: { provider: string; id: string; api?: string; baseUrl?: string },
121
+ resolvedBaseUrl?: string,
122
+ ): GrokGeneration | undefined {
123
+ if (model.provider !== "xai") return undefined;
124
+ if (model.api !== "openai-completions") return undefined;
125
+ if (!isProductionXaiApiUrl(resolvedBaseUrl ?? model.baseUrl ?? "")) return undefined;
126
+ if (model.id.includes("/")) return undefined;
127
+ const generation = parseGrokGeneration(model.id);
128
+ if (!generation) return undefined;
129
+ return generation.major > 4 || (generation.major === 4 && generation.minor >= 5) ? generation : undefined;
130
+ }
131
+
132
+ export function isDirectXaiReasoningEffortModel(
133
+ model: { provider: string; id: string; api?: string; baseUrl?: string },
134
+ resolvedBaseUrl?: string,
135
+ ): boolean {
136
+ return parseDirectXaiReasoningEffortGeneration(model, resolvedBaseUrl) !== undefined;
137
+ }
138
+
66
139
  export type ResolvedOpenAICompat = Required<
67
140
  Omit<
68
141
  OpenAICompat,
@@ -184,8 +257,8 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
184
257
  baseUrl.includes("chutes.ai") ||
185
258
  baseUrl.includes("fireworks.ai") ||
186
259
  isDirectDeepseekApi;
187
- const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
188
- const isDirectXaiReasoningEffortModel = provider === "xai" && (model.id === "grok-4.5" || model.id === "grok-4.6");
260
+ const isGrok = provider === "xai" || baseUrl.includes("api.x.ai") || parseGrokGeneration(model.id) !== undefined;
261
+
189
262
  const isMistral = provider === "mistral" || baseUrl.includes("mistral.ai");
190
263
 
191
264
  // Hosts whose chat-completions endpoints are known to accept multiple
@@ -278,7 +351,8 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
278
351
  supportsResponsesSessionAffinity: false,
279
352
  supportsMultipleSystemMessages: supportsMultipleSystemMessagesDefault,
280
353
  supportsReasoningEffort:
281
- hasAuditedReasoningEffortTransport && ((!isGrok && !isZai) || isDirectXaiReasoningEffortModel),
354
+ hasAuditedReasoningEffortTransport &&
355
+ ((!isGrok && !isZai) || isOpenRouter || isDirectXaiReasoningEffortModel(model, resolvedBaseUrl)),
282
356
  reasoningEffortMap,
283
357
  supportsUsageInStreaming: !isCerebras,
284
358
  disableReasoningOnForcedToolChoice: isKimiModel || isAnthropicModel || isOpenCodeGoReasoning,
@@ -6,8 +6,9 @@
6
6
  * No `@aws-sdk/*`, no `@smithy/*`, no `proxy-agent`. Proxies are honored via
7
7
  * Bun's native `HTTPS_PROXY` support.
8
8
  */
9
+ import { parseBedrockClaudeGeneration } from "../bedrock-claude-cache-policy";
9
10
  import type { Effort } from "../model-thinking";
10
- import type { StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
+ import type { Model, StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
12
  export type BedrockThinkingDisplay = "summarized" | "omitted";
12
13
  export interface BedrockOptions extends StreamOptions {
13
14
  region?: string;
@@ -51,10 +52,24 @@ interface WireToolConfig {
51
52
  toolChoice?: WireToolChoice;
52
53
  }
53
54
  export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
55
+ /**
56
+ * Check if the model supports prompt caching.
57
+ * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
58
+ * generation:
59
+ * https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
60
+ *
61
+ * For base models and system-defined inference profiles the model ID / ARN
62
+ * contains the model name, so we can decide locally.
63
+ *
64
+ * For application inference profiles (whose ARNs don't contain the model name),
65
+ * set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
66
+ * have automatic caching and don't need explicit cache points.
67
+ */
68
+ export declare function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean;
69
+ export { parseBedrockClaudeGeneration };
54
70
  export declare function stripBedrockForcedToolChoiceForRetry<T extends {
55
71
  toolConfig?: {
56
72
  toolChoice?: unknown;
57
73
  };
58
74
  }>(body: T): T;
59
75
  export declare function convertToolConfig(tools: Tool[] | undefined, toolChoice: BedrockOptions["toolChoice"]): WireToolConfig | undefined;
60
- export {};
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { $credentialEnv, $env, $flag, extractHttpStatusFromError, fetchWithRetry } from "@gajae-code/utils";
11
11
  import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
12
+ import { parseBedrockClaudeGeneration, supportsBedrockClaudePromptCaching } from "../bedrock-claude-cache-policy";
12
13
  import type { Effort } from "../model-thinking";
13
14
  import {
14
15
  mapEffortToAnthropicAdaptiveEffort,
@@ -219,7 +220,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
219
220
  if (tc.any || tc.tool) additionalModelRequestFields = undefined;
220
221
  }
221
222
 
222
- const commandInput: ConverseStreamRequest = {
223
+ let commandInput: ConverseStreamRequest = {
223
224
  messages: convertMessages(context, model, cacheRetention),
224
225
  system: buildSystemPrompt(context.systemPrompt, model, cacheRetention),
225
226
  inferenceConfig: {
@@ -230,7 +231,15 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
230
231
  toolConfig,
231
232
  additionalModelRequestFields,
232
233
  };
233
- options?.onPayload?.(commandInput, model, options?.attemptScope);
234
+ const replacementPayload = await options?.onPayload?.(
235
+ commandInput,
236
+ model,
237
+ options?.attemptScope,
238
+ options?.signal,
239
+ );
240
+ if (replacementPayload !== undefined) {
241
+ commandInput = replacementPayload as typeof commandInput;
242
+ }
234
243
 
235
244
  const host = `bedrock-runtime.${region}.amazonaws.com`;
236
245
  const url = `https://${host}/model/${encodeURIComponent(model.id)}/converse-stream`;
@@ -610,7 +619,9 @@ function handleContentBlockStop(
610
619
 
611
620
  /**
612
621
  * Check if the model supports prompt caching.
613
- * Supported: Anthropic model 3.5 Haiku, Anthropic model 3.7 Sonnet, Anthropic model 4.x+ models, Haiku 4.5+
622
+ * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
623
+ * generation:
624
+ * https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
614
625
  *
615
626
  * For base models and system-defined inference profiles the model ID / ARN
616
627
  * contains the model name, so we can decide locally.
@@ -619,21 +630,18 @@ function handleContentBlockStop(
619
630
  * set AWS_BEDROCK_FORCE_CACHE=1 to enable cache points. Amazon Nova models
620
631
  * have automatic caching and don't need explicit cache points.
621
632
  */
622
- function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean {
633
+ export function supportsPromptCaching(model: Model<"bedrock-converse-stream">): boolean {
634
+ const claudeSupport = supportsBedrockClaudePromptCaching(model.id);
635
+ if (claudeSupport !== undefined) return claudeSupport;
623
636
  if (model.cost.cacheRead || model.cost.cacheWrite) return true;
624
- const id = model.id.toLowerCase();
625
- // Anthropic model 4.x models (opus-4, sonnet-4, haiku-4)
626
- if (id.includes("claude") && (id.includes("-4-") || id.includes("-4."))) return true;
627
- // Anthropic model 3.5 Haiku, Anthropic model 3.7 Sonnet (legacy naming)
628
- if (id.includes("claude-3-7-sonnet") || id.includes("claude-3-5-haiku")) return true;
629
- // Anthropic model Haiku 4.5+ (new naming)
630
- if (id.includes("claude-haiku")) return true;
631
637
  // Application inference profiles don't contain the model name in the ARN.
632
638
  // Allow users to force cache points via environment variable.
633
639
  if (typeof process !== "undefined" && $flag("AWS_BEDROCK_FORCE_CACHE")) return true;
634
640
  return false;
635
641
  }
636
642
 
643
+ export { parseBedrockClaudeGeneration };
644
+
637
645
  /**
638
646
  * Check if the model supports thinking signatures in reasoningContent.
639
647
  * Only Anthropic Anthropic model models support the signature field.
@@ -203,6 +203,7 @@ export type AnthropicClientOptionsArgs = {
203
203
  maxRetryDelayMs?: number;
204
204
  streamFirstEventTimeoutMs?: number;
205
205
  streamIdleTimeoutMs?: number;
206
+ providerSessionId?: string;
206
207
  };
207
208
  export type AnthropicClientOptionsResult = {
208
209
  isOAuthToken: boolean;
@@ -111,6 +111,11 @@ import {
111
111
  hasCopilotVisionInput,
112
112
  resolveGitHubCopilotBaseUrl,
113
113
  } from "./github-copilot-headers";
114
+ import {
115
+ applyOpenCodeGoSessionHeader,
116
+ resolveOpenCodeGoSessionId,
117
+ wrapFetchForOpenCodeGoSession,
118
+ } from "./opencode-go-session";
114
119
  import { hasAdjacentPrivateThinkingBlocks, transformMessages } from "./transform-messages";
115
120
  import { NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
116
121
 
@@ -1126,6 +1131,7 @@ export type AnthropicClientOptionsArgs = {
1126
1131
  maxRetryDelayMs?: number;
1127
1132
  streamFirstEventTimeoutMs?: number;
1128
1133
  streamIdleTimeoutMs?: number;
1134
+ providerSessionId?: string;
1129
1135
  };
1130
1136
 
1131
1137
  export type AnthropicClientOptionsResult = {
@@ -1940,6 +1946,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1940
1946
  maxRetryDelayMs: options?.maxRetryDelayMs,
1941
1947
  streamFirstEventTimeoutMs: options?.streamFirstEventTimeoutMs,
1942
1948
  streamIdleTimeoutMs: options?.streamIdleTimeoutMs,
1949
+ providerSessionId: options?.providerSessionId,
1943
1950
  });
1944
1951
  client = created.client;
1945
1952
  isOAuthToken = created.isOAuthToken;
@@ -1992,7 +1999,12 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
1992
1999
  if (dropFastMode) {
1993
2000
  dropAnthropicFastMode(nextParams);
1994
2001
  }
1995
- const replacementPayload = await options?.onPayload?.(nextParams, model, options?.attemptScope);
2002
+ const replacementPayload = await options?.onPayload?.(
2003
+ nextParams,
2004
+ model,
2005
+ options?.attemptScope,
2006
+ options?.signal,
2007
+ );
1996
2008
  if (replacementPayload !== undefined) {
1997
2009
  nextParams = replacementPayload as typeof nextParams;
1998
2010
  }
@@ -3107,7 +3119,11 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
3107
3119
  const tlsFetchOptions = buildClaudeCodeTlsFetchOptions(model, baseUrl);
3108
3120
  const baseFetch = args.fetch ?? fetch;
3109
3121
  const boundedFetch = wrapAnthropicFetchForBoundedRateLimits(baseFetch, args.maxRetryDelayMs);
3110
- const debugFetch = onSseEvent ? wrapFetchForSseDebug(boundedFetch, event => onSseEvent(event, model)) : boundedFetch;
3122
+ const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, args.providerSessionId, "anthropic");
3123
+ const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
3124
+ const debugFetch = onSseEvent
3125
+ ? wrapFetchForSseDebug(providerScopedFetch, event => onSseEvent(event, model))
3126
+ : providerScopedFetch;
3111
3127
  // Bound the connect/headers phase. The first-event watchdog arms only after
3112
3128
  // response headers arrive, so a request whose connection dies before headers
3113
3129
  // was previously governed only by the Anthropic SDK's 10-minute default per
@@ -3124,16 +3140,19 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
3124
3140
  if (needsFineGrainedToolStreamingBeta) {
3125
3141
  betaFeatures.push(fineGrainedToolStreamingBeta);
3126
3142
  }
3127
- const defaultHeaders = mergeHeaders(
3128
- {
3129
- Accept: stream ? "text/event-stream" : "application/json",
3130
- "Anthropic-Dangerous-Direct-Browser-Access": "true",
3131
- Authorization: `Bearer ${copilotApiKey}`,
3132
- ...(betaFeatures.length > 0 ? { "anthropic-beta": buildBetaHeader([], betaFeatures) } : {}),
3133
- },
3134
- model.headers,
3135
- dynamicHeaders,
3136
- headers,
3143
+ const defaultHeaders = applyOpenCodeGoSessionHeader(
3144
+ mergeHeaders(
3145
+ {
3146
+ Accept: stream ? "text/event-stream" : "application/json",
3147
+ "Anthropic-Dangerous-Direct-Browser-Access": "true",
3148
+ Authorization: `Bearer ${copilotApiKey}`,
3149
+ ...(betaFeatures.length > 0 ? { "anthropic-beta": buildBetaHeader([], betaFeatures) } : {}),
3150
+ },
3151
+ model.headers,
3152
+ dynamicHeaders,
3153
+ headers,
3154
+ ),
3155
+ openCodeGoSessionId,
3137
3156
  );
3138
3157
 
3139
3158
  return {
@@ -3159,16 +3178,19 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
3159
3178
  betaFeatures.push(interleavedThinkingBeta);
3160
3179
  }
3161
3180
 
3162
- const defaultHeaders = buildAnthropicHeaders({
3163
- apiKey,
3164
- baseUrl,
3165
- isOAuth: oauthToken,
3166
- extraBetas: betaFeatures,
3167
- stream,
3168
- modelHeaders: mergeHeaders(model.headers, foundryCustomHeaders, headers, dynamicHeaders),
3169
- isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
3170
- zcodeSourceHeaders: model.provider === "glm-zcode",
3171
- });
3181
+ const defaultHeaders = applyOpenCodeGoSessionHeader(
3182
+ buildAnthropicHeaders({
3183
+ apiKey,
3184
+ baseUrl,
3185
+ isOAuth: oauthToken,
3186
+ extraBetas: betaFeatures,
3187
+ stream,
3188
+ modelHeaders: mergeHeaders(model.headers, foundryCustomHeaders, headers, dynamicHeaders),
3189
+ isCloudflareAiGateway: model.provider === "cloudflare-ai-gateway",
3190
+ zcodeSourceHeaders: model.provider === "glm-zcode",
3191
+ }),
3192
+ openCodeGoSessionId,
3193
+ );
3172
3194
 
3173
3195
  if (model.provider === "cloudflare-ai-gateway") {
3174
3196
  return {
@@ -129,9 +129,12 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
129
129
  const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
130
130
  const client = createClient(model, apiKey, options);
131
131
  const { baseUrl } = resolveAzureConfig(model, options);
132
- const params = buildParams(model, context, options, deploymentName, baseUrl);
132
+ let params = buildParams(model, context, options, deploymentName, baseUrl);
133
133
  const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
134
- options?.onPayload?.(params, model, options?.attemptScope);
134
+ const replacementPayload = await options?.onPayload?.(params, model, options?.attemptScope, options?.signal);
135
+ if (replacementPayload !== undefined) {
136
+ params = replacementPayload as typeof params;
137
+ }
135
138
  rawRequestDump = {
136
139
  provider: model.provider,
137
140
  api: output.api,
@@ -689,7 +689,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = (
689
689
  conversationUsageContextCache.set(conversationId, usageContext);
690
690
  const reusableCachedState =
691
691
  cachedState && canReuseCursorUsageContext(previousUsageContext, usageContext) ? cachedState : undefined;
692
- const { requestBytes, conversationState } = buildGrpcRequest(model, context, options, {
692
+ const { requestBytes, conversationState } = await buildGrpcRequest(model, context, options, {
693
693
  conversationId,
694
694
  blobStore,
695
695
  conversationState: reusableCachedState,
@@ -3459,7 +3459,7 @@ function extractImages(content: (TextContent | ImageContent)[]) {
3459
3459
  );
3460
3460
  }
3461
3461
 
3462
- function buildGrpcRequest(
3462
+ async function buildGrpcRequest(
3463
3463
  model: Model<"cursor-agent">,
3464
3464
  context: Context,
3465
3465
  options: CursorOptions | undefined,
@@ -3468,11 +3468,11 @@ function buildGrpcRequest(
3468
3468
  blobStore: Map<string, Uint8Array>;
3469
3469
  conversationState?: ConversationStateStructure;
3470
3470
  },
3471
- ): {
3471
+ ): Promise<{
3472
3472
  requestBytes: Uint8Array;
3473
3473
  blobStore: Map<string, Uint8Array>;
3474
3474
  conversationState: ConversationStateStructure;
3475
- } {
3475
+ }> {
3476
3476
  const blobStore = state.blobStore;
3477
3477
 
3478
3478
  const systemPromptIds = buildCursorSystemPromptJsons(context.systemPrompt, model.id).map(json =>
@@ -3558,7 +3558,7 @@ function buildGrpcRequest(
3558
3558
  displayName: model.name,
3559
3559
  });
3560
3560
 
3561
- const runRequest = create(AgentRunRequestSchema, {
3561
+ let runRequest = create(AgentRunRequestSchema, {
3562
3562
  conversationState,
3563
3563
  action,
3564
3564
  modelDetails,
@@ -3573,7 +3573,13 @@ function buildGrpcRequest(
3573
3573
  : {}),
3574
3574
  });
3575
3575
 
3576
- options?.onPayload?.(runRequest, model, options?.attemptScope);
3576
+ if (options?.onPayload) {
3577
+ const payload = toJson(AgentRunRequestSchema, runRequest);
3578
+ const replacement = await options.onPayload(payload, model, options.attemptScope, options.signal);
3579
+ if (replacement !== undefined) {
3580
+ runRequest = fromJson(AgentRunRequestSchema, replacement as JsonValue);
3581
+ }
3582
+ }
3577
3583
 
3578
3584
  // Tools are sent later via requestContext (exec handshake)
3579
3585
 
@@ -354,7 +354,12 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
354
354
  const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
355
355
 
356
356
  let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
357
- const replacementPayload = await options?.onPayload?.(requestBody, model, options?.attemptScope);
357
+ const replacementPayload = await options?.onPayload?.(
358
+ requestBody,
359
+ model,
360
+ options?.attemptScope,
361
+ options?.signal,
362
+ );
358
363
  if (replacementPayload !== undefined) {
359
364
  requestBody = replacementPayload as typeof requestBody;
360
365
  }
@@ -899,7 +899,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
899
899
  try {
900
900
  const plan = await prepare();
901
901
  let params = plan.params;
902
- const replacement = await options?.onPayload?.(params, model, options?.attemptScope);
902
+ const replacement = await options?.onPayload?.(params, model, options?.attemptScope, options?.signal);
903
903
  if (replacement !== undefined) {
904
904
  params = replacement as GenerateContentParameters;
905
905
  }
@@ -611,8 +611,11 @@ export const streamKiroApiKey: StreamFunction<"kiro-codewhisperer-stream"> = (
611
611
  const configuredBaseUrl = model.baseUrl;
612
612
  const usesExplicitBaseUrl = Boolean(configuredBaseUrl) && !isRegionDerivedKiroApiBaseUrl(configuredBaseUrl);
613
613
  const endpoint = configuredBaseUrl || kiroApiBaseUrl(kiroApiRegion(options));
614
- const request = buildApiKeyRequest(model, context, options);
615
- options?.onPayload?.(request, model, options?.attemptScope);
614
+ let request = buildApiKeyRequest(model, context, options);
615
+ const replacementPayload = await options?.onPayload?.(request, model, options?.attemptScope, options?.signal);
616
+ if (replacementPayload !== undefined) {
617
+ request = replacementPayload;
618
+ }
616
619
 
617
620
  const response = await fetch(endpoint, {
618
621
  method: "POST",
@@ -193,11 +193,19 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
193
193
 
194
194
  // Build request
195
195
  const conversationState = buildConversationState(context, model, options);
196
- const requestBody: GenerateAssistantResponseRequest = {
196
+ let requestBody: GenerateAssistantResponseRequest = {
197
197
  conversationState,
198
198
  };
199
199
 
200
- options?.onPayload?.(requestBody, model, options?.attemptScope);
200
+ const replacementPayload = await options?.onPayload?.(
201
+ requestBody,
202
+ model,
203
+ options?.attemptScope,
204
+ options?.signal,
205
+ );
206
+ if (replacementPayload !== undefined) {
207
+ requestBody = replacementPayload as typeof requestBody;
208
+ }
201
209
 
202
210
  const host = `${STREAMING_SERVICE_NAME}.${region}.amazonaws.com`;
203
211
  const url = `https://${host}/`;
@@ -339,6 +339,7 @@ async function runMock(
339
339
  },
340
340
  model,
341
341
  options.attemptScope,
342
+ options.signal,
342
343
  );
343
344
  } catch (err) {
344
345
  stream.fail(err);
@@ -407,7 +407,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
407
407
  const baseUrl = normalizeBaseUrl(model.baseUrl);
408
408
  let body = createChatBody(model, context, options);
409
409
  const sentForcedToolChoice = body.tool_choice === "required";
410
- const replacementPayload = await options.onPayload?.(body, model, options?.attemptScope);
410
+ const replacementPayload = await options.onPayload?.(body, model, options?.attemptScope, options?.signal);
411
411
  if (replacementPayload !== undefined) {
412
412
  body = replacementPayload as typeof body;
413
413
  }
@@ -25,7 +25,7 @@ const REQUEST_BLOCKED_MESSAGE_RE = /^\s*request blocked\b/i;
25
25
 
26
26
  export async function parseCodexError(response: Response): Promise<CodexErrorInfo> {
27
27
  const raw = await response.text();
28
- let message = raw || response.statusText || "Request failed";
28
+ let message = raw || response.statusText || `Codex request failed (HTTP ${response.status})`;
29
29
  let friendlyMessage: string | undefined;
30
30
  let rateLimits: CodexRateLimits | undefined;
31
31
  let code: string | undefined;
@@ -717,8 +717,16 @@ async function buildCodexRequestContext(
717
717
  const baseUrl = model.baseUrl || CODEX_BASE_URL;
718
718
  const url = resolveCodexResponsesUrl(baseUrl);
719
719
  const promptCacheKey = normalizeOpenAIResponsesPromptCacheKey(options?.sessionId);
720
- const transformedBody = await buildTransformedCodexRequestBody(model, context, options);
721
- options?.onPayload?.(transformedBody, model, options?.attemptScope);
720
+ let transformedBody = await buildTransformedCodexRequestBody(model, context, options);
721
+ const replacementPayload = await options?.onPayload?.(
722
+ transformedBody,
723
+ model,
724
+ options?.attemptScope,
725
+ options?.signal,
726
+ );
727
+ if (replacementPayload !== undefined) {
728
+ transformedBody = replacementPayload as typeof transformedBody;
729
+ }
722
730
 
723
731
  const requestHeaders = { ...(model.headers ?? {}), ...(options?.headers ?? {}) };
724
732
  const rawRequestDump: RawHttpRequestDump = {
@@ -3,4 +3,4 @@
3
3
  * the core-safe module so model metadata can use it without loading provider
4
4
  * implementations during startup.
5
5
  */
6
- export { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "../openai-completions-compat";
6
+ export { detectOpenAICompat, type GrokGeneration, isDirectXaiReasoningEffortModel, parseDirectXaiReasoningEffortGeneration, type ResolvedOpenAICompat, resolveOpenAICompat, } from "../openai-completions-compat";
@@ -3,4 +3,11 @@
3
3
  * the core-safe module so model metadata can use it without loading provider
4
4
  * implementations during startup.
5
5
  */
6
- export { detectOpenAICompat, type ResolvedOpenAICompat, resolveOpenAICompat } from "../openai-completions-compat";
6
+ export {
7
+ detectOpenAICompat,
8
+ type GrokGeneration,
9
+ isDirectXaiReasoningEffortModel,
10
+ parseDirectXaiReasoningEffortGeneration,
11
+ type ResolvedOpenAICompat,
12
+ resolveOpenAICompat,
13
+ } from "../openai-completions-compat";
@@ -100,6 +100,11 @@ import {
100
100
  wrapFetchForOpenAIRequestTransform,
101
101
  } from "./openai-request-transform";
102
102
  import { createInitialResponsesAssistantMessage } from "./openai-responses-shared";
103
+ import {
104
+ applyOpenCodeGoSessionHeader,
105
+ resolveOpenCodeGoSessionId,
106
+ wrapFetchForOpenCodeGoSession,
107
+ } from "./opencode-go-session";
103
108
  import { transformMessages } from "./transform-messages";
104
109
  import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
105
110
 
@@ -602,6 +607,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
602
607
  options?.authCredentialType,
603
608
  options?.requestMaxRetries,
604
609
  options?.sessionId,
610
+ options?.providerSessionId,
605
611
  options?.maxRetryDelayMs,
606
612
  options?.attemptScope,
607
613
  );
@@ -618,15 +624,24 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
618
624
  const createCompletionsStream = async (toolStrictModeOverride?: ToolStrictModeOverride) => {
619
625
  clearCapturedErrorResponse();
620
626
  const effectiveToolStrictModeOverride = disableStrictTools ? "none" : toolStrictModeOverride;
621
- const { params, toolStrictMode } = buildParams(
627
+ const { params: builtParams, toolStrictMode } = buildParams(
622
628
  model,
623
629
  context,
624
630
  options,
625
631
  baseUrl,
626
632
  effectiveToolStrictModeOverride,
627
633
  );
634
+ let params = builtParams;
628
635
  appliedToolStrictMode = toolStrictMode;
629
- options?.onPayload?.(params, undefined, options?.attemptScope);
636
+ const replacementPayload = await options?.onPayload?.(
637
+ params,
638
+ undefined,
639
+ options?.attemptScope,
640
+ options?.signal,
641
+ );
642
+ if (replacementPayload !== undefined) {
643
+ params = replacementPayload as typeof params;
644
+ }
630
645
  rawRequestDump = {
631
646
  provider: model.provider,
632
647
  api: output.api,
@@ -1259,6 +1274,7 @@ async function createClient(
1259
1274
  authCredentialType?: OpenAICompletionsOptions["authCredentialType"],
1260
1275
  requestMaxRetries?: number,
1261
1276
  sessionId?: string,
1277
+ providerSessionId?: string,
1262
1278
  maxRetryDelayMs?: number,
1263
1279
  attemptScope?: import("../types.js").AttemptScopeRef,
1264
1280
  ): Promise<{
@@ -1356,6 +1372,11 @@ async function createClient(
1356
1372
  const endpointRequestQuery = endpointQuery;
1357
1373
  const requestQuery =
1358
1374
  [endpointRequestQuery, azureQuery].filter((query): query is string => query !== undefined).join("&") || undefined;
1375
+ const openCodeGoSessionId = resolveOpenCodeGoSessionId(model, baseUrl, providerSessionId, "openai");
1376
+ // Reserve the provider-specific header on every OpenAI-compatible route. Caller,
1377
+ // model, and transform values are removed unless the exact OpenCode Go endpoint
1378
+ // has an opaque identity owned by the agent's conversation lifecycle.
1379
+ headers = applyOpenCodeGoSessionHeader(headers, openCodeGoSessionId);
1359
1380
  let capturedErrorResponse: CapturedHttpErrorResponse | undefined;
1360
1381
  const baseFetch = fetchOverride ?? fetch;
1361
1382
  const wrappedFetch = Object.assign(
@@ -1389,8 +1410,9 @@ async function createClient(
1389
1410
  baseFetch.preconnect ? { preconnect: baseFetch.preconnect } : {},
1390
1411
  );
1391
1412
  const boundedFetch = wrapOpenAIFetchForBoundedRateLimits(wrappedFetch, maxRetryDelayMs);
1413
+ const providerScopedFetch = wrapFetchForOpenCodeGoSession(boundedFetch, openCodeGoSessionId);
1392
1414
  const transformedFetch = wrapFetchForOpenAIRequestTransform(
1393
- boundedFetch,
1415
+ providerScopedFetch,
1394
1416
  model.requestTransform,
1395
1417
  `Gajae-Code/${packageJson.version}`,
1396
1418
  );