@opencode-ai/ai 0.0.0-dev-18001 → 0.0.0-dev-18017

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 (30) hide show
  1. package/dist/protocols/anthropic-messages.js +3 -2
  2. package/dist/protocols/gemini.d.ts +40 -40
  3. package/dist/protocols/gemini.js +47 -34
  4. package/dist/protocols/open-responses.d.ts +6 -6
  5. package/dist/protocols/open-responses.js +1 -1
  6. package/dist/protocols/openai-chat.d.ts +42 -2
  7. package/dist/protocols/openai-chat.js +34 -5
  8. package/dist/protocols/openai-compatible-responses.d.ts +1 -1
  9. package/dist/protocols/openai-responses.d.ts +6 -6
  10. package/dist/protocols/openai-responses.js +1 -2
  11. package/dist/protocols/utils/open-responses-options.d.ts +4 -3
  12. package/dist/protocols/utils/open-responses-options.js +2 -1
  13. package/dist/protocols/utils/openai-options.d.ts +3 -3
  14. package/dist/protocols/utils/openai-options.js +1 -1
  15. package/dist/protocols/xai-responses.d.ts +1 -1
  16. package/dist/providers/amazon-bedrock-mantle.d.ts +11 -11
  17. package/dist/providers/azure.d.ts +8 -8
  18. package/dist/providers/cloudflare.d.ts +2 -2
  19. package/dist/providers/google-vertex-chat.d.ts +2 -2
  20. package/dist/providers/google-vertex-responses.d.ts +1 -1
  21. package/dist/providers/google-vertex.d.ts +7 -7
  22. package/dist/providers/google-vertex.js +1 -1
  23. package/dist/providers/google.d.ts +7 -7
  24. package/dist/providers/openai-compatible-responses.d.ts +1 -1
  25. package/dist/providers/openai-compatible.d.ts +9 -9
  26. package/dist/providers/openai-options.d.ts +6 -2
  27. package/dist/providers/openai.d.ts +12 -12
  28. package/dist/providers/openrouter.d.ts +22 -0
  29. package/dist/providers/xai.d.ts +11 -11
  30. package/package.json +5 -5
@@ -24,6 +24,7 @@ const SSE_EVENTS = new Set([
24
24
  "content_block_start",
25
25
  "content_block_delta",
26
26
  "content_block_stop",
27
+ "ping",
27
28
  "error",
28
29
  ]);
29
30
  export const framing = Framing.sseEvents(SSE_EVENTS);
@@ -829,11 +830,11 @@ const providerErrorMessage = (event) => {
829
830
  return `${type}: ${message}`;
830
831
  return message || type || "Anthropic Messages stream error";
831
832
  };
832
- const onError = (event) => new AIError({
833
+ const onError = (event) => Effect.fail(new AIError({
833
834
  module: ADAPTER,
834
835
  method: "stream",
835
836
  reason: classifyProviderFailure({ message: providerErrorMessage(event), code: event.error?.type }),
836
- });
837
+ }));
837
838
  const step = (state, event) => {
838
839
  if (event.type === "message_start")
839
840
  return Effect.succeed(onMessageStart(state, event));
@@ -21,11 +21,11 @@ export type ProviderOptionsInput = OptionsInput;
21
21
  declare const GeminiBody: Schema.Struct<{
22
22
  cachedContent: Schema.optional<Schema.String>;
23
23
  contents: Schema.$Array<Schema.Struct<{
24
- readonly role: Schema.Literals<readonly ["user", "model"]>;
25
- readonly parts: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
24
+ readonly role: Schema.optional<Schema.NullOr<Schema.Literals<readonly ["user", "model"]>>>;
25
+ readonly parts: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
26
26
  readonly text: Schema.String;
27
- readonly thought: Schema.optional<Schema.Boolean>;
28
- readonly thoughtSignature: Schema.optional<Schema.String>;
27
+ readonly thought: Schema.optional<Schema.NullOr<Schema.Boolean>>;
28
+ readonly thoughtSignature: Schema.optional<Schema.NullOr<Schema.String>>;
29
29
  }>, Schema.Struct<{
30
30
  readonly inlineData: Schema.Struct<{
31
31
  readonly mimeType: Schema.String;
@@ -33,11 +33,11 @@ declare const GeminiBody: Schema.Struct<{
33
33
  }>;
34
34
  }>, Schema.Struct<{
35
35
  readonly functionCall: Schema.Struct<{
36
- readonly id: Schema.optional<Schema.String>;
36
+ readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
37
37
  readonly name: Schema.String;
38
38
  readonly args: Schema.optional<Schema.Unknown>;
39
39
  }>;
40
- readonly thoughtSignature: Schema.optional<Schema.String>;
40
+ readonly thoughtSignature: Schema.optional<Schema.NullOr<Schema.String>>;
41
41
  }>, Schema.Struct<{
42
42
  readonly functionResponse: Schema.Struct<{
43
43
  readonly id: Schema.optional<Schema.String>;
@@ -50,7 +50,7 @@ declare const GeminiBody: Schema.Struct<{
50
50
  }>;
51
51
  }>>>;
52
52
  }>;
53
- }>]>>;
53
+ }>]>>>>;
54
54
  }>>;
