@telemetry-dev/bedrock 0.1.3 → 0.1.5

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/src/internal.ts CHANGED
@@ -20,6 +20,7 @@ export type JsonValue =
20
20
  | JsonValue[]
21
21
  | JsonRecord
22
22
  | ((...args: JsonValue[]) => JsonValue);
23
+
23
24
  export interface JsonRecord {
24
25
  [key: string]: JsonValue;
25
26
  }
@@ -53,6 +54,7 @@ export function createInstrumentedSend(
53
54
  const rawRest = rest.map(asJsonValue);
54
55
  const name = isRecord(rawCommand) ? rawCommand.constructor?.name : undefined;
55
56
  const handler = String(name) === name ? handlers[name] : undefined;
57
+
56
58
  if (!handler) {
57
59
  return originalSend.apply(this, [rawCommand, ...rawRest]);
58
60
  }
@@ -64,25 +66,32 @@ export function createInstrumentedSend(
64
66
  const fields = safe(() => handler.requestFields(input, options)) ?? {};
65
67
  const span = startSpan(spanName, { type: spanType, ...fields });
66
68
  const callbackIndex = rawRest.findIndex((arg) => arg instanceof Function);
69
+
67
70
  if (callbackIndex !== -1) {
68
71
  const callback = rawRest[callbackIndex] as (cause: JsonValue, data?: JsonValue) => void;
69
72
  const wrappedRest = [...rawRest];
70
73
  wrappedRest[callbackIndex] = <TError, TData>(error: TError, data?: TData) => {
71
74
  const rawError: unknown = error;
72
75
  const rawData: unknown = data;
76
+
73
77
  if (rawError != null) {
74
78
  endSpan(span, { ...awsMetadataFields(metadataOf(rawError)), error: asError(rawError) });
75
79
  callback(asJsonValue(rawError), asJsonValue(rawData));
80
+
76
81
  return;
77
82
  }
83
+
78
84
  let nextData: JsonValue | object = asJsonValue(rawData);
85
+
79
86
  try {
80
87
  nextData = handler.onResult(asJsonValue(rawData), span, t0, input, options);
81
88
  } catch {
82
89
  endSpan(span, awsMetadataFields(metadataOf(rawData)));
83
90
  }
91
+
84
92
  callback(asJsonValue(rawError), asJsonValue(nextData));
85
93
  };
94
+
86
95
  try {
87
96
  return originalSend.apply(this, [rawCommand, ...wrappedRest]);
88
97
  } catch (error) {
@@ -92,6 +101,7 @@ export function createInstrumentedSend(
92
101
  }
93
102
 
94
103
  let result: JsonValue | object | Promise<JsonValue | object>;
104
+
95
105
  try {
96
106
  result = originalSend.apply(this, [rawCommand, ...rawRest]);
97
107
  } catch (error) {
@@ -105,6 +115,7 @@ export function createInstrumentedSend(
105
115
  return handler.onResult(asJsonValue(resolved), span, t0, input, options);
106
116
  } catch {
107
117
  endSpan(span, awsMetadataFields(metadataOf(resolved)));
118
+
108
119
  return resolved;
109
120
  }
110
121
  },
@@ -118,6 +129,7 @@ export function createInstrumentedSend(
118
129
 
119
130
  export function commandInput<T>(command: T): JsonRecord {
120
131
  if (!isRecord(command)) return {};
132
+
121
133
  return isRecord(command.input) ? command.input : {};
122
134
  }
123
135
 
@@ -151,6 +163,7 @@ export function updateSpan(span: SpanHandle, fields: SpanFields): void {
151
163
 
152
164
  export function metadataOf<T>(value: T): JsonValue {
153
165
  if (!isRecord(value)) return undefined;
166
+
154
167
  return value.$metadata;
155
168
  }
156
169
 
@@ -172,31 +185,42 @@ export function modeledStreamError(
172
185
  throttlingException: "ThrottlingException",
173
186
  validationException: "ValidationException",
174
187
  } as const;
188
+
175
189
  for (const [key, name] of Object.entries(keys)) {
176
190
  const exception = isRecord(event[key]) ? event[key] : undefined;
191
+
177
192
  if (!exception) continue;
193
+
178
194
  const error = new Error(
179
195
  stringValue(exception.message) ?? stringValue(exception.originalMessage) ?? name,
180
196
  );
197
+
181
198
  error.name = stringValue(exception.name) ?? name;
199
+
182
200
  return { error, fields: awsMetadataFields(exception.$metadata) };
183
201
  }
202
+
184
203
  return undefined;
185
204
  }
186
205
 
187
206
  export function awsMetadataFields<T>(metadata: T): SpanFields {
188
207
  const raw: unknown = metadata;
208
+
189
209
  if (!isRecord(raw)) return {};
190
210
  const requestId = stringValue(raw.requestId);
191
211
  const attempts = numberValue(raw.attempts);
192
212
  const httpStatusCode = numberValue(raw.httpStatusCode);
193
213
  const totalRetryDelay = numberValue(raw.totalRetryDelay);
194
214
  const attributes: SpanFields["attributes"] = {};
215
+
195
216
  if (httpStatusCode !== undefined) attributes["aws.http.status_code"] = httpStatusCode;
217
+
196
218
  if (attempts !== undefined && attempts > 1) attributes["aws.request.attempts"] = attempts;
219
+
197
220
  if (totalRetryDelay !== undefined) {
198
221
  attributes["aws.request.total_retry_delay_ms"] = totalRetryDelay;
199
222
  }
223
+
200
224
  return omitUndefined({
201
225
  responseId: requestId,
202
226
  attributes: Object.keys(attributes).length > 0 ? attributes : undefined,
@@ -205,14 +229,19 @@ export function awsMetadataFields<T>(metadata: T): SpanFields {
205
229
 
206
230
  export function mergeFields(...fields: Array<SpanFields | undefined>): SpanFields {
207
231
  const merged: SpanFields = {};
232
+
208
233
  for (const field of fields) {
209
234
  if (!field) continue;
210
235
  const { metadata, attributes, usage, ...rest } = field;
211
236
  Object.assign(merged, omitUndefined(rest));
237
+
212
238
  if (usage) merged.usage = { ...merged.usage, ...usage };
239
+
213
240
  if (metadata) merged.metadata = { ...merged.metadata, ...metadata };
241
+
214
242
  if (attributes) merged.attributes = { ...merged.attributes, ...attributes };
215
243
  }
244
+
216
245
  return merged;
217
246
  }
218
247
 
@@ -222,11 +251,13 @@ export function omitUndefined<T extends object>(value: T): T {
222
251
 
223
252
  export function stringValue<T>(value: T): string | undefined {
224
253
  const raw: unknown = value;
254
+
225
255
  return String(raw) === raw && raw.length > 0 ? raw : undefined;
226
256
  }
227
257
 
228
258
  export function numberValue<T>(value: T): number | undefined {
229
259
  const raw: unknown = value;
260
+
230
261
  return Number(raw) === raw && Number.isFinite(raw) ? raw : undefined;
231
262
  }
232
263
 
@@ -236,8 +267,11 @@ export function arrayValue<T, TValue = unknown>(value: TValue): T[] | undefined
236
267
 
237
268
  export function bytesToString<T>(value: T): string | undefined {
238
269
  const raw: unknown = value;
270
+
239
271
  if (String(raw) === raw) return raw;
272
+
240
273
  if (raw instanceof Uint8Array) return new TextDecoder().decode(raw);
274
+
241
275
  if (ArrayBuffer.isView(raw)) {
242
276
  return new TextDecoder().decode(new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength));
243
277
  }
@@ -245,7 +279,9 @@ export function bytesToString<T>(value: T): string | undefined {
245
279
 
246
280
  export function parseJson<T>(value: T): JsonValue {
247
281
  const text = bytesToString(value);
282
+
248
283
  if (text === undefined) return undefined;
284
+
249
285
  try {
250
286
  return asJsonValue(JSON.parse(text));
251
287
  } catch {
@@ -254,7 +290,7 @@ export function parseJson<T>(value: T): JsonValue {
254
290
  }
255
291
 
256
292
  export interface StreamState {
257
- feed<T>(event: T): void;
293
+ feed<T>(event: T): boolean | void;
258
294
  finish(partial: boolean): SpanFields;
259
295
  }
260
296
 
@@ -264,15 +300,18 @@ export function wrapAsyncIterable<T>(
264
300
  span: SpanHandle,
265
301
  t0: number,
266
302
  baseFields: SpanFields,
303
+ trackOutputChunks = true,
267
304
  ): T {
268
305
  if (!iterable || !isAsyncIterable(iterable)) {
269
306
  endSpan(span, baseFields);
307
+
270
308
  return iterable;
271
309
  }
272
310
 
273
311
  let ended = false;
274
312
  let first = false;
275
313
  const unregisterToken = {};
314
+
276
315
  const endOnce = (fields?: SpanFields) => {
277
316
  if (ended) return;
278
317
  ended = true;
@@ -282,15 +321,25 @@ export function wrapAsyncIterable<T>(
282
321
 
283
322
  const generator = async function* () {
284
323
  let completed = false;
324
+
285
325
  try {
286
326
  for await (const event of iterable) {
327
+ const receivedAt = performance.now();
328
+
287
329
  if (!first) {
288
330
  first = true;
289
- updateSpan(span, { timeToFirstChunkMs: performance.now() - t0 });
331
+ updateSpan(span, { timeToFirstChunkMs: receivedAt - t0 });
332
+ }
333
+
334
+ const stateHasOutput = safe(() => state.feed(event));
335
+
336
+ if (trackOutputChunks && (stateHasOutput ?? bedrockEventHasOutput(event))) {
337
+ safe(() => span.recordOutputChunk?.(receivedAt));
290
338
  }
291
- safe(() => state.feed(event));
339
+
292
340
  yield event;
293
341
  }
342
+
294
343
  completed = true;
295
344
  } catch (error) {
296
345
  endOnce(
@@ -305,21 +354,90 @@ export function wrapAsyncIterable<T>(
305
354
  };
306
355
 
307
356
  const wrapped: { [key: PropertyKey]: JsonValue | (() => AsyncGenerator<unknown>) } = {};
357
+
308
358
  if (isRecord(iterable)) {
309
359
  for (const key of Reflect.ownKeys(iterable)) {
310
360
  wrapped[key] = (iterable as { [key: PropertyKey]: JsonValue })[key];
311
361
  }
312
362
  }
363
+
313
364
  const finishAbandoned = () => endOnce(safe(() => state.finish(true)));
314
365
  wrapped[Symbol.asyncIterator] = () => {
315
366
  const iterator = generator();
316
367
  STREAM_FINALIZER?.register(iterator, finishAbandoned, unregisterToken);
368
+
317
369
  return iterator;
318
370
  };
371
+
319
372
  STREAM_FINALIZER?.register(wrapped, finishAbandoned, unregisterToken);
373
+
320
374
  return wrapped as T;
321
375
  }
322
376
 
377
+ function bedrockEventHasOutput(event: unknown): boolean {
378
+ const record = isRecord(event) ? event : undefined;
379
+
380
+ if (!record) return false;
381
+ const output = isRecord(record.output) ? record.output : undefined;
382
+
383
+ if (typeof output?.text === "string" && output.text.length > 0) return true;
384
+ const block = isRecord(record.contentBlockDelta) ? record.contentBlockDelta : undefined;
385
+ const delta = isRecord(block?.delta) ? block.delta : undefined;
386
+ const reasoning = isRecord(delta?.reasoningContent) ? delta.reasoningContent : undefined;
387
+ const toolUse = isRecord(delta?.toolUse) ? delta.toolUse : undefined;
388
+
389
+ if (
390
+ (typeof delta?.text === "string" && delta.text.length > 0) ||
391
+ (typeof reasoning?.text === "string" && reasoning.text.length > 0) ||
392
+ (typeof toolUse?.input === "string" && toolUse.input.length > 0)
393
+ )
394
+ return true;
395
+ const chunk = isRecord(record.chunk) ? record.chunk : undefined;
396
+ const raw = chunk?.bytes;
397
+
398
+ if (raw === undefined) return false;
399
+
400
+ const text =
401
+ typeof raw === "string"
402
+ ? raw
403
+ : raw instanceof Uint8Array
404
+ ? new TextDecoder().decode(raw)
405
+ : undefined;
406
+
407
+ if (!text) return false;
408
+ const parsed = parseJson(text);
409
+
410
+ if (parsed === undefined) return text.length > 0;
411
+
412
+ if (!isRecord(parsed)) return false;
413
+ const parsedDelta = isRecord(parsed.delta) ? parsed.delta : undefined;
414
+
415
+ const contentDelta = isRecord(parsed.contentBlockDelta ?? parsed.content_block_delta)
416
+ ? (parsed.contentBlockDelta ?? parsed.content_block_delta)
417
+ : undefined;
418
+
419
+ const nested =
420
+ isRecord(contentDelta) && isRecord(contentDelta.delta) ? contentDelta.delta : undefined;
421
+
422
+ const generations = Array.isArray(parsed.generations) ? parsed.generations : [];
423
+ const generation = isRecord(generations[0]) ? generations[0] : undefined;
424
+ const outputs = Array.isArray(parsed.outputs) ? parsed.outputs : [];
425
+ const firstOutput = isRecord(outputs[0]) ? outputs[0] : undefined;
426
+
427
+ return [
428
+ parsed.outputText,
429
+ parsed.generation,
430
+ parsed.completion,
431
+ parsed.text,
432
+ parsedDelta?.text,
433
+ parsedDelta?.thinking,
434
+ parsedDelta?.partial_json,
435
+ nested?.text,
436
+ firstOutput?.text,
437
+ generation?.text,
438
+ ].some((value) => typeof value === "string" && value.length > 0);
439
+ }
440
+
323
441
  function isAsyncIterable<T>(value: T): value is T & AsyncIterable<unknown> {
324
442
  return Object(value) === value && Symbol.asyncIterator in Object(value);
325
443
  }
@@ -334,6 +452,8 @@ function asError<T>(value: T): Error {
334
452
 
335
453
  export function basenameArn<T>(value: T): string | undefined {
336
454
  const text = stringValue(value);
455
+
337
456
  if (!text) return undefined;
457
+
338
458
  return text.split(/[/:]/).filter(Boolean).at(-1) ?? text;
339
459
  }
@@ -18,6 +18,7 @@ import {
18
18
 
19
19
  export function isEmbeddingModel<T>(modelId: T): boolean {
20
20
  const model = stringValue(modelId)?.toLowerCase() ?? "";
21
+
21
22
  return (
22
23
  model.includes("titan-embed") || model.includes("cohere.embed") || model.includes("-embedding")
23
24
  );
@@ -25,12 +26,14 @@ export function isEmbeddingModel<T>(modelId: T): boolean {
25
26
 
26
27
  export function invokeModelSpanName(input: JsonRecord): string {
27
28
  const model = stringValue(input.modelId) ?? "unknown";
29
+
28
30
  return `${isEmbeddingModel(model) ? "embeddings" : "chat"} ${model}`;
29
31
  }
30
32
 
31
33
  export function invokeModelRequestFields(input: JsonRecord): SpanFields {
32
34
  const model = stringValue(input.modelId);
33
35
  const body = parseBody(input.body, input.contentType);
36
+
34
37
  return omitUndefined({
35
38
  provider: PROVIDER,
36
39
  model,
@@ -44,20 +47,25 @@ export function invokeModelResponseFields(input: JsonRecord, output: JsonValue):
44
47
  const model = stringValue(input.modelId);
45
48
  const body = isRecord(output) ? parseBody(output.body, output.contentType) : undefined;
46
49
  const metadata = isRecord(output) ? output.$metadata : undefined;
50
+
47
51
  return mergeFields(awsMetadataFields(metadata), nativeResponseFields(model, body, metadata));
48
52
  }
49
53
 
50
54
  export function parseBody<TBody, TContentType>(body: TBody, contentType: TContentType): JsonValue {
51
55
  const type = stringValue(contentType) ?? "application/json";
56
+
52
57
  if (!type.includes("json")) return undefined;
58
+
53
59
  return parseJson(body);
54
60
  }
55
61
 
56
62
  function nativeSamplingFields<T>(model: string | undefined, body: T): SpanFields {
57
63
  if (!isRecord(body)) return {};
58
64
  const lower = model?.toLowerCase() ?? "";
65
+
59
66
  if (lower.includes("amazon.titan")) {
60
67
  const cfg = isRecord(body.textGenerationConfig) ? body.textGenerationConfig : {};
68
+
61
69
  return omitUndefined({
62
70
  temperature: numberValue(cfg.temperature),
63
71
  topP: numberValue(cfg.topP),
@@ -65,8 +73,10 @@ function nativeSamplingFields<T>(model: string | undefined, body: T): SpanFields
65
73
  stopSequences: arrayValue<string>(cfg.stopSequences),
66
74
  });
67
75
  }
76
+
68
77
  if (lower.includes("amazon.nova")) {
69
78
  const cfg = isRecord(body.inferenceConfig) ? body.inferenceConfig : {};
79
+
70
80
  return omitUndefined({
71
81
  temperature: numberValue(cfg.temperature),
72
82
  topP: numberValue(cfg.topP) ?? numberValue(cfg.top_p),
@@ -75,6 +85,7 @@ function nativeSamplingFields<T>(model: string | undefined, body: T): SpanFields
75
85
  stopSequences: arrayValue<string>(cfg.stopSequences),
76
86
  });
77
87
  }
88
+
78
89
  if (lower.includes("anthropic.claude")) {
79
90
  return omitUndefined({
80
91
  temperature: numberValue(body.temperature),
@@ -84,6 +95,7 @@ function nativeSamplingFields<T>(model: string | undefined, body: T): SpanFields
84
95
  stopSequences: arrayValue<string>(body.stop_sequences),
85
96
  });
86
97
  }
98
+
87
99
  if (lower.includes("meta.llama")) {
88
100
  return omitUndefined({
89
101
  temperature: numberValue(body.temperature),
@@ -91,6 +103,7 @@ function nativeSamplingFields<T>(model: string | undefined, body: T): SpanFields
91
103
  maxTokens: numberValue(body.max_gen_len),
92
104
  });
93
105
  }
106
+
94
107
  return omitUndefined({
95
108
  temperature: numberValue(body.temperature),
96
109
  topP: numberValue(body.top_p) ?? numberValue(body.topP),
@@ -105,6 +118,7 @@ function nativeResponseFields(
105
118
  ): SpanFields {
106
119
  if (!isRecord(body)) return headerUsageFields(metadata);
107
120
  const lower = model?.toLowerCase() ?? "";
121
+
108
122
  if (isEmbeddingModel(model)) {
109
123
  return withHeaderUsage(
110
124
  {
@@ -115,8 +129,10 @@ function nativeResponseFields(
115
129
  metadata,
116
130
  );
117
131
  }
132
+
118
133
  if (lower.includes("amazon.titan")) {
119
134
  const first = Array.isArray(body.results) && isRecord(body.results[0]) ? body.results[0] : {};
135
+
120
136
  return omitUndefined({
121
137
  output: body,
122
138
  finishReason: stringValue(first.completionReason),
@@ -126,8 +142,10 @@ function nativeResponseFields(
126
142
  }),
127
143
  });
128
144
  }
145
+
129
146
  if (lower.includes("amazon.nova")) {
130
147
  const usage = isRecord(body.usage) ? body.usage : {};
148
+
131
149
  return omitUndefined({
132
150
  output: body,
133
151
  finishReason: stringValue(body.stopReason),
@@ -138,8 +156,10 @@ function nativeResponseFields(
138
156
  }),
139
157
  });
140
158
  }
159
+
141
160
  if (lower.includes("anthropic.claude")) {
142
161
  const usage = isRecord(body.usage) ? body.usage : {};
162
+
143
163
  return withHeaderUsage(
144
164
  {
145
165
  output: body,
@@ -154,6 +174,7 @@ function nativeResponseFields(
154
174
  metadata,
155
175
  );
156
176
  }
177
+
157
178
  if (lower.includes("meta.llama")) {
158
179
  return omitUndefined({
159
180
  output: body,
@@ -164,32 +185,39 @@ function nativeResponseFields(
164
185
  }),
165
186
  });
166
187
  }
188
+
167
189
  if (lower.includes("mistral")) {
168
190
  const first = Array.isArray(body.outputs) && isRecord(body.outputs[0]) ? body.outputs[0] : {};
191
+
169
192
  return withHeaderUsage(
170
193
  { output: body, finishReason: stringValue(first.stop_reason) },
171
194
  metadata,
172
195
  );
173
196
  }
197
+
174
198
  if (lower.includes("cohere.command-r")) {
175
199
  return withHeaderUsage(
176
200
  { output: body, finishReason: stringValue(body.finish_reason) },
177
201
  metadata,
178
202
  );
179
203
  }
204
+
180
205
  if (lower.includes("cohere.command")) {
181
206
  const first =
182
207
  Array.isArray(body.generations) && isRecord(body.generations[0]) ? body.generations[0] : {};
208
+
183
209
  return withHeaderUsage(
184
210
  { output: body, finishReason: stringValue(first.finish_reason) },
185
211
  metadata,
186
212
  );
187
213
  }
214
+
188
215
  return withHeaderUsage({ output: body }, metadata);
189
216
  }
190
217
 
191
218
  function withHeaderUsage<T>(fields: SpanFields, metadata: T): SpanFields {
192
219
  if (fields.usage && Object.keys(fields.usage).length > 0) return fields;
220
+
193
221
  return mergeFields(fields, headerUsageFields(metadata));
194
222
  }
195
223
 
@@ -201,19 +229,24 @@ function headerUsageFields<T>(metadata: T): SpanFields {
201
229
  ? metadata.HTTPHeaders
202
230
  : undefined
203
231
  : undefined;
232
+
204
233
  const usage = omitUndefined({
205
234
  inputTokens: headerNumber(headers?.["x-amzn-bedrock-input-token-count"]),
206
235
  outputTokens: headerNumber(headers?.["x-amzn-bedrock-output-token-count"]),
207
236
  });
237
+
208
238
  return { usage: Object.keys(usage).length > 0 ? usage : undefined };
209
239
  }
210
240
 
211
241
  function headerNumber<T>(value: T): number | undefined {
212
242
  const text = stringValue(value);
243
+
213
244
  if (text !== undefined) {
214
245
  const parsed = Number.parseInt(text, 10);
246
+
215
247
  return Number.isFinite(parsed) ? parsed : undefined;
216
248
  }
249
+
217
250
  return numberValue(value);
218
251
  }
219
252
 
@@ -224,35 +257,49 @@ export class InvokeModelStreamState implements StreamState {
224
257
  private finishReason: string | undefined;
225
258
  private error: Error | undefined;
226
259
  private errorFields: SpanFields | undefined;
227
- feed<T>(event: T): void {
228
- if (!isRecord(event)) return;
260
+ feed<T>(event: T): boolean {
261
+ if (!isRecord(event)) return false;
229
262
  const streamError = modeledStreamError(event);
263
+
230
264
  if (streamError) {
231
265
  this.error = streamError.error;
232
266
  this.errorFields = streamError.fields;
233
- return;
267
+
268
+ return false;
234
269
  }
270
+
235
271
  const chunk = isRecord(event.chunk) ? event.chunk : undefined;
236
272
  const parsed = parseJson(chunk?.bytes);
237
- if (parsed === undefined) return;
273
+
274
+ if (parsed === undefined) {
275
+ const raw = chunk?.bytes;
276
+
277
+ return (typeof raw === "string" || raw instanceof Uint8Array) && raw.length > 0;
278
+ }
279
+
238
280
  this.chunks.push(parsed);
239
281
  this.text += textFromProviderChunk(parsed);
282
+
240
283
  if (isRecord(parsed)) {
241
284
  const delta = isRecord(parsed.delta) ? parsed.delta : undefined;
242
285
  const message = isRecord(parsed.message) ? parsed.message : undefined;
243
286
  const messageUsage = isRecord(message?.usage) ? message?.usage : undefined;
244
287
  const usage = isRecord(parsed.usage) ? parsed.usage : undefined;
288
+
245
289
  const metrics = isRecord(parsed["amazon-bedrock-invocationMetrics"])
246
290
  ? parsed["amazon-bedrock-invocationMetrics"]
247
291
  : undefined;
292
+
248
293
  const outputs =
249
294
  Array.isArray(parsed.outputs) && isRecord(parsed.outputs[0])
250
295
  ? parsed.outputs[0]
251
296
  : undefined;
297
+
252
298
  const generation =
253
299
  Array.isArray(parsed.generations) && isRecord(parsed.generations[0])
254
300
  ? parsed.generations[0]
255
301
  : undefined;
302
+
256
303
  this.finishReason =
257
304
  stringValue(delta?.stop_reason) ??
258
305
  stringValue(parsed.stop_reason) ??
@@ -279,6 +326,8 @@ export class InvokeModelStreamState implements StreamState {
279
326
  this.usage?.cacheCreationInputTokens,
280
327
  });
281
328
  }
329
+
330
+ return providerChunkHasOutput(parsed);
282
331
  }
283
332
 
284
333
  finish(): SpanFields {
@@ -305,16 +354,20 @@ export class InvokeModelStreamState implements StreamState {
305
354
  function textFromProviderChunk<T>(value: T): string {
306
355
  if (!isRecord(value)) return "";
307
356
  const delta = isRecord(value.delta) ? value.delta : undefined;
357
+
308
358
  const contentBlockDelta = isRecord(value.contentBlockDelta)
309
359
  ? value.contentBlockDelta
310
360
  : isRecord(value.content_block_delta)
311
361
  ? value.content_block_delta
312
362
  : undefined;
363
+
313
364
  const nestedDelta = isRecord(contentBlockDelta?.delta) ? contentBlockDelta?.delta : undefined;
365
+
314
366
  const generation =
315
367
  Array.isArray(value.generations) && isRecord(value.generations[0])
316
368
  ? value.generations[0]
317
369
  : undefined;
370
+
318
371
  return (
319
372
  stringValue(value.outputText) ??
320
373
  stringValue(value.generation) ??
@@ -329,3 +382,14 @@ function textFromProviderChunk<T>(value: T): string {
329
382
  ""
330
383
  );
331
384
  }
385
+
386
+ function providerChunkHasOutput<T>(value: T): boolean {
387
+ if (!isRecord(value)) return false;
388
+ const delta = isRecord(value.delta) ? value.delta : undefined;
389
+
390
+ return (
391
+ textFromProviderChunk(value).length > 0 ||
392
+ (typeof delta?.thinking === "string" && delta.thinking.length > 0) ||
393
+ (typeof delta?.partial_json === "string" && delta.partial_json.length > 0)
394
+ );
395
+ }