neatlogs 1.1.11 → 1.1.13

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.cjs CHANGED
@@ -66,6 +66,8 @@ __export(index_exports, {
66
66
  span: () => span,
67
67
  strandsHooks: () => strandsHooks,
68
68
  trace: () => trace,
69
+ tracePiAgentEvents: () => tracePiAgentEvents,
70
+ tracePiStream: () => tracePiStream,
69
71
  traceToolAnthropic: () => traceTool2,
70
72
  traceToolAzureOpenAI: () => traceTool3,
71
73
  traceToolBedrock: () => traceTool6,
@@ -4873,6 +4875,8 @@ var NeatlogsSpanProcessor = class {
4873
4875
  if (!currentModel) return;
4874
4876
  const llmOutput = attrs["neatlogs.llm.output"];
4875
4877
  if (!llmOutput) return;
4878
+ const trimmed = llmOutput.trimStart();
4879
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
4876
4880
  try {
4877
4881
  const output = JSON.parse(llmOutput);
4878
4882
  const generations = output?.generations;
@@ -4897,7 +4901,9 @@ var NeatlogsSpanProcessor = class {
4897
4901
  }
4898
4902
  }
4899
4903
  } catch (e) {
4900
- logger10.warn(`[ModelResolve] Failed to parse LLM output for model extraction: ${e}`);
4904
+ if (this.debug) {
4905
+ logger10.debug(`[ModelResolve] Failed to parse LLM output for model extraction: ${e}`);
4906
+ }
4901
4907
  }
4902
4908
  }
4903
4909
  // ── forceFlush / shutdown ─────────────────────────────
@@ -5058,7 +5064,8 @@ var INSTRUMENTATION_REGISTRY = {
5058
5064
  "ai_sdk",
5059
5065
  "claude_agent_sdk",
5060
5066
  "openrouter_agent",
5061
- "opencode"
5067
+ "opencode",
5068
+ "pi_agent"
5062
5069
  ],
5063
5070
  tool: ["langchain", "llamaindex", "haystack", "mcp"],
5064
5071
  http: ["requests", "httpx", "urllib3", "aiohttp"],
@@ -5433,6 +5440,13 @@ var INSTRUMENTATION_REGISTRY = {
5433
5440
  neatlogs: "neatlogs/opencode",
5434
5441
  default_span_kind: "AGENT",
5435
5442
  explicitWrapper: "NeatlogsOpencodePlugin from 'neatlogs/opencode'"
5443
+ },
5444
+ pi_agent: {
5445
+ openinference: null,
5446
+ openllmetry: null,
5447
+ neatlogs: "neatlogs/pi-agent",
5448
+ default_span_kind: "AGENT",
5449
+ explicitWrapper: "piAgentHooks() from 'neatlogs/pi-agent'"
5436
5450
  }
5437
5451
  }
5438
5452
  };
@@ -6009,7 +6023,7 @@ function _resetMastraCache() {
6009
6023
  }
6010
6024
 
6011
6025
  // src/version.ts
6012
- var __version__ = "1.1.11";
6026
+ var __version__ = "1.1.13";
6013
6027
 
6014
6028
  // src/init.ts
6015
6029
  var logger13 = getLogger();
