@ai-sdk/provider-utils 5.0.49 → 5.0.51

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,19 @@
1
1
  # @ai-sdk/provider-utils
2
2
 
3
+ ## 5.0.51
4
+
5
+ ### Patch Changes
6
+
7
+ - c2511c1: fix: use standards-compliant User-Agent header
8
+
9
+ ## 5.0.50
10
+
11
+ ### Patch Changes
12
+
13
+ - e3605f6: Fix streamed tool calls with missing, blank, or repeated IDs.
14
+ - Updated dependencies [525efc5]
15
+ - @ai-sdk/provider@4.0.19
16
+
3
17
  ## 5.0.49
4
18
 
5
19
  ### 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
@@ -404,19 +404,16 @@ function extractResponseHeaders(response) {
404
404
 
405
405
  // src/get-runtime-environment-user-agent.ts
406
406
  function getRuntimeEnvironmentUserAgent(globalThisAny = globalThis) {
407
- if (globalThisAny.window) {
408
- return `runtime/browser`;
409
- }
410
407
  if (globalThisAny.navigator?.userAgent) {
411
- return `runtime/${globalThisAny.navigator.userAgent.toLowerCase()}`;
408
+ return globalThisAny.navigator.userAgent.toLowerCase();
412
409
  }
413
410
  if (globalThisAny.process?.versions?.node) {
414
- return `runtime/node.js/${globalThisAny.process.version.substring(0)}`;
411
+ return `node.js/${globalThisAny.process.version}`;
415
412
  }
416
413
  if (globalThisAny.EdgeRuntime) {
417
- return `runtime/vercel-edge`;
414
+ return "vercel-edge";
418
415
  }
419
- return "runtime/unknown";
416
+ return "";
420
417
  }
421
418
 
422
419
  // src/handle-fetch-error.ts
@@ -503,7 +500,7 @@ function handleFetchError({
503
500
  }
504
501
 
505
502
  // src/version.ts
506
- var VERSION = true ? "5.0.49" : "0.0.0-test";
503
+ var VERSION = true ? "5.0.51" : "0.0.0-test";
507
504
 
508
505
  // src/normalize-headers.ts
509
506
  function normalizeHeaders(headers) {
@@ -554,7 +551,7 @@ var deleteFromApi = async ({
554
551
  method: "DELETE",
555
552
  headers: withUserAgentSuffix(
556
553
  headers,
557
- `ai-sdk/provider-utils/${VERSION}`,
554
+ `ai-sdk-provider-utils/${VERSION}`,
558
555
  getRuntimeEnvironmentUserAgent()
559
556
  ),
560
557
  signal: abortSignal
@@ -1549,7 +1546,7 @@ var getFromApi = async ({
1549
1546
  const outgoingHeaders = credentialedOrigin !== void 0 && !isSameOrigin(url, credentialedOrigin) ? {} : headers;
1550
1547
  const requestHeaders = withUserAgentSuffix(
1551
1548
  outgoingHeaders,
1552
- `ai-sdk/provider-utils/${VERSION}`,
1549
+ `ai-sdk-provider-utils/${VERSION}`,
1553
1550
  getRuntimeEnvironmentUserAgent()
1554
1551
  );
1555
1552
  const response = validateUrl ? await fetchWithValidatedRedirects({
@@ -3499,7 +3496,7 @@ var postMultipartStreamToApi = async ({
3499
3496
  ...headers,
3500
3497
  "Content-Type": `multipart/form-data; boundary=${boundary}`
3501
3498
  },
3502
- `ai-sdk/provider-utils/${VERSION}`,
3499
+ `ai-sdk-provider-utils/${VERSION}`,
3503
3500
  getRuntimeEnvironmentUserAgent()
3504
3501
  ),
3505
3502
  body: body.stream,
@@ -3618,7 +3615,7 @@ var postToApi = async ({
3618
3615
  method: "POST",
3619
3616
  headers: withUserAgentSuffix(
3620
3617
  headers,
3621
- `ai-sdk/provider-utils/${VERSION}`,
3618
+ `ai-sdk-provider-utils/${VERSION}`,
3622
3619
  getRuntimeEnvironmentUserAgent()
3623
3620
  ),
3624
3621
  body: body.content,
@@ -4249,11 +4246,84 @@ function resolveSync(value) {
4249
4246
  import {
4250
4247
  InvalidResponseDataError
4251
4248
  } from "@ai-sdk/provider";
4249
+
4250
+ // src/streaming-tool-call-argument-state.ts
4251
+ function startsWithStructuredValue(value) {
4252
+ if (typeof value !== "string") {
4253
+ return false;
4254
+ }
4255
+ const firstCharacter = value.trimStart()[0];
4256
+ return firstCharacter === "{" || firstCharacter === "[";
4257
+ }
4258
+ var StreamingToolCallArgumentState = class {
4259
+ constructor(initialValue = "") {
4260
+ this.structure = { kind: "undetermined" };
4261
+ this.append(initialValue);
4262
+ }
4263
+ get hasCompleteStructuredValue() {
4264
+ return this.structure.kind === "structured" && this.structure.complete === true;
4265
+ }
4266
+ append(delta) {
4267
+ let nextStructure = this.structure;
4268
+ for (const character of delta) {
4269
+ if (nextStructure.kind === "undetermined") {
4270
+ if (/\s/.test(character)) {
4271
+ continue;
4272
+ }
4273
+ if (character !== "{" && character !== "[") {
4274
+ nextStructure = { kind: "other" };
4275
+ continue;
4276
+ }
4277
+ nextStructure = {
4278
+ kind: "structured",
4279
+ stack: [character],
4280
+ inString: false,
4281
+ escaped: false,
4282
+ complete: false
4283
+ };
4284
+ continue;
4285
+ }
4286
+ if (nextStructure.kind !== "structured" || nextStructure.complete) {
4287
+ continue;
4288
+ }
4289
+ if (nextStructure.inString) {
4290
+ if (nextStructure.escaped) {
4291
+ nextStructure.escaped = false;
4292
+ } else if (character === "\\") {
4293
+ nextStructure.escaped = true;
4294
+ } else if (character === '"') {
4295
+ nextStructure.inString = false;
4296
+ }
4297
+ continue;
4298
+ }
4299
+ if (character === '"') {
4300
+ nextStructure.inString = true;
4301
+ } else if (character === "{" || character === "[") {
4302
+ nextStructure.stack.push(character);
4303
+ } else if (character === "}" || character === "]") {
4304
+ const expectedOpening = character === "}" ? "{" : "[";
4305
+ if (nextStructure.stack.at(-1) !== expectedOpening) {
4306
+ nextStructure = { kind: "other" };
4307
+ continue;
4308
+ }
4309
+ nextStructure.stack.pop();
4310
+ if (nextStructure.stack.length === 0) {
4311
+ nextStructure.complete = true;
4312
+ }
4313
+ }
4314
+ }
4315
+ this.structure = nextStructure;
4316
+ }
4317
+ };
4318
+
4319
+ // src/streaming-tool-call-tracker.ts
4252
4320
  var StreamingToolCallTracker = class {
4253
4321
  constructor(controller, options = {}) {
4254
- this.toolCalls = /* @__PURE__ */ new Set();
4322
+ this.toolCalls = [];
4255
4323
  this.toolCallsById = /* @__PURE__ */ new Map();
4256
4324
  this.toolCallsByIndex = /* @__PURE__ */ new Map();
4325
+ this.usedToolCallIds = /* @__PURE__ */ new Set();
4326
+ this.nextGeneratedIdSuffixes = /* @__PURE__ */ new Map();
4257
4327
  this.controller = controller;
4258
4328
  this._generateId = options.generateId ?? generateId;
4259
4329
  this.typeValidation = options.typeValidation ?? "none";
@@ -4266,30 +4336,162 @@ var StreamingToolCallTracker = class {
4266
4336
  * events as appropriate.
4267
4337
  */
4268
4338
  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);
4339
+ const wireName = toolCallDelta.function?.name;
4340
+ const hasBlankName = typeof wireName === "string" && wireName.trim().length === 0;
4341
+ const wireId = this.getNonBlankString(toolCallDelta.id);
4342
+ const name3 = this.getNonBlankString(wireName);
4343
+ const { index } = toolCallDelta;
4344
+ const resolution = this.resolveToolCall({
4345
+ wireId,
4346
+ index,
4347
+ name: name3,
4348
+ hasExplicitCallStart: name3 != null && startsWithStructuredValue(toolCallDelta.function?.arguments)
4349
+ });
4350
+ if (resolution.kind === "ambiguous") {
4351
+ return;
4352
+ }
4353
+ let toolCall;
4354
+ if (resolution.kind === "new") {
4355
+ if (hasBlankName) {
4356
+ return;
4357
+ }
4358
+ toolCall = this.processNewToolCall(toolCallDelta, {
4359
+ wireId,
4360
+ index,
4361
+ name: name3
4362
+ });
4273
4363
  } else {
4364
+ toolCall = resolution.toolCall;
4365
+ if (wireId != null) {
4366
+ this.associateWireId(toolCall, wireId);
4367
+ }
4274
4368
  this.processExistingToolCall(toolCall, toolCallDelta);
4275
4369
  }
4276
4370
  if (index != null) {
4277
- this.toolCallsByIndex.set(index, toolCall);
4371
+ this.associateIndex(toolCall, index);
4278
4372
  }
4279
- this.latestToolCall = toolCall;
4280
4373
  }
4281
4374
  /**
4282
4375
  * Finalize any unfinished tool calls. Should be called during the stream's
4283
4376
  * flush handler to ensure all tool calls are properly completed.
4284
4377
  */
4285
4378
  flush() {
4286
- for (const toolCall of this.toolCalls) {
4379
+ const toolCalls = this.toolCalls.every((toolCall) => toolCall.index != null) ? [...this.toolCalls].sort(
4380
+ (a, b) => a.index - b.index || a.sequence - b.sequence
4381
+ ) : this.toolCalls;
4382
+ for (const toolCall of toolCalls) {
4287
4383
  if (!toolCall.hasFinished) {
4288
4384
  this.finishToolCall(toolCall);
4289
4385
  }
4290
4386
  }
4291
4387
  }
4292
- processNewToolCall(toolCallDelta) {
4388
+ /**
4389
+ * Correlation precedence for streamed deltas:
4390
+ *
4391
+ * | ID evidence | index/name evidence | start evidence | resolution |
4392
+ * | --- | --- | --- | --- |
4393
+ * | known | matching | any | matching call, new call, or ambiguity |
4394
+ * | known | conflicting | named | new call |
4395
+ * | unseen | matching | structured start | new call |
4396
+ * | unseen | matching | continuation | matching call or ambiguity |
4397
+ * | absent | matching | any | matching call, new call, or ambiguity |
4398
+ * | absent | absent | named | new call |
4399
+ * | absent | absent | unnamed | sole unfinished call, new call, or ambiguity |
4400
+ */
4401
+ resolveToolCall({
4402
+ wireId,
4403
+ index,
4404
+ name: name3,
4405
+ hasExplicitCallStart
4406
+ }) {
4407
+ const indexedToolCalls = index != null ? this.toolCallsByIndex.get(index) : void 0;
4408
+ const matchingIndexedToolCalls = this.filterToolCallsByName(
4409
+ indexedToolCalls,
4410
+ name3
4411
+ );
4412
+ if (wireId != null) {
4413
+ const toolCallsWithId = this.toolCallsById.get(wireId);
4414
+ if (toolCallsWithId != null) {
4415
+ if (index != null) {
4416
+ const matchingToolCalls = matchingIndexedToolCalls.filter(
4417
+ (toolCall) => toolCallsWithId.has(toolCall)
4418
+ );
4419
+ const matchingToolCall = this.resolveMatchingToolCall(
4420
+ matchingToolCalls,
4421
+ hasExplicitCallStart
4422
+ );
4423
+ if (matchingToolCall.kind !== "new") {
4424
+ return matchingToolCall;
4425
+ }
4426
+ if (name3 != null) {
4427
+ return { kind: "new" };
4428
+ }
4429
+ if (indexedToolCalls != null) {
4430
+ return { kind: "ambiguous" };
4431
+ }
4432
+ return this.resolveMatchingToolCall([...toolCallsWithId], false);
4433
+ }
4434
+ if (name3 != null) {
4435
+ const matchingToolCalls = [...toolCallsWithId].filter(
4436
+ (toolCall) => toolCall.function.name === name3
4437
+ );
4438
+ return this.resolveMatchingToolCall(
4439
+ matchingToolCalls,
4440
+ hasExplicitCallStart
4441
+ );
4442
+ }
4443
+ return this.resolveMatchingToolCall([...toolCallsWithId], false);
4444
+ }
4445
+ if (matchingIndexedToolCalls.length > 0) {
4446
+ return hasExplicitCallStart ? { kind: "new" } : this.resolveMatchingToolCall(matchingIndexedToolCalls, false);
4447
+ }
4448
+ return { kind: "new" };
4449
+ }
4450
+ if (indexedToolCalls != null) {
4451
+ return this.resolveMatchingToolCall(
4452
+ matchingIndexedToolCalls,
4453
+ hasExplicitCallStart
4454
+ );
4455
+ }
4456
+ if (name3 != null) {
4457
+ return { kind: "new" };
4458
+ }
4459
+ const unfinishedToolCalls = this.toolCalls.filter(
4460
+ (toolCall) => !toolCall.hasFinished
4461
+ );
4462
+ if (unfinishedToolCalls.length === 1) {
4463
+ return { kind: "existing", toolCall: unfinishedToolCalls[0] };
4464
+ }
4465
+ return unfinishedToolCalls.length > 1 ? { kind: "ambiguous" } : { kind: "new" };
4466
+ }
4467
+ filterToolCallsByName(toolCalls, name3) {
4468
+ if (toolCalls == null) {
4469
+ return [];
4470
+ }
4471
+ return [...toolCalls].filter(
4472
+ (toolCall) => name3 == null || toolCall.function.name === name3
4473
+ );
4474
+ }
4475
+ resolveMatchingToolCall(toolCalls, hasExplicitCallStart) {
4476
+ if (toolCalls.length === 0) {
4477
+ return { kind: "new" };
4478
+ }
4479
+ if (!hasExplicitCallStart) {
4480
+ return toolCalls.length === 1 ? { kind: "existing", toolCall: toolCalls[0] } : { kind: "ambiguous" };
4481
+ }
4482
+ const continuableToolCalls = toolCalls.filter(
4483
+ (toolCall) => !toolCall.argumentState.hasCompleteStructuredValue
4484
+ );
4485
+ if (continuableToolCalls.length === 1) {
4486
+ return { kind: "existing", toolCall: continuableToolCalls[0] };
4487
+ }
4488
+ return continuableToolCalls.length > 1 ? { kind: "ambiguous" } : { kind: "new" };
4489
+ }
4490
+ processNewToolCall(toolCallDelta, {
4491
+ wireId,
4492
+ index,
4493
+ name: name3
4494
+ }) {
4293
4495
  if (this.typeValidation === "required") {
4294
4496
  if (toolCallDelta.type !== "function") {
4295
4497
  throw new InvalidResponseDataError({
@@ -4305,37 +4507,36 @@ var StreamingToolCallTracker = class {
4305
4507
  });
4306
4508
  }
4307
4509
  }
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) {
4510
+ if (name3 == null) {
4315
4511
  throw new InvalidResponseDataError({
4316
4512
  data: toolCallDelta,
4317
4513
  message: `Expected 'function.name' to be a string.`
4318
4514
  });
4319
4515
  }
4516
+ const id = this.createToolCallId(wireId);
4320
4517
  this.controller.enqueue({
4321
4518
  type: "tool-input-start",
4322
- id: toolCallDelta.id,
4323
- toolName: toolCallDelta.function.name
4519
+ id,
4520
+ toolName: name3
4324
4521
  });
4325
4522
  const metadata = this.extractMetadata?.(toolCallDelta);
4523
+ const initialArguments = toolCallDelta.function?.arguments ?? "";
4326
4524
  const toolCall = {
4327
- id: toolCallDelta.id,
4525
+ id,
4526
+ index: index ?? void 0,
4527
+ sequence: this.toolCalls.length,
4328
4528
  type: "function",
4329
4529
  function: {
4330
- name: toolCallDelta.function.name,
4331
- arguments: toolCallDelta.function.arguments ?? ""
4530
+ name: name3,
4531
+ arguments: initialArguments
4332
4532
  },
4533
+ argumentState: new StreamingToolCallArgumentState(initialArguments),
4333
4534
  hasFinished: false,
4334
4535
  metadata
4335
4536
  };
4336
- this.toolCalls.add(toolCall);
4337
- if (toolCall.id.length > 0) {
4338
- this.toolCallsById.set(toolCall.id, toolCall);
4537
+ this.toolCalls.push(toolCall);
4538
+ if (wireId != null) {
4539
+ this.associateWireId(toolCall, wireId);
4339
4540
  }
4340
4541
  if (toolCall.function.arguments.length > 0) {
4341
4542
  this.controller.enqueue({
@@ -4346,11 +4547,53 @@ var StreamingToolCallTracker = class {
4346
4547
  }
4347
4548
  return toolCall;
4348
4549
  }
4550
+ associateWireId(toolCall, wireId) {
4551
+ let toolCallsWithId = this.toolCallsById.get(wireId);
4552
+ if (toolCallsWithId == null) {
4553
+ toolCallsWithId = /* @__PURE__ */ new Set();
4554
+ this.toolCallsById.set(wireId, toolCallsWithId);
4555
+ }
4556
+ toolCallsWithId.add(toolCall);
4557
+ }
4558
+ associateIndex(toolCall, index) {
4559
+ let toolCallsWithIndex = this.toolCallsByIndex.get(index);
4560
+ if (toolCallsWithIndex == null) {
4561
+ toolCallsWithIndex = /* @__PURE__ */ new Set();
4562
+ this.toolCallsByIndex.set(index, toolCallsWithIndex);
4563
+ }
4564
+ toolCallsWithIndex.add(toolCall);
4565
+ }
4566
+ createToolCallId(wireId) {
4567
+ if (wireId != null && !this.usedToolCallIds.has(wireId)) {
4568
+ this.usedToolCallIds.add(wireId);
4569
+ return wireId;
4570
+ }
4571
+ const generatedId = this.getNonBlankString(this._generateId()) ?? "tool-call";
4572
+ if (!this.usedToolCallIds.has(generatedId)) {
4573
+ this.usedToolCallIds.add(generatedId);
4574
+ return generatedId;
4575
+ }
4576
+ const initialSuffix = this.nextGeneratedIdSuffixes.get(generatedId) ?? 1;
4577
+ const maximumSuffix = initialSuffix + this.usedToolCallIds.size;
4578
+ for (let suffix = initialSuffix; suffix <= maximumSuffix; suffix++) {
4579
+ const suffixedId = `${generatedId}-${suffix}`;
4580
+ if (!this.usedToolCallIds.has(suffixedId)) {
4581
+ this.usedToolCallIds.add(suffixedId);
4582
+ this.nextGeneratedIdSuffixes.set(generatedId, suffix + 1);
4583
+ return suffixedId;
4584
+ }
4585
+ }
4586
+ throw new Error("Failed to create a unique tool call ID.");
4587
+ }
4588
+ getNonBlankString(value) {
4589
+ return value != null && value.trim().length > 0 ? value : void 0;
4590
+ }
4349
4591
  processExistingToolCall(toolCall, toolCallDelta) {
4350
4592
  if (toolCall.hasFinished) {
4351
4593
  return;
4352
4594
  }
4353
4595
  if (toolCallDelta.function?.arguments != null) {
4596
+ toolCall.argumentState.append(toolCallDelta.function.arguments);
4354
4597
  toolCall.function.arguments += toolCallDelta.function.arguments;
4355
4598
  this.controller.enqueue({
4356
4599
  type: "tool-input-delta",
@@ -4369,7 +4612,7 @@ var StreamingToolCallTracker = class {
4369
4612
  );
4370
4613
  this.controller.enqueue({
4371
4614
  type: "tool-call",
4372
- toolCallId: toolCall.id ?? this._generateId(),
4615
+ toolCallId: toolCall.id,
4373
4616
  toolName: toolCall.function.name,
4374
4617
  input: toolCall.function.arguments,
4375
4618
  ...providerMetadata ? { providerMetadata } : {}