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.mjs CHANGED
@@ -83,6 +83,7 @@ async function embed({
83
83
  abortSignal,
84
84
  headers
85
85
  }) {
86
+ var _a;
86
87
  const retry = retryWithExponentialBackoff({ maxRetries });
87
88
  const modelResponse = await retry(
88
89
  () => model.doEmbed({ values: [value], abortSignal, headers })
@@ -90,6 +91,7 @@ async function embed({
90
91
  return new EmbedResult({
91
92
  value,
92
93
  embedding: modelResponse.embeddings[0],
94
+ usage: (_a = modelResponse.usage) != null ? _a : { tokens: NaN },
93
95
  rawResponse: modelResponse.rawResponse
94
96
  });
95
97
  }
@@ -97,6 +99,7 @@ var EmbedResult = class {
97
99
  constructor(options) {
98
100
  this.value = options.value;
99
101
  this.embedding = options.embedding;
102
+ this.usage = options.usage;
100
103
  this.rawResponse = options.rawResponse;
101
104
  }
102
105
  };
@@ -121,6 +124,7 @@ async function embedMany({
121
124
  abortSignal,
122
125
  headers
123
126
  }) {
127
+ var _a, _b, _c;
124
128
  const retry = retryWithExponentialBackoff({ maxRetries });
125
129
  const maxEmbeddingsPerCall = model.maxEmbeddingsPerCall;
126
130
  if (maxEmbeddingsPerCall == null) {
@@ -129,23 +133,27 @@ async function embedMany({
129
133
  );
130
134
  return new EmbedManyResult({
131
135
  values,
132
- embeddings: modelResponse.embeddings
136
+ embeddings: modelResponse.embeddings,
137
+ usage: (_a = modelResponse.usage) != null ? _a : { tokens: NaN }
133
138
  });
134
139
  }
135
140
  const valueChunks = splitArray(values, maxEmbeddingsPerCall);
136
141
  const embeddings = [];
142
+ let tokens = 0;
137
143
  for (const chunk of valueChunks) {
138
144
  const modelResponse = await retry(
139
145
  () => model.doEmbed({ values: chunk, abortSignal, headers })
140
146
  );
141
147
  embeddings.push(...modelResponse.embeddings);
148
+ tokens += (_c = (_b = modelResponse.usage) == null ? void 0 : _b.tokens) != null ? _c : NaN;
142
149
  }
143
- return new EmbedManyResult({ values, embeddings });
150
+ return new EmbedManyResult({ values, embeddings, usage: { tokens } });
144
151
  }
145
152
  var EmbedManyResult = class {
146
153
  constructor(options) {
147
154
  this.values = options.values;
148
155
  this.embeddings = options.embeddings;
156
+ this.usage = options.usage;
149
157
  }
150
158
  };
151
159
 
@@ -153,15 +161,6 @@ var EmbedManyResult = class {
153
161
  import { NoObjectGeneratedError } from "@ai-sdk/provider";
154
162
  import { safeParseJSON } from "@ai-sdk/provider-utils";
155
163
 
156
- // core/generate-text/token-usage.ts
157
- function calculateTokenUsage(usage) {
158
- return {
159
- promptTokens: usage.promptTokens,
160
- completionTokens: usage.completionTokens,
161
- totalTokens: usage.promptTokens + usage.completionTokens
162
- };
163
- }
164
-
165
164
  // core/util/detect-image-mimetype.ts
166
165
  var mimeTypeSignatures = [
167
166
  { mimeType: "image/gif", bytes: [71, 73, 70] },
@@ -512,12 +511,31 @@ function prepareCallSettings({
512
511
  };
513
512
  }
514
513
 
514
+ // core/types/token-usage.ts
515
+ function calculateCompletionTokenUsage(usage) {
516
+ return {
517
+ promptTokens: usage.promptTokens,
518
+ completionTokens: usage.completionTokens,
519
+ totalTokens: usage.promptTokens + usage.completionTokens
520
+ };
521
+ }
522
+
515
523
  // core/util/convert-zod-to-json-schema.ts
516
524
  import zodToJsonSchema from "zod-to-json-schema";
517
525
  function convertZodToJSONSchema(zodSchema) {
518
526
  return zodToJsonSchema(zodSchema);
519
527
  }
520
528
 
529
+ // core/util/prepare-response-headers.ts
530
+ function prepareResponseHeaders(init, { contentType }) {
531
+ var _a;
532
+ const headers = new Headers((_a = init == null ? void 0 : init.headers) != null ? _a : {});
533
+ if (!headers.has("Content-Type")) {
534
+ headers.set("Content-Type", contentType);
535
+ }
536
+ return headers;
537
+ }
538
+
521
539
  // core/generate-object/inject-json-schema-into-system.ts
522
540
  var DEFAULT_SCHEMA_PREFIX = "JSON schema:";
523
541
  var DEFAULT_SCHEMA_SUFFIX = "You MUST answer with a JSON object that matches the JSON schema above.";
@@ -537,16 +555,6 @@ function injectJsonSchemaIntoSystem({
537
555
  ].filter((line) => line != null).join("\n");
538
556
  }
539
557
 
540
- // core/util/prepare-response-headers.ts
541
- function prepareResponseHeaders(init, { contentType }) {
542
- var _a;
543
- const headers = new Headers((_a = init == null ? void 0 : init.headers) != null ? _a : {});
544
- if (!headers.has("Content-Type")) {
545
- headers.set("Content-Type", contentType);
546
- }
547
- return headers;
548
- }
549
-
550
558
  // core/generate-object/generate-object.ts
551
559
  async function generateObject({
552
560
  model,
@@ -676,7 +684,7 @@ async function generateObject({
676
684
  return new GenerateObjectResult({
677
685
  object: parseResult.value,
678
686
  finishReason,
679
- usage: calculateTokenUsage(usage),
687
+ usage: calculateCompletionTokenUsage(usage),
680
688
  warnings,
681
689
  rawResponse,
682
690
  logprobs
@@ -922,7 +930,7 @@ var StreamObjectResult = class {
922
930
  textDelta: delta
923
931
  });
924
932
  }
925
- usage = calculateTokenUsage(chunk.usage);
933
+ usage = calculateCompletionTokenUsage(chunk.usage);
926
934
  controller.enqueue({ ...chunk, usage });
927
935
  resolveUsage(usage);
928
936
  const validationResult = safeValidateTypes({
@@ -1105,6 +1113,157 @@ function prepareToolsAndToolChoice({
1105
1113
  };
1106
1114
  }
1107
1115
 
1116
+ // core/telemetry/get-base-telemetry-attributes.ts
1117
+ function getBaseTelemetryAttributes({
1118
+ operationName,
1119
+ model,
1120
+ settings,
1121
+ telemetry,
1122
+ headers
1123
+ }) {
1124
+ var _a;
1125
+ return {
1126
+ "ai.model.provider": model.provider,
1127
+ "ai.model.id": model.modelId,
1128
+ // settings:
1129
+ ...Object.entries(settings).reduce((attributes, [key, value]) => {
1130
+ attributes[`ai.settings.${key}`] = value;
1131
+ return attributes;
1132
+ }, {}),
1133
+ // special telemetry information
1134
+ "operation.name": operationName,
1135
+ "resource.name": telemetry == null ? void 0 : telemetry.functionId,
1136
+ "ai.telemetry.functionId": telemetry == null ? void 0 : telemetry.functionId,
1137
+ // add metadata as attributes:
1138
+ ...Object.entries((_a = telemetry == null ? void 0 : telemetry.metadata) != null ? _a : {}).reduce(
1139
+ (attributes, [key, value]) => {
1140
+ attributes[`ai.telemetry.metadata.${key}`] = value;
1141
+ return attributes;
1142
+ },
1143
+ {}
1144
+ ),
1145
+ // request headers
1146
+ ...Object.entries(headers != null ? headers : {}).reduce((attributes, [key, value]) => {
1147
+ if (value !== void 0) {
1148
+ attributes[`ai.request.headers.${key}`] = value;
1149
+ }
1150
+ return attributes;
1151
+ }, {})
1152
+ };
1153
+ }
1154
+
1155
+ // core/telemetry/get-tracer.ts
1156
+ import { trace } from "@opentelemetry/api";
1157
+
1158
+ // core/telemetry/noop-tracer.ts
1159
+ var noopTracer = {
1160
+ startSpan() {
1161
+ return noopSpan;
1162
+ },
1163
+ startActiveSpan(name, arg1, arg2, arg3) {
1164
+ if (typeof arg1 === "function") {
1165
+ return arg1(noopSpan);
1166
+ }
1167
+ if (typeof arg2 === "function") {
1168
+ return arg2(noopSpan);
1169
+ }
1170
+ if (typeof arg3 === "function") {
1171
+ return arg3(noopSpan);
1172
+ }
1173
+ }
1174
+ };
1175
+ var noopSpan = {
1176
+ spanContext() {
1177
+ return noopSpanContext;
1178
+ },
1179
+ setAttribute() {
1180
+ return this;
1181
+ },
1182
+ setAttributes() {
1183
+ return this;
1184
+ },
1185
+ addEvent() {
1186
+ return this;
1187
+ },
1188
+ addLink() {
1189
+ return this;
1190
+ },
1191
+ addLinks() {
1192
+ return this;
1193
+ },
1194
+ setStatus() {
1195
+ return this;
1196
+ },
1197
+ updateName() {
1198
+ return this;
1199
+ },
1200
+ end() {
1201
+ return this;
1202
+ },
1203
+ isRecording() {
1204
+ return false;
1205
+ },
1206
+ recordException() {
1207
+ return this;
1208
+ }
1209
+ };
1210
+ var noopSpanContext = {
1211
+ traceId: "",
1212
+ spanId: "",
1213
+ traceFlags: 0
1214
+ };
1215
+
1216
+ // core/telemetry/get-tracer.ts
1217
+ var testTracer = void 0;
1218
+ function getTracer({ isEnabled }) {
1219
+ if (!isEnabled) {
1220
+ return noopTracer;
1221
+ }
1222
+ if (testTracer) {
1223
+ return testTracer;
1224
+ }
1225
+ return trace.getTracer("ai");
1226
+ }
1227
+
1228
+ // core/telemetry/record-span.ts
1229
+ import { SpanStatusCode } from "@opentelemetry/api";
1230
+ function recordSpan({
1231
+ name,
1232
+ tracer,
1233
+ attributes,
1234
+ fn,
1235
+ endWhenDone = true
1236
+ }) {
1237
+ return tracer.startActiveSpan(name, { attributes }, async (span) => {
1238
+ try {
1239
+ const result = await fn(span);
1240
+ if (endWhenDone) {
1241
+ span.end();
1242
+ }
1243
+ return result;
1244
+ } catch (error) {
1245
+ try {
1246
+ if (error instanceof Error) {
1247
+ span.recordException({
1248
+ name: error.name,
1249
+ message: error.message,
1250
+ stack: error.stack
1251
+ });
1252
+ span.setStatus({
1253
+ code: SpanStatusCode.ERROR,
1254
+ message: error.message
1255
+ });
1256
+ } else {
1257
+ span.setStatus({ code: SpanStatusCode.ERROR });
1258
+ }
1259
+ } finally {
1260
+ span.end();
1261
+ }
1262
+ throw error;
1263
+ }
1264
+ });
1265
+ }
1266
+
1108
1267
  // core/generate-text/tool-call.ts
1109
1268
  import {
1110
1269
  InvalidToolArgumentsError,
@@ -1158,71 +1317,128 @@ async function generateText({
1158
1317
  headers,
1159
1318
  maxAutomaticRoundtrips = 0,
1160
1319
  maxToolRoundtrips = maxAutomaticRoundtrips,
1320
+ experimental_telemetry: telemetry,
1161
1321
  ...settings
1162
1322
  }) {
1163
- var _a, _b, _c;
1164
- const retry = retryWithExponentialBackoff({ maxRetries });
1165
- const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1166
- const mode = {
1167
- type: "regular",
1168
- ...prepareToolsAndToolChoice({ tools, toolChoice })
1169
- };
1170
- const callSettings = prepareCallSettings(settings);
1171
- const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1172
- let currentModelResponse;
1173
- let currentToolCalls = [];
1174
- let currentToolResults = [];
1175
- let roundtrips = 0;
1176
- const responseMessages = [];
1177
- do {
1178
- currentModelResponse = await retry(() => {
1179
- return model.doGenerate({
1180
- mode,
1181
- ...callSettings,
1182
- // once we have a roundtrip, we need to switch to messages format:
1183
- inputFormat: roundtrips === 0 ? validatedPrompt.type : "messages",
1184
- prompt: promptMessages,
1185
- abortSignal,
1186
- headers
1323
+ var _a;
1324
+ const baseTelemetryAttributes = getBaseTelemetryAttributes({
1325
+ operationName: "ai.generateText",
1326
+ model,
1327
+ telemetry,
1328
+ headers,
1329
+ settings: { ...settings, maxRetries }
1330
+ });
1331
+ const tracer = getTracer({ isEnabled: (_a = telemetry == null ? void 0 : telemetry.isEnabled) != null ? _a : false });
1332
+ return recordSpan({
1333
+ name: "ai.generateText",
1334
+ attributes: {
1335
+ ...baseTelemetryAttributes,
1336
+ // specific settings that only make sense on the outer level:
1337
+ "ai.prompt": JSON.stringify({ system, prompt, messages }),
1338
+ "ai.settings.maxToolRoundtrips": maxToolRoundtrips
1339
+ },
1340
+ tracer,
1341
+ fn: async (span) => {
1342
+ var _a2, _b, _c;
1343
+ const retry = retryWithExponentialBackoff({ maxRetries });
1344
+ const validatedPrompt = getValidatedPrompt({
1345
+ system,
1346
+ prompt,
1347
+ messages
1187
1348
  });
1188
- });
1189
- currentToolCalls = ((_a = currentModelResponse.toolCalls) != null ? _a : []).map(
1190
- (modelToolCall) => parseToolCall({ toolCall: modelToolCall, tools })
1191
- );
1192
- currentToolResults = tools == null ? [] : await executeTools({ toolCalls: currentToolCalls, tools });
1193
- const newResponseMessages = toResponseMessages({
1194
- text: (_b = currentModelResponse.text) != null ? _b : "",
1195
- toolCalls: currentToolCalls,
1196
- toolResults: currentToolResults
1197
- });
1198
- responseMessages.push(...newResponseMessages);
1199
- promptMessages.push(
1200
- ...newResponseMessages.map(convertToLanguageModelMessage)
1201
- );
1202
- } while (
1203
- // there are tool calls:
1204
- currentToolCalls.length > 0 && // all current tool calls have results:
1205
- currentToolResults.length === currentToolCalls.length && // the number of roundtrips is less than the maximum:
1206
- roundtrips++ < maxToolRoundtrips
1207
- );
1208
- return new GenerateTextResult({
1209
- // Always return a string so that the caller doesn't have to check for undefined.
1210
- // If they need to check if the model did not return any text,
1211
- // they can check the length of the string:
1212
- text: (_c = currentModelResponse.text) != null ? _c : "",
1213
- toolCalls: currentToolCalls,
1214
- toolResults: currentToolResults,
1215
- finishReason: currentModelResponse.finishReason,
1216
- usage: calculateTokenUsage(currentModelResponse.usage),
1217
- warnings: currentModelResponse.warnings,
1218
- rawResponse: currentModelResponse.rawResponse,
1219
- logprobs: currentModelResponse.logprobs,
1220
- responseMessages
1349
+ const mode = {
1350
+ type: "regular",
1351
+ ...prepareToolsAndToolChoice({ tools, toolChoice })
1352
+ };
1353
+ const callSettings = prepareCallSettings(settings);
1354
+ const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1355
+ let currentModelResponse;
1356
+ let currentToolCalls = [];
1357
+ let currentToolResults = [];
1358
+ let roundtrips = 0;
1359
+ const responseMessages = [];
1360
+ do {
1361
+ const currentInputFormat = roundtrips === 0 ? validatedPrompt.type : "messages";
1362
+ currentModelResponse = await retry(
1363
+ () => recordSpan({
1364
+ name: "ai.generateText.doGenerate",
1365
+ attributes: {
1366
+ ...baseTelemetryAttributes,
1367
+ "ai.prompt.format": currentInputFormat,
1368
+ "ai.prompt.messages": JSON.stringify(promptMessages)
1369
+ },
1370
+ tracer,
1371
+ fn: async (span2) => {
1372
+ const result = await model.doGenerate({
1373
+ mode,
1374
+ ...callSettings,
1375
+ inputFormat: currentInputFormat,
1376
+ prompt: promptMessages,
1377
+ abortSignal,
1378
+ headers
1379
+ });
1380
+ span2.setAttributes({
1381
+ "ai.finishReason": result.finishReason,
1382
+ "ai.usage.promptTokens": result.usage.promptTokens,
1383
+ "ai.usage.completionTokens": result.usage.completionTokens,
1384
+ "ai.result.text": result.text,
1385
+ "ai.result.toolCalls": JSON.stringify(result.toolCalls)
1386
+ });
1387
+ return result;
1388
+ }
1389
+ })
1390
+ );
1391
+ currentToolCalls = ((_a2 = currentModelResponse.toolCalls) != null ? _a2 : []).map(
1392
+ (modelToolCall) => parseToolCall({ toolCall: modelToolCall, tools })
1393
+ );
1394
+ currentToolResults = tools == null ? [] : await executeTools({
1395
+ toolCalls: currentToolCalls,
1396
+ tools,
1397
+ tracer
1398
+ });
1399
+ const newResponseMessages = toResponseMessages({
1400
+ text: (_b = currentModelResponse.text) != null ? _b : "",
1401
+ toolCalls: currentToolCalls,
1402
+ toolResults: currentToolResults
1403
+ });
1404
+ responseMessages.push(...newResponseMessages);
1405
+ promptMessages.push(
1406
+ ...newResponseMessages.map(convertToLanguageModelMessage)
1407
+ );
1408
+ } while (
1409
+ // there are tool calls:
1410
+ currentToolCalls.length > 0 && // all current tool calls have results:
1411
+ currentToolResults.length === currentToolCalls.length && // the number of roundtrips is less than the maximum:
1412
+ roundtrips++ < maxToolRoundtrips
1413
+ );
1414
+ span.setAttributes({
1415
+ "ai.finishReason": currentModelResponse.finishReason,
1416
+ "ai.usage.promptTokens": currentModelResponse.usage.promptTokens,
1417
+ "ai.usage.completionTokens": currentModelResponse.usage.completionTokens,
1418
+ "ai.result.text": currentModelResponse.text,
1419
+ "ai.result.toolCalls": JSON.stringify(currentModelResponse.toolCalls)
1420
+ });
1421
+ return new GenerateTextResult({
1422
+ // Always return a string so that the caller doesn't have to check for undefined.
1423
+ // If they need to check if the model did not return any text,
1424
+ // they can check the length of the string:
1425
+ text: (_c = currentModelResponse.text) != null ? _c : "",
1426
+ toolCalls: currentToolCalls,
1427
+ toolResults: currentToolResults,
1428
+ finishReason: currentModelResponse.finishReason,
1429
+ usage: calculateCompletionTokenUsage(currentModelResponse.usage),
1430
+ warnings: currentModelResponse.warnings,
1431
+ rawResponse: currentModelResponse.rawResponse,
1432
+ logprobs: currentModelResponse.logprobs,
1433
+ responseMessages
1434
+ });
1435
+ }
1221
1436
  });
1222
1437
  }
1223
1438
  async function executeTools({
1224
1439
  toolCalls,
1225
- tools
1440
+ tools,
1441
+ tracer
1226
1442
  }) {
1227
1443
  const toolResults = await Promise.all(
1228
1444
  toolCalls.map(async (toolCall) => {
@@ -1230,7 +1446,25 @@ async function executeTools({
1230
1446
  if ((tool2 == null ? void 0 : tool2.execute) == null) {
1231
1447
  return void 0;
1232
1448
  }
1233
- const result = await tool2.execute(toolCall.args);
1449
+ const result = await recordSpan({
1450
+ name: "ai.toolCall",
1451
+ attributes: {
1452
+ "ai.toolCall.name": toolCall.toolName,
1453
+ "ai.toolCall.id": toolCall.toolCallId,
1454
+ "ai.toolCall.args": JSON.stringify(toolCall.args)
1455
+ },
1456
+ tracer,
1457
+ fn: async (span) => {
1458
+ const result2 = await tool2.execute(toolCall.args);
1459
+ try {
1460
+ span.setAttributes({
1461
+ "ai.toolCall.result": JSON.stringify(result2)
1462
+ });
1463
+ } catch (ignored) {
1464
+ }
1465
+ return result2;
1466
+ }
1467
+ });
1234
1468
  return {
1235
1469
  toolCallId: toolCall.toolCallId,
1236
1470
  toolName: toolCall.toolName,
@@ -1286,7 +1520,8 @@ import { NoSuchToolError as NoSuchToolError2 } from "@ai-sdk/provider";
1286
1520
  import { generateId } from "@ai-sdk/ui-utils";
1287
1521
  function runToolsTransformation({
1288
1522
  tools,
1289
- generatorStream
1523
+ generatorStream,
1524
+ tracer
1290
1525
  }) {
1291
1526
  let canClose = false;
1292
1527
  const outstandingToolCalls = /* @__PURE__ */ new Set();
@@ -1334,29 +1569,44 @@ function runToolsTransformation({
1334
1569
  if (tool2.execute != null) {
1335
1570
  const toolExecutionId = generateId();
1336
1571
  outstandingToolCalls.add(toolExecutionId);
1337
- tool2.execute(toolCall.args).then(
1338
- (result) => {
1339
- toolResultsStreamController.enqueue({
1340
- ...toolCall,
1341
- type: "tool-result",
1342
- result
1343
- });
1344
- outstandingToolCalls.delete(toolExecutionId);
1345
- if (canClose && outstandingToolCalls.size === 0) {
1346
- toolResultsStreamController.close();
1347
- }
1572
+ recordSpan({
1573
+ name: "ai.toolCall",
1574
+ attributes: {
1575
+ "ai.toolCall.name": toolCall.toolName,
1576
+ "ai.toolCall.id": toolCall.toolCallId,
1577
+ "ai.toolCall.args": JSON.stringify(toolCall.args)
1348
1578
  },
1349
- (error) => {
1350
- toolResultsStreamController.enqueue({
1351
- type: "error",
1352
- error
1353
- });
1354
- outstandingToolCalls.delete(toolExecutionId);
1355
- if (canClose && outstandingToolCalls.size === 0) {
1356
- toolResultsStreamController.close();
1579
+ tracer,
1580
+ fn: async (span) => tool2.execute(toolCall.args).then(
1581
+ (result) => {
1582
+ toolResultsStreamController.enqueue({
1583
+ ...toolCall,
1584
+ type: "tool-result",
1585
+ result
1586
+ });
1587
+ outstandingToolCalls.delete(toolExecutionId);
1588
+ if (canClose && outstandingToolCalls.size === 0) {
1589
+ toolResultsStreamController.close();
1590
+ }
1591
+ try {
1592
+ span.setAttributes({
1593
+ "ai.toolCall.result": JSON.stringify(result)
1594
+ });
1595
+ } catch (ignored) {
1596
+ }
1597
+ },
1598
+ (error) => {
1599
+ toolResultsStreamController.enqueue({
1600
+ type: "error",
1601
+ error
1602
+ });
1603
+ outstandingToolCalls.delete(toolExecutionId);
1604
+ if (canClose && outstandingToolCalls.size === 0) {
1605
+ toolResultsStreamController.close();
1606
+ }
1357
1607
  }
1358
- }
1359
- );
1608
+ )
1609
+ });
1360
1610
  }
1361
1611
  } catch (error) {
1362
1612
  toolResultsStreamController.enqueue({
@@ -1371,7 +1621,7 @@ function runToolsTransformation({
1371
1621
  type: "finish",
1372
1622
  finishReason: chunk.finishReason,
1373
1623
  logprobs: chunk.logprobs,
1374
- usage: calculateTokenUsage(chunk.usage)
1624
+ usage: calculateCompletionTokenUsage(chunk.usage)
1375
1625
  });
1376
1626
  break;
1377
1627
  }
@@ -1429,32 +1679,76 @@ async function streamText({
1429
1679
  maxRetries,
1430
1680
  abortSignal,
1431
1681
  headers,
1682
+ experimental_telemetry: telemetry,
1432
1683
  onFinish,
1433
1684
  ...settings
1434
1685
  }) {
1435
- const retry = retryWithExponentialBackoff({ maxRetries });
1436
- const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1437
- const { stream, warnings, rawResponse } = await retry(
1438
- () => model.doStream({
1439
- mode: {
1440
- type: "regular",
1441
- ...prepareToolsAndToolChoice({ tools, toolChoice })
1442
- },
1443
- ...prepareCallSettings(settings),
1444
- inputFormat: validatedPrompt.type,
1445
- prompt: convertToLanguageModelPrompt(validatedPrompt),
1446
- abortSignal,
1447
- headers
1448
- })
1449
- );
1450
- return new StreamTextResult({
1451
- stream: runToolsTransformation({
1452
- tools,
1453
- generatorStream: stream
1454
- }),
1455
- warnings,
1456
- rawResponse,
1457
- onFinish
1686
+ var _a;
1687
+ const baseTelemetryAttributes = getBaseTelemetryAttributes({
1688
+ operationName: "ai.streamText",
1689
+ model,
1690
+ telemetry,
1691
+ headers,
1692
+ settings: { ...settings, maxRetries }
1693
+ });
1694
+ const tracer = getTracer({ isEnabled: (_a = telemetry == null ? void 0 : telemetry.isEnabled) != null ? _a : false });
1695
+ return recordSpan({
1696
+ name: "ai.streamText",
1697
+ attributes: {
1698
+ ...baseTelemetryAttributes,
1699
+ // specific settings that only make sense on the outer level:
1700
+ "ai.prompt": JSON.stringify({ system, prompt, messages })
1701
+ },
1702
+ tracer,
1703
+ endWhenDone: false,
1704
+ fn: async (rootSpan) => {
1705
+ const retry = retryWithExponentialBackoff({ maxRetries });
1706
+ const validatedPrompt = getValidatedPrompt({ system, prompt, messages });
1707
+ const promptMessages = convertToLanguageModelPrompt(validatedPrompt);
1708
+ const {
1709
+ result: { stream, warnings, rawResponse },
1710
+ doStreamSpan
1711
+ } = await retry(
1712
+ () => recordSpan({
1713
+ name: "ai.streamText.doStream",
1714
+ attributes: {
1715
+ ...baseTelemetryAttributes,
1716
+ "ai.prompt.format": validatedPrompt.type,
1717
+ "ai.prompt.messages": JSON.stringify(promptMessages)
1718
+ },
1719
+ tracer,
1720
+ endWhenDone: false,
1721
+ fn: async (doStreamSpan2) => {
1722
+ return {
1723
+ result: await model.doStream({
1724
+ mode: {
1725
+ type: "regular",
1726
+ ...prepareToolsAndToolChoice({ tools, toolChoice })
1727
+ },
1728
+ ...prepareCallSettings(settings),
1729
+ inputFormat: validatedPrompt.type,
1730
+ prompt: promptMessages,
1731
+ abortSignal,
1732
+ headers
1733
+ }),
1734
+ doStreamSpan: doStreamSpan2
1735
+ };
1736
+ }
1737
+ })
1738
+ );
1739
+ return new StreamTextResult({
1740
+ stream: runToolsTransformation({
1741
+ tools,
1742
+ generatorStream: stream,
1743
+ tracer
1744
+ }),
1745
+ warnings,
1746
+ rawResponse,
1747
+ onFinish,
1748
+ rootSpan,
1749
+ doStreamSpan
1750
+ });
1751
+ }
1458
1752
  });
1459
1753
  }
1460
1754
  var StreamTextResult = class {
@@ -1462,7 +1756,9 @@ var StreamTextResult = class {
1462
1756
  stream,
1463
1757
  warnings,
1464
1758
  rawResponse,
1465
- onFinish
1759
+ onFinish,
1760
+ rootSpan,
1761
+ doStreamSpan
1466
1762
  }) {
1467
1763
  this.warnings = warnings;
1468
1764
  this.rawResponse = rawResponse;
@@ -1492,41 +1788,73 @@ var StreamTextResult = class {
1492
1788
  let text = "";
1493
1789
  const toolCalls = [];
1494
1790
  const toolResults = [];
1791
+ let firstChunk = true;
1495
1792
  const self = this;
1496
1793
  this.originalStream = stream.pipeThrough(
1497
1794
  new TransformStream({
1498
1795
  async transform(chunk, controller) {
1499
1796
  controller.enqueue(chunk);
1500
- if (chunk.type === "text-delta") {
1501
- text += chunk.textDelta;
1797
+ if (firstChunk) {
1798
+ firstChunk = false;
1799
+ doStreamSpan.addEvent("ai.stream.firstChunk");
1502
1800
  }
1503
- if (chunk.type === "tool-call") {
1504
- toolCalls.push(chunk);
1505
- }
1506
- if (chunk.type === "tool-result") {
1507
- toolResults.push(chunk);
1508
- }
1509
- if (chunk.type === "finish") {
1510
- usage = chunk.usage;
1511
- finishReason = chunk.finishReason;
1512
- resolveUsage(usage);
1513
- resolveFinishReason(finishReason);
1514
- resolveText(text);
1515
- resolveToolCalls(toolCalls);
1801
+ const chunkType = chunk.type;
1802
+ switch (chunkType) {
1803
+ case "text-delta":
1804
+ text += chunk.textDelta;
1805
+ break;
1806
+ case "tool-call":
1807
+ toolCalls.push(chunk);
1808
+ break;
1809
+ case "tool-result":
1810
+ toolResults.push(chunk);
1811
+ break;
1812
+ case "finish":
1813
+ usage = chunk.usage;
1814
+ finishReason = chunk.finishReason;
1815
+ resolveUsage(usage);
1816
+ resolveFinishReason(finishReason);
1817
+ resolveText(text);
1818
+ resolveToolCalls(toolCalls);
1819
+ break;
1820
+ case "error":
1821
+ break;
1822
+ default: {
1823
+ const exhaustiveCheck = chunkType;
1824
+ throw new Error(`Unknown chunk type: ${exhaustiveCheck}`);
1825
+ }
1516
1826
  }
1517
1827
  },
1518
1828
  // invoke onFinish callback and resolve toolResults promise when the stream is about to close:
1519
1829
  async flush(controller) {
1520
1830
  var _a;
1521
1831
  try {
1832
+ const finalUsage = usage != null ? usage : {
1833
+ promptTokens: NaN,
1834
+ completionTokens: NaN,
1835
+ totalTokens: NaN
1836
+ };
1837
+ const finalFinishReason = finishReason != null ? finishReason : "unknown";
1838
+ const telemetryToolCalls = toolCalls.length > 0 ? JSON.stringify(toolCalls) : void 0;
1839
+ doStreamSpan.setAttributes({
1840
+ "ai.finishReason": finalFinishReason,
1841
+ "ai.usage.promptTokens": finalUsage.promptTokens,
1842
+ "ai.usage.completionTokens": finalUsage.completionTokens,
1843
+ "ai.result.text": text,
1844
+ "ai.result.toolCalls": telemetryToolCalls
1845
+ });
1846
+ doStreamSpan.end();
1847
+ rootSpan.setAttributes({
1848
+ "ai.finishReason": finalFinishReason,
1849
+ "ai.usage.promptTokens": finalUsage.promptTokens,
1850
+ "ai.usage.completionTokens": finalUsage.completionTokens,
1851
+ "ai.result.text": text,
1852
+ "ai.result.toolCalls": telemetryToolCalls
1853
+ });
1522
1854
  resolveToolResults(toolResults);
1523
1855
  await ((_a = self.onFinish) == null ? void 0 : _a.call(self, {
1524
- finishReason: finishReason != null ? finishReason : "unknown",
1525
- usage: usage != null ? usage : {
1526
- promptTokens: NaN,
1527
- completionTokens: NaN,
1528
- totalTokens: NaN
1529
- },
1856
+ finishReason: finalFinishReason,
1857
+ usage: finalUsage,
1530
1858
  text,
1531
1859
  toolCalls,
1532
1860
  // The tool results are inferred as a never[] type, because they are
@@ -1539,6 +1867,8 @@ var StreamTextResult = class {
1539
1867
  }));
1540
1868
  } catch (error) {
1541
1869
  controller.error(error);
1870
+ } finally {
1871
+ rootSpan.end();
1542
1872
  }
1543
1873
  }
1544
1874
  })