@opencode-ai/ai 0.0.0-beta-17963 → 0.0.0-beta-18027

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 +68 -50
  4. package/dist/protocols/open-responses.d.ts +6 -6
  5. package/dist/protocols/open-responses.js +10 -3
  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 +13 -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";
@@ -29,13 +29,24 @@ const requiresThoughtSignatureFallback = (modelID) => {
29
29
  // Gemini 3 accepts media nested inside function responses; matched Gemini 2.5 variants reject it,
30
30
  // so their tool-result attachments lower as a separate user turn instead.
31
31
  const routesLegacyToolMedia = (modelID) => /gemini-2[.-]5(?:[.-]|$)/i.test(modelID);
32
+ // Blacklist: Gemini 1.x/2.x ignore or reject explicit function call ids.
33
+ // Every other model id (Gemini 3+, gemma, anything unrecognized) gets them.
34
+ const omitsFunctionCallIds = (modelID) => {
35
+ const match = /^gemini(?:-live)?-(\d+)/i.exec(modelID);
36
+ return match !== null && Number(match[1]) < 3;
37
+ };
32
38
  // =============================================================================
33
39
  // Request Body Schema
34
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.
35
46
  const GeminiTextPart = Schema.Struct({
36
47
  text: Schema.String,
37
- thought: Schema.optional(Schema.Boolean),
38
- thoughtSignature: Schema.optional(Schema.String),
48
+ thought: optionalNull(Schema.Boolean),
49
+ thoughtSignature: optionalNull(Schema.String),
39
50
  });
40
51
  const GeminiInlineDataPart = Schema.Struct({
41
52
  inlineData: Schema.Struct({
@@ -45,11 +56,11 @@ const GeminiInlineDataPart = Schema.Struct({
45
56
  });
46
57
  const GeminiFunctionCallPart = Schema.Struct({
47
58
  functionCall: Schema.Struct({
48
- id: Schema.optional(Schema.String),
59
+ id: optionalNull(Schema.String),
49
60
  name: Schema.String,
50
61
  args: Schema.optional(Schema.Unknown),
51
62
  }),
52
- thoughtSignature: Schema.optional(Schema.String),
63
+ thoughtSignature: optionalNull(Schema.String),
53
64
  });
54
65
  const GeminiFunctionResponsePart = Schema.Struct({
55
66
  functionResponse: Schema.Struct({
@@ -66,8 +77,8 @@ const GeminiContentPart = Schema.Union([
66
77
  GeminiFunctionResponsePart,
67
78
  ]);
68
79
  const GeminiContent = Schema.Struct({
69
- role: Schema.Literals(["user", "model"]),
70
- parts: Schema.Array(GeminiContentPart),
80
+ role: optionalNull(Schema.Literals(["user", "model"])),
81
+ parts: optionalNull(Schema.Array(GeminiContentPart)),
71
82
  });
72
83
  const GeminiSystemInstruction = Schema.Struct({
73
84
  parts: Schema.Array(Schema.Struct({ text: Schema.String })),
@@ -119,25 +130,25 @@ const GeminiBodyFields = {
119
130
  };
120
131
  const GeminiBody = Schema.Struct(GeminiBodyFields);
121
132
  const GeminiUsage = Schema.Struct({
122
- cachedContentTokenCount: Schema.optional(Schema.Number),
123
- thoughtsTokenCount: Schema.optional(Schema.Number),
124
- promptTokenCount: Schema.optional(Schema.Number),
125
- candidatesTokenCount: Schema.optional(Schema.Number),
126
- 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),
127
138
  });
128
139
  const GeminiCandidate = Schema.Struct({
129
- content: Schema.optional(GeminiContent),
130
- finishReason: Schema.optional(Schema.String),
140
+ content: optionalNull(GeminiContent),
141
+ finishReason: optionalNull(Schema.String),
131
142
  });
132
143
  const GeminiPromptFeedback = Schema.StructWithRest(Schema.Struct({
133
- blockReason: Schema.optional(Schema.String),
134
- blockReasonMessage: Schema.optional(Schema.String),
135
- safetyRatings: Schema.optional(Schema.Unknown),
144
+ blockReason: optionalNull(Schema.String),
145
+ blockReasonMessage: optionalNull(Schema.String),
146
+ safetyRatings: optionalNull(Schema.Unknown),
136
147
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
137
148
  const GeminiEvent = Schema.Struct({
138
- candidates: optionalArray(GeminiCandidate),
139
- promptFeedback: Schema.optional(GeminiPromptFeedback),
140
- usageMetadata: Schema.optional(GeminiUsage),
149
+ candidates: optionalNull(Schema.Array(GeminiCandidate)),
150
+ promptFeedback: optionalNull(GeminiPromptFeedback),
151
+ usageMetadata: optionalNull(GeminiUsage),
141
152
  });
142
153
  // =============================================================================
143
154
  // Tool Schema Conversion
@@ -188,18 +199,13 @@ const thoughtSignature = (providerMetadata) => {
188
199
  ? google.thoughtSignature
189
200
  : undefined;
190
201
  };
191
- const functionCallId = (providerMetadata) => {
192
- const google = providerMetadata?.google;
193
- return ProviderShared.isRecord(google) && typeof google.functionCallId === "string"
194
- ? google.functionCallId
195
- : undefined;
196
- };
197
- const lowerToolCall = (part) => ({
198
- functionCall: { id: functionCallId(part.providerMetadata), name: part.name, args: part.input },
202
+ const lowerToolCall = (part, omitIds) => ({
203
+ functionCall: { ...(omitIds ? {} : { id: part.id }), name: part.name, args: part.input },
199
204
  thoughtSignature: thoughtSignature(part.providerMetadata),
200
205
  });
201
206
  const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
202
207
  const contents = [];
208
+ const omitCallIds = omitsFunctionCallIds(request.model.id);
203
209
  const legacyToolMedia = routesLegacyToolMedia(request.model.id);
204
210
  let pendingMedia;
205
211
  const flushMedia = () => {
@@ -216,8 +222,8 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
216
222
  const previous = contents.at(-1);
217
223
  // Gemini rejects a continuation whose function-response turn carries extra
218
224
  // parts, so an update after a tool result starts its own user turn.
219
- if (previous?.role === "user" && !previous.parts.some((item) => "functionResponse" in item))
220
- 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 }] };
221
227
  else
222
228
  contents.push({ role: "user", parts: [{ text: part.text }] });
223
229
  continue;
@@ -248,7 +254,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
248
254
  continue;
249
255
  }
250
256
  if (part.type === "tool-call") {
251
- const lowered = lowerToolCall(part);
257
+ const lowered = lowerToolCall(part, omitCallIds);
252
258
  const signature = lowered.thoughtSignature;
253
259
  parts.push({
254
260
  ...lowered,
@@ -272,7 +278,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
272
278
  if (part.result.type !== "content") {
273
279
  parts.push({
274
280
  functionResponse: {
275
- id: functionCallId(part.providerMetadata),
281
+ ...(omitCallIds ? {} : { id: part.id }),
276
282
  name: part.name,
277
283
  response: {
278
284
  name: part.name,
@@ -295,7 +301,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
295
301
  (pendingMedia ??= []).push(...media);
296
302
  parts.push({
297
303
  functionResponse: {
298
- id: functionCallId(part.providerMetadata),
304
+ ...(omitCallIds ? {} : { id: part.id }),
299
305
  name: part.name,
300
306
  response: {
301
307
  name: part.name,
@@ -308,8 +314,8 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
308
314
  // Gemini requires every response to a parallel call batch in one user turn,
309
315
  // so consecutive tool results join the open function-response turn.
310
316
  const previous = contents.at(-1);
311
- if (previous?.role === "user" && previous.parts.some((item) => "functionResponse" in item))
312
- 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] };
313
319
  else
314
320
  contents.push({ role: "user", parts });
315
321
  }
@@ -388,20 +394,25 @@ const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
388
394
  const mapUsage = (usage) => {
389
395
  if (!usage)
390
396
  return undefined;
391
- const cached = usage.cachedContentTokenCount;
392
- 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);
393
404
  // `candidatesTokenCount` is visible-only; sum with thoughts to produce the
394
405
  // inclusive `outputTokens` the contract expects. Only compute the total
395
406
  // when the visible component is reported — otherwise we'd fabricate an
396
407
  // inclusive number from a partial breakdown.
397
- const outputTokens = usage.candidatesTokenCount !== undefined ? usage.candidatesTokenCount + (usage.thoughtsTokenCount ?? 0) : undefined;
408
+ const outputTokens = visible !== undefined ? visible + (thoughts ?? 0) : undefined;
398
409
  return new Usage({
399
- inputTokens: usage.promptTokenCount,
410
+ inputTokens: promptTokens,
400
411
  outputTokens,
401
412
  nonCachedInputTokens: nonCached,
402
413
  cacheReadInputTokens: cached,
403
- reasoningTokens: usage.thoughtsTokenCount,
404
- totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
414
+ reasoningTokens: thoughts,
415
+ totalTokens: ProviderShared.totalTokens(promptTokens, outputTokens, usage.totalTokenCount ?? undefined),
405
416
  providerMetadata: { google: usage },
406
417
  });
407
418
  };
@@ -433,7 +444,9 @@ const mapFinishReason = (finishReason, hasToolCalls) => {
433
444
  return "unknown";
434
445
  };
435
446
  const finish = (state) => {
436
- 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;
437
450
  const finishReason = state.finishReason ?? promptBlockReason;
438
451
  if (finishReason === undefined && state.usage === undefined)
439
452
  return [];
@@ -470,7 +483,9 @@ const step = (state, event) => {
470
483
  let lifecycle = nextState.lifecycle;
471
484
  let reasoningSignature = nextState.reasoningSignature;
472
485
  let textSignature = nextState.textSignature;
473
- for (const part of candidate.content.parts) {
486
+ // Supplier ids must be tracked across chunks of the same response, not just within one event's parts.
487
+ const seenCallIds = new Set(nextState.seenCallIds);
488
+ for (const part of candidate.content.parts ?? []) {
474
489
  const signature = "thoughtSignature" in part && part.thoughtSignature ? part.thoughtSignature : undefined;
475
490
  // Gemini attaches replay signatures to thought parts, visible text, or function calls;
476
491
  // each block kind must retain the signature attached to its own parts.
@@ -490,20 +505,22 @@ const step = (state, event) => {
490
505
  }
491
506
  if ("functionCall" in part) {
492
507
  const input = part.functionCall.args === undefined ? {} : part.functionCall.args;
493
- // Gemini 2.0+ and Vertex supply a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
508
+ // Gemini 2.0+ supplies a unique function call ID on the part; when omitted (e.g. Gemini 1.5),
494
509
  // generate a globally unique ID rather than a per-request counter to prevent cross-request collisions in downstream registries.
495
- const id = part.functionCall.id ?? `tool_${crypto.randomUUID().replaceAll("-", "")}`;
496
- const metadata = {
497
- ...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
498
- ...(part.thoughtSignature === undefined ? {} : { thoughtSignature: part.thoughtSignature }),
499
- };
510
+ // A repeated supplier id would replay as two identical calls, so only the first occurrence keeps it.
511
+ // A `null` supplier id normalizes to absent so the generated-id fallback applies.
512
+ const supplied = part.functionCall.id ?? undefined;
513
+ const duplicate = supplied !== undefined && seenCallIds.has(supplied);
514
+ if (supplied !== undefined)
515
+ seenCallIds.add(supplied);
516
+ const id = supplied !== undefined && !duplicate ? supplied : `tool_${crypto.randomUUID().replaceAll("-", "")}`;
500
517
  lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
501
518
  lifecycle = Lifecycle.stepStart(lifecycle, events);
502
519
  events.push(LLMEvent.toolCall({
503
520
  id,
504
521
  name: part.functionCall.name,
505
522
  input,
506
- providerMetadata: Object.keys(metadata).length > 0 ? googleMetadata(metadata) : undefined,
523
+ providerMetadata: part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined,
507
524
  }));
508
525
  hasToolCalls = true;
509
526
  }
@@ -515,6 +532,7 @@ const step = (state, event) => {
515
532
  lifecycle,
516
533
  reasoningSignature,
517
534
  textSignature,
535
+ seenCallIds,
518
536
  finishReason: candidate.finishReason ?? nextState.finishReason,
519
537
  },
520
538
  events,
@@ -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;
@@ -1,7 +1,7 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import { HttpTransport } from "../route/transport/index.js";
3
3
  import { Protocol } from "../route/protocol.js";
4
- import { AIError, LLMEvent, Usage, } from "../schema/index.js";
4
+ import { AIError, LLMEvent, ProviderInternalReason, Usage, } from "../schema/index.js";
5
5
  import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
6
6
  import { classifyProviderFailure } from "../provider-error.js";
7
7
  import { OpenResponsesOptions } from "./utils/open-responses-options.js";
@@ -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 };
@@ -856,11 +856,18 @@ export const providerFailure = (id, event, fallback) => {
856
856
  : typeof event.status_code === "number"
857
857
  ? event.status_code
858
858
  : undefined;
859
+ const reason = event.type === "error" &&
860
+ event.error === undefined &&
861
+ event.response === undefined &&
862
+ summary === undefined &&
863
+ status === undefined
864
+ ? new ProviderInternalReason({ message })
865
+ : classifyProviderFailure({ message, code, status, rawBody: body });
859
866
  return new AIError({
860
867
  module: id,
861
868
  method: "stream",
862
869
  body,
863
- reason: classifyProviderFailure({ message, code, status, rawBody: body }),
870
+ reason,
864
871
  });
865
872
  };
866
873
  const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
@@ -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;