@ai-sdk/provider-utils 5.0.49 → 5.0.50

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @ai-sdk/provider-utils
2
2
 
3
+ ## 5.0.50
4
+
5
+ ### Patch Changes
6
+
7
+ - e3605f6: Fix streamed tool calls with missing, blank, or repeated IDs.
8
+ - Updated dependencies [525efc5]
9
+ - @ai-sdk/provider@4.0.19
10
+
3
11
  ## 5.0.49
4
12
 
5
13
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -2470,6 +2470,7 @@ interface StreamingToolCallDelta {
2470
2470
  interface StreamingToolCallTrackerOptions<DELTA extends StreamingToolCallDelta = StreamingToolCallDelta> {
2471
2471
  /**
2472
2472
  * ID generator function for tool call IDs.
2473
+ * Blank or repeated outputs are converted to usable unique IDs.
2473
2474
  * Defaults to the standard generateId.
2474
2475
  */
2475
2476
  generateId?: () => string;
@@ -2501,13 +2502,15 @@ type StreamingToolCallTrackerController = Pick<TransformStreamDefaultController<
2501
2502
  * emits tool-input-start/delta/end and tool-call events, and finalizes
2502
2503
  * unfinished tool calls on flush.
2503
2504
  *
2504
- * Used by openai, openai-compatible, groq, deepseek, and alibaba providers.
2505
+ * Used by openai, openai-compatible, groq, deepseek, alibaba, mistral, and
2506
+ * moonshotai providers.
2505
2507
  */
2506
2508
  declare class StreamingToolCallTracker<DELTA extends StreamingToolCallDelta = StreamingToolCallDelta> {
2507
2509
  private toolCalls;
2508
2510
  private toolCallsById;
2509
2511
  private toolCallsByIndex;
2510
- private latestToolCall;
2512
+ private usedToolCallIds;
2513
+ private nextGeneratedIdSuffixes;
2511
2514
  private readonly controller;
2512
2515
  private readonly _generateId;
2513
2516
  private readonly typeValidation;
@@ -2525,7 +2528,27 @@ declare class StreamingToolCallTracker<DELTA extends StreamingToolCallDelta = St
2525
2528
  * flush handler to ensure all tool calls are properly completed.
2526
2529
  */
2527
2530
  flush(): void;
2531
+ /**
2532
+ * Correlation precedence for streamed deltas:
2533
+ *
2534
+ * | ID evidence | index/name evidence | start evidence | resolution |
2535
+ * | --- | --- | --- | --- |
2536
+ * | known | matching | any | matching call, new call, or ambiguity |
2537
+ * | known | conflicting | named | new call |
2538
+ * | unseen | matching | structured start | new call |
2539
+ * | unseen | matching | continuation | matching call or ambiguity |
2540
+ * | absent | matching | any | matching call, new call, or ambiguity |
2541
+ * | absent | absent | named | new call |
2542
+ * | absent | absent | unnamed | sole unfinished call, new call, or ambiguity |
2543
+ */
2544
+ private resolveToolCall;
2545
+ private filterToolCallsByName;
2546
+ private resolveMatchingToolCall;
2528
2547
  private processNewToolCall;
2548
+ private associateWireId;
2549
+ private associateIndex;
2550
+ private createToolCallId;
2551
+ private getNonBlankString;
2529
2552
  private processExistingToolCall;
2530
2553
  private finishToolCall;
2531
2554
  }
package/dist/index.js CHANGED
@@ -503,7 +503,7 @@ function handleFetchError({
503
503
  }
504
504
 
505
505
  // src/version.ts
506
- var VERSION = true ? "5.0.49" : "0.0.0-test";
506
+ var VERSION = true ? "5.0.50" : "0.0.0-test";
507
507
 
508
508
  // src/normalize-headers.ts
509
509
  function normalizeHeaders(headers) {
@@ -4249,11 +4249,84 @@ function resolveSync(value) {
4249
4249
  import {
4250
4250
  InvalidResponseDataError
4251
4251
  } from "@ai-sdk/provider";
4252
+
4253
+ // src/streaming-tool-call-argument-state.ts
4254
+ function startsWithStructuredValue(value) {
4255
+ if (typeof value !== "string") {
4256
+ return false;
4257
+ }
4258
+ const firstCharacter = value.trimStart()[0];
4259
+ return firstCharacter === "{" || firstCharacter === "[";
4260
+ }
4261
+ var StreamingToolCallArgumentState = class {
4262
+ constructor(initialValue = "") {
4263
+ this.structure = { kind: "undetermined" };
4264
+ this.append(initialValue);
4265
+ }
4266
+ get hasCompleteStructuredValue() {
4267
+ return this.structure.kind === "structured" && this.structure.complete === true;
4268
+ }
4269
+ append(delta) {
4270
+ let nextStructure = this.structure;
4271
+ for (const character of delta) {
4272
+ if (nextStructure.kind === "undetermined") {
4273
+ if (/\s/.test(character)) {
4274
+ continue;
4275
+ }
4276
+ if (character !== "{" && character !== "[") {
4277
+ nextStructure = { kind: "other" };
4278
+ continue;
4279
+ }
4280
+ nextStructure = {
4281
+ kind: "structured",
4282
+ stack: [character],
4283
+ inString: false,
4284
+ escaped: false,
4285
+ complete: false
4286
+ };
4287
+ continue;
4288
+ }
4289
+ if (nextStructure.kind !== "structured" || nextStructure.complete) {
4290
+ continue;
4291
+ }
4292
+ if (nextStructure.inString) {
4293
+ if (nextStructure.escaped) {
4294
+ nextStructure.escaped = false;
4295
+ } else if (character === "\\") {
4296
+ nextStructure.escaped = true;
4297
+ } else if (character === '"') {
4298
+ nextStructure.inString = false;
4299
+ }
4300
+ continue;
4301
+ }
4302
+ if (character === '"') {
4303
+ nextStructure.inString = true;
4304
+ } else if (character === "{" || character === "[") {
4305
+ nextStructure.stack.push(character);
4306
+ } else if (character === "}" || character === "]") {
4307
+ const expectedOpening = character === "}" ? "{" : "[";
4308
+ if (nextStructure.stack.at(-1) !== expectedOpening) {
4309
+ nextStructure = { kind: "other" };
4310
+ continue;
4311
+ }
4312
+ nextStructure.stack.pop();
4313
+ if (nextStructure.stack.length === 0) {
4314
+ nextStructure.complete = true;
4315
+ }
4316
+ }
4317
+ }
4318
+ this.structure = nextStructure;
4319
+ }
4320
+ };
4321
+
4322
+ // src/streaming-tool-call-tracker.ts
4252
4323
  var StreamingToolCallTracker = class {
4253
4324
  constructor(controller, options = {}) {
4254
- this.toolCalls = /* @__PURE__ */ new Set();
4325
+ this.toolCalls = [];
4255
4326
  this.toolCallsById = /* @__PURE__ */ new Map();
4256
4327
  this.toolCallsByIndex = /* @__PURE__ */ new Map();
4328
+ this.usedToolCallIds = /* @__PURE__ */ new Set();
4329
+ this.nextGeneratedIdSuffixes = /* @__PURE__ */ new Map();
4257
4330
  this.controller = controller;
4258
4331
  this._generateId = options.generateId ?? generateId;
4259
4332
  this.typeValidation = options.typeValidation ?? "none";
@@ -4266,30 +4339,162 @@ var StreamingToolCallTracker = class {
4266
4339
  * events as appropriate.
4267
4340
  */
4268
4341
  processDelta(toolCallDelta) {
4269
- const { id, index } = toolCallDelta;
4270
- let toolCall = id != null && id.length > 0 ? this.toolCallsById.get(id) : index != null ? this.toolCallsByIndex.get(index) : this.latestToolCall;
4271
- if (toolCall == null) {
4272
- toolCall = this.processNewToolCall(toolCallDelta);
4342
+ const wireName = toolCallDelta.function?.name;
4343
+ const hasBlankName = typeof wireName === "string" && wireName.trim().length === 0;
4344
+ const wireId = this.getNonBlankString(toolCallDelta.id);
4345
+ const name3 = this.getNonBlankString(wireName);
4346
+ const { index } = toolCallDelta;
4347
+ const resolution = this.resolveToolCall({
4348
+ wireId,
4349
+ index,
4350
+ name: name3,
4351
+ hasExplicitCallStart: name3 != null && startsWithStructuredValue(toolCallDelta.function?.arguments)
4352
+ });
4353
+ if (resolution.kind === "ambiguous") {
4354
+ return;
4355
+ }
4356
+ let toolCall;
4357
+ if (resolution.kind === "new") {
4358
+ if (hasBlankName) {
4359
+ return;
4360
+ }
4361
+ toolCall = this.processNewToolCall(toolCallDelta, {
4362
+ wireId,
4363
+ index,
4364
+ name: name3
4365
+ });
4273
4366
  } else {
4367
+ toolCall = resolution.toolCall;
4368
+ if (wireId != null) {
4369
+ this.associateWireId(toolCall, wireId);
4370
+ }
4274
4371
  this.processExistingToolCall(toolCall, toolCallDelta);
4275
4372
  }
4276
4373
  if (index != null) {
4277
- this.toolCallsByIndex.set(index, toolCall);
4374
+ this.associateIndex(toolCall, index);
4278
4375
  }
4279
- this.latestToolCall = toolCall;
4280
4376
  }
4281
4377
  /**
4282
4378
  * Finalize any unfinished tool calls. Should be called during the stream's
4283
4379
  * flush handler to ensure all tool calls are properly completed.
4284
4380
  */
4285
4381
  flush() {
4286
- for (const toolCall of this.toolCalls) {
4382
+ const toolCalls = this.toolCalls.every((toolCall) => toolCall.index != null) ? [...this.toolCalls].sort(
4383
+ (a, b) => a.index - b.index || a.sequence - b.sequence
4384
+ ) : this.toolCalls;
4385
+ for (const toolCall of toolCalls) {
4287
4386
  if (!toolCall.hasFinished) {
4288
4387
  this.finishToolCall(toolCall);
4289
4388
  }
4290
4389
  }
4291
4390
  }
4292
- processNewToolCall(toolCallDelta) {
4391
+ /**
4392
+ * Correlation precedence for streamed deltas:
4393
+ *
4394
+ * | ID evidence | index/name evidence | start evidence | resolution |
4395
+ * | --- | --- | --- | --- |
4396
+ * | known | matching | any | matching call, new call, or ambiguity |
4397
+ * | known | conflicting | named | new call |
4398
+ * | unseen | matching | structured start | new call |
4399
+ * | unseen | matching | continuation | matching call or ambiguity |
4400
+ * | absent | matching | any | matching call, new call, or ambiguity |
4401
+ * | absent | absent | named | new call |
4402
+ * | absent | absent | unnamed | sole unfinished call, new call, or ambiguity |
4403
+ */
4404
+ resolveToolCall({
4405
+ wireId,
4406
+ index,
4407
+ name: name3,
4408
+ hasExplicitCallStart
4409
+ }) {
4410
+ const indexedToolCalls = index != null ? this.toolCallsByIndex.get(index) : void 0;
4411
+ const matchingIndexedToolCalls = this.filterToolCallsByName(
4412
+ indexedToolCalls,
4413
+ name3
4414
+ );
4415
+ if (wireId != null) {
4416
+ const toolCallsWithId = this.toolCallsById.get(wireId);
4417
+ if (toolCallsWithId != null) {
4418
+ if (index != null) {
4419
+ const matchingToolCalls = matchingIndexedToolCalls.filter(
4420
+ (toolCall) => toolCallsWithId.has(toolCall)
4421
+ );
4422
+ const matchingToolCall = this.resolveMatchingToolCall(
4423
+ matchingToolCalls,
4424
+ hasExplicitCallStart
4425
+ );
4426
+ if (matchingToolCall.kind !== "new") {
4427
+ return matchingToolCall;
4428
+ }
4429
+ if (name3 != null) {
4430
+ return { kind: "new" };
4431
+ }
4432
+ if (indexedToolCalls != null) {
4433
+ return { kind: "ambiguous" };
4434
+ }
4435
+ return this.resolveMatchingToolCall([...toolCallsWithId], false);
4436
+ }
4437
+ if (name3 != null) {
4438
+ const matchingToolCalls = [...toolCallsWithId].filter(
4439
+ (toolCall) => toolCall.function.name === name3
4440
+ );
4441
+ return this.resolveMatchingToolCall(
4442
+ matchingToolCalls,
4443
+ hasExplicitCallStart
4444
+ );
4445
+ }
4446
+ return this.resolveMatchingToolCall([...toolCallsWithId], false);
4447
+ }
4448
+ if (matchingIndexedToolCalls.length > 0) {
4449
+ return hasExplicitCallStart ? { kind: "new" } : this.resolveMatchingToolCall(matchingIndexedToolCalls, false);
4450
+ }
4451
+ return { kind: "new" };
4452
+ }
4453
+ if (indexedToolCalls != null) {
4454
+ return this.resolveMatchingToolCall(
4455
+ matchingIndexedToolCalls,
4456
+ hasExplicitCallStart
4457
+ );
4458
+ }
4459
+ if (name3 != null) {
4460
+ return { kind: "new" };
4461
+ }
4462
+ const unfinishedToolCalls = this.toolCalls.filter(
4463
+ (toolCall) => !toolCall.hasFinished
4464
+ );
4465
+ if (unfinishedToolCalls.length === 1) {
4466
+ return { kind: "existing", toolCall: unfinishedToolCalls[0] };
4467
+ }
4468
+ return unfinishedToolCalls.length > 1 ? { kind: "ambiguous" } : { kind: "new" };
4469
+ }
4470
+ filterToolCallsByName(toolCalls, name3) {
4471
+ if (toolCalls == null) {
4472
+ return [];
4473
+ }
4474
+ return [...toolCalls].filter(
4475
+ (toolCall) => name3 == null || toolCall.function.name === name3
4476
+ );
4477
+ }
4478
+ resolveMatchingToolCall(toolCalls, hasExplicitCallStart) {
4479
+ if (toolCalls.length === 0) {
4480
+ return { kind: "new" };
4481
+ }
4482
+ if (!hasExplicitCallStart) {
4483
+ return toolCalls.length === 1 ? { kind: "existing", toolCall: toolCalls[0] } : { kind: "ambiguous" };
4484
+ }
4485
+ const continuableToolCalls = toolCalls.filter(
4486
+ (toolCall) => !toolCall.argumentState.hasCompleteStructuredValue
4487
+ );
4488
+ if (continuableToolCalls.length === 1) {
4489
+ return { kind: "existing", toolCall: continuableToolCalls[0] };
4490
+ }
4491
+ return continuableToolCalls.length > 1 ? { kind: "ambiguous" } : { kind: "new" };
4492
+ }
4493
+ processNewToolCall(toolCallDelta, {
4494
+ wireId,
4495
+ index,
4496
+ name: name3
4497
+ }) {
4293
4498
  if (this.typeValidation === "required") {
4294
4499
  if (toolCallDelta.type !== "function") {
4295
4500
  throw new InvalidResponseDataError({
@@ -4305,37 +4510,36 @@ var StreamingToolCallTracker = class {
4305
4510
  });
4306
4511
  }
4307
4512
  }
4308
- if (toolCallDelta.id == null) {
4309
- throw new InvalidResponseDataError({
4310
- data: toolCallDelta,
4311
- message: `Expected 'id' to be a string.`
4312
- });
4313
- }
4314
- if (toolCallDelta.function?.name == null) {
4513
+ if (name3 == null) {
4315
4514
  throw new InvalidResponseDataError({
4316
4515
  data: toolCallDelta,
4317
4516
  message: `Expected 'function.name' to be a string.`
4318
4517
  });
4319
4518
  }
4519
+ const id = this.createToolCallId(wireId);
4320
4520
  this.controller.enqueue({
4321
4521
  type: "tool-input-start",
4322
- id: toolCallDelta.id,
4323
- toolName: toolCallDelta.function.name
4522
+ id,
4523
+ toolName: name3
4324
4524
  });
4325
4525
  const metadata = this.extractMetadata?.(toolCallDelta);
4526
+ const initialArguments = toolCallDelta.function?.arguments ?? "";
4326
4527
  const toolCall = {
4327
- id: toolCallDelta.id,
4528
+ id,
4529
+ index: index ?? void 0,
4530
+ sequence: this.toolCalls.length,
4328
4531
  type: "function",
4329
4532
  function: {
4330
- name: toolCallDelta.function.name,
4331
- arguments: toolCallDelta.function.arguments ?? ""
4533
+ name: name3,
4534
+ arguments: initialArguments
4332
4535
  },
4536
+ argumentState: new StreamingToolCallArgumentState(initialArguments),
4333
4537
  hasFinished: false,
4334
4538
  metadata
4335
4539
  };
4336
- this.toolCalls.add(toolCall);
4337
- if (toolCall.id.length > 0) {
4338
- this.toolCallsById.set(toolCall.id, toolCall);
4540
+ this.toolCalls.push(toolCall);
4541
+ if (wireId != null) {
4542
+ this.associateWireId(toolCall, wireId);
4339
4543
  }
4340
4544
  if (toolCall.function.arguments.length > 0) {
4341
4545
  this.controller.enqueue({
@@ -4346,11 +4550,53 @@ var StreamingToolCallTracker = class {
4346
4550
  }
4347
4551
  return toolCall;
4348
4552
  }
4553
+ associateWireId(toolCall, wireId) {
4554
+ let toolCallsWithId = this.toolCallsById.get(wireId);
4555
+ if (toolCallsWithId == null) {
4556
+ toolCallsWithId = /* @__PURE__ */ new Set();
4557
+ this.toolCallsById.set(wireId, toolCallsWithId);
4558
+ }
4559
+ toolCallsWithId.add(toolCall);
4560
+ }
4561
+ associateIndex(toolCall, index) {
4562
+ let toolCallsWithIndex = this.toolCallsByIndex.get(index);
4563
+ if (toolCallsWithIndex == null) {
4564
+ toolCallsWithIndex = /* @__PURE__ */ new Set();
4565
+ this.toolCallsByIndex.set(index, toolCallsWithIndex);
4566
+ }
4567
+ toolCallsWithIndex.add(toolCall);
4568
+ }
4569
+ createToolCallId(wireId) {
4570
+ if (wireId != null && !this.usedToolCallIds.has(wireId)) {
4571
+ this.usedToolCallIds.add(wireId);
4572
+ return wireId;
4573
+ }
4574
+ const generatedId = this.getNonBlankString(this._generateId()) ?? "tool-call";
4575
+ if (!this.usedToolCallIds.has(generatedId)) {
4576
+ this.usedToolCallIds.add(generatedId);
4577
+ return generatedId;
4578
+ }
4579
+ const initialSuffix = this.nextGeneratedIdSuffixes.get(generatedId) ?? 1;
4580
+ const maximumSuffix = initialSuffix + this.usedToolCallIds.size;
4581
+ for (let suffix = initialSuffix; suffix <= maximumSuffix; suffix++) {
4582
+ const suffixedId = `${generatedId}-${suffix}`;
4583
+ if (!this.usedToolCallIds.has(suffixedId)) {
4584
+ this.usedToolCallIds.add(suffixedId);
4585
+ this.nextGeneratedIdSuffixes.set(generatedId, suffix + 1);
4586
+ return suffixedId;
4587
+ }
4588
+ }
4589
+ throw new Error("Failed to create a unique tool call ID.");
4590
+ }
4591
+ getNonBlankString(value) {
4592
+ return value != null && value.trim().length > 0 ? value : void 0;
4593
+ }
4349
4594
  processExistingToolCall(toolCall, toolCallDelta) {
4350
4595
  if (toolCall.hasFinished) {
4351
4596
  return;
4352
4597
  }
4353
4598
  if (toolCallDelta.function?.arguments != null) {
4599
+ toolCall.argumentState.append(toolCallDelta.function.arguments);
4354
4600
  toolCall.function.arguments += toolCallDelta.function.arguments;
4355
4601
  this.controller.enqueue({
4356
4602
  type: "tool-input-delta",
@@ -4369,7 +4615,7 @@ var StreamingToolCallTracker = class {
4369
4615
  );
4370
4616
  this.controller.enqueue({
4371
4617
  type: "tool-call",
4372
- toolCallId: toolCall.id ?? this._generateId(),
4618
+ toolCallId: toolCall.id,
4373
4619
  toolName: toolCall.function.name,
4374
4620
  input: toolCall.function.arguments,
4375
4621
  ...providerMetadata ? { providerMetadata } : {}