55
55
  labels: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
56
56
  safetySettings: Schema.optional<Schema.$Array<Schema.Struct<{
@@ -99,23 +99,22 @@ export type GeminiBody = Schema.Schema.Type<typeof GeminiBody>;
99
99
  */
100
100
  export declare const protocol: Protocol<{
101
101
  readonly contents: readonly {
102
- readonly role: "model" | "user";
103
- readonly parts: readonly ({
102
+ readonly parts?: readonly ({
104
103
  readonly inlineData: {
105
104
  readonly mimeType: string;
106
105
  readonly data: string;
107
106
  };
108
107
  } | {
109
108
  readonly text: string;
110
- readonly thought?: boolean | undefined;
111
- readonly thoughtSignature?: string | undefined;
109
+ readonly thought?: boolean | null | undefined;
110
+ readonly thoughtSignature?: string | null | undefined;
112
111
  } | {
113
112
  readonly functionCall: {
114
113
  readonly name: string;
115
- readonly id?: string | undefined;
114
+ readonly id?: string | null | undefined;
116
115
  readonly args?: unknown;
117
116
  };
118
- readonly thoughtSignature?: string | undefined;
117
+ readonly thoughtSignature?: string | null | undefined;
119
118
  } | {
120
119
  readonly functionResponse: {
121
120
  readonly name: string;
@@ -128,7 +127,8 @@ export declare const protocol: Protocol<{
128
127
  };
129
128
  }[] | undefined;
130
129
  };
131
- })[];
130
+ })[] | null | undefined;
131
+ readonly role?: "model" | "user" | null | undefined;
132
132
  }[];
133
133
  readonly tools?: readonly {
134
134
  readonly functionDeclarations: readonly {
@@ -177,23 +177,22 @@ export declare const protocol: Protocol<{
177
177
  }, string, {
178
178
  readonly candidates?: readonly {
179
179
  readonly content?: {
180
- readonly role: "model" | "user";
181
- readonly parts: readonly ({
180
+ readonly parts?: readonly ({
182
181
  readonly inlineData: {
183
182
  readonly mimeType: string;
184
183
  readonly data: string;
185
184
  };
186
185
  } | {
187
186
  readonly text: string;
188
- readonly thought?: boolean | undefined;
189
- readonly thoughtSignature?: string | undefined;
187
+ readonly thought?: boolean | null | undefined;
188
+ readonly thoughtSignature?: string | null | undefined;
190
189
  } | {
191
190
  readonly functionCall: {
192
191
  readonly name: string;
193
- readonly id?: string | undefined;
192
+ readonly id?: string | null | undefined;
194
193
  readonly args?: unknown;
195
194
  };
196
- readonly thoughtSignature?: string | undefined;
195
+ readonly thoughtSignature?: string | null | undefined;
197
196
  } | {
198
197
  readonly functionResponse: {
199
198
  readonly name: string;
@@ -206,46 +205,46 @@ export declare const protocol: Protocol<{
206
205
  };
207
206
  }[] | undefined;
208
207
  };
209
- })[];
210
- } | undefined;
211
- readonly finishReason?: string | undefined;
212
- }[] | undefined;
208
+ })[] | null | undefined;
209
+ readonly role?: "model" | "user" | null | undefined;
210
+ } | null | undefined;
211
+ readonly finishReason?: string | null | undefined;
212
+ }[] | null | undefined;
213
213
  readonly promptFeedback?: {
214
214
  readonly [x: string]: unknown;
215
- readonly blockReason?: string | undefined;
216
- readonly blockReasonMessage?: string | undefined;
215
+ readonly blockReason?: string | null | undefined;
216
+ readonly blockReasonMessage?: string | null | undefined;
217
217
  readonly safetyRatings?: unknown;
218
- } | undefined;
218
+ } | null | undefined;
219
219
  readonly usageMetadata?: {
220
- readonly cachedContentTokenCount?: number | undefined;
221
- readonly thoughtsTokenCount?: number | undefined;
222
- readonly promptTokenCount?: number | undefined;
223
- readonly candidatesTokenCount?: number | undefined;
224
- readonly totalTokenCount?: number | undefined;
225
- } | undefined;
220
+ readonly cachedContentTokenCount?: number | null | undefined;
221
+ readonly thoughtsTokenCount?: number | null | undefined;
222
+ readonly promptTokenCount?: number | null | undefined;
223
+ readonly candidatesTokenCount?: number | null | undefined;
224
+ readonly totalTokenCount?: number | null | undefined;
225
+ } | null | undefined;
226
226
  }, {
227
227
  hasToolCalls: boolean;
228
228
  lifecycle: Lifecycle.State;
229
229
  }>;
