@opencode-ai/ai 0.0.0-next-16897 → 0.0.0-next-16899

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.
@@ -207,7 +207,7 @@ export declare const OpenAIChatEvent: Schema.Struct<{
207
207
  readonly reasoning_text: Schema.optional<Schema.NullOr<Schema.String>>;
208
208
  readonly reasoning_details: Schema.optional<Schema.NullOr<Schema.Unknown>>;
209
209
  readonly tool_calls: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
210
- readonly index: Schema.Number;
210
+ readonly index: Schema.optional<Schema.NullOr<Schema.Number>>;
211
211
  readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
212
212
  readonly function: Schema.optional<Schema.NullOr<Schema.Struct<{
213
213
  readonly name: Schema.optional<Schema.NullOr<Schema.String>>;
@@ -218,18 +218,20 @@ export declare const OpenAIChatEvent: Schema.Struct<{
218
218
  readonly finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
219
219
  readonly native_finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
220
220
  }>>>>;
221
- readonly usage: Schema.optional<Schema.NullOr<Schema.Struct<{
222
- readonly prompt_tokens: Schema.optional<Schema.Number>;
223
- readonly completion_tokens: Schema.optional<Schema.Number>;
224
- readonly total_tokens: Schema.optional<Schema.Number>;
225
- readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.Struct<{
226
- readonly cached_tokens: Schema.optional<Schema.Number>;
227
- readonly cache_write_tokens: Schema.optional<Schema.Number>;
228
- }>>>;
229
- readonly completion_tokens_details: Schema.optional<Schema.NullOr<Schema.Struct<{
230
- readonly reasoning_tokens: Schema.optional<Schema.Number>;
231
- }>>>;
232
- }>>>;
221
+ readonly usage: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
222
+ readonly prompt_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
223
+ readonly completion_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
224
+ readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
225
+ readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
226
+ readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
227
+ readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
228
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
229
+ readonly completion_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
230
+ readonly reasoning_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
231
+ readonly accepted_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
232
+ readonly rejected_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
233
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
234
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
233
235
  readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
234
236
  readonly code: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Number]>>>;
235
237
  readonly message: Schema.String;
@@ -252,6 +254,8 @@ export interface ParserState {
252
254
  readonly reasoningDetails: Array<unknown>;
253
255
  readonly reasoningDetailsObserved: boolean;
254
256
  readonly reasoningEmitted: boolean;
257
+ readonly latestToolIndex?: number;
258
+ readonly nextToolIndex: number;
255
259
  }
256
260
  interface LoweringOptions {
257
261
  readonly cacheControl?: (cache: CacheHint | undefined) => Schema.Schema.Type<typeof OpenAIChatCacheControl> | undefined;
@@ -552,15 +556,20 @@ export declare const protocol: Protocol<{
552
556
  readonly code?: string | number | null | undefined;
553
557
  } | null | undefined;
554
558
  readonly usage?: {
555
- readonly prompt_tokens?: number | undefined;
556
- readonly completion_tokens?: number | undefined;
557
- readonly total_tokens?: number | undefined;
559
+ readonly [x: string]: unknown;
560
+ readonly prompt_tokens?: number | null | undefined;
561
+ readonly completion_tokens?: number | null | undefined;
562
+ readonly total_tokens?: number | null | undefined;
558
563
  readonly prompt_tokens_details?: {
559
- readonly cached_tokens?: number | undefined;
560
- readonly cache_write_tokens?: number | undefined;
564
+ readonly [x: string]: unknown;
565
+ readonly cached_tokens?: number | null | undefined;
566
+ readonly cache_write_tokens?: number | null | undefined;
561
567
  } | null | undefined;
562
568
  readonly completion_tokens_details?: {
563
- readonly reasoning_tokens?: number | undefined;
569
+ readonly [x: string]: unknown;
570
+ readonly reasoning_tokens?: number | null | undefined;
571
+ readonly accepted_prediction_tokens?: number | null | undefined;
572
+ readonly rejected_prediction_tokens?: number | null | undefined;
564
573
  } | null | undefined;
565
574
  } | null | undefined;
566
575
  readonly choices?: readonly {
@@ -571,12 +580,12 @@ export declare const protocol: Protocol<{
571
580
  readonly reasoning_text?: string | null | undefined;
572
581
  readonly content?: string | null | undefined;
573
582
  readonly tool_calls?: readonly {
574
- readonly index: number;
575
583
  readonly function?: {
576
584
  readonly name?: string | null | undefined;
577
585
  readonly arguments?: string | null | undefined;
578
586
  } | null | undefined;
579
587
  readonly id?: string | null | undefined;
588
+ readonly index?: number | null | undefined;
580
589
  }[] | null | undefined;
581
590
  readonly reasoning_details?: unknown;
582
591
  } | null | undefined;
@@ -45,6 +45,11 @@ const OpenAIChatAssistantToolCall = Schema.Struct({
45
45
  arguments: Schema.String,
46
46
  }),
47
47
  });
48
+ // Intentionally omit Gemini's provider-specific `extra_content.google.thought_signature`
49
+ // extension until direct Google OpenAI-compatible routing is supported here:
50
+ // https://github.com/vercel/ai/issues/11590
51
+ // https://github.com/vercel/ai/pull/11745
52
+ // https://ai.google.dev/gemini-api/docs/thought-signatures#openai
48
53
  const OpenAIChatUserContent = Schema.Union([
49
54
  Schema.Struct({
50
55
  type: Schema.Literal("text"),
@@ -114,24 +119,26 @@ const OpenAIChatBody = Schema.Struct(bodyFields);
114
119
  // The event schema is one decoded SSE `data:` payload. `Framing.sse` splits the
115
120
  // byte stream into strings, then `Protocol.jsonEvent` decodes each string into
116
121
  // this provider-native event shape.
117
- const OpenAIChatUsage = Schema.Struct({
118
- prompt_tokens: Schema.optional(Schema.Number),
119
- completion_tokens: Schema.optional(Schema.Number),
120
- total_tokens: Schema.optional(Schema.Number),
121
- prompt_tokens_details: optionalNull(Schema.Struct({
122
- cached_tokens: Schema.optional(Schema.Number),
123
- cache_write_tokens: Schema.optional(Schema.Number),
124
- })),
125
- completion_tokens_details: optionalNull(Schema.Struct({
126
- reasoning_tokens: Schema.optional(Schema.Number),
127
- })),
128
- });
122
+ const OpenAIChatUsage = Schema.StructWithRest(Schema.Struct({
123
+ prompt_tokens: optionalNull(Schema.Number),
124
+ completion_tokens: optionalNull(Schema.Number),
125
+ total_tokens: optionalNull(Schema.Number),
126
+ prompt_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
127
+ cached_tokens: optionalNull(Schema.Number),
128
+ cache_write_tokens: optionalNull(Schema.Number),
129
+ }), [Schema.Record(Schema.String, Schema.Unknown)])),
130
+ completion_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
131
+ reasoning_tokens: optionalNull(Schema.Number),
132
+ accepted_prediction_tokens: optionalNull(Schema.Number),
133
+ rejected_prediction_tokens: optionalNull(Schema.Number),
134
+ }), [Schema.Record(Schema.String, Schema.Unknown)])),
135
+ }), [Schema.Record(Schema.String, Schema.Unknown)]);
129
136
  const OpenAIChatToolCallDeltaFunction = Schema.Struct({
130
137
  name: optionalNull(Schema.String),
131
138
  arguments: optionalNull(Schema.String),
132
139
  });
