neatlogs 1.1.12 → 1.1.14

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.12";
6026
+ var __version__ = "1.1.14";
6013
6027
 
6014
6028
  // src/init.ts
6015
6029
  var logger13 = getLogger();
@@ -6898,29 +6912,25 @@ function tracedResponsesCreate(original) {
6898
6912
  return function(opts, ...rest) {
6899
6913
  const tracer = getProviderTracer(TRACER_NAME3);
6900
6914
  const model = opts?.model ?? "";
6915
+ const isStream = opts?.stream === true;
6901
6916
  const span2 = tracer.startSpan("openai.responses.create", {
6902
6917
  attributes: {
6903
6918
  "neatlogs.span.kind": "LLM",
6904
6919
  "neatlogs.llm.provider": "openai",
6905
6920
  "neatlogs.llm.system": "openai",
6906
6921
  "neatlogs.llm.model_name": model,
6922
+ "neatlogs.llm.is_streaming": isStream,
6907
6923
  "input.value": safeStringify3(opts?.input ?? "")
6908
6924
  }
6909
6925
  }, getNeatlogsParentContext());
6926
+ setInvocationParams(span2, opts);
6910
6927
  const promise = withNeatlogsSpan(span2, () => original(opts, ...rest));
6911
6928
  return promise.then(
6912
6929
  (response) => {
6913
- if (response?.output_text) {
6914
- span2.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
6915
- span2.setAttribute("neatlogs.llm.output_messages.0.content", response.output_text);
6916
- }
6917
- if (response?.model) span2.setAttribute("neatlogs.llm.model_name", response.model);
6918
- if (response?.usage) {
6919
- if (response.usage.input_tokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", response.usage.input_tokens);
6920
- if (response.usage.output_tokens != null) span2.setAttribute("neatlogs.llm.token_count.completion", response.usage.output_tokens);
6930
+ if (isStream) {
6931
+ return wrapResponsesAsyncIterableStream(response, span2);
6921
6932
  }
6922
- span2.setStatus({ code: import_api10.SpanStatusCode.OK });
6923
- span2.end();
6933
+ finalizeResponsesResponse(span2, response);
6924
6934
  return response;
6925
6935
  },
6926
6936
  (err) => {
@@ -6930,6 +6940,110 @@ function tracedResponsesCreate(original) {
6930
6940
  );
6931
6941
  };
6932
6942
  }
6943
+ function wrapResponsesAsyncIterableStream(stream, span2) {
6944
+ const originalAsyncIterator = stream?.[Symbol.asyncIterator]?.bind(stream);
6945
+ if (!originalAsyncIterator) {
6946
+ finalizeResponsesResponse(span2, stream);
6947
+ return stream;
6948
+ }
6949
+ const textParts = [];
6950
+ let completedResponse;
6951
+ let finalized = false;
6952
+ const finalize = () => {
6953
+ if (finalized) return;
6954
+ finalized = true;
6955
+ finalizeResponsesResponse(span2, completedResponse, textParts.join(""));
6956
+ };
6957
+ return new Proxy(stream, {
6958
+ get(target, prop) {
6959
+ if (prop === Symbol.asyncIterator) {
6960
+ return () => {
6961
+ const iterator = originalAsyncIterator();
6962
+ return {
6963
+ async next() {
6964
+ try {
6965
+ const result = await iterator.next();
6966
+ if (result.done) {
6967
+ finalize();
6968
+ return result;
6969
+ }
6970
+ const event = result.value;
6971
+ if (event?.type === "response.output_text.delta" && typeof event.delta === "string") {
6972
+ textParts.push(event.delta);
6973
+ } else if (event?.type === "response.completed") {
6974
+ completedResponse = event.response;
6975
+ }
6976
+ return result;
6977
+ } catch (err) {
6978
+ if (!finalized) {
6979
+ finalized = true;
6980
+ recordError(span2, err);
6981
+ }
6982
+ throw err;
6983
+ }
6984
+ },
6985
+ async return(value2) {
6986
+ try {
6987
+ return await (iterator.return?.(value2) ?? { done: true, value: value2 });
6988
+ } finally {
6989
+ finalize();
6990
+ }
6991
+ },
6992
+ async throw(err) {
6993
+ if (!finalized) {
6994
+ finalized = true;
6995
+ recordError(span2, err);
6996
+ }
6997
+ if (iterator.throw) return iterator.throw(err);
6998
+ throw err;
6999
+ }
7000
+ };
7001
+ };
7002
+ }
7003
+ const value = Reflect.get(target, prop, target);
7004
+ return typeof value === "function" ? value.bind(target) : value;
7005
+ }
7006
+ });
7007
+ }
7008
+ function finalizeResponsesResponse(span2, response, streamedText = "") {
7009
+ const text = response?.output_text || streamedText || extractResponsesOutputText(response?.output);
7010
+ if (text) {
7011
+ span2.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
7012
+ span2.setAttribute("neatlogs.llm.output_messages.0.content", text);
7013
+ span2.setAttribute("output.value", text);
7014
+ } else if (response?.output) {
7015
+ span2.setAttribute("output.value", safeStringify3(response.output));
7016
+ }
7017
+ if (response?.model) span2.setAttribute("neatlogs.llm.model_name", response.model);
7018
+ if (response?.status) span2.setAttribute("neatlogs.llm.finish_reason", response.status);
7019
+ const usage = response?.usage;
7020
+ if (usage) {
7021
+ if (usage.input_tokens != null) span2.setAttribute("neatlogs.llm.token_count.prompt", usage.input_tokens);
7022
+ if (usage.output_tokens != null) span2.setAttribute("neatlogs.llm.token_count.completion", usage.output_tokens);
7023
+ if (usage.total_tokens != null) span2.setAttribute("neatlogs.llm.token_count.total", usage.total_tokens);
7024
+ if (usage.input_tokens_details?.cached_tokens != null) {
7025
+ span2.setAttribute("neatlogs.llm.token_count.cache_read", usage.input_tokens_details.cached_tokens);
7026
+ }
7027
+ if (usage.output_tokens_details?.reasoning_tokens != null) {
7028
+ span2.setAttribute("neatlogs.llm.token_count.reasoning", usage.output_tokens_details.reasoning_tokens);
7029
+ }
7030
+ }
7031
+ span2.setStatus({ code: import_api10.SpanStatusCode.OK });
7032
+ span2.end();
7033
+ }
7034
+ function extractResponsesOutputText(output) {
7035
+ if (!Array.isArray(output)) return "";
7036
+ const parts = [];
7037
+ for (const item of output) {
7038
+ if (item?.type !== "message" || !Array.isArray(item.content)) continue;
7039
+ for (const content of item.content) {
7040
+ if (content?.type === "output_text" && typeof content.text === "string") {
7041
+ parts.push(content.text);
7042
+ }
7043
+ }
7044
+ }
7045
+ return parts.join("");
7046
+ }
6933
7047
  function wrapAsyncIterableStream(stream, span2) {
6934
7048
  const chunks = [];
6935
7049
  const originalAsyncIterator = stream[Symbol.asyncIterator]?.bind(stream);
@@ -10273,22 +10387,342 @@ function recordError7(span2, err) {
10273
10387
  var import_api19 = require("@opentelemetry/api");
10274
10388
  var TRACER_NAME12 = "neatlogs.pi-agent";
10275
10389
  var PATCH_FLAG2 = "_neatlogs_patched";
10390
+ var HARNESS_METHOD_FLAG = "_neatlogs_harness_methods_patched";
10276
10391
  function piAgentHooks(agent) {
10277
10392
  if (!agent || agent[PATCH_FLAG2]) return agent;
10278
10393
  const a = agent;
10279
10394
  if (typeof a.subscribe !== "function") return agent;
10280
- const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
10395
+ const listener = tracePiAgentEvents(() => a.state?.messages);
10396
+ a.subscribe((event) => listener(event));
10397
+ wrapHarnessModelOperations(a);
10398
+ markPatched2(a);
10399
+ return agent;
10400
+ }
10401
+ function wrapHarnessModelOperations(harness) {
10402
+ if (harness[HARNESS_METHOD_FLAG]) return;
10403
+ const isHarness = typeof harness.compact === "function" && typeof harness.navigateTree === "function" && typeof harness.getModel === "function";
10404
+ if (!isHarness) return;
10405
+ patchHarnessMethod(harness, "compact", (args) => ({ customInstructions: args[0] }));
10406
+ patchHarnessMethod(harness, "navigateTree", (args) => ({ targetId: args[0], options: args[1] }));
10407
+ try {
10408
+ Object.defineProperty(harness, HARNESS_METHOD_FLAG, { value: true });
10409
+ } catch {
10410
+ harness[HARNESS_METHOD_FLAG] = true;
10411
+ }
10412
+ }
10413
+ function patchHarnessMethod(harness, method, inputOf) {
10414
+ const original = harness[method].bind(harness);
10415
+ harness[method] = async (...args) => {
10416
+ const startedAt = Date.now();
10417
+ const parent = getNeatlogsParentContext();
10418
+ const input = inputOf(args);
10419
+ let observedModelCall;
10420
+ let stopObserving;
10421
+ try {
10422
+ stopObserving = harness.subscribe((event) => {
10423
+ if (method === "compact" && event?.type === "session_compact") {
10424
+ observedModelCall = !event.fromHook;
10425
+ } else if (method === "navigateTree" && event?.type === "session_tree") {
10426
+ observedModelCall = Boolean(event.summaryEntry) && !event.fromHook;
10427
+ }
10428
+ });
10429
+ } catch {
10430
+ }
10431
+ let result;
10432
+ let failure;
10433
+ try {
10434
+ result = await original(...args);
10435
+ return result;
10436
+ } catch (err) {
10437
+ failure = err;
10438
+ throw err;
10439
+ } finally {
10440
+ stopObserving?.();
10441
+ try {
10442
+ emitHarnessOperation(
10443
+ method,
10444
+ input,
10445
+ result,
10446
+ failure,
10447
+ harness.getModel?.(),
10448
+ parent,
10449
+ startedAt,
10450
+ Date.now(),
10451
+ observedModelCall
10452
+ );
10453
+ } catch {
10454
+ }
10455
+ }
10456
+ };
10457
+ }
10458
+ function emitHarnessOperation(method, input, result, failure, model, capturedParent, startedAt, endedAt, observedModelCall) {
10459
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10460
+ let parent = capturedParent;
10461
+ let root;
10462
+ const inputText = safeStringify12(input ?? {});
10463
+ const outputText = failure ? `[error] ${failure instanceof Error ? failure.message : String(failure)}` : harnessOperationOutput(method, result);
10464
+ if (!import_api19.trace.getSpan(parent)?.isRecording()) {
10465
+ root = tracer.startSpan(
10466
+ `pi_agent.harness.${method}`,
10467
+ {
10468
+ startTime: startedAt,
10469
+ attributes: {
10470
+ "neatlogs.span.kind": "WORKFLOW",
10471
+ "input.value": inputText
10472
+ }
10473
+ },
10474
+ parent
10475
+ );
10476
+ parent = import_api19.trace.setSpan(parent, root);
10477
+ }
10478
+ const chain = tracer.startSpan(
10479
+ `pi_agent.harness.${method}`,
10480
+ {
10481
+ startTime: startedAt,
10482
+ attributes: {
10483
+ "neatlogs.span.kind": "CHAIN",
10484
+ "neatlogs.pi.operation": method,
10485
+ "input.value": inputText
10486
+ }
10487
+ },
10488
+ parent
10489
+ );
10490
+ const chainCtx = import_api19.trace.setSpan(parent, chain);
10491
+ const usage = harnessOperationUsage(method, result);
10492
+ const madeModelCall = observedModelCall ?? (failure ? isHarnessSummarizationFailure(failure) : method === "compact" ? Boolean(result?.usage) : Boolean(result?.summaryEntry) && result.summaryEntry.fromHook !== true);
10493
+ if (madeModelCall || failure) {
10494
+ const llm = openLlmSpan(
10495
+ tracer,
10496
+ {
10497
+ toolSpans: /* @__PURE__ */ new Map(),
10498
+ inputMessages: [{ role: "user", content: inputText }],
10499
+ turnIndex: 0,
10500
+ turnCtx: chainCtx,
10501
+ turnStartEpochMs: startedAt
10502
+ },
10503
+ {
10504
+ role: "assistant",
10505
+ model: model?.id ?? model?.model,
10506
+ provider: model?.provider,
10507
+ timestamp: startedAt
10508
+ }
10509
+ );
10510
+ if (failure) {
10511
+ closeLlmFailure(llm, failure, endedAt);
10512
+ } else {
10513
+ finishLlmSpan(
10514
+ llm,
10515
+ {
10516
+ role: "assistant",
10517
+ model: model?.id ?? model?.model,
10518
+ provider: model?.provider,
10519
+ content: outputText ? [{ type: "text", text: outputText }] : [],
10520
+ usage,
10521
+ stopReason: "stop"
10522
+ },
10523
+ endedAt
10524
+ );
10525
+ }
10526
+ }
10527
+ if (outputText) chain.setAttribute("output.value", outputText);
10528
+ if (failure) {
10529
+ const message = failure instanceof Error ? failure.message : String(failure);
10530
+ chain.setAttribute("neatlogs.error.message", message);
10531
+ chain.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10532
+ } else {
10533
+ chain.setStatus({ code: import_api19.SpanStatusCode.OK });
10534
+ }
10535
+ chain.end(endedAt);
10536
+ if (root) {
10537
+ if (outputText) root.setAttribute("output.value", outputText);
10538
+ if (failure) {
10539
+ const message = failure instanceof Error ? failure.message : String(failure);
10540
+ root.setAttribute("neatlogs.error.message", message);
10541
+ root.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10542
+ } else {
10543
+ root.setStatus({ code: import_api19.SpanStatusCode.OK });
10544
+ }
10545
+ root.end(endedAt);
10546
+ }
10547
+ }
10548
+ function isHarnessSummarizationFailure(failure) {
10549
+ let current = failure;
10550
+ const seen = /* @__PURE__ */ new Set();
10551
+ while (current && !seen.has(current)) {
10552
+ seen.add(current);
10553
+ if (current.code === "summarization_failed") return true;
10554
+ current = current.cause;
10555
+ }
10556
+ return false;
10557
+ }
10558
+ function harnessOperationOutput(method, result) {
10559
+ if (method === "compact") return typeof result?.summary === "string" ? result.summary : safeStringify12(result ?? {});
10560
+ const summary = result?.summaryEntry?.summary;
10561
+ return typeof summary === "string" ? summary : safeStringify12(result ?? {});
10562
+ }
10563
+ function harnessOperationUsage(method, result) {
10564
+ return method === "compact" ? result?.usage : result?.summaryEntry?.usage;
10565
+ }
10566
+ function closeLlmFailure(llm, failure, endedAt) {
10567
+ const message = failure instanceof Error ? failure.message : String(failure);
10568
+ llm.span.setAttribute("output.value", `[error] ${message}`);
10569
+ llm.span.setAttribute("neatlogs.error.message", message);
10570
+ llm.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10571
+ llm.span.end(endedAt);
10572
+ }
10573
+ function tracePiAgentEvents(getTranscript) {
10574
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10281
10575
  const tracer = getNeatlogsTracer(TRACER_NAME12);
10282
- a.subscribe((event) => {
10576
+ return (event) => {
10283
10577
  try {
10284
- handleEvent(tracer, state, event);
10578
+ handleEvent(tracer, state, event, getTranscript);
10285
10579
  } catch {
10286
10580
  }
10581
+ };
10582
+ }
10583
+ function tracePiStream(streamFn) {
10584
+ const tracer = getNeatlogsTracer(TRACER_NAME12);
10585
+ return ((model, context2, options) => {
10586
+ let llm;
10587
+ let root;
10588
+ try {
10589
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
10590
+ state.inputMessages = promptMessagesOf(context2);
10591
+ let parent = getNeatlogsParentContext();
10592
+ if (!import_api19.trace.getSpan(parent)?.isRecording()) {
10593
+ root = tracer.startSpan(
10594
+ "pi_agent.stream",
10595
+ { attributes: { "neatlogs.span.kind": "WORKFLOW" } },
10596
+ parent
10597
+ );
10598
+ parent = import_api19.trace.setSpan(parent, root);
10599
+ const rootInput = state.inputMessages.length ? safeStringify12({ messages: state.inputMessages }) : void 0;
10600
+ if (rootInput) root.setAttribute("input.value", rootInput);
10601
+ }
10602
+ state.turnCtx = parent;
10603
+ llm = openLlmSpan(tracer, state, {
10604
+ role: "assistant",
10605
+ model: model?.id ?? model?.model,
10606
+ provider: model?.provider
10607
+ });
10608
+ } catch {
10609
+ }
10610
+ let returned;
10611
+ try {
10612
+ returned = streamFn(model, context2, options);
10613
+ } catch (err) {
10614
+ closeStreamFailure(llm, root, err);
10615
+ throw err;
10616
+ }
10617
+ if (isThenable(returned)) {
10618
+ return returned.then(
10619
+ (stream) => attachStreamResult(stream, llm, root),
10620
+ (err) => {
10621
+ closeStreamFailure(llm, root, err);
10622
+ throw err;
10623
+ }
10624
+ );
10625
+ }
10626
+ return attachStreamResult(returned, llm, root);
10287
10627
  });
10288
- markPatched2(a);
10289
- return agent;
10290
10628
  }
10291
- function handleEvent(tracer, state, event) {
10629
+ function attachStreamResult(stream, llm, root) {
10630
+ if (!llm || !stream || typeof stream.result !== "function") {
10631
+ closeStreamRoot(root);
10632
+ return stream;
10633
+ }
10634
+ observeStreamDeltas(stream, llm);
10635
+ stream.result().then(
10636
+ (msg) => {
10637
+ const message = msg ?? { role: "assistant" };
10638
+ finishLlmSpan(llm, message);
10639
+ closeStreamRoot(root, message);
10640
+ },
10641
+ (err) => closeStreamFailure(llm, root, err)
10642
+ );
10643
+ return stream;
10644
+ }
10645
+ function observeStreamDeltas(stream, llm) {
10646
+ const original = stream?.[Symbol.asyncIterator];
10647
+ if (typeof original !== "function" || original.__neatlogsObserved) return;
10648
+ const observed = function() {
10649
+ const iterator = original.call(this);
10650
+ return {
10651
+ next: async (...args) => {
10652
+ const item = await iterator.next(...args);
10653
+ if (!item.done && isContentDelta(item.value?.type)) markFirstStreamDelta(llm);
10654
+ return item;
10655
+ },
10656
+ return: iterator.return?.bind(iterator),
10657
+ throw: iterator.throw?.bind(iterator)
10658
+ };
10659
+ };
10660
+ Object.defineProperty(observed, "__neatlogsObserved", { value: true });
10661
+ try {
10662
+ stream[Symbol.asyncIterator] = observed;
10663
+ } catch {
10664
+ }
10665
+ }
10666
+ function markFirstStreamDelta(llm) {
10667
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10668
+ if (llm.ttftMs !== void 0) return;
10669
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10670
+ }
10671
+ function closeStreamFailure(llm, root, err) {
10672
+ const message = err instanceof Error ? err.message : String(err);
10673
+ try {
10674
+ llm?.span.setAttribute("output.value", `[error] ${message}`);
10675
+ llm?.span.setAttribute("neatlogs.error.message", message);
10676
+ llm?.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message });
10677
+ llm?.span.end();
10678
+ } catch {
10679
+ }
10680
+ closeStreamRoot(root, void 0, message);
10681
+ }
10682
+ function isThenable(value) {
10683
+ return value != null && typeof value.then === "function";
10684
+ }
10685
+ function closeStreamRoot(root, msg, error) {
10686
+ if (!root) return;
10687
+ try {
10688
+ if (msg) {
10689
+ const { text, toolCalls } = splitAssistantContent(msg.content);
10690
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10691
+ if (out) root.setAttribute("output.value", out);
10692
+ applyStopReasonStatus(root, msg);
10693
+ } else if (error) {
10694
+ root.setAttribute("output.value", `[error] ${error}`);
10695
+ root.setAttribute("neatlogs.error.message", error);
10696
+ root.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: error });
10697
+ }
10698
+ root.end();
10699
+ } catch {
10700
+ }
10701
+ }
10702
+ function transcriptRows(messages) {
10703
+ if (!Array.isArray(messages)) return [];
10704
+ const rows = [];
10705
+ for (const m of messages) {
10706
+ const content = messageText(m);
10707
+ if (!content) continue;
10708
+ rows.push({ role: m?.role === "toolResult" ? "tool" : String(m?.role ?? "user"), content });
10709
+ }
10710
+ return rows;
10711
+ }
10712
+ function promptMessagesOf(context2) {
10713
+ const rows = [];
10714
+ if (typeof context2?.systemPrompt === "string" && context2.systemPrompt) {
10715
+ rows.push({ role: "system", content: context2.systemPrompt });
10716
+ }
10717
+ if (Array.isArray(context2?.messages)) {
10718
+ for (const m of context2.messages) {
10719
+ const content = messageText(m);
10720
+ if (content) rows.push({ role: String(m?.role ?? "user"), content });
10721
+ }
10722
+ }
10723
+ return rows;
10724
+ }
10725
+ function handleEvent(tracer, state, event, getTranscript) {
10292
10726
  switch (event.type) {
10293
10727
  case "agent_start": {
10294
10728
  const base = getNeatlogsParentContext();
@@ -10299,31 +10733,98 @@ function handleEvent(tracer, state, event) {
10299
10733
  );
10300
10734
  state.agentSpan = span2;
10301
10735
  state.agentCtx = import_api19.trace.setSpan(base, span2);
10302
- state.inputMessages = [];
10736
+ state.inputMessages = transcriptRows(getTranscript?.());
10737
+ state.turnIndex = 0;
10738
+ state.runInput = void 0;
10739
+ state.runError = void 0;
10740
+ break;
10741
+ }
10742
+ case "turn_start": {
10743
+ const parent = state.agentCtx ?? getNeatlogsParentContext();
10744
+ state.turnIndex += 1;
10745
+ const pending = state.inputMessages[state.inputMessages.length - 1];
10746
+ const priorInput = pending && (pending.role === "user" || pending.role === "tool") ? pending.content : void 0;
10747
+ const span2 = tracer.startSpan(
10748
+ `pi_agent.turn.${state.turnIndex}`,
10749
+ {
10750
+ attributes: {
10751
+ "neatlogs.span.kind": "CHAIN",
10752
+ "neatlogs.chain.turn_index": state.turnIndex,
10753
+ ...priorInput ? { "input.value": priorInput } : {}
10754
+ }
10755
+ },
10756
+ parent
10757
+ );
10758
+ state.turnSpan = span2;
10759
+ state.turnCtx = import_api19.trace.setSpan(parent, span2);
10760
+ state.turnHasInput = !!priorInput;
10761
+ state.turnStartEpochMs = Date.now();
10762
+ break;
10763
+ }
10764
+ case "turn_end": {
10765
+ if (!state.turnSpan) break;
10766
+ const msg = event.message;
10767
+ const { text, toolCalls } = splitAssistantContent(msg?.content);
10768
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10769
+ if (out) state.turnSpan.setAttribute("output.value", out);
10770
+ if (Array.isArray(event.toolResults) && event.toolResults.length) {
10771
+ state.turnSpan.setAttribute("neatlogs.chain.tool_result_count", event.toolResults.length);
10772
+ }
10773
+ applyStopReasonStatus(state.turnSpan, msg);
10774
+ state.turnSpan.end();
10775
+ state.turnSpan = void 0;
10776
+ state.turnCtx = void 0;
10777
+ state.turnHasInput = false;
10778
+ state.turnStartEpochMs = void 0;
10779
+ break;
10780
+ }
10781
+ case "message_start": {
10782
+ const msg = event.message;
10783
+ if (!msg || msg.role !== "assistant") break;
10784
+ state.llm = openLlmSpan(tracer, state, msg);
10785
+ break;
10786
+ }
10787
+ case "message_update": {
10788
+ const llm = state.llm;
10789
+ if (!llm) break;
10790
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
10791
+ if (llm.ttftMs === void 0 && isContentDelta(event.assistantMessageEvent?.type)) {
10792
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
10793
+ }
10303
10794
  break;
10304
10795
  }
10305
10796
  case "message_end": {
10306
10797
  const msg = event.message;
10307
10798
  if (!msg) return;
10308
10799
  if (msg.role === "assistant") {
10309
- emitLlmSpan(tracer, state, msg);
10800
+ const llm = state.llm ?? openLlmSpan(tracer, state, msg);
10801
+ state.llm = void 0;
10802
+ state.runError = finishLlmSpan(llm, msg) ?? state.runError;
10310
10803
  const { text } = splitAssistantContent(msg.content);
10311
10804
  if (text) state.inputMessages.push({ role: "assistant", content: text });
10312
10805
  } else {
10313
10806
  const role = msg.role === "toolResult" ? "tool" : String(msg.role || "user");
10314
10807
  const content = messageText(msg);
10315
- if (content) state.inputMessages.push({ role, content });
10808
+ if (content) {
10809
+ state.inputMessages.push({ role, content });
10810
+ if (role === "user" && state.runInput === void 0) state.runInput = content;
10811
+ if (state.turnSpan && !state.turnHasInput) {
10812
+ state.turnSpan.setAttribute("input.value", content);
10813
+ state.turnHasInput = true;
10814
+ }
10815
+ }
10316
10816
  }
10317
10817
  break;
10318
10818
  }
10319
10819
  case "tool_execution_start": {
10320
- const parent = state.agentCtx ?? getNeatlogsParentContext();
10820
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10321
10821
  const span2 = tracer.startSpan(
10322
10822
  `pi_agent.tool.${event.toolName ?? "tool"}`,
10323
10823
  {
10324
10824
  attributes: {
10325
10825
  "neatlogs.span.kind": "TOOL",
10326
10826
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
10827
+ ...event.toolCallId ? { "neatlogs.tool.call_id": String(event.toolCallId) } : {},
10327
10828
  ...event.args !== void 0 ? { "input.value": safeStringify12(event.args) } : {}
10328
10829
  }
10329
10830
  },
@@ -10332,6 +10833,12 @@ function handleEvent(tracer, state, event) {
10332
10833
  if (event.toolCallId) state.toolSpans.set(event.toolCallId, span2);
10333
10834
  break;
10334
10835
  }
10836
+ case "tool_execution_update": {
10837
+ const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10838
+ if (!span2) break;
10839
+ span2.setAttribute("neatlogs.tool.is_streaming", true);
10840
+ break;
10841
+ }
10335
10842
  case "tool_execution_end": {
10336
10843
  const span2 = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
10337
10844
  if (!span2) return;
@@ -10339,7 +10846,7 @@ function handleEvent(tracer, state, event) {
10339
10846
  span2.setAttribute("output.value", safeStringify12(event.result));
10340
10847
  }
10341
10848
  if (event.isError) {
10342
- span2.setStatus({ code: import_api19.SpanStatusCode.ERROR });
10849
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: toolErrorText(event.result) });
10343
10850
  span2.setAttribute("neatlogs.tool.is_error", true);
10344
10851
  } else {
10345
10852
  span2.setStatus({ code: import_api19.SpanStatusCode.OK });
@@ -10351,17 +10858,47 @@ function handleEvent(tracer, state, event) {
10351
10858
  case "agent_end": {
10352
10859
  for (const ts of state.toolSpans.values()) {
10353
10860
  try {
10861
+ ts.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: "run ended before tool completed" });
10354
10862
  ts.end();
10355
10863
  } catch {
10356
10864
  }
10357
10865
  }
10358
10866
  state.toolSpans.clear();
10867
+ if (state.llm) {
10868
+ try {
10869
+ state.llm.span.setAttribute("output.value", "[incomplete] run ended mid-stream");
10870
+ state.llm.span.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: "run ended mid-stream" });
10871
+ state.llm.span.end();
10872
+ } catch {
10873
+ }
10874
+ state.llm = void 0;
10875
+ }
10876
+ if (state.turnSpan) {
10877
+ try {
10878
+ state.turnSpan.end();
10879
+ } catch {
10880
+ }
10881
+ state.turnSpan = void 0;
10882
+ state.turnCtx = void 0;
10883
+ state.turnStartEpochMs = void 0;
10884
+ }
10359
10885
  if (state.agentSpan) {
10360
- const firstUser = state.inputMessages.find((m) => m.role === "user");
10361
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
10886
+ const input = state.runInput ?? lastUserTextFrom(event.messages) ?? lastUserTextFrom(getTranscript?.());
10887
+ if (input) state.agentSpan.setAttribute("input.value", input);
10362
10888
  const finalText = lastAssistantText(event.messages);
10363
- if (finalText) state.agentSpan.setAttribute("output.value", finalText);
10364
- state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
10889
+ const err = state.runError ?? terminalErrorFrom(event.messages);
10890
+ if (finalText) {
10891
+ state.agentSpan.setAttribute("output.value", finalText);
10892
+ } else if (err) {
10893
+ state.agentSpan.setAttribute("output.value", `[${err.stopReason}]${err.message ? ` ${err.message}` : ""}`);
10894
+ }
10895
+ if (err) {
10896
+ state.agentSpan.setAttribute("neatlogs.agent.stop_reason", err.stopReason);
10897
+ if (err.message) state.agentSpan.setAttribute("neatlogs.error.message", err.message);
10898
+ state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: err.message ?? err.stopReason });
10899
+ } else {
10900
+ state.agentSpan.setStatus({ code: import_api19.SpanStatusCode.OK });
10901
+ }
10365
10902
  state.agentSpan.end();
10366
10903
  state.agentSpan = void 0;
10367
10904
  state.agentCtx = void 0;
@@ -10372,55 +10909,158 @@ function handleEvent(tracer, state, event) {
10372
10909
  break;
10373
10910
  }
10374
10911
  }
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;
10912
+ function openLlmSpan(tracer, state, msg) {
10913
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
10914
+ const startTime = callStartFrom(msg, state.turnStartEpochMs);
10915
+ const span2 = tracer.startSpan(
10916
+ `pi_agent.llm.${msg.model || "model"}`,
10917
+ {
10918
+ ...startTime !== void 0 ? { startTime } : {},
10919
+ attributes: {
10920
+ "neatlogs.span.kind": "LLM",
10921
+ // Pi streams by default; message_update deltas confirm it per call.
10922
+ "neatlogs.llm.is_streaming": false,
10923
+ ...msg.model ? { "neatlogs.llm.model_name": String(msg.model) } : {},
10924
+ ...msg.provider ? { "neatlogs.llm.provider": String(msg.provider) } : {}
10925
+ }
10926
+ },
10927
+ parent
10928
+ );
10929
+ return {
10930
+ span: span2,
10931
+ startHr: nowMs(),
10932
+ callStartEpochMs: startTime,
10933
+ inputMessages: state.inputMessages.slice()
10934
+ };
10935
+ }
10936
+ function callStartFrom(msg, turnStart) {
10937
+ const ts = msg.timestamp;
10938
+ if (typeof ts !== "number" || !Number.isFinite(ts)) return void 0;
10939
+ const now = Date.now();
10940
+ const age = now - ts;
10941
+ if (age < 0 || age > MAX_CALL_AGE_MS) return void 0;
10942
+ return turnStart !== void 0 ? Math.max(ts, turnStart) : ts;
10943
+ }
10944
+ var MAX_CALL_AGE_MS = 30 * 6e4;
10945
+ function terminalSummary(msg) {
10946
+ const reason = msg?.stopReason;
10947
+ if (reason !== "aborted" && reason !== "error") return "";
10948
+ return `[${reason}]${msg?.errorMessage ? ` ${msg.errorMessage}` : ""}`;
10949
+ }
10950
+ function applyStopReasonStatus(span2, msg) {
10951
+ const reason = msg?.stopReason;
10952
+ if (reason === "aborted" || reason === "error") {
10953
+ span2.setStatus({ code: import_api19.SpanStatusCode.ERROR, message: msg?.errorMessage ?? reason });
10954
+ } else {
10955
+ span2.setStatus({ code: import_api19.SpanStatusCode.OK });
10956
+ }
10957
+ }
10958
+ function isContentDelta(type) {
10959
+ return type === "text_delta" || type === "thinking_delta" || type === "toolcall_delta";
10960
+ }
10961
+ function nowMs() {
10962
+ const p = globalThis.performance;
10963
+ return typeof p?.now === "function" ? p.now() : Date.now();
10964
+ }
10965
+ function toolErrorText(result) {
10966
+ if (!result || typeof result !== "object") return "tool error";
10967
+ const content = result.content;
10968
+ if (Array.isArray(content)) {
10969
+ const text = content.map((b) => typeof b?.text === "string" ? b.text : "").filter(Boolean).join(" ").trim();
10970
+ if (text) return text;
10971
+ }
10972
+ return "tool error";
10973
+ }
10974
+ function lastUserTextFrom(messages) {
10975
+ if (!Array.isArray(messages)) return void 0;
10976
+ for (let i = messages.length - 1; i >= 0; i--) {
10977
+ const m = messages[i];
10978
+ if (m && m.role === "user") {
10979
+ const text = messageText(m);
10980
+ if (text) return text;
10981
+ }
10982
+ }
10983
+ return void 0;
10984
+ }
10985
+ function terminalErrorFrom(messages) {
10986
+ if (!Array.isArray(messages)) return void 0;
10987
+ for (let i = messages.length - 1; i >= 0; i--) {
10988
+ const m = messages[i];
10989
+ if (m && m.role === "assistant") {
10990
+ if (m.stopReason === "aborted" || m.stopReason === "error") {
10991
+ return { stopReason: m.stopReason, message: m.errorMessage };
10992
+ }
10993
+ return void 0;
10994
+ }
10995
+ }
10996
+ return void 0;
10997
+ }
10998
+ function finishLlmSpan(llm, msg, endTime) {
10999
+ const span2 = llm.span;
11000
+ if (msg.model) span2.setAttribute("neatlogs.llm.model_name", String(msg.model));
11001
+ if (msg.provider) span2.setAttribute("neatlogs.llm.provider", String(msg.provider));
11002
+ if (msg.responseModel) span2.setAttribute("neatlogs.llm.response_model", String(msg.responseModel));
11003
+ if (msg.api) span2.setAttribute("neatlogs.llm.api", String(msg.api));
11004
+ if (msg.stopReason) span2.setAttribute("neatlogs.llm.stop_reason", String(msg.stopReason));
11005
+ const inMsgs = llm.inputMessages;
10381
11006
  if (inMsgs.length) {
10382
11007
  inMsgs.forEach((m, i) => {
10383
- attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
10384
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
11008
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.role`, m.role);
11009
+ span2.setAttribute(`neatlogs.llm.input_messages.${i}.content`, m.content);
10385
11010
  });
10386
- attrs["neatlogs.llm.input"] = safeStringify12({ messages: inMsgs });
10387
- attrs["input.value"] = safeStringify12({ messages: inMsgs });
11011
+ const inBlob = safeStringify12({ messages: inMsgs });
11012
+ span2.setAttribute("neatlogs.llm.input", inBlob);
11013
+ span2.setAttribute("input.value", inBlob);
10388
11014
  }
10389
11015
  const { text, toolCalls } = splitAssistantContent(msg.content);
10390
- const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n");
11016
+ const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify12(tc.arguments)})`).join("\n") || terminalSummary(msg);
10391
11017
  if (outText || toolCalls.length) {
10392
- attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
10393
- attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
11018
+ span2.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
11019
+ span2.setAttribute("neatlogs.llm.output_messages.0.content", outText || "");
10394
11020
  const outBlob = { role: "assistant", content: outText || "" };
10395
11021
  if (toolCalls.length) {
10396
11022
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
10397
11023
  toolCalls.forEach((tc, j) => {
10398
- if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;
11024
+ if (tc.name) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
10399
11025
  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);
11026
+ span2.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify12(tc.arguments));
11027
+ if (tc.id) span2.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, String(tc.id));
10402
11028
  });
10403
11029
  }
10404
- attrs["neatlogs.llm.output"] = safeStringify12(outBlob);
10405
- attrs["output.value"] = outText || "";
11030
+ span2.setAttribute("neatlogs.llm.output", safeStringify12(outBlob));
11031
+ span2.setAttribute("output.value", outText || "");
10406
11032
  }
10407
11033
  const usage = msg.usage;
10408
11034
  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;
11035
+ if (usage.input != null) span2.setAttribute("neatlogs.llm.token_count.prompt", usage.input);
11036
+ if (usage.output != null) span2.setAttribute("neatlogs.llm.token_count.completion", usage.output);
10411
11037
  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;
11038
+ if (total) span2.setAttribute("neatlogs.llm.token_count.total", total);
11039
+ if (usage.cacheRead) span2.setAttribute("neatlogs.llm.token_count.cache_read", usage.cacheRead);
11040
+ if (usage.cacheWrite) span2.setAttribute("neatlogs.llm.token_count.cache_write", usage.cacheWrite);
11041
+ const cost = usage.cost;
11042
+ if (cost) {
11043
+ const totalCost = cost.total ?? [cost.input, cost.output, cacheReadOf(cost), cost.cacheWrite].reduce(
11044
+ (sum, v) => sum + (typeof v === "number" ? v : 0),
11045
+ 0
11046
+ );
11047
+ if (typeof totalCost === "number" && totalCost > 0) {
11048
+ span2.setAttribute("neatlogs.llm.cost_usd", totalCost);
11049
+ }
11050
+ if (typeof cost.input === "number") span2.setAttribute("neatlogs.llm.cost.prompt", cost.input);
11051
+ if (typeof cost.output === "number") span2.setAttribute("neatlogs.llm.cost.completion", cost.output);
11052
+ }
10415
11053
  }
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();
11054
+ if (llm.ttftMs !== void 0) {
11055
+ span2.setAttribute("neatlogs.llm.metrics.ttft_ms", Math.round(llm.ttftMs));
11056
+ }
11057
+ if (msg.errorMessage) span2.setAttribute("neatlogs.error.message", String(msg.errorMessage));
11058
+ applyStopReasonStatus(span2, msg);
11059
+ span2.end(endTime);
11060
+ return msg.stopReason === "aborted" || msg.stopReason === "error" ? { stopReason: msg.stopReason, message: msg.errorMessage } : void 0;
11061
+ }
11062
+ function cacheReadOf(cost) {
11063
+ return typeof cost.cacheRead === "number" ? cost.cacheRead : cost.cacheReads;
10424
11064
  }
10425
11065
  function splitAssistantContent(content) {
10426
11066
  const texts = [];
@@ -11652,7 +12292,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11652
12292
  if (st.processed.has(id)) return void 0;
11653
12293
  st.processed.add(id);
11654
12294
  startRoot(st, String(sessionID));
11655
- emitLlmSpan2(shipper, st, info, String(sessionID));
12295
+ emitLlmSpan(shipper, st, info, String(sessionID));
11656
12296
  return shipper.flush().catch(() => void 0);
11657
12297
  }
11658
12298
  if (type === "session.idle" || type === "session.deleted") {
@@ -11667,7 +12307,7 @@ function handleEvent2(shipper, sessions, stateFor, startRoot, closeAndFlush, eve
11667
12307
  }
11668
12308
  return void 0;
11669
12309
  }
11670
- function emitLlmSpan2(shipper, st, info, sessionID) {
12310
+ function emitLlmSpan(shipper, st, info, sessionID) {
11671
12311
  if (!st.traceId) return;
11672
12312
  const model = info?.modelID ?? info?.model ?? "";
11673
12313
  const provider = info?.providerID ?? info?.provider ?? "";
@@ -11864,6 +12504,8 @@ function bindTemplates(llm, systemTpl, userTpl, compiledVars) {
11864
12504
  span,
11865
12505
  strandsHooks,
11866
12506
  trace,
12507
+ tracePiAgentEvents,
12508
+ tracePiStream,
11867
12509
  traceToolAnthropic,
11868
12510
  traceToolAzureOpenAI,
11869
12511
  traceToolBedrock,