@@ -10273,22 +10287,342 @@ function recordError7(span2, err) {
10273
10287
  var import_api19 = require("@opentelemetry/api");
10274
10288
  var TRACER_NAME12 = "neatlogs.pi-agent";
10275
10289
  var PATCH_FLAG2 = "_neatlogs_patched";
10290
+ var HARNESS_METHOD_FLAG = "_neatlogs_harness_methods_patched";
10276
10291
  function piAgentHooks(agent) {
10277
10292
  if (!agent || agent[PATCH_FLAG2]) return agent;
10278
10293
  const a = agent;
10279
10294
  if (typeof a.subscribe !== "function") return agent;
10280
- const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
10295
+ const listener = tracePiAgentEvents(() => a.state?.messages);
10296
+ a.subscribe((event) => listener(event));
10297
+ wrapHarnessModelOperations(a);
10298
+ markPatched2(a);
10299
+ return agent;
10300
+ }
10301
+ function wrapHarnessModelOperations(harness) {
10302
+ if (harness[HARNESS_METHOD_FLAG]) return;
10303
+ const isHarness = typeof harness.compact === "function" && typeof harness.navigateTree === "function" && typeof harness.getModel === "function";
10304
+ if (!isHarness) return;
10305
+ patchHarnessMethod(harness, "compact", (args) => ({ customInstructions: args[0] }));
10306
+ patchHarnessMethod(harness, "navigateTree", (args) => ({ targetId: args[0], options: args[1] }));
10307
+ try {
10308
+ Object.defineProperty(harness, HARNESS_METHOD_FLAG, { value: true });
10309
+ } catch {
10310
+ harness[HARNESS_METHOD_FLAG] = true;
10311
+ }
10312
+ }
10313
+ function patchHarnessMethod(harness, method, inputOf) {
10314
+ const original = harness[method].bind(harness);
10315
+ harness[method] = async (...args) => {
10316
+ const startedAt = Date.now();
10317
+ const parent = getNeatlogsParentContext();
10318
+ const input = inputOf(args);
10319
+ let observedModelCall;
10320
+ let stopObserving;
10321
+ try {
10322
+ stopObserving = harness.subscribe((event) => {
10323
+ if (method === "compact" && event?.type === "session_compact") {
10324
+ observedModelCall = !event.fromHook;
10325
+ } else if (method === "navigateTree" && event?.type === "session_tree") {
10326
+ observedModelCall = Boolean(event.summaryEntry) && !event.fromHook;
10327
+ }
10328
+ });
10329
+ } catch {
10330
+ }
10331
+ let result;
10332
+ let failure;
10333
+ try {
10334
+ result = await original(...args);
10335
+ return result;
10336
+ } catch (err) {
10337
+ failure = err;
10338
+ throw err;
10339
+ } finally {
10340
+ stopObserving?.();
10341
+ try {
10342
+ emitHarnessOperation(
10343
+ method,
10344
+ input,
10345
+ result,
10346
+ failure,
10347
+ harness.getModel?.(),
10348
+ parent,
10349
+ startedAt,
10350
+ Date.now(),
10351
+ observedModelCall
10352
+ );
10353
+ } catch {
10354
+ }
10355
+ }
10356
+ };
10357
+ }
10358
+ function emitHarnessOperation(method, input, result, failure, model, capturedParent, startedAt, endedAt, observedModelCall) {
10281
10359
  const tracer = getNeatlogsTracer(TRACER_NAME12);
10282
- a.subscribe((event) => {
10360
+ let parent = capturedParent;
10361
+ let root;
10362
+ const inputText = safeStringify12(input ?? {});
10363
+ const outputText = failure ? `[error] ${failure instanceof Error ? failure.message : String(failure)}` : harnessOperationOutput(method, result);
10364
+ if (!import_api19.trace.getSpan(parent)?.isRecording()) {
10365
+ root = tracer.startSpan(
10366
+ `pi_agent.harness.${method}`,
10367
+ {
10368
+ startTime: startedAt,
10369
+ attributes: {
10370
+ "neatlogs.span.kind": "WORKFLOW",
10371
+ "input.value": inputText
10372
+ }
10373
+ },
10374
+ parent
10375
+ );
10376
+ parent = import_api19.trace.setSpan(parent, root);
10377
+ }
10378
+ const chain = tracer.startSpan(
10379
+ `pi_agent.harness.${method}`,
10380
+ {
10381
+ startTime: startedAt,
10382
+ attributes: {
10383
+ "neatlogs.span.kind": "CHAIN",
10384
+ "neatlogs.pi.operation": method,
10385
+ "input.value": inputText
10386
+ }
10387
+ },
10388
+ parent
10389
+ );
10390
+ const chainCtx = import_api19.trace.setSpan(parent, chain);
10391
+ const usage = harnessOperationUsage(method, result);
10392
+ const madeModelCall = observedModelCall ?? (failure ? isHarnessSummarizationFailure(failure) : method === "compact" ? Boolean(result?.usage) : Boolean(result?.summaryEntry) && result.summaryEntry.fromHook !== true);
10393
+ if (madeModelCall || failure) {
10394
+ const llm = openLlmSpan(
10395
+ tracer,
10396
+ {
10397
+ toolSpans: /* @__PURE__ */ new Map(),
10398
+ inputMessages: [{ role: "user", content: inputText }],
10399
+ turnIndex: 0,
10400
+ turnCtx: chainCtx,
10401
+ turnStartEpochMs: startedAt
10402
+ },
10403
+ {
10404
+ role: "assistant",
10405
+ model: model?.id ?? model?.model,
10406
+ provider: model?.provider,
10407
+ timestamp: startedAt
10408
+ }
10409
+ );
10410
+ if (failure) {
10411
+ closeLlmFailure(llm, failure, endedAt);
10412
+ } else {
10413
+ finishLlmSpan(
10414
+ llm,
10415
+ {
10416
+ role: "assistant",
10417
+ model: model?.id ?? model?.model,
10418
+ provider: model?.provider,
10419
+ content: outputText ? [{ type: "text", text: outputText }] : [],
10420
+ usage,
10421
+ stopReason: "stop"
10422
+ },
10423
+ endedAt
10424
+ );
10425
+ }
10426
+ }
10427
+ if (outputText) chain.setAttribute("output.value", outputText);
10428
+ if (failure) {
10429
+ const message = failure instanceof Error ? failure.message : String(failure);
10430
+ chain.setAttribute("neatlogs.error.message", message);
10431
+ chain.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10432
+ } else {
10433
+ chain.setStatus({ code: import_api19.SpanStatusCode.OK });
10434
+ }
10435
+ chain.end(endedAt);
10436
+ if (root) {
10437
+ if (outputText) root.setAttribute("output.value", outputText);
10438
+ if (failure) {
10439
+ const message = failure instanceof Error ? failure.message : String(failure);
10440
+ root.setAttribute("neatlogs.error.message", message);
10441
+ root.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10442
+ } else {
10443
+ root.setStatus({ code: import_api19.SpanStatusCode.OK });
10444
+ }
10445
+ root.end(endedAt);
10446
+ }
10447
+ }
10448
+ function isHarnessSummarizationFailure(failure) {
10449
+ let current = failure;
10450
+ const seen = /* @__PURE__ */ new Set();
10451
+ while (current && !seen.has(current)) {
10452
+ seen.add(current);
10453
+ if (current.code === "summarization_failed") return true;
10454
+ current = current.cause;
10455
+ }
10456
+ return false;
10457
+ }
10458
+ function harnessOperationOutput(method, result) {
10459
+ if (method === "compact") return typeof result?.summary === "string" ? result.summary : safeStringify12(result ?? {});
10460
+ const summary = result?.summaryEntry?.summary;
10461
+ return typeof summary === "string" ? summary : safeStringify12(result ?? {});
10462
+ }
10463
+ function harnessOperationUsage(method, result) {
10464
+ return method === "compact" ? result?.usage : result?.summaryEntry?.usage;
10465
+ }
10466
+ function closeLlmFailure(llm, failure, endedAt) {
10467
+ const message = failure instanceof Error ? failure.message : String(failure);
10468
+ llm.span.setAttribute("output.value", `[error] ${message}`);
10469
+ llm.span.setAttribute("neatlogs.error.message", message);
10470
+ llm.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10471
+ llm.span.end(endedAt);
10472
+ }
10473
+ function tracePiAgentEvents(getTranscript) {
10474
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10475
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10476
+ return (event) => {
10477
+ try {
10478
+ handleEvent(tracer, state, event, getTranscript);
10479
+ } catch {
10480
+ }
10481
+ };
10482
+ }
10483
+ function tracePiStream(streamFn) {
10484
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10485
+ return ((model, context2, options) => {
10486
+ let llm;
10487
+ let root;
10283
10488
  try {
10284
- handleEvent(tracer, state, event);
10489
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10490
+ state.inputMessages = promptMessagesOf(context2);
10491
+ let parent = getNeatlogsParentContext();
10492
+ if (!import_api19.trace.getSpan(parent)?.isRecording()) {
10493
+ root = tracer.startSpan(
10494
+ "pi_agent.stream",
10495
+ { attributes: { "neatlogs.span.kind": "WORKFLOW" } },
10496
+ parent
10497
+ );
10498
+ parent = import_api19.trace.setSpan(parent, root);
10499
+ const rootInput = state.inputMessages.length ? safeStringify12({ messages: state.inputMessages }) : void 0;
10500
+ if (rootInput) root.setAttribute("input.value", rootInput);
10501
+ }
10502
+ state.turnCtx = parent;
10503
+ llm = openLlmSpan(tracer, state, {
10504
+ role: "assistant",
10505
+ model: model?.id ?? model?.model,
10506
+ provider: model?.provider
10507
+ });
10285
10508
  } catch {
10286
10509
  }
10510
+ let returned;
10511
+ try {
10512
+ returned = streamFn(model, context2, options);
10513
+ } catch (err) {
10514
+ closeStreamFailure(llm, root, err);
10515
+ throw err;
10516
+ }
10517
+ if (isThenable(returned)) {
10518
+ return returned.then(
10519
+ (stream) => attachStreamResult(stream, llm, root),
10520
+ (err) => {
10521
+ closeStreamFailure(llm, root, err);
10522
+ throw err;
10523
+ }
10524
+ );
10525
+ }
10526
+ return attachStreamResult(returned, llm, root);
10287
10527
  });
10288
- markPatched2(a);
10289
- return agent;
10290
10528
  }
10291
- function handleEvent(tracer, state, event) {
10529
+ function attachStreamResult(stream, llm, root) {
10530
+ if (!llm || !stream || typeof stream.result !== "function") {
10531
+ closeStreamRoot(root);
10532
+ return stream;
10533
+ }
10534
+ observeStreamDeltas(stream, llm);
10535
+ stream.result().then(
10536
+ (msg) => {
10537
+ const message = msg ?? { role: "assistant" };
10538
+ finishLlmSpan(llm, message);
10539
+ closeStreamRoot(root, message);
10540
+ },
10541
+ (err) => closeStreamFailure(llm, root, err)
10542
+ );
10543
+ return stream;
10544
+ }
10545
+ function observeStreamDeltas(stream, llm) {
10546
+ const original = stream?.[Symbol.asyncIterator];
10547
+ if (typeof original !== "function" || original.__neatlogsObserved) return;
10548
+ const observed = function() {
10549
+ const iterator = original.call(this);
10550
+ return {
10551
+ next: async (...args) => {
10552
+ const item = await iterator.next(...args);
10553
+ if (!item.done && isContentDelta(item.value?.type)) markFirstStreamDelta(llm);
10554
+ return item;
10555
+ },
10556
+ return: iterator.return?.bind(iterator),
10557
+ throw: iterator.throw?.bind(iterator)
10558
+ };
10559
+ };
10560
+ Object.defineProperty(observed, "__neatlogsObserved", { value: true });
10561
+ try {
10562
+ stream[Symbol.asyncIterator] = observed;
10563
+ } catch {
10564
+ }
10565
+ }
10566
+ function markFirstStreamDelta(llm) {
10567
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10568
+ if (llm.ttftMs !== void 0) return;
10569
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10570
+ }
10571
+ function closeStreamFailure(llm, root, err) {
10572
+ const message = err instanceof Error ? err.message : String(err);
10573
+ try {
10574
+ llm?.span.setAttribute("output.value", `[error] ${message}`);
10575
+ llm?.span.setAttribute("neatlogs.error.message", message);
10576
+ llm?.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10577
+ llm?.span.end();
10578
+ } catch {
10579
+ }
10580
+ closeStreamRoot(root, void 0, message);
10581
+ }
10582
+ function isThenable(value) {
10583
+ return value != null && typeof value.then === "function";
10584
+ }
10585
+ function closeStreamRoot(root, msg, error) {
10586
+ if (!root) return;
10587
+ try {
10588
+ if (msg) {
10589
+ const { text, toolCalls } = splitAssistantContent(msg.content);
10590
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10591
+ if (out) root.setAttribute("output.value", out);
10592
+ applyStopReasonStatus(root, msg);
10593
+ } else if (error) {
10594
+ root.setAttribute("output.value", `[error] ${error}`);
10595
+ root.setAttribute("neatlogs.error.message", error);
10596
+ root.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: error });
10597
+ }
10598
+ root.end();
10599
+ } catch {
10600
+ }
10601
+ }
10602
+ function transcriptRows(messages) {
10603
+ if (!Array.isArray(messages)) return [];
10604
+ const rows = [];
10605
+ for (const m of messages) {
10606
+ const content = messageText(m);
10607
+ if (!content) continue;
10608
+ rows.push({ role: m?.role === "toolResult" ? "tool" : String(m?.role ?? "user"), content });
10609
+ }
10610
+ return rows;
10611
+ }
10612
+ function promptMessagesOf(context2) {
10613
+ const rows = [];
10614
+ if (typeof context2?.systemPrompt === "string" && context2.systemPrompt) {
10615
+ rows.push({ role: "system", content: context2.systemPrompt });
10616
+ }
10617
+ if (Array.isArray(context2?.messages)) {
10618
+ for (const m of context2.messages) {
10619
+ const content = messageText(m);
10620
+ if (content) rows.push({ role: String(m?.role ?? "user"), content });
10621
+ }
10622
+ }
10623
+ return rows;
10624
+ }
10625
+ function handleEvent(tracer, state, event, getTranscript) {
10292
10626
  switch (event.type) {
10293
10627
  case "agent_start": {
10294
10628
  const base = getNeatlogsParentContext();
@@ -10299,31 +10633,98 @@ function handleEvent(tracer, state, event) {
10299
10633
  );
10300
10634
  state.agentSpan = span2;
10301
10635
  state.agentCtx = import_api19.trace.setSpan(base, span2);
10302
- state.inputMessages = [];
10636
+ state.inputMessages = transcriptRows(getTranscript?.());
10637
+ state.turnIndex = 0;
10638
+ state.runInput = void 0;
10639
+ state.runError = void 0;
10640
+ break;
10641
+ }
10642
+ case "turn_start": {
10643
+ const parent = state.agentCtx ?? getNeatlogsParentContext();
10644
+ state.turnIndex += 1;
10645
+ const pending = state.inputMessages[state.inputMessages.length - 1];
10646
+ const priorInput = pending && (pending.role === "user" || pending.role === "tool") ? pending.content : void 0;
10647
+ const span2 = tracer.startSpan(
10648
+ `pi_agent.turn.${state.turnIndex}`,
10649
+ {
10650
+ attributes: {
10651
+ "neatlogs.span.kind": "CHAIN",
10652
+ "neatlogs.chain.turn_index": state.turnIndex,
10653
+ ...priorInput ? { "input.value": priorInput } : {}
10654
+ }
10655
+ },
10656
+ parent
10657
+ );
10658
+ state.turnSpan = span2;
10659
+ state.turnCtx = import_api19.trace.setSpan(parent, span2);
10660
+ state.turnHasInput = !!priorInput;
10661
+ state.turnStartEpochMs = Date.now();
10662
+ break;
10663
+ }
10664
+ case "turn_end": {
10665
+ if (!state.turnSpan) break;
10666
+ const msg = event.message;
10667
+ const { text, toolCalls } = splitAssistantContent(msg?.content);
10668
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10669
+ if (out) state.turnSpan.setAttribute("output.value", out);
10670
+ if (Array.isArray(event.toolResults) && event.toolResults.length) {
10671
+ state.turnSpan.setAttribute("neatlogs.chain.tool_result_count", event.toolResults.length);
10672
+ }
10673
+ applyStopReasonStatus(state.turnSpan, msg);
10674
+ state.turnSpan.end();
10675
+ state.turnSpan = void 0;
10676
+ state.turnCtx = void 0;
10677
+ state.turnHasInput = false;
10678
+ state.turnStartEpochMs = void 0;
10679
+ break;
10680
+ }
10681
+ case "message_start": {
10682
+ const msg = event.message;
10683
+ if (!msg || msg.role !== "assistant") break;
10684
+ state.llm = openLlmSpan(tracer, state, msg);
10685
+ break;
10686
+ }
10687
+ case "message_update": {
10688
+ const llm = state.llm;
10689
+ if (!llm) break;
10690
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10691
+ if (llm.ttftMs === void 0 && isContentDelta(event.assistantMessageEvent?.type)) {
10692
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10693
+ }
10303
10694
  break;
10304
10695
  }
10305
10696
  case "message_end": {
10306
10697
  const msg = event.message;
10307
10698
  if (!msg) return;
10308
10699
  if (msg.role === "assistant") {
10309
- emitLlmSpan(tracer, state, msg);
10700
+ const llm = state.llm ?? openLlmSpan(tracer, state, msg);
10701
+ state.llm = void 0;
10702
+ state.runError = finishLlmSpan(llm, msg) ?? state.runError;
10310
10703
  const { text } = splitAssistantContent(msg.content);
10311
10704
  if (text) state.inputMessages.push({ role: "assistant", content: text });
10312
10705
  } else {
10313
10706
  const role = msg.role === "toolResult" ? "tool" : String(msg.role || "user");
10314
10707
  const content = messageText(msg);
10315
- if (content) state.inputMessages.push({ role, content });
10708
+ if (content) {
10709
+ state.inputMessages.push({ role, content });
10710
+ if (role === "user" && state.runInput === void 0) state.runInput = content;
10711
+ if (state.turnSpan && !state.turnHasInput) {
10712
+ state.turnSpan.setAttribute("input.value", content);
10713
+ state.turnHasInput = true;
10714
+ }
10715
+ }
10316
10716
  }
10317
10717
  break;
10318
10718
  }
10319
10719
  case "tool_execution_start": {
10320
- const parent = state.agentCtx ?? getNeatlogsParentContext();
10720
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10321
10721
  const span2 = tracer.startSpan(
10322
10722
  `pi_agent.tool.${event.toolName ?? "tool"}`,
10323
10723
  {
10324
10724
  attributes: {
10325
10725
  "neatlogs.span.kind": "TOOL",
10326
10726
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
10727
+ ...event.toolCallId ? { "neatlogs.tool.call_id": String(event.toolCallId) } : {},
10327
10728
  ...event.args !== void 0 ? { "input.value": safeStringify12(event.args) } : {}
10328
10729
  }
10329
10730
  },
@@ -10332,6 +10733,12 @@ function handleEvent(tracer, state, event) {
10332
10733
  if (event.toolCallId) state.toolSpans.set(event.toolCallId, span2);
10333
10734
  break;
10334
10735
  }
10736
+ case "tool_execution_update": {
10737
+ const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10738
+ if (!span2) break;
10739
+ span2.setAttribute("neatlogs.tool.is_streaming", true);
10740
+ break;
10741
+ }
10335
10742
  case "tool_execution_end": {
10336
10743
  const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10337
10744
  if (!span2) return;
@@ -10339,7 +10746,7 @@ function handleEvent(tracer, state, event) {
10339
10746
  span2.setAttribute("output.value", safeStringify12(event.result));
10340
10747
  }
10341
10748
  if (event.isError) {
10342
- span2.setStatus({ code: import_api19.SpanStatusCode.ERROR });
10749
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: toolErrorText(event.result) });
10343
10750
  span2.setAttribute("neatlogs.tool.is_error", true);
10344
10751
  } else {
10345
10752
  span2.setStatus({ code: import_api19.SpanStatusCode.OK });
@@ -10351,17 +10758,47 @@ function handleEvent(tracer, state, event) {
10351
10758
  case "agent_end": {
10352
10759
  for (const ts of state.toolSpans.values()) {
10353
10760
  try {
10761
+ ts.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: "run ended before tool completed" });
10354
10762
  ts.end();
10355
10763
  } catch {
10356
10764
  }
10357
10765
  }
10358
10766
  state.toolSpans.clear();
10767
+ if (state.llm) {
10768
+ try {
10769
+ state.llm.span.setAttribute("output.value", "[incomplete] run ended mid-stream");
10770
+ state.llm.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: "run ended mid-stream" });
10771
+ state.llm.span.end();
10772
+ } catch {
10773
+ }
10774
+ state.llm = void 0;
10775
+ }
10776
+ if (state.turnSpan) {
10777
+ try {
10778
+ state.turnSpan.end();
10779
+ } catch {
10780
+ }
10781
+ state.turnSpan = void 0;
10782
+ state.turnCtx = void 0;
10783
+ state.turnStartEpochMs = void 0;
10784
+ }
10359
10785
  if (state.agentSpan) {
10360
- const firstUser = state.inputMessages.find((m) => m.role === "user");
10361
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
10786
+ const input = state.runInput ?? lastUserTextFrom(event.messages) ?? lastUserTextFrom(getTranscript?.());
10787
+ if (input) state.agentSpan.setAttribute("input.value", input);
10362
10788
  const finalText = lastAssistantText(event.messages);
10363
- if (finalText) state.agentSpan.setAttribute("output.value", finalText);
10364
- state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
10789
+ const err = state.runError ?? terminalErrorFrom(event.messages);
10790
+ if (finalText) {
10791
+ state.agentSpan.setAttribute("output.value", finalText);
10792
+ } else if (err) {
10793
+ state.agentSpan.setAttribute("output.value", `[${err.stopReason}]${err.message ? ` ${err.message}` : ""}`);
10794
+ }
10795
+ if (err) {
10796
+ state.agentSpan.setAttribute("neatlogs.agent.stop_reason", err.stopReason);
10797
+ if (err.message) state.agentSpan.setAttribute("neatlogs.error.message", err.message);
10798
+ state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: err.message ?? err.stopReason });
10799
+ } else {
10800
+ state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
10801
+ }
10365
10802
  state.agentSpan.end();
10366
10803
  state.agentSpan = void 0;
10367
10804
  state.agentCtx = void 0;
@@ -10372,55 +10809,158 @@ function handleEvent(tracer, state, event) {
10372
10809
  break;
10373
10810
  }
10374
10811
  }
10375
- function emitLlmSpan(tracer, state, msg) {
10376
- const attrs = { "neatlogs.span.kind": "LLM" };
10377
- if (msg.model) attrs["neatlogs.llm.model_name"] = String(msg.model);
10378
- if (msg.provider) attrs["neatlogs.llm.provider"] = String(msg.provider);
10379
- if (msg.stopReason) attrs["neatlogs.llm.stop_reason"] = String(msg.stopReason);
10380
- const inMsgs = state.inputMessages;
10812
+ function openLlmSpan(tracer, state, msg) {
10813
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10814
+ const startTime = callStartFrom(msg, state.turnStartEpochMs);
10815
+ const span2 = tracer.startSpan(
10816
+ `pi_agent.llm.${msg.model || "model"}`,
10817
+ {
10818
+ ...startTime !== void 0 ? { startTime } : {},
10819
+ attributes: {
10820
+ "neatlogs.span.kind": "LLM",
10821
+ // Pi streams by default; message_update deltas confirm it per call.
10822
+ "neatlogs.llm.is_streaming": false,
10823
+ ...msg.model ? { "neatlogs.llm.model_name": String(msg.model) } : {},
10824
+ ...msg.provider ? { "neatlogs.llm.provider": String(msg.provider) } : {}
10825
+ }
10826
+ },
10827
+ parent
10828
+ );
10829
+ return {
10830
+ span: span2,
10831
+ startHr: nowMs(),
10832
+ callStartEpochMs: startTime,
10833
+ inputMessages: state.inputMessages.slice()
10834
+ };
10835
+ }
10836
+ function callStartFrom(msg, turnStart) {
10837
+ const ts = msg.timestamp;
10838
+ if (typeof ts !== "number" || !Number.isFinite(ts)) return void 0;
10839
+ const now = Date.now();
10840
+ const age = now - ts;
10841
+ if (age < 0 || age > MAX_CALL_AGE_MS) return void 0;
10842
+ return turnStart !== void 0 ? Math.max(ts, turnStart) : ts;
10843
+ }
10844
+ var MAX_CALL_AGE_MS = 30 * 6e4;
10845
+ function terminalSummary(msg) {
10846
+ const reason = msg?.stopReason;
10847
+ if (reason !== "aborted" && reason !== "error") return "";
10848
+ return `[${reason}]${msg?.errorMessage ? ` ${msg.errorMessage}` : ""}`;
10849
+ }
10850
+ function applyStopReasonStatus(span2, msg) {
10851
+ const reason = msg?.stopReason;
10852
+ if (reason === "aborted" || reason === "error") {
10853
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: msg?.errorMessage ?? reason });
10854
+ } else {
10855
+ span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10856
+ }
10857
+ }
10858
+ function isContentDelta(type) {
10859
+ return type === "text_delta" || type === "thinking_delta" || type === "toolcall_delta";
10860
+ }
10861
+ function nowMs() {
10862
+ const p = globalThis.performance;
10863
+ return typeof p?.now === "function" ? p.now() : Date.now();
10864
+ }
10865
+ function toolErrorText(result) {
10866
+ if (!result || typeof result !== "object") return "tool error";
10867
+ const content = result.content;
10868
+ if (Array.isArray(content)) {
10869
+ const text = content.map((b) => typeof b?.text === "string" ? b.text : "").filter(Boolean).join(" ").trim();
10870
+ if (text) return text;
10871
+ }
10872
+ return "tool error";
10873
+ }
10874
+ function lastUserTextFrom(messages) {
10875
+ if (!Array.isArray(messages)) return void 0;
10876
+ for (let i = messages.length - 1; i >= 0; i--) {
10877
+ const m = messages[i];
10878
+ if (m && m.role === "user") {
10879
+ const text = messageText(m);
10880
+ if (text) return text;
10881
+ }
10882
+ }
10883
+ return void 0;
10884
+ }
10885
+ function terminalErrorFrom(messages) {
10886
+ if (!Array.isArray(messages)) return void 0;
10887
+ for (let i = messages.length - 1; i >= 0; i--) {
10888
+ const m = messages[i];
10889
+ if (m && m.role === "assistant") {
10890
+ if (m.stopReason === "aborted" || m.stopReason === "error") {
10891
+ return { stopReason: m.stopReason, message: m.errorMessage };
10892
+ }
10893
+ return void 0;
10894
+ }
10895
+ }
10896
+ return void 0;
10897
+ }
10898
+ function finishLlmSpan(llm, msg, endTime) {
10899
+ const span2 = llm.span;
10900
+ if (msg.model) span2.setAttribute("neatlogs.llm.model_name", String(msg.model));
10901
+ if (msg.provider) span2.setAttribute("neatlogs.llm.provider", String(msg.provider));
10902
+ if (msg.responseModel) span2.setAttribute("neatlogs.llm.response_model", String(msg.responseModel));
10903
+ if (msg.api) span2.setAttribute("neatlogs.llm.api", String(msg.api));
10904
+ if (msg.stopReason) span2.setAttribute("neatlogs.llm.stop_reason", String(msg.stopReason));
10905
+ const inMsgs = llm.inputMessages;
10381
10906
  if (inMsgs.length) {
10382
10907
  inMsgs.forEach((m, i) => {
10383
- attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
10384
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
10908
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.role`, m.role);
10909
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.content`, m.content);
10385
10910
  });
10386
- attrs["neatlogs.llm.input"] = safeStringify12({ messages: inMsgs });
10387
- attrs["input.value"] = safeStringify12({ messages: inMsgs });
10911
+ const inBlob = safeStringify12({ messages: inMsgs });
10912
+ span2.setAttribute("neatlogs.llm.input", inBlob);
10913
+ span2.setAttribute("input.value", inBlob);
10388
10914
  }
10389
10915
  const { text, toolCalls } = splitAssistantContent(msg.content);
10390
- const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n");
10916
+ const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10391
10917
  if (outText || toolCalls.length) {
10392
- attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
10393
- attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
10918
+ span2.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
10919
+ span2.setAttribute("neatlogs.llm.output_messages.0.content", outText || "");
10394
10920
  const outBlob = { role: "assistant", content: outText || "" };
10395
10921
  if (toolCalls.length) {
10396
10922
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
10397
10923
  toolCalls.forEach((tc, j) => {
10398
- if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;
10924
+ if (tc.name) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
10399
10925
  if (tc.arguments !== void 0)
10400
- attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify12(tc.arguments);
10401
- if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
10926
+ span2.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify12(tc.arguments));
10927
+ if (tc.id) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, String(tc.id));
10402
10928
  });
10403
10929
  }