133
140
  const OpenAIChatToolCallDelta = Schema.Struct({
134
- index: Schema.Number,
141
+ index: optionalNull(Schema.Number),
135
142
  id: optionalNull(Schema.String),
136
143
  function: optionalNull(OpenAIChatToolCallDeltaFunction),
137
144
  });
@@ -449,21 +456,29 @@ const mapFinishReason = (reason) => {
449
456
  const mapUsage = (usage) => {
450
457
  if (!usage)
451
458
  return undefined;
452
- const cached = usage.prompt_tokens_details?.cached_tokens;
453
- const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens;
454
- const reasoning = usage.completion_tokens_details?.reasoning_tokens;
455
- const nonCached = ProviderShared.subtractTokens(usage.prompt_tokens, ProviderShared.sumTokens(cached, cacheWrite));
459
+ const input = usage.prompt_tokens ?? undefined;
460
+ const output = usage.completion_tokens ?? undefined;
461
+ const cached = usage.prompt_tokens_details?.cached_tokens ?? undefined;
462
+ const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? undefined;
463
+ const reasoning = usage.completion_tokens_details?.reasoning_tokens ?? undefined;
464
+ const nonCached = ProviderShared.subtractTokens(input, ProviderShared.sumTokens(cached, cacheWrite));
456
465
  return new Usage({
457
- inputTokens: usage.prompt_tokens,
458
- outputTokens: usage.completion_tokens,
466
+ inputTokens: input,
467
+ outputTokens: output,
459
468
  nonCachedInputTokens: nonCached,
460
469
  cacheReadInputTokens: cached,
461
470
  cacheWriteInputTokens: cacheWrite,
462
471
  reasoningTokens: reasoning,
463
- totalTokens: ProviderShared.totalTokens(usage.prompt_tokens, usage.completion_tokens, usage.total_tokens),
472
+ totalTokens: ProviderShared.totalTokens(input, output, usage.total_tokens ?? undefined),
464
473
  providerMetadata: { openai: usage },
465
474
  });
466
475
  };
476
+ const toolIndexByID = (tools, pendingTools, id) => {
477
+ if (!id)
478
+ return undefined;
479
+ const entry = Object.entries({ ...pendingTools, ...tools }).find(([, tool]) => tool?.id === id);
480
+ return entry ? Number(entry[0]) : undefined;
481
+ };
467
482
  const reasoningDelta = (delta, configuredField) => {
468
483
  if (!delta)
469
484
  return undefined;
@@ -536,15 +551,27 @@ const step = (state, event) => Effect.gen(function* () {
536
551
  const events = [];
537
552
  const usage = mapUsage(event.usage) ?? state.usage;
538
553
  const choice = event.choices?.[0];
539
- const finishReason = choice?.finish_reason
540
- ? { normalized: mapFinishReason(choice.finish_reason), raw: choice.native_finish_reason ?? choice.finish_reason }
554
+ const rawFinishReason = choice?.finish_reason;
555
+ const finishReason = rawFinishReason !== undefined && rawFinishReason !== null
556
+ ? { normalized: mapFinishReason(rawFinishReason), raw: choice?.native_finish_reason ?? rawFinishReason }
541
557
  : state.finishReason;
542
558
  const delta = choice?.delta;
543
559
  const toolDeltas = delta?.tool_calls ?? [];
544
560
  let tools = state.tools;
545
561
  let pendingTools = state.pendingTools;
562
+ let latestToolIndex = state.latestToolIndex;
563
+ let nextToolIndex = state.nextToolIndex;
546
564
  let lifecycle = state.lifecycle;
547
565
  const reasoning = reasoningDelta(delta, state.reasoningField);
566
+ const hasLateContent = Boolean(delta?.content) ||
567
+ reasoning !== undefined ||
568
+ (Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) ||
569
+ toolDeltas.some((tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments));
570
+ if (state.finishReason !== undefined) {
571
+ if (hasLateContent)
572
+ return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason");
573
+ return [{ ...state, usage }, events];
574
+ }
548
575
  const reasoningField = state.reasoningField ?? (!state.lifecycle.text.has("text-0") ? reasoning?.field : undefined);
549
576
  const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined;
550
577
  if (detailDelta !== undefined)
@@ -563,21 +590,33 @@ const step = (state, event) => Effect.gen(function* () {
563
590
  lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined));
564
591
  lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content);
565
592
  }
566
- for (const tool of toolDeltas) {
567
- const current = tools[tool.index];
568
- const pending = pendingTools[tool.index];
593
+ // Compatible providers may omit indexes. Prefer durable identity, then use
594
+ // batch position for parallel deltas or the latest call for sparse chunks.
595
+ for (const [position, tool] of toolDeltas.entries()) {
596
+ const matched = toolIndexByID(tools, pendingTools, tool.id || undefined);
597
+ const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position);
598
+ const fallbackTool = tools[fallback] ?? pendingTools[fallback];
599
+ const index = tool.index ?? matched ??
600
+ (tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback);
601
+ const current = tools[index];
602
+ const pending = pendingTools[index];
569
603
  const id = current?.id ?? pending?.id ?? (tool.id || undefined);
570
604
  const name = current?.name ?? pending?.name ?? (tool.function?.name || undefined);
571
605
  const text = `${pending?.input ?? ""}${tool.function?.arguments ?? ""}`;
606
+ latestToolIndex = index;
607
+ nextToolIndex = Math.max(nextToolIndex, index + 1);
572
608
  if (!current && (!id || !name)) {
573
- pendingTools = { ...pendingTools, [tool.index]: { id: id || undefined, name: name || undefined, input: text } };
609
+ pendingTools = {
610
+ ...pendingTools,
611
+ [index]: { id: id || undefined, name: name || undefined, input: text },
612
+ };
574
613
  continue;
575
614
  }
576
615
  if (pending) {
577
616
  pendingTools = { ...pendingTools };
578
- delete pendingTools[tool.index];
617
+ delete pendingTools[index];
579
618
  }
580
- const result = ToolStream.appendOrStart(ADAPTER, tools, tool.index, { id: id || undefined, name: name || undefined, text }, "OpenAI Chat tool call delta is missing id or name");
619
+ const result = ToolStream.appendOrStart(ADAPTER, tools, index, { id: id || undefined, name: name || undefined, text }, "OpenAI Chat tool call delta is missing id or name");
581
620
  if (ToolStream.isError(result))
582
621
  return yield* result;
583
622
  tools = result.tools;
@@ -604,6 +643,8 @@ const step = (state, event) => Effect.gen(function* () {
604
643
  reasoningDetails: state.reasoningDetails,
605
644
  reasoningDetailsObserved,
606
645
  reasoningEmitted,
646
+ latestToolIndex,
647
+ nextToolIndex,
607
648
  },
608
649
  events,
609
650
  ];
@@ -654,6 +695,7 @@ export const protocol = Protocol.make({
654
695
  reasoningDetails: [],
655
696
  reasoningDetailsObserved: false,
656
697
  reasoningEmitted: false,
698
+ nextToolIndex: 0,
657
699
  }),
658
700
  step,
659
701
  onHalt: finishEvents,
@@ -285,15 +285,20 @@ export declare const protocol: Protocol<{
285
285
  readonly code?: string | number | null | undefined;
286
286
  } | null | undefined;
287
287
  readonly usage?: {
288
- readonly prompt_tokens?: number | undefined;
289
- readonly completion_tokens?: number | undefined;
290
- readonly total_tokens?: number | undefined;
288
+ readonly [x: string]: unknown;
289
+ readonly prompt_tokens?: number | null | undefined;
290
+ readonly completion_tokens?: number | null | undefined;
291
+ readonly total_tokens?: number | null | undefined;
291
292
  readonly prompt_tokens_details?: {
292
- readonly cached_tokens?: number | undefined;
293
- readonly cache_write_tokens?: number | undefined;
293
+ readonly [x: string]: unknown;
294
+ readonly cached_tokens?: number | null | undefined;
295
+ readonly cache_write_tokens?: number | null | undefined;
294
296
  } | null | undefined;
295
297
  readonly completion_tokens_details?: {
296
- readonly reasoning_tokens?: number | undefined;
298
+ readonly [x: string]: unknown;
299
+ readonly reasoning_tokens?: number | null | undefined;
300
+ readonly accepted_prediction_tokens?: number | null | undefined;
301
+ readonly rejected_prediction_tokens?: number | null | undefined;
297
302
  } | null | undefined;
298
303
  } | null | undefined;
299
304
  readonly choices?: readonly {
@@ -304,12 +309,12 @@ export declare const protocol: Protocol<{
304
309
  readonly reasoning_text?: string | null | undefined;
305
310
  readonly content?: string | null | undefined;
306
311
  readonly tool_calls?: readonly {
307
- readonly index: number;
308
312
  readonly function?: {
309
313
  readonly name?: string | null | undefined;
310
314
  readonly arguments?: string | null | undefined;
311
315
  } | null | undefined;
312
316
  readonly id?: string | null | undefined;
317
+ readonly index?: number | null | undefined;
313
318
  }[] | null | undefined;
314
319
  readonly reasoning_details?: unknown;
315
320
  } | null | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-next-16897",
3
+ "version": "0.0.0-next-16899",
4
4
  "name": "@opencode-ai/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.0.0-alpha.1",
32
32
  "@effect/platform-node": "4.0.0-beta.101",
33
- "@opencode-ai/http-recorder": "0.0.0-next-16897",
33
+ "@opencode-ai/http-recorder": "0.0.0-next-16899",
34
34
  "@tsconfig/bun": "1.0.9",
35
35
  "@types/bun": "1.3.13",
36
36
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@smithy/eventstream-codec": "4.2.14",
41
41
  "@smithy/util-utf8": "4.2.2",
42
- "@opencode-ai/schema": "0.0.0-next-16897",
42
+ "@opencode-ai/schema": "0.0.0-next-16899",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-beta.101",
45
45
  "google-auth-library": "10.5.0"