230
230
  export declare const route: Route<{
231
231
  readonly contents: readonly {
232
- readonly role: "model" | "user";
233
- readonly parts: readonly ({
232
+ readonly parts?: readonly ({
234
233
  readonly inlineData: {
235
234
  readonly mimeType: string;
236
235
  readonly data: string;
237
236
  };
238
237
  } | {
239
238
  readonly text: string;
240
- readonly thought?: boolean | undefined;
241
- readonly thoughtSignature?: string | undefined;
239
+ readonly thought?: boolean | null | undefined;
240
+ readonly thoughtSignature?: string | null | undefined;
242
241
  } | {
243
242
  readonly functionCall: {
244
243
  readonly name: string;
245
- readonly id?: string | undefined;
244
+ readonly id?: string | null | undefined;
246
245
  readonly args?: unknown;
247
246
  };
248
- readonly thoughtSignature?: string | undefined;
247
+ readonly thoughtSignature?: string | null | undefined;
249
248
  } | {
250
249
  readonly functionResponse: {
251
250
  readonly name: string;
@@ -258,7 +257,8 @@ export declare const route: Route<{
258
257
  };
259
258
  }[] | undefined;
260
259
  };
261
- })[];
260
+ })[] | null | undefined;
261
+ readonly role?: "model" | "user" | null | undefined;
262
262
  }[];