10404
- attrs["neatlogs.llm.output"] = safeStringify12(outBlob);
10405
- attrs["output.value"] = outText || "";
10930
+ span2.setAttribute("neatlogs.llm.output", safeStringify12(outBlob));
10931
+ span2.setAttribute("output.value", outText || "");
10406
10932
  }
10407
10933
  const usage = msg.usage;
10408
10934
  if (usage) {
10409
- if (usage.input != null) attrs["neatlogs.llm.token_count.prompt"] = usage.input;
10410
- if (usage.output != null) attrs["neatlogs.llm.token_count.completion"] = usage.output;
10935
+ if (usage.input != null) span2.setAttribute("neatlogs.llm.token_count.prompt", usage.input);
10936
+ if (usage.output != null) span2.setAttribute("neatlogs.llm.token_count.completion", usage.output);
10411
10937
  const total = usage.totalTokens ?? (usage.input ?? 0) + (usage.output ?? 0);
10412
- if (total) attrs["neatlogs.llm.token_count.total"] = total;
10413
- if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
10414
- if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
10938
+ if (total) span2.setAttribute("neatlogs.llm.token_count.total", total);
10939
+ if (usage.cacheRead) span2.setAttribute("neatlogs.llm.token_count.cache_read", usage.cacheRead);
10940
+ if (usage.cacheWrite) span2.setAttribute("neatlogs.llm.token_count.cache_write", usage.cacheWrite);
10941
+ const cost = usage.cost;
10942
+ if (cost) {
10943
+ const totalCost = cost.total ?? [cost.input, cost.output, cacheReadOf(cost), cost.cacheWrite].reduce(
10944
+ (sum, v) => sum + (typeof v === "number" ? v : 0),
10945
+ 0
10946
+ );
10947
+ if (typeof totalCost === "number" && totalCost > 0) {
10948
+ span2.setAttribute("neatlogs.llm.cost_usd", totalCost);
10949
+ }
10950
+ if (typeof cost.input === "number") span2.setAttribute("neatlogs.llm.cost.prompt", cost.input);
10951
+ if (typeof cost.output === "number") span2.setAttribute("neatlogs.llm.cost.completion", cost.output);
10952
+ }
10415
10953
  }
