@lelemondev/sdk 0.6.3 → 0.7.1

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
@@ -1,3 +1,5 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+
1
3
  /* @lelemondev/sdk - LLM Observability */
2
4
  var __defProp = Object.defineProperty;
3
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -102,9 +104,9 @@ var Transport = class {
102
104
  * Enqueue a trace for sending
103
105
  * Fire-and-forget - never blocks
104
106
  */
105
- enqueue(trace) {
107
+ enqueue(trace2) {
106
108
  if (this.config.disabled) return;
107
- this.queue.push(trace);
109
+ this.queue.push(trace2);
108
110
  if (this.queue.length >= this.config.batchSize) {
109
111
  this.flush();
110
112
  } else {
@@ -286,6 +288,64 @@ function getNestedValue(obj, path) {
286
288
  function isValidNumber(value) {
287
289
  return typeof value === "number" && !isNaN(value) && isFinite(value);
288
290
  }
291
+ var traceStorage = new AsyncLocalStorage();
292
+ function generateId() {
293
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
294
+ return crypto.randomUUID();
295
+ }
296
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
297
+ }
298
+ function getTraceContext() {
299
+ return traceStorage.getStore();
300
+ }
301
+ async function trace(nameOrOptions, fn) {
302
+ const options = typeof nameOrOptions === "string" ? { name: nameOrOptions } : nameOrOptions;
303
+ const parentContext = getTraceContext();
304
+ const traceId = parentContext?.traceId ?? generateId();
305
+ const rootSpanId = generateId();
306
+ const context = {
307
+ traceId,
308
+ rootSpanId,
309
+ currentSpanId: rootSpanId,
310
+ parentSpanId: parentContext?.currentSpanId,
311
+ name: options.name,
312
+ startTime: Date.now(),
313
+ input: options.input,
314
+ metadata: options.metadata,
315
+ tags: options.tags
316
+ };
317
+ return traceStorage.run(context, async () => {
318
+ try {
319
+ const result = await fn();
320
+ return result;
321
+ } finally {
322
+ }
323
+ });
324
+ }
325
+ function span(options) {
326
+ const context = getTraceContext();
327
+ if (!context) {
328
+ if (process.env.NODE_ENV !== "production") {
329
+ console.warn("[Lelemon] span() called outside of trace() - span will not be captured");
330
+ }
331
+ return;
332
+ }
333
+ captureSpan({
334
+ type: options.type,
335
+ name: options.name,
336
+ input: options.input,
337
+ output: options.output,
338
+ durationMs: options.durationMs ?? 0,
339
+ status: options.status ?? "success",
340
+ errorMessage: options.errorMessage,
341
+ metadata: {
342
+ ...options.metadata,
343
+ // Include trace context
344
+ _traceId: context.traceId,
345
+ _parentSpanId: context.currentSpanId
346
+ }
347
+ });
348
+ }
289
349
 
290
350
  // src/core/capture.ts
291
351
  var globalContext = {};
@@ -303,7 +363,8 @@ function captureTrace(params) {
303
363
  debug("Transport disabled, skipping trace capture");
304
364
  return;
305
365
  }
306
- const context = getGlobalContext();
366
+ const globalContext2 = getGlobalContext();
367
+ const traceContext = getTraceContext();
307
368
  const request = {
308
369
  provider: params.provider,
309
370
  model: params.model,
@@ -314,10 +375,26 @@ function captureTrace(params) {
314
375
  durationMs: params.durationMs,
315
376
  status: params.status,
316
377
  streaming: params.streaming,
317
- sessionId: context.sessionId,
318
- userId: context.userId,
319
- metadata: { ...context.metadata, ...params.metadata },
320
- tags: context.tags
378
+ sessionId: globalContext2.sessionId,
379
+ userId: globalContext2.userId,
380
+ // Hierarchy fields (Phase 7.2) - use trace context if available
381
+ traceId: traceContext?.traceId,
382
+ spanId: generateId(),
383
+ parentSpanId: traceContext?.currentSpanId,
384
+ metadata: {
385
+ ...globalContext2.metadata,
386
+ ...params.metadata,
387
+ // Include trace name for debugging
388
+ ...traceContext ? { _traceName: traceContext.name } : {}
389
+ },
390
+ tags: globalContext2.tags,
391
+ // Extended fields (Phase 7.1)
392
+ stopReason: params.stopReason,
393
+ cacheReadTokens: params.cacheReadTokens,
394
+ cacheWriteTokens: params.cacheWriteTokens,
395
+ reasoningTokens: params.reasoningTokens,
396
+ firstTokenMs: params.firstTokenMs,
397
+ thinking: params.thinking
321
398
  };
322
399
  traceCapture(params.provider, params.model, params.durationMs, params.status);
323
400
  transport.enqueue(request);
@@ -332,7 +409,8 @@ function captureError(params) {
332
409
  debug("Transport disabled, skipping error capture");
333
410
  return;
334
411
  }
335
- const context = getGlobalContext();
412
+ const globalContext2 = getGlobalContext();
413
+ const traceContext = getTraceContext();
336
414
  const request = {
337
415
  provider: params.provider,
338
416
  model: params.model,
@@ -345,10 +423,18 @@ function captureError(params) {
345
423
  errorMessage: params.error.message,
346
424
  errorStack: params.error.stack,
347
425
  streaming: params.streaming,
348
- sessionId: context.sessionId,
349
- userId: context.userId,
350
- metadata: { ...context.metadata, ...params.metadata },
351
- tags: context.tags
426
+ sessionId: globalContext2.sessionId,
427
+ userId: globalContext2.userId,
428
+ // Hierarchy fields (Phase 7.2)
429
+ traceId: traceContext?.traceId,
430
+ spanId: generateId(),
431
+ parentSpanId: traceContext?.currentSpanId,
432
+ metadata: {
433
+ ...globalContext2.metadata,
434
+ ...params.metadata,
435
+ ...traceContext ? { _traceName: traceContext.name } : {}
436
+ },
437
+ tags: globalContext2.tags
352
438
  };
353
439
  traceCapture(params.provider, params.model, params.durationMs, "error");
354
440
  debug("Error details", { message: params.error.message, stack: params.error.stack });
@@ -357,6 +443,93 @@ function captureError(params) {
357
443
  traceCaptureError(params.provider, err instanceof Error ? err : new Error(String(err)));
358
444
  }
359
445
  }
446
+ function captureSpan(options) {
447
+ try {
448
+ const transport = getTransport();
449
+ if (!transport.isEnabled()) {
450
+ debug("Transport disabled, skipping span capture");
451
+ return;
452
+ }
453
+ const globalContext2 = getGlobalContext();
454
+ const traceContext = getTraceContext();
455
+ const metadataTraceId = options.metadata?._traceId;
456
+ const metadataParentSpanId = options.metadata?._parentSpanId;
457
+ const cleanMetadata = { ...globalContext2.metadata, ...options.metadata };
458
+ delete cleanMetadata._traceId;
459
+ delete cleanMetadata._parentSpanId;
460
+ const request = {
461
+ spanType: options.type,
462
+ name: options.name,
463
+ provider: "unknown",
464
+ // Manual spans don't have a provider
465
+ model: options.name,
466
+ // Use name as model for compatibility
467
+ input: sanitizeInput(options.input),
468
+ output: sanitizeOutput(options.output),
469
+ inputTokens: 0,
470
+ outputTokens: 0,
471
+ durationMs: options.durationMs,
472
+ status: options.status || "success",
473
+ errorMessage: options.errorMessage,
474
+ streaming: false,
475
+ sessionId: globalContext2.sessionId,
476
+ userId: globalContext2.userId,
477
+ // Hierarchy fields (Phase 7.2)
478
+ traceId: metadataTraceId ?? traceContext?.traceId,
479
+ spanId: generateId(),
480
+ parentSpanId: metadataParentSpanId ?? traceContext?.currentSpanId,
481
+ toolCallId: options.toolCallId,
482
+ metadata: cleanMetadata,
483
+ tags: globalContext2.tags
484
+ };
485
+ debug(`Span captured: ${options.type}/${options.name}`, { durationMs: options.durationMs });
486
+ transport.enqueue(request);
487
+ } catch (err) {
488
+ traceCaptureError("unknown", err instanceof Error ? err : new Error(String(err)));
489
+ }
490
+ }
491
+ function captureToolSpans(toolCalls, provider) {
492
+ for (const tool of toolCalls) {
493
+ try {
494
+ const transport = getTransport();
495
+ if (!transport.isEnabled()) continue;
496
+ const globalContext2 = getGlobalContext();
497
+ const traceContext = getTraceContext();
498
+ const request = {
499
+ spanType: "tool",
500
+ name: tool.name,
501
+ provider,
502
+ model: tool.name,
503
+ input: sanitizeInput(tool.input),
504
+ output: null,
505
+ // Tool result will come later
506
+ inputTokens: 0,
507
+ outputTokens: 0,
508
+ durationMs: 0,
509
+ // Duration unknown at this point
510
+ status: "success",
511
+ streaming: false,
512
+ sessionId: globalContext2.sessionId,
513
+ userId: globalContext2.userId,
514
+ // Hierarchy fields (Phase 7.2)
515
+ traceId: traceContext?.traceId,
516
+ spanId: generateId(),
517
+ parentSpanId: traceContext?.currentSpanId,
518
+ toolCallId: tool.id,
519
+ metadata: {
520
+ ...globalContext2.metadata,
521
+ toolUseDetected: true,
522
+ ...traceContext ? { _traceName: traceContext.name } : {}
523
+ },
524
+ tags: globalContext2.tags
525
+ };
526
+ debug(`Tool span captured: ${tool.name}`, { toolCallId: tool.id });
527
+ transport.enqueue(request);
528
+ } catch (err) {
529
+ traceCaptureError(provider, err instanceof Error ? err : new Error(String(err)));
530
+ }
531
+ }
532
+ }
360
533
  var MAX_STRING_LENGTH = 1e5;
361
534
  var SENSITIVE_KEYS = ["api_key", "apikey", "password", "secret", "token", "authorization"];
362
535
  function sanitizeInput(input) {
@@ -421,7 +594,10 @@ function wrapChatCreate(originalFn) {
421
594
  outputTokens: extracted.tokens?.outputTokens || 0,
422
595
  durationMs,
423
596
  status: "success",
424
- streaming: false
597
+ streaming: false,
598
+ // Extended fields
599
+ stopReason: extracted.finishReason || void 0,
600
+ reasoningTokens: extracted.tokens?.reasoningTokens
425
601
  });
426
602
  return response;
427
603
  } catch (error) {
@@ -551,14 +727,26 @@ function isAsyncIterable(value) {
551
727
  async function* wrapStream(stream, request, startTime) {
552
728
  const chunks = [];
553
729
  let tokens = null;
730
+ let finishReason;
554
731
  let error = null;
732
+ let firstTokenMs;
733
+ let firstTokenReceived = false;
555
734
  try {
556
735
  for await (const chunk of stream) {
557
- const content = extractStreamChunkContent(chunk);
736
+ const streamChunk = chunk;
737
+ const content = extractStreamChunkContent(streamChunk);
558
738
  if (content) {
739
+ if (!firstTokenReceived) {
740
+ firstTokenReceived = true;
741
+ firstTokenMs = Date.now() - startTime;
742
+ }
559
743
  chunks.push(content);
560
744
  }
561
- const chunkTokens = extractStreamChunkTokens(chunk);
745
+ const chunkFinishReason = streamChunk?.choices?.[0]?.finish_reason;
746
+ if (chunkFinishReason) {
747
+ finishReason = chunkFinishReason;
748
+ }
749
+ const chunkTokens = extractStreamChunkTokens(streamChunk);
562
750
  if (chunkTokens) {
563
751
  tokens = chunkTokens;
564
752
  }
@@ -589,7 +777,11 @@ async function* wrapStream(stream, request, startTime) {
589
777
  outputTokens: tokens?.outputTokens || 0,
590
778
  durationMs,
591
779
  status: "success",
592
- streaming: true
780
+ streaming: true,
781
+ // Extended fields
782
+ stopReason: finishReason,
783
+ reasoningTokens: tokens?.reasoningTokens,
784
+ firstTokenMs
593
785
  });
594
786
  }
595
787
  }
@@ -600,8 +792,12 @@ function extractChatCompletion(response) {
600
792
  () => getNestedValue(response, "choices.0.message.content"),
601
793
  null
602
794
  );
795
+ const finishReason = safeExtract(
796
+ () => getNestedValue(response, "choices.0.finish_reason"),
797
+ null
798
+ );
603
799
  const tokens = extractTokens(response);
604
- return { model, output, tokens };
800
+ return { model, output, tokens, finishReason };
605
801
  }
606
802
  function extractLegacyCompletion(response) {
607
803
  const model = safeExtract(() => getNestedValue(response, "model"), null);
@@ -623,10 +819,16 @@ function extractTokens(response) {
623
819
  if (!isValidNumber(promptTokens) && !isValidNumber(completionTokens)) {
624
820
  return null;
625
821
  }
822
+ let reasoningTokens;
823
+ const completionDetails = u.completion_tokens_details;
824
+ if (completionDetails && isValidNumber(completionDetails.reasoning_tokens)) {
825
+ reasoningTokens = completionDetails.reasoning_tokens;
826
+ }
626
827
  return {
627
828
  inputTokens: isValidNumber(promptTokens) ? promptTokens : 0,
628
829
  outputTokens: isValidNumber(completionTokens) ? completionTokens : 0,
629
- totalTokens: isValidNumber(totalTokens) ? totalTokens : 0
830
+ totalTokens: isValidNumber(totalTokens) ? totalTokens : 0,
831
+ reasoningTokens
630
832
  };
631
833
  } catch {
632
834
  return null;
@@ -768,7 +970,8 @@ function wrapMessagesCreate(originalFn) {
768
970
  return wrapStream2(response, request, startTime);
769
971
  }
770
972
  const durationMs = Date.now() - startTime;
771
- const extracted = extractMessageResponse(response);
973
+ const messageResponse = response;
974
+ const extracted = extractMessageResponse(messageResponse);
772
975
  captureTrace({
773
976
  provider: PROVIDER_NAME2,
774
977
  model: request.model || extracted.model || "unknown",
@@ -778,8 +981,17 @@ function wrapMessagesCreate(originalFn) {
778
981
  outputTokens: extracted.tokens?.outputTokens || 0,
779
982
  durationMs,
780
983
  status: "success",
781
- streaming: false
984
+ streaming: false,
985
+ // Extended fields
986
+ stopReason: extracted.stopReason || void 0,
987
+ cacheReadTokens: extracted.tokens?.cacheReadTokens,
988
+ cacheWriteTokens: extracted.tokens?.cacheWriteTokens,
989
+ thinking: extracted.thinking || void 0
782
990
  });
991
+ const toolCalls = extractToolCalls(messageResponse);
992
+ if (toolCalls.length > 0) {
993
+ captureToolSpans(toolCalls, PROVIDER_NAME2);
994
+ }
783
995
  return response;
784
996
  } catch (error) {
785
997
  const durationMs = Date.now() - startTime;
@@ -828,10 +1040,19 @@ function wrapAnthropicStream(stream, request, startTime) {
828
1040
  return stream;
829
1041
  }
830
1042
  const chunks = [];
1043
+ const thinkingChunks = [];
831
1044
  let inputTokens = 0;
832
1045
  let outputTokens = 0;
1046
+ let cacheReadTokens;
1047
+ let cacheWriteTokens;
1048
+ let stopReason;
833
1049
  let model = request.model || "unknown";
834
1050
  let captured = false;
1051
+ let firstTokenMs;
1052
+ let firstTokenReceived = false;
1053
+ const toolCalls = [];
1054
+ let currentToolIndex = null;
1055
+ let currentBlockType = null;
835
1056
  const wrappedIterator = async function* () {
836
1057
  try {
837
1058
  for await (const event of originalStream) {
@@ -839,13 +1060,63 @@ function wrapAnthropicStream(stream, request, startTime) {
839
1060
  model = event.message.model || model;
840
1061
  if (event.message.usage) {
841
1062
  inputTokens = event.message.usage.input_tokens || 0;
1063
+ cacheReadTokens = event.message.usage.cache_read_input_tokens;
1064
+ cacheWriteTokens = event.message.usage.cache_creation_input_tokens;
842
1065
  }
843
1066
  }
1067
+ if (event.type === "content_block_start" && event.content_block) {
1068
+ currentBlockType = event.content_block.type;
1069
+ }
844
1070
  if (event.type === "content_block_delta" && event.delta?.text) {
845
- chunks.push(event.delta.text);
1071
+ if (!firstTokenReceived) {
1072
+ firstTokenReceived = true;
1073
+ firstTokenMs = Date.now() - startTime;
1074
+ }
1075
+ if (currentBlockType === "thinking") {
1076
+ thinkingChunks.push(event.delta.text);
1077
+ } else {
1078
+ chunks.push(event.delta.text);
1079
+ }
846
1080
  }
847
- if (event.type === "message_delta" && event.usage) {
848
- outputTokens = event.usage.output_tokens || 0;
1081
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
1082
+ const block = event.content_block;
1083
+ if (block.id && block.name) {
1084
+ currentToolIndex = event.index ?? toolCalls.length;
1085
+ toolCalls.push({
1086
+ id: block.id,
1087
+ name: block.name,
1088
+ input: block.input ?? {},
1089
+ inputJson: ""
1090
+ });
1091
+ }
1092
+ }
1093
+ if (event.type === "content_block_delta" && event.delta?.partial_json && currentToolIndex !== null) {
1094
+ const tool = toolCalls.find((_, i) => i === currentToolIndex);
1095
+ if (tool) {
1096
+ tool.inputJson += event.delta.partial_json;
1097
+ }
1098
+ }
1099
+ if (event.type === "content_block_stop") {
1100
+ if (currentToolIndex !== null) {
1101
+ const tool = toolCalls[currentToolIndex];
1102
+ if (tool && tool.inputJson) {
1103
+ try {
1104
+ tool.input = JSON.parse(tool.inputJson);
1105
+ } catch {
1106
+ }
1107
+ }
1108
+ currentToolIndex = null;
1109
+ }
1110
+ currentBlockType = null;
1111
+ }
1112
+ if (event.type === "message_delta") {
1113
+ if (event.usage) {
1114
+ outputTokens = event.usage.output_tokens || 0;
1115
+ }
1116
+ const delta = event;
1117
+ if (delta.delta?.stop_reason) {
1118
+ stopReason = delta.delta.stop_reason;
1119
+ }
849
1120
  }
850
1121
  yield event;
851
1122
  }
@@ -876,8 +1147,20 @@ function wrapAnthropicStream(stream, request, startTime) {
876
1147
  outputTokens,
877
1148
  durationMs,
878
1149
  status: "success",
879
- streaming: true
1150
+ streaming: true,
1151
+ // Extended fields
1152
+ stopReason,
1153
+ cacheReadTokens,
1154
+ cacheWriteTokens,
1155
+ firstTokenMs,
1156
+ thinking: thinkingChunks.length > 0 ? thinkingChunks.join("") : void 0
880
1157
  });
1158
+ if (toolCalls.length > 0) {
1159
+ captureToolSpans(
1160
+ toolCalls.map((t) => ({ id: t.id, name: t.name, input: t.input })),
1161
+ PROVIDER_NAME2
1162
+ );
1163
+ }
881
1164
  }
882
1165
  }
883
1166
  };
@@ -892,23 +1175,82 @@ function wrapAnthropicStream(stream, request, startTime) {
892
1175
  }
893
1176
  async function* wrapStream2(stream, request, startTime) {
894
1177
  const chunks = [];
1178
+ const thinkingChunks = [];
895
1179
  let inputTokens = 0;
896
1180
  let outputTokens = 0;
1181
+ let cacheReadTokens;
1182
+ let cacheWriteTokens;
1183
+ let stopReason;
897
1184
  let model = request.model || "unknown";
898
1185
  let error = null;
1186
+ let firstTokenMs;
1187
+ let firstTokenReceived = false;
1188
+ const toolCalls = [];
1189
+ let currentToolIndex = null;
1190
+ let currentBlockType = null;
899
1191
  try {
900
1192
  for await (const event of stream) {
901
1193
  if (event.type === "message_start" && event.message) {
902
1194
  model = event.message.model || model;
903
1195
  if (event.message.usage) {
904
1196
  inputTokens = event.message.usage.input_tokens || 0;
1197
+ cacheReadTokens = event.message.usage.cache_read_input_tokens;
1198
+ cacheWriteTokens = event.message.usage.cache_creation_input_tokens;
905
1199
  }
906
1200
  }
1201
+ if (event.type === "content_block_start" && event.content_block) {
1202
+ currentBlockType = event.content_block.type;
1203
+ }
907
1204
  if (event.type === "content_block_delta" && event.delta?.text) {
908
- chunks.push(event.delta.text);
1205
+ if (!firstTokenReceived) {
1206
+ firstTokenReceived = true;
1207
+ firstTokenMs = Date.now() - startTime;
1208
+ }
1209
+ if (currentBlockType === "thinking") {
1210
+ thinkingChunks.push(event.delta.text);
1211
+ } else {
1212
+ chunks.push(event.delta.text);
1213
+ }
909
1214
  }
910
- if (event.type === "message_delta" && event.usage) {
911
- outputTokens = event.usage.output_tokens || 0;
1215
+ if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
1216
+ const block = event.content_block;
1217
+ if (block.id && block.name) {
1218
+ currentToolIndex = event.index ?? toolCalls.length;
1219
+ toolCalls.push({
1220
+ id: block.id,
1221
+ name: block.name,
1222
+ input: block.input ?? {},
1223
+ inputJson: ""
1224
+ });
1225
+ }
1226
+ }
1227
+ if (event.type === "content_block_delta" && event.delta?.partial_json && currentToolIndex !== null) {
1228
+ const tool = toolCalls.find((_, i) => i === currentToolIndex);
1229
+ if (tool) {
1230
+ tool.inputJson += event.delta.partial_json;
1231
+ }
1232
+ }
1233
+ if (event.type === "content_block_stop") {
1234
+ if (currentToolIndex !== null) {
1235
+ const tool = toolCalls[currentToolIndex];
1236
+ if (tool && tool.inputJson) {
1237
+ try {
1238
+ tool.input = JSON.parse(tool.inputJson);
1239
+ } catch {
1240
+ }
1241
+ }
1242
+ currentToolIndex = null;
1243
+ }
1244
+ currentBlockType = null;
1245
+ }
1246
+ if (event.type === "message_delta") {
1247
+ if (event.usage) {
1248
+ outputTokens = event.usage.output_tokens || 0;
1249
+ }
1250
+ const delta = event;
1251
+ if (delta.delta?.stop_reason) {
1252
+ stopReason = delta.delta.stop_reason;
1253
+ }
912
1254
  }
913
1255
  yield event;
914
1256
  }
@@ -936,20 +1278,38 @@ async function* wrapStream2(stream, request, startTime) {
936
1278
  outputTokens,
937
1279
  durationMs,
938
1280
  status: "success",
939
- streaming: true
1281
+ streaming: true,
1282
+ // Extended fields
1283
+ stopReason,
1284
+ cacheReadTokens,
1285
+ cacheWriteTokens,
1286
+ firstTokenMs,
1287
+ thinking: thinkingChunks.length > 0 ? thinkingChunks.join("") : void 0
940
1288
  });
1289
+ if (toolCalls.length > 0) {
1290
+ captureToolSpans(
1291
+ toolCalls.map((t) => ({ id: t.id, name: t.name, input: t.input })),
1292
+ PROVIDER_NAME2
1293
+ );
1294
+ }
941
1295
  }
942
1296
  }
943
1297
  }
944
1298
  function extractMessageResponse(response) {
945
1299
  const model = safeExtract(() => response.model ?? null, null);
1300
+ const stopReason = safeExtract(() => response.stop_reason ?? null, null);
946
1301
  const output = safeExtract(() => {
947
1302
  if (!response.content || !Array.isArray(response.content)) return null;
948
1303
  const textBlocks = response.content.filter((block) => block.type === "text" && block.text).map((block) => block.text);
949
1304
  return textBlocks.join("") || null;
950
1305
  }, null);
1306
+ const thinking = safeExtract(() => {
1307
+ if (!response.content || !Array.isArray(response.content)) return null;
1308
+ const thinkingBlocks = response.content.filter((block) => block.type === "thinking" && block.thinking).map((block) => block.thinking);
1309
+ return thinkingBlocks.join("\n\n") || null;
1310
+ }, null);
951
1311
  const tokens = extractTokens2(response);
952
- return { model, output, tokens };
1312
+ return { model, output, tokens, stopReason, thinking };
953
1313
  }
954
1314
  function extractTokens2(response) {
955
1315
  try {
@@ -963,12 +1323,31 @@ function extractTokens2(response) {
963
1323
  return {
964
1324
  inputTokens: isValidNumber(inputTokens) ? inputTokens : 0,
965
1325
  outputTokens: isValidNumber(outputTokens) ? outputTokens : 0,
966
- totalTokens: (isValidNumber(inputTokens) ? inputTokens : 0) + (isValidNumber(outputTokens) ? outputTokens : 0)
1326
+ totalTokens: (isValidNumber(inputTokens) ? inputTokens : 0) + (isValidNumber(outputTokens) ? outputTokens : 0),
1327
+ // Cache tokens (Anthropic prompt caching)
1328
+ cacheReadTokens: isValidNumber(usage.cache_read_input_tokens) ? usage.cache_read_input_tokens : void 0,
1329
+ cacheWriteTokens: isValidNumber(usage.cache_creation_input_tokens) ? usage.cache_creation_input_tokens : void 0
967
1330
  };
968
1331
  } catch {
969
1332
  return null;
970
1333
  }
971
1334
  }
1335
+ function extractToolCalls(response) {
1336
+ try {
1337
+ if (!response.content || !Array.isArray(response.content)) {
1338
+ return [];
1339
+ }
1340
+ return response.content.filter(
1341
+ (block) => block.type === "tool_use" && !!block.id && !!block.name
1342
+ ).map((block) => ({
1343
+ id: block.id,
1344
+ name: block.name,
1345
+ input: block.input ?? {}
1346
+ }));
1347
+ } catch {
1348
+ return [];
1349
+ }
1350
+ }
972
1351
 
973
1352
  // src/providers/bedrock.ts
974
1353
  var PROVIDER_NAME3 = "bedrock";
@@ -1027,14 +1406,19 @@ async function handleConverse(send, command) {
1027
1406
  durationMs,
1028
1407
  status: "success",
1029
1408
  streaming: false,
1409
+ // Extended fields (Phase 7.1)
1410
+ stopReason: response.stopReason,
1411
+ cacheReadTokens: extracted.cacheReadTokens,
1412
+ cacheWriteTokens: extracted.cacheWriteTokens,
1030
1413
  metadata: {
1031
- stopReason: response.stopReason,
1032
1414
  hasToolUse: extracted.hasToolUse,
1033
- cacheReadTokens: extracted.cacheReadTokens,
1034
- cacheWriteTokens: extracted.cacheWriteTokens,
1035
1415
  latencyMs: response.metrics?.latencyMs
1036
1416
  }
1037
1417
  });
1418
+ const toolCalls = extractToolCalls2(response);
1419
+ if (toolCalls.length > 0) {
1420
+ captureToolSpans(toolCalls, PROVIDER_NAME3);
1421
+ }
1038
1422
  return response;
1039
1423
  } catch (error) {
1040
1424
  captureError({
@@ -1077,11 +1461,36 @@ async function* wrapConverseStream(stream, input, startTime) {
1077
1461
  let inputTokens = 0;
1078
1462
  let outputTokens = 0;
1079
1463
  let error = null;
1464
+ let stopReason;
1465
+ let firstTokenMs;
1466
+ let firstContentReceived = false;
1467
+ const toolCalls = /* @__PURE__ */ new Map();
1080
1468
  try {
1081
1469
  for await (const event of stream) {
1082
- if (event.contentBlockDelta?.delta?.text) {
1470
+ if (event.contentBlockDelta?.delta?.text && !firstContentReceived) {
1471
+ firstContentReceived = true;
1472
+ firstTokenMs = Date.now() - startTime;
1473
+ chunks.push(event.contentBlockDelta.delta.text);
1474
+ } else if (event.contentBlockDelta?.delta?.text) {
1083
1475
  chunks.push(event.contentBlockDelta.delta.text);
1084
1476
  }
1477
+ if (event.contentBlockStart?.start?.toolUse) {
1478
+ const tool = event.contentBlockStart.start.toolUse;
1479
+ toolCalls.set(event.contentBlockStart.contentBlockIndex, {
1480
+ id: tool.toolUseId,
1481
+ name: tool.name,
1482
+ inputJson: ""
1483
+ });
1484
+ }
1485
+ if (event.contentBlockDelta?.delta?.toolUse?.input) {
1486
+ const tool = toolCalls.get(event.contentBlockDelta.contentBlockIndex);
1487
+ if (tool) {
1488
+ tool.inputJson += event.contentBlockDelta.delta.toolUse.input;
1489
+ }
1490
+ }
1491
+ if (event.messageStop?.stopReason) {
1492
+ stopReason = event.messageStop.stopReason;
1493
+ }
1085
1494
  if (event.metadata?.usage) {
1086
1495
  inputTokens = event.metadata.usage.inputTokens || 0;
1087
1496
  outputTokens = event.metadata.usage.outputTokens || 0;
@@ -1112,8 +1521,24 @@ async function* wrapConverseStream(stream, input, startTime) {
1112
1521
  outputTokens,
1113
1522
  durationMs,
1114
1523
  status: "success",
1115
- streaming: true
1524
+ streaming: true,
1525
+ // Extended fields (Phase 7.1)
1526
+ stopReason,
1527
+ firstTokenMs
1116
1528
  });
1529
+ if (toolCalls.size > 0) {
1530
+ const tools = Array.from(toolCalls.values()).map((t) => {
1531
+ let parsedInput = {};
1532
+ try {
1533
+ if (t.inputJson) {
1534
+ parsedInput = JSON.parse(t.inputJson);
1535
+ }
1536
+ } catch {
1537
+ }
1538
+ return { id: t.id, name: t.name, input: parsedInput };
1539
+ });
1540
+ captureToolSpans(tools, PROVIDER_NAME3);
1541
+ }
1117
1542
  }
1118
1543
  }
1119
1544
  }
@@ -1238,6 +1663,23 @@ function extractConverseOutput(response) {
1238
1663
  hasToolUse
1239
1664
  };
1240
1665
  }
1666
+ function extractToolCalls2(response) {
1667
+ try {
1668
+ const content = response.output?.message?.content;
1669
+ if (!content || !Array.isArray(content)) {
1670
+ return [];
1671
+ }
1672
+ return content.filter(
1673
+ (block) => !!block.toolUse && !!block.toolUse.toolUseId && !!block.toolUse.name
1674
+ ).map((block) => ({
1675
+ id: block.toolUse.toolUseId,
1676
+ name: block.toolUse.name,
1677
+ input: block.toolUse.input ?? {}
1678
+ }));
1679
+ } catch {
1680
+ return [];
1681
+ }
1682
+ }
1241
1683
  function parseInvokeModelBody(body) {
1242
1684
  try {
1243
1685
  const text = new TextDecoder().decode(body);
@@ -1907,6 +2349,6 @@ function createObserve(defaultOptions) {
1907
2349
  };
1908
2350
  }
1909
2351
 
1910
- export { createObserve, flush, init, isEnabled, observe };
2352
+ export { captureSpan, createObserve, flush, getTraceContext, init, isEnabled, observe, span, trace };
1911
2353
  //# sourceMappingURL=index.mjs.map
1912
2354
  //# sourceMappingURL=index.mjs.map