neatlogs 1.1.12 → 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.mjs CHANGED
@@ -4798,6 +4798,8 @@ var NeatlogsSpanProcessor = class {
4798
4798
  if (!currentModel) return;
4799
4799
  const llmOutput = attrs["neatlogs.llm.output"];
4800
4800
  if (!llmOutput) return;
4801
+ const trimmed = llmOutput.trimStart();
4802
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return;
4801
4803
  try {
4802
4804
  const output = JSON.parse(llmOutput);
4803
4805
  const generations = output?.generations;
@@ -4822,7 +4824,9 @@ var NeatlogsSpanProcessor = class {
4822
4824
  }
4823
4825
  }
4824
4826
  } catch (e) {
4825
- logger10.warn(`[ModelResolve] Failed to parse LLM output for model extraction: ${e}`);
4827
+ if (this.debug) {
4828
+ logger10.debug(`[ModelResolve] Failed to parse LLM output for model extraction: ${e}`);
4829
+ }
4826
4830
  }
4827
4831
  }
4828
4832
  // ── forceFlush / shutdown ─────────────────────────────
@@ -4983,7 +4987,8 @@ var INSTRUMENTATION_REGISTRY = {
4983
4987
  "ai_sdk",
4984
4988
  "claude_agent_sdk",
4985
4989
  "openrouter_agent",
4986
- "opencode"
4990
+ "opencode",
4991
+ "pi_agent"
4987
4992
  ],
4988
4993
  tool: ["langchain", "llamaindex", "haystack", "mcp"],
4989
4994
  http: ["requests", "httpx", "urllib3", "aiohttp"],
@@ -5358,6 +5363,13 @@ var INSTRUMENTATION_REGISTRY = {
5358
5363
  neatlogs: "neatlogs/opencode",
5359
5364
  default_span_kind: "AGENT",
5360
5365
  explicitWrapper: "NeatlogsOpencodePlugin from 'neatlogs/opencode'"
5366
+ },
5367
+ pi_agent: {
5368
+ openinference: null,
5369
+ openllmetry: null,
5370
+ neatlogs: "neatlogs/pi-agent",
5371
+ default_span_kind: "AGENT",
5372
+ explicitWrapper: "piAgentHooks() from 'neatlogs/pi-agent'"
5361
5373
  }
5362
5374
  }
5363
5375
  };
@@ -5934,7 +5946,7 @@ function _resetMastraCache() {
5934
5946
  }
5935
5947
 
5936
5948
  // src/version.ts
5937
- var __version__ = "1.1.12";
5949
+ var __version__ = "1.1.13";
5938
5950
 
5939
5951
  // src/init.ts
5940
5952
  var logger13 = getLogger();
@@ -10205,22 +10217,342 @@ import {
10205
10217
  } from "@opentelemetry/api";
10206
10218
  var TRACER_NAME12 = "neatlogs.pi-agent";
10207
10219
  var PATCH_FLAG2 = "_neatlogs_patched";
