ai 3.2.18 → 3.2.20

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/dist/index.js CHANGED
@@ -181,6 +181,7 @@ async function embed({
181
181
  abortSignal,
182
182
  headers
183
183
  }) {
184
+ var _a;
184
185
  const retry = retryWithExponentialBackoff({ maxRetries });
185
186
  const modelResponse = await retry(
186
187
  () => model.doEmbed({ values: [value], abortSignal, headers })
@@ -188,6 +189,7 @@ async function embed({
188
189
  return new EmbedResult({
189
190
  value,
190
191
  embedding: modelResponse.embeddings[0],
192
+ usage: (_a = modelResponse.usage) != null ? _a : { tokens: NaN },
191
193
  rawResponse: modelResponse.rawResponse
192
194
  });
193
195
  }
@@ -195,6 +197,7 @@ var EmbedResult = class {
195
197
  constructor(options) {
196
198
  this.value = options.value;
197
199
  this.embedding = options.embedding;
200
+ this.usage = options.usage;
198
201
  this.rawResponse = options.rawResponse;
199
202
  }
200
203
  };
@@ -219,6 +222,7 @@ async function embedMany({
219
222
  abortSignal,
220
223
  headers
221
224
  }) {
225
+ var _a, _b, _c;
222
226
  const retry = retryWithExponentialBackoff({ maxRetries });
223
227
  const maxEmbeddingsPerCall = model.maxEmbeddingsPerCall;
224
228
  if (maxEmbeddingsPerCall == null) {
@@ -227,23 +231,27 @@ async function embedMany({
227
231
  );
228
232
  return new EmbedManyResult({
229
233
  values,
230
- embeddings: modelResponse.embeddings
234
+ embeddings: modelResponse.embeddings,
235
+ usage: (_a = modelResponse.usage) != null ? _a : { tokens: NaN }
231
236
  });
232
237
  }
233
238
  const valueChunks = splitArray(values, maxEmbeddingsPerCall);
234
239
  const embeddings = [];
240
+ let tokens = 0;
235
241
  for (const chunk of valueChunks) {
236
242
  const modelResponse = await retry(
237
243
  () => model.doEmbed({ values: chunk, abortSignal, headers })
238
244
  );
239
245
  embeddings.push(...modelResponse.embeddings);
246
+ tokens += (_c = (_b = modelResponse.usage) == null ? void 0 : _b.tokens) != null ? _c : NaN;
240
247
  }
241
- return new EmbedManyResult({ values, embeddings });
248
+ return new EmbedManyResult({ values, embeddings, usage: { tokens } });
242
249
  }
243
250
  var EmbedManyResult = class {
244
251
  constructor(options) {
245
252
  this.values = options.values;
246
253
  this.embeddings = options.embeddings;
254
+ this.usage = options.usage;
247
255
  }
248
256
  };
249
257
 
@@ -251,15 +259,6 @@ var EmbedManyResult = class {
251
259
  var import_provider5 = require("@ai-sdk/provider");
252
260
  var import_provider_utils4 = require("@ai-sdk/provider-utils");
253
261
 
254
- // core/generate-text/token-usage.ts
255
- function calculateTokenUsage(usage) {
256
- return {
257
- promptTokens: usage.promptTokens,
258
- completionTokens: usage.completionTokens,
259
- totalTokens: usage.promptTokens + usage.completionTokens
260
- };
261
- }
262
-
263
262
  // core/util/detect-image-mimetype.ts
264
263
  var mimeTypeSignatures = [
265
264
  { mimeType: "image/gif", bytes: [71, 73, 70] },
@@ -607,12 +606,31 @@ function prepareCallSettings({
607
606
  };
608
607
  }
609
608
 
609
+ // core/types/token-usage.ts
610
+ function calculateCompletionTokenUsage(usage) {
611
+ return {
612
+ promptTokens: usage.promptTokens,
613
+ completionTokens: usage.completionTokens,
614
+ totalTokens: usage.promptTokens + usage.completionTokens
615
+ };
616
+ }
617
+
610
618
  // core/util/convert-zod-to-json-schema.ts
611
619
  var import_zod_to_json_schema = __toESM(require("zod-to-json-schema"));
612
620
  function convertZodToJSONSchema(zodSchema) {
613
621
  return (0, import_zod_to_json_schema.default)(zodSchema);
614
622
  }
615
623
 
624
+ // core/util/prepare-response-headers.ts
625
+ function prepareResponseHeaders(init, { contentType }) {
626
+ var _a;
627
+ const headers = new Headers((_a = init == null ? void 0 : init.headers) != null ? _a : {});
628
+ if (!headers.has("Content-Type")) {
629
+ headers.set("Content-Type", contentType);
630
+ }
631
+ return headers;
632
+ }
633
+
616
634
  // core/generate-object/inject-json-schema-into-system.ts
617
635
  var DEFAULT_SCHEMA_PREFIX = "JSON schema:";
618
636
  var DEFAULT_SCHEMA_SUFFIX = "You MUST answer with a JSON object that matches the JSON schema above.";
@@ -632,16 +650,6 @@ function injectJsonSchemaIntoSystem({
632
650
  ].filter((line) => line != null).join("\n");
633
651
  }
634
652
 
635
- // core/util/prepare-response-headers.ts
636
- function prepareResponseHeaders(init, { contentType }) {
637
- var _a;
638
- const headers = new Headers((_a = init == null ? void 0 : init.headers) != null ? _a : {});
639
- if (!headers.has("Content-Type")) {
640
- headers.set("Content-Type", contentType);
641
- }
642
- return headers;
643
- }
644
-
645
653
  // core/generate-object/generate-object.ts
646
654
  async function generateObject({
647
655
  model,
@@ -771,7 +779,7 @@ async function generateObject({
771
779
  return new GenerateObjectResult({
772
780
  object: parseResult.value,
773
781
  finishReason,
774
- usage: calculateTokenUsage(usage),
782
+ usage: calculateCompletionTokenUsage(usage),
775
783
  warnings,
776
784
  rawResponse,
777
785
  logprobs
@@ -1014,7 +1022,7 @@ var StreamObjectResult = class {
1014
1022
  textDelta: delta
1015
1023
  });
1016
1024
  }
1017
- usage = calculateTokenUsage(chunk.usage);
1025
+ usage = calculateCompletionTokenUsage(chunk.usage);
1018
1026
  controller.enqueue({ ...chunk, usage });
1019
1027
  resolveUsage(usage);
1020
1028
  const validationResult = (0, import_provider_utils5.safeValidateTypes)({
@@ -1197,6 +1205,157 @@ function prepareToolsAndToolChoice({
1197
1205
  };
1198
1206
  }
1199
1207
 
1208
+ // core/telemetry/get-base-telemetry-attributes.ts
1209
+ function getBaseTelemetryAttributes({
1210
+ operationName,
1211
+ model,
1212
+ settings,
1213
+ telemetry,
1214
+ headers
1215
+ }) {
1216
+ var _a;
1217
+ return {
1218
+ "ai.model.provider": model.provider,
1219
+ "ai.model.id": model.modelId,
1220
+ // settings:
1221
+ ...Object.entries(settings).reduce((attributes, [key, value]) => {
1222
+ attributes[`ai.settings.${key}`] = value;
1223
+ return attributes;
1224
+ }, {}),
1225
+ // special telemetry information
1226
+ "operation.name": operationName,
1227
+ "resource.name": telemetry == null ? void 0 : telemetry.functionId,
1228
+ "ai.telemetry.functionId": telemetry == null ? void 0 : telemetry.functionId,
1229
+ // add metadata as attributes:
1230
+ ...Object.entries((_a = telemetry == null ? void 0 : telemetry.metadata) != null ? _a : {}).reduce(
1231
+ (attributes, [key, value]) => {
1232
+ attributes[`ai.telemetry.metadata.${key}`] = value;
1233
+ return attributes;
1234
+ },
1235
+ {}
1236
+ ),
1237
+ // request headers
1238
+ ...Object.entries(headers != null ? headers : {}).reduce((attributes, [key, value]) => {
1239
+ if (value !== void 0) {
1240
+ attributes[`ai.request.headers.${key}`] = value;
1241
+ }
1242
+ return attributes;
1243
+ }, {})
1244
+ };
1245
+ }
1246
+
1247
+ // core/telemetry/get-tracer.ts
1248
+ var import_api = require("@opentelemetry/api");
1249
+
1250
+ // core/telemetry/noop-tracer.ts
1251
+ var noopTracer = {
1252
+ startSpan() {
1253
+ return noopSpan;
1254
+ },
1255
+ startActiveSpan(name, arg1, arg2, arg3) {
1256
+ if (typeof arg1 === "function") {
1257
+ return arg1(noopSpan);
1258
+ }
1259
+ if (typeof arg2 === "function") {
1260
+ return arg2(noopSpan);
1261
+ }
1262
+ if (typeof arg3 === "function") {
1263
+ return arg3(noopSpan);
1264
+ }
1265
+ }
1266
+ };
1267
+ var noopSpan = {
1268
+ spanContext() {
1269
+ return noopSpanContext;
1270
+ },
1271
+ setAttribute() {
1272
+ return this;
1273
+ },
1274
+ setAttributes() {
1275
+ return this;
1276
+ },
1277
+ addEvent() {
1278
+ return this;
1279
+ },
1280
+ addLink() {
1281
+ return this;
1282
+ },
1283
+ addLinks() {
1284
+ return this;
1285
+ },
1286
+ setStatus() {
1287
+ return this;
1288
+ },
1289
+ updateName() {
1290
+ return this;
1291
+ },
1292
+ end() {
1293
+ return this;
1294
+ },
1295
+ isRecording() {
1296
+ return false;
1297
+ },
1298
+ recordException() {
1299
+ return this;
1300
+ }
1301
+ };
1302
+ var noopSpanContext = {
1303
+ traceId: "",
1304
+ spanId: "",
1305
+ traceFlags: 0
1306
+ };
1307
+
1308
+ // core/telemetry/get-tracer.ts
1309
+ var testTracer = void 0;
1310
+ function getTracer({ isEnabled }) {
1311
+ if (!isEnabled) {
1312
+ return noopTracer;
1313
+ }
1314
+ if (testTracer) {
1315
+ return testTracer;
1316
+ }
1317
+ return import_api.trace.getTracer("ai");
1318
+ }
1319
+
1320
+ // core/telemetry/record-span.ts
1321
+ var import_api2 = require("@opentelemetry/api");
1322
+ function recordSpan({
1323
+ name,
1324
+ tracer,
1325
+ attributes,
1326
+ fn,
1327
+ endWhenDone = true
1328
+ }) {
1329
+ return tracer.startActiveSpan(name, { attributes }, async (span) => {
1330
+ try {
1331
+ const result = await fn(span);
1332
+ if (endWhenDone) {
1333
+ span.end();
1334
+ }
1335
+ return result;
1336
+ } catch (error) {
1337
+ try {
1338
+ if (error instanceof Error) {
1339
+ span.recordException({
1340
+ name: error.name,
1341
+ message: error.message,
1342
+ stack: error.stack
1343
+ });
1344
+ span.setStatus({
1345
+ code: import_api2.SpanStatusCode.ERROR,
1346
+ message: error.message
1347
+ });
1348
+ } else {
1349
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
1350
+ }
1351
+ } finally {
1352
+ span.end();
1353
+ }
1354
+ throw error;
1355
+ }
1356
+ });
1357
+ }
1358
+
1200
1359
  // core/generate-text/tool-call.ts
1201
1360
  var import_provider6 = require("@ai-sdk/provider");
1202
1361
  var import_provider_utils6 = require("@ai-sdk/provider-utils");
@@ -1247,71 +1406,128 @@ async function generateText({
1247
1406
  headers,
1248
1407
  maxAutomaticRoundtrips = 0,
1249
1408
  maxToolRoundtrips = maxAutomaticRoundtrips,
1409
+ experimental_telemetry: telemetry,
1250
1410
  ...settings
1251
1411
  }) {
1252
- var _a, _b, _c;
1253
- const retry = retryWithExponentialBackoff({ maxRetries });
1254
- const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1255
- const mode = {
1256
- type: "regular",
1257
- ...prepareToolsAndToolChoice({ tools, toolChoice })
1258
- };
1259
- const callSettings = prepareCallSettings(settings);
1260
- const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1261
- let currentModelResponse;
1262
- let currentToolCalls = [];
1263
- let currentToolResults = [];
1264
- let roundtrips = 0;
1265
- const responseMessages = [];
1266
- do {
1267
- currentModelResponse = await retry(() => {
1268
- return model.doGenerate({
1269
- mode,
1270
- ...callSettings,
1271
- // once we have a roundtrip, we need to switch to messages format:
1272
- inputFormat: roundtrips === 0 ? validatedPrompt.type : "messages",
1273
- prompt: promptMessages,
1274
- abortSignal,
1275
- headers
1412
+ var _a;
1413
+ const baseTelemetryAttributes = getBaseTelemetryAttributes({
1414
+ operationName: "ai.generateText",
1415
+ model,
1416
+ telemetry,
1417
+ headers,
1418
+ settings: { ...settings, maxRetries }
1419
+ });
1420
+ const tracer = getTracer({ isEnabled: (_a = telemetry == null ? void 0 : telemetry.isEnabled) != null ? _a : false });
1421
+ return recordSpan({
1422
+ name: "ai.generateText",
1423
+ attributes: {
1424
+ ...baseTelemetryAttributes,
1425
+ // specific settings that only make sense on the outer level:
1426
+ "ai.prompt": JSON.stringify({ system, prompt, messages }),
1427
+ "ai.settings.maxToolRoundtrips": maxToolRoundtrips
1428
+ },
1429
+ tracer,
1430
+ fn: async (span) => {
1431
+ var _a2, _b, _c;
1432
+ const retry = retryWithExponentialBackoff({ maxRetries });
1433
+ const validatedPrompt = getValidatedPrompt({
1434
+ system,
1435
+ prompt,
1436
+ messages
1276
1437
  });
1277
- });
1278
- currentToolCalls = ((_a = currentModelResponse.toolCalls) != null ? _a : []).map(
1279
- (modelToolCall) => parseToolCall({ toolCall: modelToolCall, tools })
1280
- );
1281
- currentToolResults = tools == null ? [] : await executeTools({ toolCalls: currentToolCalls, tools });
1282
- const newResponseMessages = toResponseMessages({
1283
- text: (_b = currentModelResponse.text) != null ? _b : "",
1284
- toolCalls: currentToolCalls,
1285
- toolResults: currentToolResults
1286
- });
1287
- responseMessages.push(...newResponseMessages);
1288
- promptMessages.push(
1289
- ...newResponseMessages.map(convertToLanguageModelMessage)
1290
- );
1291
- } while (
1292
- // there are tool calls:
1293
- currentToolCalls.length > 0 && // all current tool calls have results:
1294
- currentToolResults.length === currentToolCalls.length && // the number of roundtrips is less than the maximum:
1295
- roundtrips++ < maxToolRoundtrips
1296
- );
1297
- return new GenerateTextResult({
1298
- // Always return a string so that the caller doesn't have to check for undefined.
1299
- // If they need to check if the model did not return any text,
1300
- // they can check the length of the string:
1301
- text: (_c = currentModelResponse.text) != null ? _c : "",
1302
- toolCalls: currentToolCalls,
1303
- toolResults: currentToolResults,
1304
- finishReason: currentModelResponse.finishReason,
1305
- usage: calculateTokenUsage(currentModelResponse.usage),
1306
- warnings: currentModelResponse.warnings,
1307
- rawResponse: currentModelResponse.rawResponse,
1308
- logprobs: currentModelResponse.logprobs,
1309
- responseMessages
1438
+ const mode = {
1439
+ type: "regular",
1440
+ ...prepareToolsAndToolChoice({ tools, toolChoice })
1441
+ };
1442
+ const callSettings = prepareCallSettings(settings);
1443
+ const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1444
+ let currentModelResponse;
1445
+ let currentToolCalls = [];
1446
+ let currentToolResults = [];
1447
+ let roundtrips = 0;
1448
+ const responseMessages = [];
1449
+ do {
1450
+ const currentInputFormat = roundtrips === 0 ? validatedPrompt.type : "messages";
1451
+ currentModelResponse = await retry(
1452
+ () => recordSpan({
1453
+ name: "ai.generateText.doGenerate",
1454
+ attributes: {
1455
+ ...baseTelemetryAttributes,
1456
+ "ai.prompt.format": currentInputFormat,
1457
+ "ai.prompt.messages": JSON.stringify(promptMessages)
1458
+ },
1459
+ tracer,
1460
+ fn: async (span2) => {
1461
+ const result = await model.doGenerate({
1462
+ mode,
1463
+ ...callSettings,
1464
+ inputFormat: currentInputFormat,
1465
+ prompt: promptMessages,
1466
+ abortSignal,
1467
+ headers
1468
+ });
1469
+ span2.setAttributes({
1470
+ "ai.finishReason": result.finishReason,
1471
+ "ai.usage.promptTokens": result.usage.promptTokens,
1472
+ "ai.usage.completionTokens": result.usage.completionTokens,
1473
+ "ai.result.text": result.text,
1474
+ "ai.result.toolCalls": JSON.stringify(result.toolCalls)
1475
+ });
1476
+ return result;
1477
+ }
1478
+ })
1479
+ );
1480
+ currentToolCalls = ((_a2 = currentModelResponse.toolCalls) != null ? _a2 : []).map(
1481
+ (modelToolCall) => parseToolCall({ toolCall: modelToolCall, tools })
1482
+ );
1483
+ currentToolResults = tools == null ? [] : await executeTools({
1484
+ toolCalls: currentToolCalls,
1485
+ tools,
1486
+ tracer
1487
+ });
1488
+ const newResponseMessages = toResponseMessages({
1489
+ text: (_b = currentModelResponse.text) != null ? _b : "",
1490
+ toolCalls: currentToolCalls,
1491
+ toolResults: currentToolResults
1492
+ });
1493
+ responseMessages.push(...newResponseMessages);
1494
+ promptMessages.push(
1495
+ ...newResponseMessages.map(convertToLanguageModelMessage)
1496
+ );
1497
+ } while (
1498
+ // there are tool calls:
1499
+ currentToolCalls.length > 0 && // all current tool calls have results:
1500
+ currentToolResults.length === currentToolCalls.length && // the number of roundtrips is less than the maximum:
1501
+ roundtrips++ < maxToolRoundtrips
1502
+ );
1503
+ span.setAttributes({
1504
+ "ai.finishReason": currentModelResponse.finishReason,
1505
+ "ai.usage.promptTokens": currentModelResponse.usage.promptTokens,
1506
+ "ai.usage.completionTokens": currentModelResponse.usage.completionTokens,
1507
+ "ai.result.text": currentModelResponse.text,
1508
+ "ai.result.toolCalls": JSON.stringify(currentModelResponse.toolCalls)
1509
+ });
1510
+ return new GenerateTextResult({
1511
+ // Always return a string so that the caller doesn't have to check for undefined.
1512
+ // If they need to check if the model did not return any text,
1513
+ // they can check the length of the string:
1514
+ text: (_c = currentModelResponse.text) != null ? _c : "",
1515
+ toolCalls: currentToolCalls,
1516
+ toolResults: currentToolResults,
1517
+ finishReason: currentModelResponse.finishReason,
1518
+ usage: calculateCompletionTokenUsage(currentModelResponse.usage),
1519
+ warnings: currentModelResponse.warnings,
1520
+ rawResponse: currentModelResponse.rawResponse,
1521
+ logprobs: currentModelResponse.logprobs,
1522
+ responseMessages
1523
+ });
1524
+ }
1310
1525
  });
1311
1526
  }
1312
1527
  async function executeTools({
1313
1528
  toolCalls,
1314
- tools
1529
+ tools,
1530
+ tracer
1315
1531
  }) {
1316
1532
  const toolResults = await Promise.all(
1317
1533
  toolCalls.map(async (toolCall) => {
@@ -1319,7 +1535,25 @@ async function executeTools({
1319
1535
  if ((tool2 == null ? void 0 : tool2.execute) == null) {
1320
1536
  return void 0;
1321
1537
  }
1322
- const result = await tool2.execute(toolCall.args);
1538
+ const result = await recordSpan({
1539
+ name: "ai.toolCall",
1540
+ attributes: {
1541
+ "ai.toolCall.name": toolCall.toolName,
1542
+ "ai.toolCall.id": toolCall.toolCallId,
1543
+ "ai.toolCall.args": JSON.stringify(toolCall.args)
1544
+ },
1545
+ tracer,
1546
+ fn: async (span) => {
1547
+ const result2 = await tool2.execute(toolCall.args);
1548
+ try {
1549
+ span.setAttributes({
1550
+ "ai.toolCall.result": JSON.stringify(result2)
1551
+ });
1552
+ } catch (ignored) {
1553
+ }
1554
+ return result2;
1555
+ }
1556
+ });
1323
1557
  return {
1324
1558
  toolCallId: toolCall.toolCallId,
1325
1559
  toolName: toolCall.toolName,
@@ -1375,7 +1609,8 @@ var import_provider7 = require("@ai-sdk/provider");
1375
1609
  var import_ui_utils2 = require("@ai-sdk/ui-utils");
1376
1610
  function runToolsTransformation({
1377
1611
  tools,
1378
- generatorStream
1612
+ generatorStream,
1613
+ tracer
1379
1614
  }) {
1380
1615
  let canClose = false;
1381
1616
  const outstandingToolCalls = /* @__PURE__ */ new Set();
@@ -1423,29 +1658,44 @@ function runToolsTransformation({
1423
1658
  if (tool2.execute != null) {
1424
1659
  const toolExecutionId = (0, import_ui_utils2.generateId)();
1425
1660
  outstandingToolCalls.add(toolExecutionId);
1426
- tool2.execute(toolCall.args).then(
1427
- (result) => {
1428
- toolResultsStreamController.enqueue({
1429
- ...toolCall,
1430
- type: "tool-result",
1431
- result
1432
- });
1433
- outstandingToolCalls.delete(toolExecutionId);
1434
- if (canClose && outstandingToolCalls.size === 0) {
1435
- toolResultsStreamController.close();
1436
- }
1661
+ recordSpan({
1662
+ name: "ai.toolCall",
1663
+ attributes: {
1664
+ "ai.toolCall.name": toolCall.toolName,
1665
+ "ai.toolCall.id": toolCall.toolCallId,
1666
+ "ai.toolCall.args": JSON.stringify(toolCall.args)
1437
1667
  },
1438
- (error) => {
1439
- toolResultsStreamController.enqueue({
1440
- type: "error",
1441
- error
1442
- });
1443
- outstandingToolCalls.delete(toolExecutionId);
1444
- if (canClose && outstandingToolCalls.size === 0) {
1445
- toolResultsStreamController.close();
1668
+ tracer,
1669
+ fn: async (span) => tool2.execute(toolCall.args).then(
1670
+ (result) => {
1671
+ toolResultsStreamController.enqueue({
1672
+ ...toolCall,
1673
+ type: "tool-result",
1674
+ result
1675
+ });
1676
+ outstandingToolCalls.delete(toolExecutionId);
1677
+ if (canClose && outstandingToolCalls.size === 0) {
1678
+ toolResultsStreamController.close();
1679
+ }
1680
+ try {
1681
+ span.setAttributes({
1682
+ "ai.toolCall.result": JSON.stringify(result)
1683
+ });
1684
+ } catch (ignored) {
1685
+ }
1686
+ },
1687
+ (error) => {
1688
+ toolResultsStreamController.enqueue({
1689
+ type: "error",
1690
+ error
1691
+ });
1692
+ outstandingToolCalls.delete(toolExecutionId);
1693
+ if (canClose && outstandingToolCalls.size === 0) {
1694
+ toolResultsStreamController.close();
1695
+ }
1446
1696
  }
1447
- }
1448
- );
1697
+ )
1698
+ });
1449
1699
  }
1450
1700
  } catch (error) {
1451
1701
  toolResultsStreamController.enqueue({
@@ -1460,7 +1710,7 @@ function runToolsTransformation({
1460
1710
  type: "finish",
1461
1711
  finishReason: chunk.finishReason,
1462
1712
  logprobs: chunk.logprobs,
1463
- usage: calculateTokenUsage(chunk.usage)
1713
+ usage: calculateCompletionTokenUsage(chunk.usage)
1464
1714
  });
1465
1715
  break;
1466
1716
  }
@@ -1518,32 +1768,76 @@ async function streamText({
1518
1768
  maxRetries,
1519
1769
  abortSignal,
1520
1770
  headers,
1771
+ experimental_telemetry: telemetry,
1521
1772
  onFinish,
1522
1773
  ...settings
1523
1774
  }) {
1524
- const retry = retryWithExponentialBackoff({ maxRetries });
1525
- const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1526
- const { stream, warnings, rawResponse } = await retry(
1527
- () => model.doStream({
1528
- mode: {
1529
- type: "regular",
1530
- ...prepareToolsAndToolChoice({ tools, toolChoice })
1531
- },
1532
- ...prepareCallSettings(settings),
1533
- inputFormat: validatedPrompt.type,
1534
- prompt: convertToLanguageModelPrompt(validatedPrompt),
1535
- abortSignal,
1536
- headers
1537
- })
1538
- );
1539
- return new StreamTextResult({
1540
- stream: runToolsTransformation({
1541
- tools,
1542
- generatorStream: stream
1543
- }),
1544
- warnings,
1545
- rawResponse,
1546
- onFinish
1775
+ var _a;
1776
+ const baseTelemetryAttributes = getBaseTelemetryAttributes({
1777
+ operationName: "ai.streamText",
1778
+ model,
1779
+ telemetry,
1780
+ headers,
1781
+ settings: { ...settings, maxRetries }
1782
+ });
1783
+ const tracer = getTracer({ isEnabled: (_a = telemetry == null ? void 0 : telemetry.isEnabled) != null ? _a : false });
1784
+ return recordSpan({
1785
+ name: "ai.streamText",
1786
+ attributes: {
1787
+ ...baseTelemetryAttributes,
1788
+ // specific settings that only make sense on the outer level:
1789
+ "ai.prompt": JSON.stringify({ system, prompt, messages })
1790
+ },
1791
+ tracer,
1792
+ endWhenDone: false,
1793
+ fn: async (rootSpan) => {
1794
+ const retry = retryWithExponentialBackoff({ maxRetries });
1795
+ const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1796
+ const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1797
+ const {
1798
+ result: { stream, warnings, rawResponse },
1799
+ doStreamSpan
1800
+ } = await retry(
1801
+ () => recordSpan({
1802
+ name: "ai.streamText.doStream",
1803
+ attributes: {
1804
+ ...baseTelemetryAttributes,
1805
+ "ai.prompt.format": validatedPrompt.type,
1806
+ "ai.prompt.messages": JSON.stringify(promptMessages)
1807
+ },
1808
+ tracer,
1809
+ endWhenDone: false,
1810
+ fn: async (doStreamSpan2) => {
1811
+ return {
1812
+ result: await model.doStream({
1813
+ mode: {
1814
+ type: "regular",
1815
+ ...prepareToolsAndToolChoice({ tools, toolChoice })
1816
+ },
1817
+ ...prepareCallSettings(settings),
1818
+ inputFormat: validatedPrompt.type,
1819
+ prompt: promptMessages,
1820
+ abortSignal,
1821
+ headers
1822
+ }),
1823
+ doStreamSpan: doStreamSpan2
1824
+ };
1825
+ }
1826
+ })
1827
+ );
1828
+ return new StreamTextResult({
1829
+ stream: runToolsTransformation({
1830
+ tools,
1831
+ generatorStream: stream,
1832
+ tracer
1833
+ }),
1834
+ warnings,
1835
+ rawResponse,
1836
+ onFinish,
1837
+ rootSpan,
1838
+ doStreamSpan
1839
+ });
1840
+ }
1547
1841
  });
1548
1842
  }
1549
1843
  var StreamTextResult = class {
@@ -1551,7 +1845,9 @@ var StreamTextResult = class {
1551
1845
  stream,
1552
1846
  warnings,
1553
1847
  rawResponse,
1554
- onFinish
1848
+ onFinish,
1849
+ rootSpan,
1850
+ doStreamSpan
1555
1851
  }) {
1556
1852
  this.warnings = warnings;
1557
1853
  this.rawResponse = rawResponse;
@@ -1581,41 +1877,73 @@ var StreamTextResult = class {
1581
1877
  let text = "";
1582
1878
  const toolCalls = [];
1583
1879
  const toolResults = [];
1880
+ let firstChunk = true;
1584
1881
  const self = this;
1585
1882
  this.originalStream = stream.pipeThrough(
1586
1883
  new TransformStream({
1587
1884
  async transform(chunk, controller) {
1588
1885
  controller.enqueue(chunk);
1589
- if (chunk.type === "text-delta") {
1590
- text += chunk.textDelta;
1886
+ if (firstChunk) {
1887
+ firstChunk = false;
1888
+ doStreamSpan.addEvent("ai.stream.firstChunk");
1591
1889
  }
1592
- if (chunk.type === "tool-call") {
1593
- toolCalls.push(chunk);
1594
- }
1595
- if (chunk.type === "tool-result") {
1596
- toolResults.push(chunk);
1597
- }
1598
- if (chunk.type === "finish") {
1599
- usage = chunk.usage;
1600
- finishReason = chunk.finishReason;
1601
- resolveUsage(usage);
1602
- resolveFinishReason(finishReason);
1603
- resolveText(text);
1604
- resolveToolCalls(toolCalls);
1890
+ const chunkType = chunk.type;
1891
+ switch (chunkType) {
1892
+ case "text-delta":
1893
+ text += chunk.textDelta;
1894
+ break;
1895
+ case "tool-call":
1896
+ toolCalls.push(chunk);
1897
+ break;
1898
+ case "tool-result":
1899
+ toolResults.push(chunk);
1900
+ break;
1901
+ case "finish":
1902
+ usage = chunk.usage;
1903
+ finishReason = chunk.finishReason;
1904
+ resolveUsage(usage);
1905
+ resolveFinishReason(finishReason);
1906
+ resolveText(text);
1907
+ resolveToolCalls(toolCalls);
1908
+ break;
1909
+ case "error":
1910
+ break;
1911
+ default: {
1912
+ const exhaustiveCheck = chunkType;
1913
+ throw new Error(`Unknown chunk type: ${exhaustiveCheck}`);
1914
+ }
1605
1915
  }
1606
1916
  },
1607
1917
  // invoke onFinish callback and resolve toolResults promise when the stream is about to close:
1608
1918
  async flush(controller) {
1609
1919
  var _a;
1610
1920
  try {
1921
+ const finalUsage = usage != null ? usage : {
1922
+ promptTokens: NaN,
1923
+ completionTokens: NaN,
1924
+ totalTokens: NaN
1925
+ };
1926
+ const finalFinishReason = finishReason != null ? finishReason : "unknown";
1927
+ const telemetryToolCalls = toolCalls.length > 0 ? JSON.stringify(toolCalls) : void 0;
1928
+ doStreamSpan.setAttributes({
1929
+ "ai.finishReason": finalFinishReason,
1930
+ "ai.usage.promptTokens": finalUsage.promptTokens,
1931
+ "ai.usage.completionTokens": finalUsage.completionTokens,
1932
+ "ai.result.text": text,
1933
+ "ai.result.toolCalls": telemetryToolCalls
1934
+ });
1935
+ doStreamSpan.end();
1936
+ rootSpan.setAttributes({
1937
+ "ai.finishReason": finalFinishReason,
1938
+ "ai.usage.promptTokens": finalUsage.promptTokens,
1939
+ "ai.usage.completionTokens": finalUsage.completionTokens,
1940
+ "ai.result.text": text,
1941
+ "ai.result.toolCalls": telemetryToolCalls
1942
+ });
1611
1943
  resolveToolResults(toolResults);
1612
1944
  await ((_a = self.onFinish) == null ? void 0 : _a.call(self, {
1613
- finishReason: finishReason != null ? finishReason : "unknown",
1614
- usage: usage != null ? usage : {
1615
- promptTokens: NaN,
1616
- completionTokens: NaN,
1617
- totalTokens: NaN
1618
- },
1945
+ finishReason: finalFinishReason,
1946
+ usage: finalUsage,
1619
1947
  text,
1620
1948
  toolCalls,
1621
1949
  // The tool results are inferred as a never[] type, because they are
@@ -1628,6 +1956,8 @@ var StreamTextResult = class {
1628
1956
  }));
1629
1957
  } catch (error) {
1630
1958
  controller.error(error);
1959
+ } finally {
1960
+ rootSpan.end();
1631
1961
  }
1632
1962
  }
1633
1963
  })