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