10220
+ var HARNESS_METHOD_FLAG = "_neatlogs_harness_methods_patched";
10208
10221
  function piAgentHooks(agent) {
10209
10222
  if (!agent || agent[PATCH_FLAG2]) return agent;
10210
10223
  const a = agent;
10211
10224
  if (typeof a.subscribe !== "function") return agent;
10212
- const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
10225
+ const listener = tracePiAgentEvents(() => a.state?.messages);
10226
+ a.subscribe((event) => listener(event));
10227
+ wrapHarnessModelOperations(a);
10228
+ markPatched2(a);
10229
+ return agent;
10230
+ }
10231
+ function wrapHarnessModelOperations(harness) {
10232
+ if (harness[HARNESS_METHOD_FLAG]) return;
10233
+ const isHarness = typeof harness.compact === "function" && typeof harness.navigateTree === "function" && typeof harness.getModel === "function";
10234
+ if (!isHarness) return;
10235
+ patchHarnessMethod(harness, "compact", (args) => ({ customInstructions: args[0] }));
10236
+ patchHarnessMethod(harness, "navigateTree", (args) => ({ targetId: args[0], options: args[1] }));
10237
+ try {
10238
+ Object.defineProperty(harness, HARNESS_METHOD_FLAG, { value: true });
10239
+ } catch {
10240
+ harness[HARNESS_METHOD_FLAG] = true;
10241
+ }
10242
+ }
10243
+ function patchHarnessMethod(harness, method, inputOf) {
10244
+ const original = harness[method].bind(harness);
10245
+ harness[method] = async (...args) => {
10246
+ const startedAt = Date.now();
10247
+ const parent = getNeatlogsParentContext();
10248
+ const input = inputOf(args);
10249
+ let observedModelCall;
10250
+ let stopObserving;
10251
+ try {
10252
+ stopObserving = harness.subscribe((event) => {
10253
+ if (method === "compact" && event?.type === "session_compact") {
10254
+ observedModelCall = !event.fromHook;
10255
+ } else if (method === "navigateTree" && event?.type === "session_tree") {
10256
+ observedModelCall = Boolean(event.summaryEntry) && !event.fromHook;
10257
+ }
10258
+ });
10259
+ } catch {
10260
+ }
10261
+ let result;
10262
+ let failure;
10263
+ try {
10264
+ result = await original(...args);
10265
+ return result;
10266
+ } catch (err) {
10267
+ failure = err;
10268
+ throw err;
10269
+ } finally {
10270
+ stopObserving?.();
10271
+ try {
10272
+ emitHarnessOperation(
10273
+ method,
10274
+ input,
10275
+ result,
10276
+ failure,
10277
+ harness.getModel?.(),
10278
+ parent,
10279
+ startedAt,
10280
+ Date.now(),
10281
+ observedModelCall
10282
+ );
10283
+ } catch {
10284
+ }
10285
+ }
10286
+ };
10287
+ }
10288
+ function emitHarnessOperation(method, input, result, failure, model, capturedParent, startedAt, endedAt, observedModelCall) {
10213
10289
  const tracer = getNeatlogsTracer(TRACER_NAME12);
10214
- a.subscribe((event) => {
10290
+ let parent = capturedParent;
10291
+ let root;
10292
+ const inputText = safeStringify12(input ?? {});
10293
+ const outputText = failure ? `[error] ${failure instanceof Error ? failure.message : String(failure)}` : harnessOperationOutput(method, result);
10294
+ if (!trace4.getSpan(parent)?.isRecording()) {
10295
+ root = tracer.startSpan(
10296
+ `pi_agent.harness.${method}`,
10297
+ {
10298
+ startTime: startedAt,
10299
+ attributes: {
10300
+ "neatlogs.span.kind": "WORKFLOW",
10301
+ "input.value": inputText
10302
+ }
10303
+ },
10304
+ parent
10305
+ );
10306
+ parent = trace4.setSpan(parent, root);
10307
+ }
10308
+ const chain = tracer.startSpan(
10309
+ `pi_agent.harness.${method}`,
10310
+ {
10311
+ startTime: startedAt,
10312
+ attributes: {
10313
+ "neatlogs.span.kind": "CHAIN",
10314
+ "neatlogs.pi.operation": method,
10315
+ "input.value": inputText
10316
+ }
10317
+ },
10318
+ parent
10319
+ );
10320
+ const chainCtx = trace4.setSpan(parent, chain);
10321
+ const usage = harnessOperationUsage(method, result);
10322
+ const madeModelCall = observedModelCall ?? (failure ? isHarnessSummarizationFailure(failure) : method === "compact" ? Boolean(result?.usage) : Boolean(result?.summaryEntry) && result.summaryEntry.fromHook !== true);
10323
+ if (madeModelCall || failure) {
10324
+ const llm = openLlmSpan(
10325
+ tracer,
10326
+ {
10327
+ toolSpans: /* @__PURE__ */ new Map(),
10328
+ inputMessages: [{ role: "user", content: inputText }],
10329
+ turnIndex: 0,
10330
+ turnCtx: chainCtx,
10331
+ turnStartEpochMs: startedAt
10332
+ },
10333
+ {
10334
+ role: "assistant",
10335
+ model: model?.id ?? model?.model,
10336
+ provider: model?.provider,
10337
+ timestamp: startedAt
10338
+ }
10339
+ );
10340
+ if (failure) {
10341
+ closeLlmFailure(llm, failure, endedAt);
10342
+ } else {
10343
+ finishLlmSpan(
10344
+ llm,
10345
+ {
10346
+ role: "assistant",
10347
+ model: model?.id ?? model?.model,
10348
+ provider: model?.provider,
10349
+ content: outputText ? [{ type: "text", text: outputText }] : [],
10350
+ usage,
10351
+ stopReason: "stop"
10352
+ },
10353
+ endedAt
10354
+ );
10355
+ }
10356
+ }
10357
+ if (outputText) chain.setAttribute("output.value", outputText);
10358
+ if (failure) {
10359
+ const message = failure instanceof Error ? failure.message : String(failure);
10360
+ chain.setAttribute("neatlogs.error.message", message);
10361
+ chain.setStatus({ code: SpanStatusCode14.ERROR, message });
10362
+ } else {
10363
+ chain.setStatus({ code: SpanStatusCode14.OK });
10364
+ }
10365
+ chain.end(endedAt);
10366
+ if (root) {
10367
+ if (outputText) root.setAttribute("output.value", outputText);
10368
+ if (failure) {
10369
+ const message = failure instanceof Error ? failure.message : String(failure);
10370
+ root.setAttribute("neatlogs.error.message", message);
10371
+ root.setStatus({ code: SpanStatusCode14.ERROR, message });
10372
+ } else {
10373
+ root.setStatus({ code: SpanStatusCode14.OK });
10374
+ }
10375
+ root.end(endedAt);
10376
+ }
10377
+ }
10378
+ function isHarnessSummarizationFailure(failure) {
10379
+ let current = failure;
10380
+ const seen = /* @__PURE__ */ new Set();
10381
+ while (current && !seen.has(current)) {
10382
+ seen.add(current);
10383
+ if (current.code === "summarization_failed") return true;
10384
+ current = current.cause;
10385
+ }
10386
+ return false;
10387
+ }
10388
+ function harnessOperationOutput(method, result) {
10389
+ if (method === "compact") return typeof result?.summary === "string" ? result.summary : safeStringify12(result ?? {});
10390
+ const summary = result?.summaryEntry?.summary;
10391
+ return typeof summary === "string" ? summary : safeStringify12(result ?? {});
10392
+ }
10393
+ function harnessOperationUsage(method, result) {
10394
+ return method === "compact" ? result?.usage : result?.summaryEntry?.usage;
10395
+ }
10396
+ function closeLlmFailure(llm, failure, endedAt) {
10397
+ const message = failure instanceof Error ? failure.message : String(failure);
10398
+ llm.span.setAttribute("output.value", `[error] ${message}`);
10399
+ llm.span.setAttribute("neatlogs.error.message", message);
10400
+ llm.span.setStatus({ code: SpanStatusCode14.ERROR, message });
10401
+ llm.span.end(endedAt);
10402
+ }
10403
+ function tracePiAgentEvents(getTranscript) {
10404
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10405
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10406
+ return (event) => {
10407
+ try {
10408
+ handleEvent(tracer, state, event, getTranscript);
10409
+ } catch {
10410
+ }
10411
+ };
10412
+ }
10413
+ function tracePiStream(streamFn) {
10414
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10415
+ return ((model, context2, options) => {
10416
+ let llm;
10417
+ let root;
10215
10418
  try {
10216
- handleEvent(tracer, state, event);
10419
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10420
+ state.inputMessages = promptMessagesOf(context2);
10421
+ let parent = getNeatlogsParentContext();
10422
+ if (!trace4.getSpan(parent)?.isRecording()) {
10423
+ root = tracer.startSpan(
10424
+ "pi_agent.stream",
10425
+ { attributes: { "neatlogs.span.kind": "WORKFLOW" } },
10426
+ parent
10427
+ );
10428
+ parent = trace4.setSpan(parent, root);
10429
+ const rootInput = state.inputMessages.length ? safeStringify12({ messages: state.inputMessages }) : void 0;
10430
+ if (rootInput) root.setAttribute("input.value", rootInput);
10431
+ }
10432
+ state.turnCtx = parent;
10433
+ llm = openLlmSpan(tracer, state, {
10434
+ role: "assistant",
10435
+ model: model?.id ?? model?.model,
10436
+ provider: model?.provider
10437
+ });
10217
10438
  } catch {
10218
10439
  }
10440
+ let returned;
10441
+ try {
10442
+ returned = streamFn(model, context2, options);
10443
+ } catch (err) {
10444
+ closeStreamFailure(llm, root, err);
10445
+ throw err;
10446
+ }
10447
+ if (isThenable(returned)) {
10448
+ return returned.then(
10449
+ (stream) => attachStreamResult(stream, llm, root),
10450
+ (err) => {
10451
+ closeStreamFailure(llm, root, err);
10452
+ throw err;
10453
+ }
10454
+ );
10455
+ }
10456
+ return attachStreamResult(returned, llm, root);
10219
10457
  });
10220
- markPatched2(a);
10221
- return agent;
10222
10458
  }
10223
- function handleEvent(tracer, state, event) {
10459
+ function attachStreamResult(stream, llm, root) {
10460
+ if (!llm || !stream || typeof stream.result !== "function") {
10461
+ closeStreamRoot(root);
10462
+ return stream;
10463
+ }
10464
+ observeStreamDeltas(stream, llm);
10465
+ stream.result().then(
10466
+ (msg) => {
10467
+ const message = msg ?? { role: "assistant" };
10468
+ finishLlmSpan(llm, message);
10469
+ closeStreamRoot(root, message);
10470
+ },
10471
+ (err) => closeStreamFailure(llm, root, err)
10472
+ );
10473
+ return stream;
10474
+ }
10475
+ function observeStreamDeltas(stream, llm) {
10476
+ const original = stream?.[Symbol.asyncIterator];
10477
+ if (typeof original !== "function" || original.__neatlogsObserved) return;
10478
+ const observed = function() {
10479
+ const iterator = original.call(this);
10480
+ return {
10481
+ next: async (...args) => {
10482
+ const item = await iterator.next(...args);
10483
+ if (!item.done && isContentDelta(item.value?.type)) markFirstStreamDelta(llm);
10484
+ return item;
10485
+ },
10486
+ return: iterator.return?.bind(iterator),
10487
+ throw: iterator.throw?.bind(iterator)
10488
+ };
10489
+ };
10490
+ Object.defineProperty(observed, "__neatlogsObserved", { value: true });
10491
+ try {
10492
+ stream[Symbol.asyncIterator] = observed;
10493
+ } catch {
10494
+ }
10495
+ }
10496
+ function markFirstStreamDelta(llm) {
10497
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10498
+ if (llm.ttftMs !== void 0) return;
10499
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10500
+ }
10501
+ function closeStreamFailure(llm, root, err) {
10502
+ const message = err instanceof Error ? err.message : String(err);
10503
+ try {
10504
+ llm?.span.setAttribute("output.value", `[error] ${message}`);
10505
+ llm?.span.setAttribute("neatlogs.error.message", message);
10506
+ llm?.span.setStatus({ code: SpanStatusCode14.ERROR, message });
10507
+ llm?.span.end();
10508
+ } catch {
10509
+ }
10510
+ closeStreamRoot(root, void 0, message);
10511
+ }
10512
+ function isThenable(value) {
10513
+ return value != null && typeof value.then === "function";
10514
+ }
10515
+ function closeStreamRoot(root, msg, error) {
10516
+ if (!root) return;
10517
+ try {
10518
+ if (msg) {
10519
+ const { text, toolCalls } = splitAssistantContent(msg.content);
10520
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10521
+ if (out) root.setAttribute("output.value", out);
10522
+ applyStopReasonStatus(root, msg);
10523
+ } else if (error) {
10524
+ root.setAttribute("output.value", `[error] ${error}`);
10525
+ root.setAttribute("neatlogs.error.message", error);
10526
+ root.setStatus({ code: SpanStatusCode14.ERROR, message: error });
10527
+ }
10528
+ root.end();
10529
+ } catch {
10530
+ }
10531
+ }
10532
+ function transcriptRows(messages) {
10533
+ if (!Array.isArray(messages)) return [];
10534
+ const rows = [];
10535
+ for (const m of messages) {
10536
+ const content = messageText(m);
10537
+ if (!content) continue;
10538
+ rows.push({ role: m?.role === "toolResult" ? "tool" : String(m?.role ?? "user"), content });
10539
+ }
10540
+ return rows;
10541
+ }
10542
+ function promptMessagesOf(context2) {
10543
+ const rows = [];
10544
+ if (typeof context2?.systemPrompt === "string" && context2.systemPrompt) {
10545
+ rows.push({ role: "system", content: context2.systemPrompt });
10546
+ }
10547
+ if (Array.isArray(context2?.messages)) {
10548
+ for (const m of context2.messages) {
10549
+ const content = messageText(m);
10550
+ if (content) rows.push({ role: String(m?.role ?? "user"), content });
10551
+ }
10552
+ }
10553
+ return rows;
10554
+ }
10555
+ function handleEvent(tracer, state, event, getTranscript) {
10224
10556
  switch (event.type) {
10225
10557
  case "agent_start": {
10226
10558
  const base = getNeatlogsParentContext();
@@ -10231,31 +10563,98 @@ function handleEvent(tracer, state, event) {
10231
10563
  );
10232
10564
  state.agentSpan = span2;
10233
10565
  state.agentCtx = trace4.setSpan(base, span2);
10234
- state.inputMessages = [];
10566
+ state.inputMessages = transcriptRows(getTranscript?.());
10567
+ state.turnIndex = 0;
10568
+ state.runInput = void 0;
10569
+ state.runError = void 0;
10570
+ break;
10571
+ }
10572
+ case "turn_start": {
10573
+ const parent = state.agentCtx ?? getNeatlogsParentContext();
10574
+ state.turnIndex += 1;
10575
+ const pending = state.inputMessages[state.inputMessages.length - 1];
10576
+ const priorInput = pending && (pending.role === "user" || pending.role === "tool") ? pending.content : void 0;
10577
+ const span2 = tracer.startSpan(
10578
+ `pi_agent.turn.${state.turnIndex}`,
10579
+ {
10580
+ attributes: {
10581
+ "neatlogs.span.kind": "CHAIN",
10582
+ "neatlogs.chain.turn_index": state.turnIndex,
10583
+ ...priorInput ? { "input.value": priorInput } : {}
10584
+ }
10585
+ },
10586
+ parent
10587
+ );
10588
+ state.turnSpan = span2;
10589
+ state.turnCtx = trace4.setSpan(parent, span2);
10590
+ state.turnHasInput = !!priorInput;
10591
+ state.turnStartEpochMs = Date.now();
10592
+ break;
10593
+ }
10594
+ case "turn_end": {
10595
+ if (!state.turnSpan) break;
10596
+ const msg = event.message;
10597
+ const { text, toolCalls } = splitAssistantContent(msg?.content);
10598
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10599
+ if (out) state.turnSpan.setAttribute("output.value", out);
10600
+ if (Array.isArray(event.toolResults) && event.toolResults.length) {
10601
+ state.turnSpan.setAttribute("neatlogs.chain.tool_result_count", event.toolResults.length);
10602
+ }
10603
+ applyStopReasonStatus(state.turnSpan, msg);
10604
+ state.turnSpan.end();
10605
+ state.turnSpan = void 0;
10606
+ state.turnCtx = void 0;
10607
+ state.turnHasInput = false;
10608
+ state.turnStartEpochMs = void 0;
10609
+ break;
10610
+ }
10611
+ case "message_start": {
10612
+ const msg = event.message;
10613
+ if (!msg || msg.role !== "assistant") break;
10614
+ state.llm = openLlmSpan(tracer, state, msg);
10615
+ break;
10616
+ }
10617
+ case "message_update": {
10618
+ const llm = state.llm;
10619
+ if (!llm) break;
10620
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10621
+ if (llm.ttftMs === void 0 && isContentDelta(event.assistantMessageEvent?.type)) {
10622
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10623
+ }
10235
10624
  break;
10236
10625
  }
10237
10626
  case "message_end": {
10238
10627
  const msg = event.message;
10239
10628
  if (!msg) return;
10240
10629
  if (msg.role === "assistant") {
10241
- emitLlmSpan(tracer, state, msg);
10630
+ const llm = state.llm ?? openLlmSpan(tracer, state, msg);
10631
+ state.llm = void 0;
10632
+ state.runError = finishLlmSpan(llm, msg) ?? state.runError;
10242
10633
  const { text } = splitAssistantContent(msg.content);
10243
10634
  if (text) state.inputMessages.push({ role: "assistant", content: text });
10244
10635
  } else {
10245
10636
  const role = msg.role === "toolResult" ? "tool" : String(msg.role || "user");
10246
10637
  const content = messageText(msg);
10247
- if (content) state.inputMessages.push({ role, content });
10638
+ if (content) {
10639
+ state.inputMessages.push({ role, content });
10640
+ if (role === "user" && state.runInput === void 0) state.runInput = content;
10641
+ if (state.turnSpan && !state.turnHasInput) {
10642
+ state.turnSpan.setAttribute("input.value", content);
10643
+ state.turnHasInput = true;
10644
+ }
10645
+ }
10248
10646
  }
10249
10647
  break;
10250
10648
  }
10251
10649
  case "tool_execution_start": {
10252
- const parent = state.agentCtx ?? getNeatlogsParentContext();
10650
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10253
10651
  const span2 = tracer.startSpan(
10254
10652
  `pi_agent.tool.${event.toolName ?? "tool"}`,
10255
10653
  {
10256
10654
  attributes: {
10257
10655
  "neatlogs.span.kind": "TOOL",
10258
10656
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
10657
+ ...event.toolCallId ? { "neatlogs.tool.call_id": String(event.toolCallId) } : {},
10259
10658
  ...event.args !== void 0 ? { "input.value": safeStringify12(event.args) } : {}
10260
10659
  }
10261
10660
  },
@@ -10264,6 +10663,12 @@ function handleEvent(tracer, state, event) {
10264
10663
  if (event.toolCallId) state.toolSpans.set(event.toolCallId, span2);
10265
10664
  break;
10266
10665
  }
10666
+ case "tool_execution_update": {
10667
+ const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10668
+ if (!span2) break;
10669
+ span2.setAttribute("neatlogs.tool.is_streaming", true);
10670
+ break;
10671
+ }
10267
10672
  case "tool_execution_end": {
10268
10673
  const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10269
10674
  if (!span2) return;
@@ -10271,7 +10676,7 @@ function handleEvent(tracer, state, event) {
10271
10676
  span2.setAttribute("output.value", safeStringify12(event.result));
10272
10677
  }
10273
10678
  if (event.isError) {
10274
- span2.setStatus({ code: SpanStatusCode14.ERROR });
10679
+ span2.setStatus({ code: SpanStatusCode14.ERROR, message: toolErrorText(event.result) });
10275
10680
  span2.setAttribute("neatlogs.tool.is_error", true);
10276
10681
  } else {
10277
10682
  span2.setStatus({ code: SpanStatusCode14.OK });
@@ -10283,17 +10688,47 @@ function handleEvent(tracer, state, event) {
10283
10688
  case "agent_end": {
10284
10689
  for (const ts of state.toolSpans.values()) {
10285
10690
  try {
10691
+ ts.setStatus({ code: SpanStatusCode14.ERROR, message: "run ended before tool completed" });
10286
10692
  ts.end();
10287
10693
  } catch {
10288
10694
  }
10289
10695
  }
10290
10696
  state.toolSpans.clear();
10697
+ if (state.llm) {
10698
+ try {
10699
+ state.llm.span.setAttribute("output.value", "[incomplete] run ended mid-stream");
10700
+ state.llm.span.setStatus({ code: SpanStatusCode14.ERROR, message: "run ended mid-stream" });
10701
+ state.llm.span.end();
10702
+ } catch {
10703
+ }
10704
+ state.llm = void 0;
10705
+ }
10706
+ if (state.turnSpan) {
10707
+ try {
10708
+ state.turnSpan.end();
10709
+ } catch {
10710
+ }
10711
+ state.turnSpan = void 0;
10712
+ state.turnCtx = void 0;
10713
+ state.turnStartEpochMs = void 0;
10714
+ }
10291
10715
  if (state.agentSpan) {
10292
- const firstUser = state.inputMessages.find((m) => m.role === "user");
10293
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
10716
+ const input = state.runInput ?? lastUserTextFrom(event.messages) ?? lastUserTextFrom(getTranscript?.());
10717
+ if (input) state.agentSpan.setAttribute("input.value", input);
10294
10718
  const finalText = lastAssistantText(event.messages);
10295
- if (finalText) state.agentSpan.setAttribute("output.value", finalText);
10296
- state.agentSpan.setStatus({ code: SpanStatusCode14.OK });
10719
+ const err = state.runError ?? terminalErrorFrom(event.messages);
10720
+ if (finalText) {
10721
+ state.agentSpan.setAttribute("output.value", finalText);
10722
+ } else if (err) {
10723
+ state.agentSpan.setAttribute("output.value", `[${err.stopReason}]${err.message ? ` ${err.message}` : ""}`);
10724
+ }
10725
+ if (err) {
10726
+ state.agentSpan.setAttribute("neatlogs.agent.stop_reason", err.stopReason);
10727
+ if (err.message) state.agentSpan.setAttribute("neatlogs.error.message", err.message);
10728
+ state.agentSpan.setStatus({ code: SpanStatusCode14.ERROR, message: err.message ?? err.stopReason });
10729
+ } else {
10730
+ state.agentSpan.setStatus({ code: SpanStatusCode14.OK });
10731
+ }
10297
10732
  state.agentSpan.end();
10298
10733
  state.agentSpan = void 0;
10299
10734
  state.agentCtx = void 0;
@@ -10304,55 +10739,158 @@ function handleEvent(tracer, state, event) {
10304
10739
  break;
10305
10740
  }
10306
10741
  }
10307
- function emitLlmSpan(tracer, state, msg) {
10308
- const attrs = { "neatlogs.span.kind": "LLM" };
10309
- if (msg.model) attrs["neatlogs.llm.model_name"] = String(msg.model);
10310
- if (msg.provider) attrs["neatlogs.llm.provider"] = String(msg.provider);
10311
- if (msg.stopReason) attrs["neatlogs.llm.stop_reason"] = String(msg.stopReason);
10312
- const inMsgs = state.inputMessages;
10742
+ function openLlmSpan(tracer, state, msg) {
10743
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10744
+ const startTime = callStartFrom(msg, state.turnStartEpochMs);
10745
+ const span2 = tracer.startSpan(
10746
+ `pi_agent.llm.${msg.model || "model"}`,
10747
+ {
10748
+ ...startTime !== void 0 ? { startTime } : {},
10749
+ attributes: {
10750
+ "neatlogs.span.kind": "LLM",
10751
+ // Pi streams by default; message_update deltas confirm it per call.
10752
+ "neatlogs.llm.is_streaming": false,
10753
+ ...msg.model ? { "neatlogs.llm.model_name": String(msg.model) } : {},
10754
+ ...msg.provider ? { "neatlogs.llm.provider": String(msg.provider) } : {}
10755
+ }
10756
+ },
10757
+ parent
10758
+ );
10759
+ return {
10760
+ span: span2,
10761
+ startHr: nowMs(),
10762
+ callStartEpochMs: startTime,
10763
+ inputMessages: state.inputMessages.slice()
10764
+ };
10765
+ }
10766
+ function callStartFrom(msg, turnStart) {
10767
+ const ts = msg.timestamp;
10768
+ if (typeof ts !== "number" || !Number.isFinite(ts)) return void 0;
10769
+ const now = Date.now();
10770
+ const age = now - ts;
10771
+ if (age < 0 || age > MAX_CALL_AGE_MS) return void 0;
10772
+ return turnStart !== void 0 ? Math.max(ts, turnStart) : ts;
10773
+ }
10774
+ var MAX_CALL_AGE_MS = 30 * 6e4;
10775
+ function terminalSummary(msg) {
10776
+ const reason = msg?.stopReason;
10777
+ if (reason !== "aborted" && reason !== "error") return "";
10778
+ return `[${reason}]${msg?.errorMessage ? ` ${msg.errorMessage}` : ""}`;
10779
+ }
10780
+ function applyStopReasonStatus(span2, msg) {
10781
+ const reason = msg?.stopReason;
10782
+ if (reason === "aborted" || reason === "error") {
10783
+ span2.setStatus({ code: SpanStatusCode14.ERROR, message: msg?.errorMessage ?? reason });
10784
+ } else {
10785
+ span2.setStatus({ code: SpanStatusCode14.OK });
10786
+ }
10787
+ }
10788
+ function isContentDelta(type) {
10789
+ return type === "text_delta" || type === "thinking_delta" || type === "toolcall_delta";
10790
+ }
10791
+ function nowMs() {
10792
+ const p = globalThis.performance;
10793
+ return typeof p?.now === "function" ? p.now() : Date.now();
10794
+ }
10795
+ function toolErrorText(result) {
10796
+ if (!result || typeof result !== "object") return "tool error";
10797
+ const content = result.content;
10798
+ if (Array.isArray(content)) {
10799
+ const text = content.map((b) => typeof b?.text === "string" ? b.text : "").filter(Boolean).join(" ").trim();
10800
+ if (text) return text;
10801
+ }
10802
+ return "tool error";
10803
+ }
10804
+ function lastUserTextFrom(messages) {
10805
+ if (!Array.isArray(messages)) return void 0;
10806
+ for (let i = messages.length - 1; i >= 0; i--) {
10807
+ const m = messages[i];
10808
+ if (m && m.role === "user") {
10809
+ const text = messageText(m);
10810
+ if (text) return text;
10811
+ }
10812
+ }
10813
+ return void 0;
10814
+ }
10815
+ function terminalErrorFrom(messages) {
10816
+ if (!Array.isArray(messages)) return void 0;
10817
+ for (let i = messages.length - 1; i >= 0; i--) {
10818
+ const m = messages[i];
10819
+ if (m && m.role === "assistant") {
10820
+ if (m.stopReason === "aborted" || m.stopReason === "error") {
10821
+ return { stopReason: m.stopReason, message: m.errorMessage };
10822
+ }
10823
+ return void 0;
10824
+ }
10825
+ }
10826
+ return void 0;
10827
+ }
10828
+ function finishLlmSpan(llm, msg, endTime) {
10829
+ const span2 = llm.span;
10830
+ if (msg.model) span2.setAttribute("neatlogs.llm.model_name", String(msg.model));
10831
+ if (msg.provider) span2.setAttribute("neatlogs.llm.provider", String(msg.provider));
10832
+ if (msg.responseModel) span2.setAttribute("neatlogs.llm.response_model", String(msg.responseModel));
10833
+ if (msg.api) span2.setAttribute("neatlogs.llm.api", String(msg.api));
10834
+ if (msg.stopReason) span2.setAttribute("neatlogs.llm.stop_reason", String(msg.stopReason));
10835
+ const inMsgs = llm.inputMessages;
10313
10836
  if (inMsgs.length) {
10314
10837
  inMsgs.forEach((m, i) => {
10315
- attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
10316
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
10838
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.role`, m.role);
10839
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.content`, m.content);
10317
10840
  });
10318
- attrs["neatlogs.llm.input"] = safeStringify12({ messages: inMsgs });
10319
- attrs["input.value"] = safeStringify12({ messages: inMsgs });
10841
+ const inBlob = safeStringify12({ messages: inMsgs });
10842
+ span2.setAttribute("neatlogs.llm.input", inBlob);
10843
+ span2.setAttribute("input.value", inBlob);
10320
10844
  }
10321
10845
  const { text, toolCalls } = splitAssistantContent(msg.content);
10322
- const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n");
10846
+ const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10323
10847
  if (outText || toolCalls.length) {
10324
- attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
10325
- attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
10848
+ span2.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
10849
+ span2.setAttribute("neatlogs.llm.output_messages.0.content", outText || "");
10326
10850
  const outBlob = { role: "assistant", content: outText || "" };
10327
10851
  if (toolCalls.length) {
10328
10852
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
10329
10853
  toolCalls.forEach((tc, j) => {
10330
- if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;
10854
+ if (tc.name) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
10331
10855
  if (tc.arguments !== void 0)
10332
- attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify12(tc.arguments);
10333
- if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
10856
+ span2.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify12(tc.arguments));
10857
+ if (tc.id) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, String(tc.id));
10334
10858
  });
10335
10859
  }
10336
- attrs["neatlogs.llm.output"] = safeStringify12(outBlob);
10337
- attrs["output.value"] = outText || "";
10860
+ span2.setAttribute("neatlogs.llm.output", safeStringify12(outBlob));
10861
+ span2.setAttribute("output.value", outText || "");
10338
10862
  }
10339
10863
  const usage = msg.usage;
10340
10864
  if (usage) {
10341
- if (usage.input != null) attrs["neatlogs.llm.token_count.prompt"] = usage.input;
10342
- if (usage.output != null) attrs["neatlogs.llm.token_count.completion"] = usage.output;
10865
+ if (usage.input != null) span2.setAttribute("neatlogs.llm.token_count.prompt", usage.input);
10866
+ if (usage.output != null) span2.setAttribute("neatlogs.llm.token_count.completion", usage.output);
10343
10867
  const total = usage.totalTokens ?? (usage.input ?? 0) + (usage.output ?? 0);
10344
- if (total) attrs["neatlogs.llm.token_count.total"] = total;
10345
- if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
10346
- if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
10868
+ if (total) span2.setAttribute("neatlogs.llm.token_count.total", total);
10869
+ if (usage.cacheRead) span2.setAttribute("neatlogs.llm.token_count.cache_read", usage.cacheRead);
10870
+ if (usage.cacheWrite) span2.setAttribute("neatlogs.llm.token_count.cache_write", usage.cacheWrite);
10871
+ const cost = usage.cost;
10872
+ if (cost) {
10873
+ const totalCost = cost.total ?? [cost.input, cost.output, cacheReadOf(cost), cost.cacheWrite].reduce(
10874
+ (sum, v) => sum + (typeof v === "number" ? v : 0),
10875
+ 0
10876
+ );
10877
+ if (typeof totalCost === "number" && totalCost > 0) {
10878
+ span2.setAttribute("neatlogs.llm.cost_usd", totalCost);
10879
+ }
10880
+ if (typeof cost.input === "number") span2.setAttribute("neatlogs.llm.cost.prompt", cost.input);
10881
+ if (typeof cost.output === "number") span2.setAttribute("neatlogs.llm.cost.completion", cost.output);
10882
+ }
10347
10883
  }
10348
- const parent = state.agentCtx ?? getNeatlogsParentContext();
10349
- const span2 = tracer.startSpan(
10350
- `pi_agent.llm.${msg.model || "model"}`,
10351
- { attributes: attrs },
10352
- parent
10353
- );
10354
- span2.setStatus({ code: SpanStatusCode14.OK });
10355
- span2.end();
10884
+ if (llm.ttftMs !== void 0) {
10885
+ span2.setAttribute("neatlogs.llm.metrics.ttft_ms", Math.round(llm.ttftMs));
10886
+ }
10887
+ if (msg.errorMessage) span2.setAttribute("neatlogs.error.message", String(msg.errorMessage));
10888
+ applyStopReasonStatus(span2, msg);
10889
+ span2.end(endTime);
10890
+ return msg.stopReason === "aborted" || msg.stopReason === "error" ? { stopReason: msg.stopReason, message: msg.errorMessage } : void 0;
10891
+ }
10892
+ function cacheReadOf(cost) {
10893
+ return typeof cost.cacheRead === "number" ? cost.cacheRead : cost.cacheReads;
10356
10894
  }
10357
10895
  function splitAssistantContent(content) {
10358
10896
  const texts = [];
@@ -11584,7 +12122,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11584
12122
  if (st.processed.has(id)) return void 0;
11585
12123
  st.processed.add(id);
11586
12124
  startRoot(st, String(sessionID));
11587
- emitLlmSpan2(shipper, st, info, String(sessionID));
12125
+ emitLlmSpan(shipper, st, info, String(sessionID));
11588
12126
  return shipper.flush().catch(() => void 0);
11589
12127
  }
11590
12128
  if (type === "session.idle" || type === "session.deleted") {
@@ -11599,7 +12137,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11599
12137
  }
11600
12138
  return void 0;
11601
12139
  }
11602
- function emitLlmSpan2(shipper, st, info, sessionID) {
12140
+ function emitLlmSpan(shipper, st, info, sessionID) {
11603
12141
  if (!st.traceId) return;
11604
12142
  const model = info?.modelID ?? info?.model ?? "";
11605
12143
  const provider = info?.providerID ?? info?.provider ?? "";
@@ -11795,6 +12333,8 @@ export {
11795
12333
  span,
11796
12334
  strandsHooks,
11797
12335
  trace,
12336
+ tracePiAgentEvents,
12337
+ tracePiStream,
11798
12338
  traceTool2 as traceToolAnthropic,
11799
12339
  traceTool3 as traceToolAzureOpenAI,
11800
12340
  traceTool6 as traceToolBedrock,