263
263
  readonly tools?: readonly {
264
264
  readonly functionDeclarations: readonly {
@@ -6,7 +6,7 @@ import { Endpoint } from "../route/endpoint.js";
6
6
  import { Framing } from "../route/framing.js";
7
7
  import { Protocol } from "../route/protocol.js";
8
8
  import { LLMEvent, Usage, } from "../schema/index.js";
9
- import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
9
+ import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
10
10
  import { GeminiToolSchema } from "./utils/gemini-tool-schema.js";
11
11
  import { Lifecycle } from "./utils/lifecycle.js";
12
12
  import { ToolSchemaProjection } from "./utils/tool-schema.js";
@@ -38,10 +38,15 @@ const omitsFunctionCallIds = (modelID) => {
38
38
  // =============================================================================
39
39
  // Request Body Schema
40
40
  // =============================================================================
41
+ // Gemini is known to send explicit `null` for optional streaming fields
42
+ // (usage counts, flags, whole subtrees), so every response-side optional uses
43
+ // `optionalNull` instead of bare `Schema.optional`. The same part/content
44
+ // schemas lower the outbound request body; encoding drops `undefined` keys,
45
+ // so the shared schemas stay safe there.
41
46
  const GeminiTextPart = Schema.Struct({
42
47
  text: Schema.String,
43
- thought: Schema.optional(Schema.Boolean),
44
- thoughtSignature: Schema.optional(Schema.String),
48
+ thought: optionalNull(Schema.Boolean),
49
+ thoughtSignature: optionalNull(Schema.String),
45
50
  });
46
51
  const GeminiInlineDataPart = Schema.Struct({
47
52
  inlineData: Schema.Struct({
@@ -51,11 +56,11 @@ const GeminiInlineDataPart = Schema.Struct({
51
56
  });
52
57
  const GeminiFunctionCallPart = Schema.Struct({
53
58
  functionCall: Schema.Struct({
54
- id: Schema.optional(Schema.String),
59
+ id: optionalNull(Schema.String),
55
60
  name: Schema.String,
56
61
  args: Schema.optional(Schema.Unknown),
57
62
  }),
58
- thoughtSignature: Schema.optional(Schema.String),
63
+ thoughtSignature: optionalNull(Schema.String),
59
64
  });
60
65
  const GeminiFunctionResponsePart = Schema.Struct({
61
66
  functionResponse: Schema.Struct({
@@ -72,8 +77,8 @@ const GeminiContentPart = Schema.Union([
72
77
  GeminiFunctionResponsePart,
73
78
  ]);
74
79
  const GeminiContent = Schema.Struct({
75
- role: Schema.Literals(["user", "model"]),
76
- parts: Schema.Array(GeminiContentPart),
80
+ role: optionalNull(Schema.Literals(["user", "model"])),
81
+ parts: optionalNull(Schema.Array(GeminiContentPart)),
77
82
  });
78
83
  const GeminiSystemInstruction = Schema.Struct({
79
84
  parts: Schema.Array(Schema.Struct({ text: Schema.String })),
@@ -125,25 +130,25 @@ const GeminiBodyFields = {
125
130
  };
126
131
  const GeminiBody = Schema.Struct(GeminiBodyFields);
127
132
  const GeminiUsage = Schema.Struct({
128
- cachedContentTokenCount: Schema.optional(Schema.Number),
129
- thoughtsTokenCount: Schema.optional(Schema.Number),
130
- promptTokenCount: Schema.optional(Schema.Number),
131
- candidatesTokenCount: Schema.optional(Schema.Number),
132
- totalTokenCount: Schema.optional(Schema.Number),
133
+ cachedContentTokenCount: optionalNull(Schema.Number),
134
+ thoughtsTokenCount: optionalNull(Schema.Number),
135
+ promptTokenCount: optionalNull(Schema.Number),
136
+ candidatesTokenCount: optionalNull(Schema.Number),
137
+ totalTokenCount: optionalNull(Schema.Number),
133
138
  });
134
139
  const GeminiCandidate = Schema.Struct({
135
- content: Schema.optional(GeminiContent),
136
- finishReason: Schema.optional(Schema.String),
140
+ content: optionalNull(GeminiContent),
141
+ finishReason: optionalNull(Schema.String),
137
142
  });
138
143
  const GeminiPromptFeedback = Schema.StructWithRest(Schema.Struct({
139
- blockReason: Schema.optional(Schema.String),
140
- blockReasonMessage: Schema.optional(Schema.String),
141
- safetyRatings: Schema.optional(Schema.Unknown),
144
+ blockReason: optionalNull(Schema.String),
145
+ blockReasonMessage: optionalNull(Schema.String),
146
+ safetyRatings: optionalNull(Schema.Unknown),
142
147
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
143
148
  const GeminiEvent = Schema.Struct({
144
- candidates: optionalArray(GeminiCandidate),
145
- promptFeedback: Schema.optional(GeminiPromptFeedback),
146
- usageMetadata: Schema.optional(GeminiUsage),
149
+ candidates: optionalNull(Schema.Array(GeminiCandidate)),
150
+ promptFeedback: optionalNull(GeminiPromptFeedback),
151
+ usageMetadata: optionalNull(GeminiUsage),
147
152
  });
148
153
  // =============================================================================
149
154
  // Tool Schema Conversion
@@ -217,8 +222,8 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
217
222
  const previous = contents.at(-1);
218
223
  // Gemini rejects a continuation whose function-response turn carries extra
219
224
  // parts, so an update after a tool result starts its own user turn.
220
- if (previous?.role === "user" && !previous.parts.some((item) => "functionResponse" in item))
221
- contents[contents.length - 1] = { role: "user", parts: [...previous.parts, { text: part.text }] };
225
+ if (previous?.role === "user" && !(previous.parts ?? []).some((item) => "functionResponse" in item))
226
+ contents[contents.length - 1] = { role: "user", parts: [...(previous.parts ?? []), { text: part.text }] };
222
227
  else
223
228
  contents.push({ role: "user", parts: [{ text: part.text }] });
224
229
  continue;
@@ -309,8 +314,8 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
309
314
  // Gemini requires every response to a parallel call batch in one user turn,
310
315
  // so consecutive tool results join the open function-response turn.
311
316
  const previous = contents.at(-1);
312
- if (previous?.role === "user" && previous.parts.some((item) => "functionResponse" in item))
313
- contents[contents.length - 1] = { role: "user", parts: [...previous.parts, ...parts] };
317
+ if (previous?.role === "user" && (previous.parts ?? []).some((item) => "functionResponse" in item))
318
+ contents[contents.length - 1] = { role: "user", parts: [...(previous.parts ?? []), ...parts] };
314
319
  else
315
320
  contents.push({ role: "user", parts });
316
321
  }
@@ -389,20 +394,25 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
389
394
  const mapUsage = (usage) => {
390
395
  if (!usage)
391
396
  return undefined;
392
- const cached = usage.cachedContentTokenCount;
393
- const nonCached = ProviderShared.subtractTokens(usage.promptTokenCount, cached);
397
+ // Explicit provider nulls decode as `null`; normalize to `undefined` so the
398
+ // token arithmetic below treats them like absent counts.
399
+ const promptTokens = usage.promptTokenCount ?? undefined;
400
+ const cached = usage.cachedContentTokenCount ?? undefined;
401
+ const thoughts = usage.thoughtsTokenCount ?? undefined;
402
+ const visible = usage.candidatesTokenCount ?? undefined;
403
+ const nonCached = ProviderShared.subtractTokens(promptTokens, cached);
394
404
  // `candidatesTokenCount` is visible-only; sum with thoughts to produce the
395
405
  // inclusive `outputTokens` the contract expects. Only compute the total
396
406
  // when the visible component is reported — otherwise we'd fabricate an
397
407
  // inclusive number from a partial breakdown.
398
- const outputTokens = usage.candidatesTokenCount !== undefined ? usage.candidatesTokenCount + (usage.thoughtsTokenCount ?? 0) : undefined;
408
+ const outputTokens = visible !== undefined ? visible + (thoughts ?? 0) : undefined;
399
409
  return new Usage({
400
- inputTokens: usage.promptTokenCount,
410
+ inputTokens: promptTokens,
401
411
  outputTokens,
402
412
  nonCachedInputTokens: nonCached,
403
413
  cacheReadInputTokens: cached,
404
- reasoningTokens: usage.thoughtsTokenCount,
405
- totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
414
+ reasoningTokens: thoughts,
415
+ totalTokens: ProviderShared.totalTokens(promptTokens, outputTokens, usage.totalTokenCount ?? undefined),
406
416
  providerMetadata: { google: usage },
407
417
  });
408
418
  };
@@ -434,7 +444,9 @@ const mapFinishReason = (finishReason, hasToolCalls) => {
434
444
  return "unknown";
435
445
  };
436
446
  const finish = (state) => {
437
- const promptBlockReason = state.finishReason === undefined ? state.promptFeedback?.blockReason : undefined;
447
+ // `?? undefined` normalizes an explicit `null` blockReason back to absent so
448
+ // the "nothing to finish" check below keeps its meaning.
449
+ const promptBlockReason = state.finishReason === undefined ? (state.promptFeedback?.blockReason ?? undefined) : undefined;
438
450
  const finishReason = state.finishReason ?? promptBlockReason;
439
451
  if (finishReason === undefined && state.usage === undefined)
440
452
  return [];
@@ -473,7 +485,7 @@ const step = (state, event) => {
473
485
  let textSignature = nextState.textSignature;
474
486
  // Supplier ids must be tracked across chunks of the same response, not just within one event's parts.
475
487
  const seenCallIds = new Set(nextState.seenCallIds);
476
- for (const part of candidate.content.parts) {
488
+ for (const part of candidate.content.parts ?? []) {
477
489
  const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined;
478
490
  // Gemini attaches replay signatures to thought parts, visible text, or function calls;
479
491
  // each block kind must retain the signature attached to its own parts.
@@ -496,7 +508,8 @@ const step = (state, event) => {
496
508
  // Gemini 2.0+ supplies a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
497
509
  // generate a globally unique ID rather than a per-request counter to prevent cross-request collisions in downstream registries.
498
510
  // A repeated supplier id would replay as two identical calls, so only the first occurrence keeps it.
499
- const supplied = part.functionCall.id;
511
+ // A `null` supplier id normalizes to absent so the generated-id fallback applies.
512
+ const supplied = part.functionCall.id ?? undefined;
500
513
  const duplicate = supplied !== undefined && seenCallIds.has(supplied);
501
514
  if (supplied !== undefined)
502
515
  seenCallIds.add(supplied);
@@ -507,7 +520,7 @@ const step = (state, event) => {
507
520
  id,
508
521
  name: part.functionCall.name,
509
522
  input,
510
- providerMetadata: part.thoughtSignature === undefined ? undefined : googleMetadata({ thoughtSignature: part.thoughtSignature }),
523
+ providerMetadata: part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined,
511
524
  }));
512
525
  hasToolCalls = true;
513
526
  }
@@ -207,7 +207,7 @@ export declare const coreFields: {
207
207
  }>>;
208
208
  top_logprobs: Schema.optional<Schema.Int>;
209
209
  truncation: Schema.optional<Schema.Literals<readonly ["auto", "disabled"]>>;
210
- service_tier: Schema.optional<Schema.Literals<readonly ["auto", "default", "flex", "priority"]>>;
210
+ service_tier: Schema.optional<Schema.declare<OpenResponsesOptions.ServiceTier, OpenResponsesOptions.ServiceTier>>;
211
211
  prompt_cache_key: Schema.optional<Schema.String>;
212
212
  include: Schema.optional<Schema.$Array<Schema.declare<OpenResponsesOptions.ResponseIncludable, OpenResponsesOptions.ResponseIncludable>>>;
213
213
  reasoning: Schema.optional<Schema.Struct<{
@@ -320,7 +320,7 @@ declare const OpenResponsesBody: Schema.Struct<{
320
320
  }>>;
321
321
  readonly top_logprobs: Schema.optional<Schema.Int>;
322
322
  readonly truncation: Schema.optional<Schema.Literals<readonly ["auto", "disabled"]>>;
323
- readonly service_tier: Schema.optional<Schema.Literals<readonly ["auto", "default", "flex", "priority"]>>;
323
+ readonly service_tier: Schema.optional<Schema.declare<OpenResponsesOptions.ServiceTier, OpenResponsesOptions.ServiceTier>>;
324
324
  readonly prompt_cache_key: Schema.optional<Schema.String>;
325
325
  readonly include: Schema.optional<Schema.$Array<Schema.declare<OpenResponsesOptions.ResponseIncludable, OpenResponsesOptions.ResponseIncludable>>>;
326
326
  readonly reasoning: Schema.optional<Schema.Struct<{
@@ -479,7 +479,7 @@ export declare const fromRequestWithExtension: (request: LLMRequest, extension:
479
479
  truncation?: "auto" | "disabled" | undefined;
480
480
  parallel_tool_calls?: boolean | undefined;
481
481
  max_tool_calls?: number | undefined;
482
- service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
482
+ service_tier?: OpenResponsesOptions.ServiceTier | undefined;
483
483
  text?: {
484
484
  verbosity: OpenResponsesOptions.TextVerbosity;
485
485
  } | undefined;
@@ -640,7 +640,7 @@ export declare const fromRequest: (request: LLMRequest) => Effect.Effect<{
640
640
  readonly presence_penalty?: number | undefined;
641
641
  readonly safety_identifier?: string | undefined;
642
642
  readonly top_logprobs?: number | undefined;
643
- readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
643
+ readonly service_tier?: OpenResponsesOptions.ServiceTier | undefined;
644
644
  readonly max_output_tokens?: number | undefined;
645
645
  readonly max_tool_calls?: number | undefined;
646
646
  readonly parallel_tool_calls?: boolean | undefined;
@@ -950,7 +950,7 @@ export declare const protocol: Protocol<{
950
950
  readonly presence_penalty?: number | undefined;
951
951
  readonly safety_identifier?: string | undefined;
952
952
  readonly top_logprobs?: number | undefined;
953
- readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
953
+ readonly service_tier?: OpenResponsesOptions.ServiceTier | undefined;
954
954
  readonly max_output_tokens?: number | undefined;
955
955
  readonly max_tool_calls?: number | undefined;
956
956
  readonly parallel_tool_calls?: boolean | undefined;
@@ -1121,7 +1121,7 @@ export declare const httpTransport: HttpTransport.HttpJsonTransport<{
1121
1121
  readonly presence_penalty?: number | undefined;
1122
1122
  readonly safety_identifier?: string | undefined;
1123
1123
  readonly top_logprobs?: number | undefined;
1124
- readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
1124
+ readonly service_tier?: OpenResponsesOptions.ServiceTier | undefined;
1125
1125
  readonly max_output_tokens?: number | undefined;
1126
1126
  readonly max_tool_calls?: number | undefined;
1127
1127
  readonly parallel_tool_calls?: boolean | undefined;
@@ -290,7 +290,7 @@ const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, reques
290
290
  return {
291
291
  type: "input_file",
292
292
  filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
293
- ...(url ? { file_url: url } : { file_data: media.base64 }),
293
+ ...(url ? { file_url: url } : { file_data: media.dataUrl }),
294
294
  };
295
295
  }
296
296
  return { type: "input_image", image_url: url ?? media.dataUrl };
@@ -201,7 +201,7 @@ declare const OpenAIChatBody: Schema.Struct<{
201
201
  }>;
202
202
  export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>;
203
203
  export declare const OpenAIChatEvent: Schema.Struct<{
204
- readonly choices: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
204
+ readonly choices: Schema.optional<Schema.NullOr<Schema.$Array<Schema.StructWithRest<Schema.Struct<{
205
205
  readonly delta: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
206
206
  readonly content: Schema.optional<Schema.NullOr<Schema.String>>;
207
207
  readonly refusal: Schema.optional<Schema.NullOr<Schema.String>>;
@@ -220,11 +220,29 @@ export declare const OpenAIChatEvent: Schema.Struct<{
220
220
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
221
221
  readonly finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
222
222
  readonly native_finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
223
- }>>>>;
223
+ readonly usage: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
224
+ readonly prompt_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
225
+ readonly completion_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
226
+ readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
227
+ readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
228
+ readonly prompt_cache_hit_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
229
+ readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
230
+ readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
231
+ readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
232
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
233
+ readonly completion_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
234
+ readonly reasoning_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
235
+ readonly accepted_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
236
+ readonly rejected_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
237
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
238
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
239
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>>;
224
240
  readonly usage: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
225
241
  readonly prompt_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
226
242
  readonly completion_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
227
243
  readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
244
+ readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
245
+ readonly prompt_cache_hit_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
228
246
  readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
229
247
  readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
230
248
  readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
@@ -566,6 +584,8 @@ export declare const protocol: Protocol<{
566
584
  readonly prompt_tokens?: number | null | undefined;
567
585
  readonly completion_tokens?: number | null | undefined;
568
586
  readonly total_tokens?: number | null | undefined;
587
+ readonly cached_tokens?: number | null | undefined;
588
+ readonly prompt_cache_hit_tokens?: number | null | undefined;
569
589
  readonly prompt_tokens_details?: {
570
590
  readonly [x: string]: unknown;
571
591
  readonly cached_tokens?: number | null | undefined;
@@ -579,6 +599,7 @@ export declare const protocol: Protocol<{
579
599
  } | null | undefined;
580
600
  } | null | undefined;
581
601
  readonly choices?: readonly {
602
+ readonly [x: string]: unknown;
582
603
  readonly delta?: {
583
604
  readonly [x: string]: unknown;
584
605
  readonly reasoning?: string | null | undefined;
@@ -596,6 +617,25 @@ export declare const protocol: Protocol<{
596
617
  }[] | null | undefined;
597
618
  readonly reasoning_details?: unknown;
598
619
  } | null | undefined;
620
+ readonly usage?: {
621
+ readonly [x: string]: unknown;
622
+ readonly prompt_tokens?: number | null | undefined;
623
+ readonly completion_tokens?: number | null | undefined;
624
+ readonly total_tokens?: number | null | undefined;
625
+ readonly cached_tokens?: number | null | undefined;
626
+ readonly prompt_cache_hit_tokens?: number | null | undefined;
627
+ readonly prompt_tokens_details?: {
628
+ readonly [x: string]: unknown;
629
+ readonly cached_tokens?: number | null | undefined;
630
+ readonly cache_write_tokens?: number | null | undefined;
631
+ } | null | undefined;
632
+ readonly completion_tokens_details?: {
633
+ readonly [x: string]: unknown;
634
+ readonly reasoning_tokens?: number | null | undefined;
635
+ readonly accepted_prediction_tokens?: number | null | undefined;
636
+ readonly rejected_prediction_tokens?: number | null | undefined;
637
+ } | null | undefined;
638
+ } | null | undefined;
599
639
  readonly finish_reason?: string | null | undefined;
600
640
  readonly native_finish_reason?: string | null | undefined;
601
641
  }[] | null | undefined;
@@ -123,6 +123,9 @@ const OpenAIChatUsage = Schema.StructWithRest(Schema.Struct({
123
123
  prompt_tokens: optionalNull(Schema.Number),
124
124
  completion_tokens: optionalNull(Schema.Number),
125
125
  total_tokens: optionalNull(Schema.Number),
126
+ // Zai reports cache hits as top-level `cached_tokens`; DeepSeek uses `prompt_cache_hit_tokens`.
127
+ cached_tokens: optionalNull(Schema.Number),
128
+ prompt_cache_hit_tokens: optionalNull(Schema.Number),
126
129
  prompt_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
127
130
  cached_tokens: optionalNull(Schema.Number),
128
131
  cache_write_tokens: optionalNull(Schema.Number),
@@ -151,11 +154,13 @@ const OpenAIChatDelta = Schema.StructWithRest(Schema.Struct({
151
154
  reasoning_details: optionalNull(Schema.Unknown),
152
155
  tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
153
156
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
154
- const OpenAIChatChoice = Schema.Struct({
157
+ const OpenAIChatChoice = Schema.StructWithRest(Schema.Struct({
155
158
  delta: optionalNull(OpenAIChatDelta),
156
159
  finish_reason: optionalNull(Schema.String),
157
160
  native_finish_reason: optionalNull(Schema.String),
158
- });
161
+ // Moonshot streams usage on `choice.usage` instead of top-level `usage`.
162
+ usage: optionalNull(OpenAIChatUsage),
163
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
159
164
  const OpenAIChatError = Schema.Struct({
160
165
  code: optionalNull(Schema.Union([Schema.String, Schema.Number])),
161
166
  message: Schema.String,
@@ -396,6 +401,18 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request,
396
401
  flushImages();
397
402
  return messages;
398
403
  });
404
+ // Anthropic via LiteLLM and Amazon Bedrock require `tools` to be present
405
+ // whenever the conversation history contains tool calls/results. Send an
406
+ // explicit empty array when we have history but no active tools.
407
+ const hasToolHistory = (messages) => {
408
+ for (const message of messages) {
409
+ if (message.role === "tool")
410
+ return true;
411
+ if (message.role === "assistant" && message.content.some((part) => part.type === "tool-call"))
412
+ return true;
413
+ }
414
+ return false;
415
+ };
399
416
  const lowerOptions = (request) => {
400
417
  const options = OpenAIOptions.resolve(request);
401
418
  return {
@@ -413,11 +430,14 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
413
430
  const generation = request.generation;
414
431
  const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
415
432
  const maxTokensField = request.model.compatibility?.maxTokensField ?? "max_tokens";
433
+ const hasHistory = hasToolHistory(request.messages);
416
434
  return {
417
435
  model: request.model.id,
418
436
  messages: yield* lowerMessages(request, options),
419
437
  tools: request.tools.length === 0
420
- ? undefined
438
+ ? hasHistory
439
+ ? []
440
+ : undefined
421
441
  : request.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility), options)),
422
442
  tool_choice: request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
423
443
  stream: true,
@@ -458,12 +478,18 @@ const mapFinishReason = (reason) => {
458
478
  // total) with a `reasoning_tokens` subset. We pass the inclusive totals
459
479
  // through and derive the non-cached breakdown so the `AI.Usage` contract is
460
480
  // satisfied on both sides.
481
+ // Providers differ on cache-hit location: OpenAI uses
482
+ // `prompt_tokens_details.cached_tokens`, DeepSeek uses
483
+ // `prompt_cache_hit_tokens`, and Zai uses top-level `cached_tokens`.
461
484
  const mapUsage = (usage) => {
462
485
  if (!usage)
463
486
  return undefined;
464
487
  const input = usage.prompt_tokens ?? undefined;
465
488
  const output = usage.completion_tokens ?? undefined;
466
- const cached = usage.prompt_tokens_details?.cached_tokens ?? undefined;
489
+ const cached = (usage.prompt_tokens_details?.cached_tokens ??
490
+ usage.prompt_cache_hit_tokens ??
491
+ usage.cached_tokens ??
492
+ undefined);
467
493
  const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? undefined;
468
494
  const reasoning = usage.completion_tokens_details?.reasoning_tokens ?? undefined;
469
495
  const nonCached = ProviderShared.subtractTokens(input, ProviderShared.sumTokens(cached, cacheWrite));
@@ -554,8 +580,11 @@ const step = (state, event) => Effect.gen(function* () {
554
580
  }),
555
581
  });
556
582
  const events = [];
557
- const usage = mapUsage(event.usage) ?? state.usage;
558
583
  const choice = event.choices?.[0];
584
+ // Moonshot (and a few other OpenAI-compatible providers) attach usage to
585
+ // `choice.usage` instead of the top-level `usage` field.
586
+ const choiceUsage = choice?.usage;
587
+ const usage = mapUsage(event.usage) ?? (choiceUsage ? mapUsage(choiceUsage) : undefined) ?? state.usage;
559
588
  const rawFinishReason = choice?.finish_reason;
560
589
  const finishReason = rawFinishReason !== undefined && rawFinishReason !== null
561
590
  ? { normalized: mapFinishReason(rawFinishReason), raw: choice?.native_finish_reason ?? rawFinishReason }
@@ -117,7 +117,7 @@ export declare const route: Route<{
117
117
  readonly presence_penalty?: number | undefined;
118
118
  readonly safety_identifier?: string | undefined;
119
119
  readonly top_logprobs?: number | undefined;
120
- readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
120
+ readonly service_tier?: import("./utils/open-responses-options.js").ServiceTier | undefined;
121
121
  readonly max_output_tokens?: number | undefined;
122
122
  readonly max_tool_calls?: number | undefined;
123
123
  readonly parallel_tool_calls?: boolean | undefined;