@lelemondev/sdk 0.7.0 → 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 });
@@ -364,7 +450,13 @@ function captureSpan(options) {
364
450
  debug("Transport disabled, skipping span capture");
365
451
  return;
366
452
  }
367
- const context = getGlobalContext();
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;
368
460
  const request = {
369
461
  spanType: options.type,
370
462
  name: options.name,
@@ -380,11 +472,15 @@ function captureSpan(options) {
380
472
  status: options.status || "success",
381
473
  errorMessage: options.errorMessage,
382
474
  streaming: false,
383
- sessionId: context.sessionId,
384
- userId: context.userId,
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,
385
481
  toolCallId: options.toolCallId,
386
- metadata: { ...context.metadata, ...options.metadata },
387
- tags: context.tags
482
+ metadata: cleanMetadata,
483
+ tags: globalContext2.tags
388
484
  };
389
485
  debug(`Span captured: ${options.type}/${options.name}`, { durationMs: options.durationMs });
390
486
  transport.enqueue(request);
@@ -397,7 +493,8 @@ function captureToolSpans(toolCalls, provider) {
397
493
  try {
398
494
  const transport = getTransport();
399
495
  if (!transport.isEnabled()) continue;
400
- const context = getGlobalContext();
496
+ const globalContext2 = getGlobalContext();
497
+ const traceContext = getTraceContext();
401
498
  const request = {
402
499
  spanType: "tool",
403
500
  name: tool.name,
@@ -412,11 +509,19 @@ function captureToolSpans(toolCalls, provider) {
412
509
  // Duration unknown at this point
413
510
  status: "success",
414
511
  streaming: false,
415
- sessionId: context.sessionId,
416
- userId: context.userId,
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,
417
518
  toolCallId: tool.id,
418
- metadata: { ...context.metadata, toolUseDetected: true },
419
- tags: context.tags
519
+ metadata: {
520
+ ...globalContext2.metadata,
521
+ toolUseDetected: true,
522
+ ...traceContext ? { _traceName: traceContext.name } : {}
523
+ },
524
+ tags: globalContext2.tags
420
525
  };
421
526
  debug(`Tool span captured: ${tool.name}`, { toolCallId: tool.id });
422
527
  transport.enqueue(request);
@@ -489,7 +594,10 @@ function wrapChatCreate(originalFn) {
489
594
  outputTokens: extracted.tokens?.outputTokens || 0,
490
595
  durationMs,
491
596
  status: "success",
492
- streaming: false
597
+ streaming: false,
598
+ // Extended fields
599
+ stopReason: extracted.finishReason || void 0,
600
+ reasoningTokens: extracted.tokens?.reasoningTokens
493
601
  });
494
602
  return response;
495
603
  } catch (error) {
@@ -619,14 +727,26 @@ function isAsyncIterable(value) {
619
727
  async function* wrapStream(stream, request, startTime) {
620
728
  const chunks = [];
621
729
  let tokens = null;
730
+ let finishReason;
622
731
  let error = null;
732
+ let firstTokenMs;
733
+ let firstTokenReceived = false;
623
734
  try {
624
735
  for await (const chunk of stream) {
625
- const content = extractStreamChunkContent(chunk);
736
+ const streamChunk = chunk;
737
+ const content = extractStreamChunkContent(streamChunk);
626
738
  if (content) {
739
+ if (!firstTokenReceived) {
740
+ firstTokenReceived = true;
741
+ firstTokenMs = Date.now() - startTime;
742
+ }
627
743
  chunks.push(content);
628
744
  }
629
- const chunkTokens = extractStreamChunkTokens(chunk);
745
+ const chunkFinishReason = streamChunk?.choices?.[0]?.finish_reason;
746
+ if (chunkFinishReason) {
747
+ finishReason = chunkFinishReason;
748
+ }
749
+ const chunkTokens = extractStreamChunkTokens(streamChunk);
630
750
  if (chunkTokens) {
631
751
  tokens = chunkTokens;
632
752
  }
@@ -657,7 +777,11 @@ async function* wrapStream(stream, request, startTime) {
657
777
  outputTokens: tokens?.outputTokens || 0,
658
778
  durationMs,
659
779
  status: "success",
660
- streaming: true
780
+ streaming: true,
781
+ // Extended fields
782
+ stopReason: finishReason,
783
+ reasoningTokens: tokens?.reasoningTokens,
784
+ firstTokenMs
661
785
  });
662
786
  }
663
787
  }
@@ -668,8 +792,12 @@ function extractChatCompletion(response) {
668
792
  () => getNestedValue(response, "choices.0.message.content"),
669
793
  null
670
794
  );
795
+ const finishReason = safeExtract(
796
+ () => getNestedValue(response, "choices.0.finish_reason"),
797
+ null
798
+ );
671
799
  const tokens = extractTokens(response);
672
- return { model, output, tokens };
800
+ return { model, output, tokens, finishReason };
673
801
  }
674
802
  function extractLegacyCompletion(response) {
675
803
  const model = safeExtract(() => getNestedValue(response, "model"), null);
@@ -691,10 +819,16 @@ function extractTokens(response) {
691
819
  if (!isValidNumber(promptTokens) && !isValidNumber(completionTokens)) {
692
820
  return null;
693
821
  }
822
+ let reasoningTokens;
823
+ const completionDetails = u.completion_tokens_details;
824
+ if (completionDetails && isValidNumber(completionDetails.reasoning_tokens)) {
825
+ reasoningTokens = completionDetails.reasoning_tokens;
826
+ }
694
827
  return {
695
828
  inputTokens: isValidNumber(promptTokens) ? promptTokens : 0,
696
829
  outputTokens: isValidNumber(completionTokens) ? completionTokens : 0,
697
- totalTokens: isValidNumber(totalTokens) ? totalTokens : 0
830
+ totalTokens: isValidNumber(totalTokens) ? totalTokens : 0,
831
+ reasoningTokens
698
832
  };
699
833
  } catch {
700
834
  return null;
@@ -847,7 +981,12 @@ function wrapMessagesCreate(originalFn) {
847
981
  outputTokens: extracted.tokens?.outputTokens || 0,
848
982
  durationMs,
849
983
  status: "success",
850
- 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
851
990
  });
852
991
  const toolCalls = extractToolCalls(messageResponse);
853
992
  if (toolCalls.length > 0) {
@@ -901,12 +1040,19 @@ function wrapAnthropicStream(stream, request, startTime) {
901
1040
  return stream;
902
1041
  }
903
1042
  const chunks = [];
1043
+ const thinkingChunks = [];
904
1044
  let inputTokens = 0;
905
1045
  let outputTokens = 0;
1046
+ let cacheReadTokens;
1047
+ let cacheWriteTokens;
1048
+ let stopReason;
906
1049
  let model = request.model || "unknown";
907
1050
  let captured = false;
1051
+ let firstTokenMs;
1052
+ let firstTokenReceived = false;
908
1053
  const toolCalls = [];
909
1054
  let currentToolIndex = null;
1055
+ let currentBlockType = null;
910
1056
  const wrappedIterator = async function* () {
911
1057
  try {
912
1058
  for await (const event of originalStream) {
@@ -914,10 +1060,23 @@ function wrapAnthropicStream(stream, request, startTime) {
914
1060
  model = event.message.model || model;
915
1061
  if (event.message.usage) {
916
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;
917
1065
  }
918
1066
  }
1067
+ if (event.type === "content_block_start" && event.content_block) {
1068
+ currentBlockType = event.content_block.type;
1069
+ }
919
1070
  if (event.type === "content_block_delta" && event.delta?.text) {
920
- 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
+ }
921
1080
  }
922
1081
  if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
923
1082
  const block = event.content_block;
@@ -937,18 +1096,27 @@ function wrapAnthropicStream(stream, request, startTime) {
937
1096
  tool.inputJson += event.delta.partial_json;
938
1097
  }
939
1098
  }
940
- if (event.type === "content_block_stop" && currentToolIndex !== null) {
941
- const tool = toolCalls[currentToolIndex];
942
- if (tool && tool.inputJson) {
943
- try {
944
- tool.input = JSON.parse(tool.inputJson);
945
- } catch {
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
+ }
946
1107
  }
1108
+ currentToolIndex = null;
947
1109
  }
948
- currentToolIndex = null;
1110
+ currentBlockType = null;
949
1111
  }
950
- if (event.type === "message_delta" && event.usage) {
951
- outputTokens = event.usage.output_tokens || 0;
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
+ }
952
1120
  }
953
1121
  yield event;
954
1122
  }
@@ -979,7 +1147,13 @@ function wrapAnthropicStream(stream, request, startTime) {
979
1147
  outputTokens,
980
1148
  durationMs,
981
1149
  status: "success",
982
- 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
983
1157
  });
984
1158
  if (toolCalls.length > 0) {
985
1159
  captureToolSpans(
@@ -1001,22 +1175,42 @@ function wrapAnthropicStream(stream, request, startTime) {
1001
1175
  }
1002
1176
  async function* wrapStream2(stream, request, startTime) {
1003
1177
  const chunks = [];
1178
+ const thinkingChunks = [];
1004
1179
  let inputTokens = 0;
1005
1180
  let outputTokens = 0;
1181
+ let cacheReadTokens;
1182
+ let cacheWriteTokens;
1183
+ let stopReason;
1006
1184
  let model = request.model || "unknown";
1007
1185
  let error = null;
1186
+ let firstTokenMs;
1187
+ let firstTokenReceived = false;
1008
1188
  const toolCalls = [];
1009
1189
  let currentToolIndex = null;
1190
+ let currentBlockType = null;
1010
1191
  try {
1011
1192
  for await (const event of stream) {
1012
1193
  if (event.type === "message_start" && event.message) {
1013
1194
  model = event.message.model || model;
1014
1195
  if (event.message.usage) {
1015
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;
1016
1199
  }
1017
1200
  }
1201
+ if (event.type === "content_block_start" && event.content_block) {
1202
+ currentBlockType = event.content_block.type;
1203
+ }
1018
1204
  if (event.type === "content_block_delta" && event.delta?.text) {
1019
- 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
+ }
1020
1214
  }
1021
1215
  if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
1022
1216
  const block = event.content_block;
@@ -1036,18 +1230,27 @@ async function* wrapStream2(stream, request, startTime) {
1036
1230
  tool.inputJson += event.delta.partial_json;
1037
1231
  }
1038
1232
  }
1039
- if (event.type === "content_block_stop" && currentToolIndex !== null) {
1040
- const tool = toolCalls[currentToolIndex];
1041
- if (tool && tool.inputJson) {
1042
- try {
1043
- tool.input = JSON.parse(tool.inputJson);
1044
- } catch {
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
+ }
1045
1241
  }
1242
+ currentToolIndex = null;
1046
1243
  }
1047
- currentToolIndex = null;
1244
+ currentBlockType = null;
1048
1245
  }
1049
- if (event.type === "message_delta" && event.usage) {
1050
- outputTokens = event.usage.output_tokens || 0;
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
+ }
1051
1254
  }
1052
1255
  yield event;
1053
1256
  }
@@ -1075,7 +1278,13 @@ async function* wrapStream2(stream, request, startTime) {
1075
1278
  outputTokens,
1076
1279
  durationMs,
1077
1280
  status: "success",
1078
- 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
1079
1288
  });
1080
1289
  if (toolCalls.length > 0) {
1081
1290
  captureToolSpans(
@@ -1088,13 +1297,19 @@ async function* wrapStream2(stream, request, startTime) {
1088
1297
  }
1089
1298
  function extractMessageResponse(response) {
1090
1299
  const model = safeExtract(() => response.model ?? null, null);
1300
+ const stopReason = safeExtract(() => response.stop_reason ?? null, null);
1091
1301
  const output = safeExtract(() => {
1092
1302
  if (!response.content || !Array.isArray(response.content)) return null;
1093
1303
  const textBlocks = response.content.filter((block) => block.type === "text" && block.text).map((block) => block.text);
1094
1304
  return textBlocks.join("") || null;
1095
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);
1096
1311
  const tokens = extractTokens2(response);
1097
- return { model, output, tokens };
1312
+ return { model, output, tokens, stopReason, thinking };
1098
1313
  }
1099
1314
  function extractTokens2(response) {
1100
1315
  try {
@@ -1108,7 +1323,10 @@ function extractTokens2(response) {
1108
1323
  return {
1109
1324
  inputTokens: isValidNumber(inputTokens) ? inputTokens : 0,
1110
1325
  outputTokens: isValidNumber(outputTokens) ? outputTokens : 0,
1111
- 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
1112
1330
  };
1113
1331
  } catch {
1114
1332
  return null;
@@ -1188,11 +1406,12 @@ async function handleConverse(send, command) {
1188
1406
  durationMs,
1189
1407
  status: "success",
1190
1408
  streaming: false,
1409
+ // Extended fields (Phase 7.1)
1410
+ stopReason: response.stopReason,
1411
+ cacheReadTokens: extracted.cacheReadTokens,
1412
+ cacheWriteTokens: extracted.cacheWriteTokens,
1191
1413
  metadata: {
1192
- stopReason: response.stopReason,
1193
1414
  hasToolUse: extracted.hasToolUse,
1194
- cacheReadTokens: extracted.cacheReadTokens,
1195
- cacheWriteTokens: extracted.cacheWriteTokens,
1196
1415
  latencyMs: response.metrics?.latencyMs
1197
1416
  }
1198
1417
  });
@@ -1242,10 +1461,17 @@ async function* wrapConverseStream(stream, input, startTime) {
1242
1461
  let inputTokens = 0;
1243
1462
  let outputTokens = 0;
1244
1463
  let error = null;
1464
+ let stopReason;
1465
+ let firstTokenMs;
1466
+ let firstContentReceived = false;
1245
1467
  const toolCalls = /* @__PURE__ */ new Map();
1246
1468
  try {
1247
1469
  for await (const event of stream) {
1248
- 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) {
1249
1475
  chunks.push(event.contentBlockDelta.delta.text);
1250
1476
  }
1251
1477
  if (event.contentBlockStart?.start?.toolUse) {
@@ -1262,6 +1488,9 @@ async function* wrapConverseStream(stream, input, startTime) {
1262
1488
  tool.inputJson += event.contentBlockDelta.delta.toolUse.input;
1263
1489
  }
1264
1490
  }
1491
+ if (event.messageStop?.stopReason) {
1492
+ stopReason = event.messageStop.stopReason;
1493
+ }
1265
1494
  if (event.metadata?.usage) {
1266
1495
  inputTokens = event.metadata.usage.inputTokens || 0;
1267
1496
  outputTokens = event.metadata.usage.outputTokens || 0;
@@ -1292,7 +1521,10 @@ async function* wrapConverseStream(stream, input, startTime) {
1292
1521
  outputTokens,
1293
1522
  durationMs,
1294
1523
  status: "success",
1295
- streaming: true
1524
+ streaming: true,
1525
+ // Extended fields (Phase 7.1)
1526
+ stopReason,
1527
+ firstTokenMs
1296
1528
  });
1297
1529
  if (toolCalls.size > 0) {
1298
1530
  const tools = Array.from(toolCalls.values()).map((t) => {
@@ -2117,6 +2349,6 @@ function createObserve(defaultOptions) {
2117
2349
  };
2118
2350
  }
2119
2351
 
2120
- export { captureSpan, createObserve, flush, init, isEnabled, observe };
2352
+ export { captureSpan, createObserve, flush, getTraceContext, init, isEnabled, observe, span, trace };
2121
2353
  //# sourceMappingURL=index.mjs.map
2122
2354
  //# sourceMappingURL=index.mjs.map