10416
- const parent = state.agentCtx ?? getNeatlogsParentContext();
10417
- const span2 = tracer.startSpan(
10418
- `pi_agent.llm.${msg.model || "model"}`,
10419
- { attributes: attrs },
10420
- parent
10421
- );
10422
- span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10423
- span2.end();
10954
+ if (llm.ttftMs !== void 0) {
10955
+ span2.setAttribute("neatlogs.llm.metrics.ttft_ms", Math.round(llm.ttftMs));
10956
+ }
10957
+ if (msg.errorMessage) span2.setAttribute("neatlogs.error.message", String(msg.errorMessage));
10958
+ applyStopReasonStatus(span2, msg);
10959
+ span2.end(endTime);
10960
+ return msg.stopReason === "aborted" || msg.stopReason === "error" ? { stopReason: msg.stopReason, message: msg.errorMessage } : void 0;
10961
+ }
10962
+ function cacheReadOf(cost) {
10963
+ return typeof cost.cacheRead === "number" ? cost.cacheRead : cost.cacheReads;
10424
10964
  }
10425
10965
  function splitAssistantContent(content) {
10426
10966
  const texts = [];
@@ -11652,7 +12192,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11652
12192
  if (st.processed.has(id)) return void 0;
11653
12193
  st.processed.add(id);
11654
12194
  startRoot(st, String(sessionID));
11655
- emitLlmSpan2(shipper, st, info, String(sessionID));
12195
+ emitLlmSpan(shipper, st, info, String(sessionID));
11656
12196
  return shipper.flush().catch(() => void 0);
11657
12197
  }
11658
12198
  if (type === "session.idle" || type === "session.deleted") {
@@ -11667,7 +12207,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11667
12207
  }
11668
12208
  return void 0;
11669
12209
  }
11670
- function emitLlmSpan2(shipper, st, info, sessionID) {
12210
+ function emitLlmSpan(shipper, st, info, sessionID) {
11671
12211
  if (!st.traceId) return;
11672
12212
  const model = info?.modelID ?? info?.model ?? "";
11673
12213
  const provider = info?.providerID ?? info?.provider ?? "";
@@ -11864,6 +12404,8 @@ function bindTemplates(llm, systemTpl, userTpl, compiledVars) {
11864
12404
  span,
11865
12405
  strandsHooks,
11866
12406
  trace,
12407
+ tracePiAgentEvents,
12408
+ tracePiStream,
11867
12409
  traceToolAnthropic,
11868
12410
  traceToolAzureOpenAI,
11869
12411
  traceToolBedrock,