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/pi-agent.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/pi-agent.ts
21
21
  var pi_agent_exports = {};
22
22
  __export(pi_agent_exports, {
23
- piAgentHooks: () => piAgentHooks
23
+ piAgentHooks: () => piAgentHooks,
24
+ tracePiAgentEvents: () => tracePiAgentEvents,
25
+ tracePiStream: () => tracePiStream
24
26
  });
25
27
  module.exports = __toCommonJS(pi_agent_exports);
26
28
  var import_api2 = require("@opentelemetry/api");
@@ -62,22 +64,342 @@ function getNeatlogsParentContext(baseContext) {
62
64
  // src/pi-agent.ts
63
65
  var TRACER_NAME = "neatlogs.pi-agent";
64
66
  var PATCH_FLAG = "_neatlogs_patched";
67
+ var HARNESS_METHOD_FLAG = "_neatlogs_harness_methods_patched";
65
68
  function piAgentHooks(agent) {
66
69
  if (!agent || agent[PATCH_FLAG]) return agent;
67
70
  const a = agent;
68
71
  if (typeof a.subscribe !== "function") return agent;
69
- const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
72
+ const listener = tracePiAgentEvents(() => a.state?.messages);
73
+ a.subscribe((event) => listener(event));
74
+ wrapHarnessModelOperations(a);
75
+ markPatched(a);
76
+ return agent;
77
+ }
78
+ function wrapHarnessModelOperations(harness) {
79
+ if (harness[HARNESS_METHOD_FLAG]) return;
80
+ const isHarness = typeof harness.compact === "function" && typeof harness.navigateTree === "function" && typeof harness.getModel === "function";
81
+ if (!isHarness) return;
82
+ patchHarnessMethod(harness, "compact", (args) => ({ customInstructions: args[0] }));
83
+ patchHarnessMethod(harness, "navigateTree", (args) => ({ targetId: args[0], options: args[1] }));
84
+ try {
85
+ Object.defineProperty(harness, HARNESS_METHOD_FLAG, { value: true });
86
+ } catch {
87
+ harness[HARNESS_METHOD_FLAG] = true;
88
+ }
89
+ }
90
+ function patchHarnessMethod(harness, method, inputOf) {
91
+ const original = harness[method].bind(harness);
92
+ harness[method] = async (...args) => {
93
+ const startedAt = Date.now();
94
+ const parent = getNeatlogsParentContext();
95
+ const input = inputOf(args);
96
+ let observedModelCall;
97
+ let stopObserving;
98
+ try {
99
+ stopObserving = harness.subscribe((event) => {
100
+ if (method === "compact" && event?.type === "session_compact") {
101
+ observedModelCall = !event.fromHook;
102
+ } else if (method === "navigateTree" && event?.type === "session_tree") {
103
+ observedModelCall = Boolean(event.summaryEntry) && !event.fromHook;
104
+ }
105
+ });
106
+ } catch {
107
+ }
108
+ let result;
109
+ let failure;
110
+ try {
111
+ result = await original(...args);
112
+ return result;
113
+ } catch (err) {
114
+ failure = err;
115
+ throw err;
116
+ } finally {
117
+ stopObserving?.();
118
+ try {
119
+ emitHarnessOperation(
120
+ method,
121
+ input,
122
+ result,
123
+ failure,
124
+ harness.getModel?.(),
125
+ parent,
126
+ startedAt,
127
+ Date.now(),
128
+ observedModelCall
129
+ );
130
+ } catch {
131
+ }
132
+ }
133
+ };
134
+ }
135
+ function emitHarnessOperation(method, input, result, failure, model, capturedParent, startedAt, endedAt, observedModelCall) {
136
+ const tracer = getNeatlogsTracer(TRACER_NAME);
137
+ let parent = capturedParent;
138
+ let root;
139
+ const inputText = safeStringify(input ?? {});
140
+ const outputText = failure ? `[error] ${failure instanceof Error ? failure.message : String(failure)}` : harnessOperationOutput(method, result);
141
+ if (!import_api2.trace.getSpan(parent)?.isRecording()) {
142
+ root = tracer.startSpan(
143
+ `pi_agent.harness.${method}`,
144
+ {
145
+ startTime: startedAt,
146
+ attributes: {
147
+ "neatlogs.span.kind": "WORKFLOW",
148
+ "input.value": inputText
149
+ }
150
+ },
151
+ parent
152
+ );
153
+ parent = import_api2.trace.setSpan(parent, root);
154
+ }
155
+ const chain = tracer.startSpan(
156
+ `pi_agent.harness.${method}`,
157
+ {
158
+ startTime: startedAt,
159
+ attributes: {
160
+ "neatlogs.span.kind": "CHAIN",
161
+ "neatlogs.pi.operation": method,
162
+ "input.value": inputText
163
+ }
164
+ },
165
+ parent
166
+ );
167
+ const chainCtx = import_api2.trace.setSpan(parent, chain);
168
+ const usage = harnessOperationUsage(method, result);
169
+ const madeModelCall = observedModelCall ?? (failure ? isHarnessSummarizationFailure(failure) : method === "compact" ? Boolean(result?.usage) : Boolean(result?.summaryEntry) && result.summaryEntry.fromHook !== true);
170
+ if (madeModelCall || failure) {
171
+ const llm = openLlmSpan(
172
+ tracer,
173
+ {
174
+ toolSpans: /* @__PURE__ */ new Map(),
175
+ inputMessages: [{ role: "user", content: inputText }],
176
+ turnIndex: 0,
177
+ turnCtx: chainCtx,
178
+ turnStartEpochMs: startedAt
179
+ },
180
+ {
181
+ role: "assistant",
182
+ model: model?.id ?? model?.model,
183
+ provider: model?.provider,
184
+ timestamp: startedAt
185
+ }
186
+ );
187
+ if (failure) {
188
+ closeLlmFailure(llm, failure, endedAt);
189
+ } else {
190
+ finishLlmSpan(
191
+ llm,
192
+ {
193
+ role: "assistant",
194
+ model: model?.id ?? model?.model,
195
+ provider: model?.provider,
196
+ content: outputText ? [{ type: "text", text: outputText }] : [],
197
+ usage,
198
+ stopReason: "stop"
199
+ },
200
+ endedAt
201
+ );
202
+ }
203
+ }
204
+ if (outputText) chain.setAttribute("output.value", outputText);
205
+ if (failure) {
206
+ const message = failure instanceof Error ? failure.message : String(failure);
207
+ chain.setAttribute("neatlogs.error.message", message);
208
+ chain.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
209
+ } else {
210
+ chain.setStatus({ code: import_api2.SpanStatusCode.OK });
211
+ }
212
+ chain.end(endedAt);
213
+ if (root) {
214
+ if (outputText) root.setAttribute("output.value", outputText);
215
+ if (failure) {
216
+ const message = failure instanceof Error ? failure.message : String(failure);
217
+ root.setAttribute("neatlogs.error.message", message);
218
+ root.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
219
+ } else {
220
+ root.setStatus({ code: import_api2.SpanStatusCode.OK });
221
+ }
222
+ root.end(endedAt);
223
+ }
224
+ }
225
+ function isHarnessSummarizationFailure(failure) {
226
+ let current = failure;
227
+ const seen = /* @__PURE__ */ new Set();
228
+ while (current && !seen.has(current)) {
229
+ seen.add(current);
230
+ if (current.code === "summarization_failed") return true;
231
+ current = current.cause;
232
+ }
233
+ return false;
234
+ }
235
+ function harnessOperationOutput(method, result) {
236
+ if (method === "compact") return typeof result?.summary === "string" ? result.summary : safeStringify(result ?? {});
237
+ const summary = result?.summaryEntry?.summary;
238
+ return typeof summary === "string" ? summary : safeStringify(result ?? {});
239
+ }
240
+ function harnessOperationUsage(method, result) {
241
+ return method === "compact" ? result?.usage : result?.summaryEntry?.usage;
242
+ }
243
+ function closeLlmFailure(llm, failure, endedAt) {
244
+ const message = failure instanceof Error ? failure.message : String(failure);
245
+ llm.span.setAttribute("output.value", `[error] ${message}`);
246
+ llm.span.setAttribute("neatlogs.error.message", message);
247
+ llm.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
248
+ llm.span.end(endedAt);
249
+ }
250
+ function tracePiAgentEvents(getTranscript) {
251
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
70
252
  const tracer = getNeatlogsTracer(TRACER_NAME);
71
- a.subscribe((event) => {
253
+ return (event) => {
72
254
  try {
73
- handleEvent(tracer, state, event);
255
+ handleEvent(tracer, state, event, getTranscript);
74
256
  } catch {
75
257
  }
258
+ };
259
+ }
260
+ function tracePiStream(streamFn) {
261
+ const tracer = getNeatlogsTracer(TRACER_NAME);
262
+ return ((model, context, options) => {
263
+ let llm;
264
+ let root;
265
+ try {
266
+ const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [], turnIndex: 0 };
267
+ state.inputMessages = promptMessagesOf(context);
268
+ let parent = getNeatlogsParentContext();
269
+ if (!import_api2.trace.getSpan(parent)?.isRecording()) {
270
+ root = tracer.startSpan(
271
+ "pi_agent.stream",
272
+ { attributes: { "neatlogs.span.kind": "WORKFLOW" } },
273
+ parent
274
+ );
275
+ parent = import_api2.trace.setSpan(parent, root);
276
+ const rootInput = state.inputMessages.length ? safeStringify({ messages: state.inputMessages }) : void 0;
277
+ if (rootInput) root.setAttribute("input.value", rootInput);
278
+ }
279
+ state.turnCtx = parent;
280
+ llm = openLlmSpan(tracer, state, {
281
+ role: "assistant",
282
+ model: model?.id ?? model?.model,
283
+ provider: model?.provider
284
+ });
285
+ } catch {
286
+ }
287
+ let returned;
288
+ try {
289
+ returned = streamFn(model, context, options);
290
+ } catch (err) {
291
+ closeStreamFailure(llm, root, err);
292
+ throw err;
293
+ }
294
+ if (isThenable(returned)) {
295
+ return returned.then(
296
+ (stream) => attachStreamResult(stream, llm, root),
297
+ (err) => {
298
+ closeStreamFailure(llm, root, err);
299
+ throw err;
300
+ }
301
+ );
302
+ }
303
+ return attachStreamResult(returned, llm, root);
76
304
  });
77
- markPatched(a);
78
- return agent;
79
305
  }
80
- function handleEvent(tracer, state, event) {
306
+ function attachStreamResult(stream, llm, root) {
307
+ if (!llm || !stream || typeof stream.result !== "function") {
308
+ closeStreamRoot(root);
309
+ return stream;
310
+ }
311
+ observeStreamDeltas(stream, llm);
312
+ stream.result().then(
313
+ (msg) => {
314
+ const message = msg ?? { role: "assistant" };
315
+ finishLlmSpan(llm, message);
316
+ closeStreamRoot(root, message);
317
+ },
318
+ (err) => closeStreamFailure(llm, root, err)
319
+ );
320
+ return stream;
321
+ }
322
+ function observeStreamDeltas(stream, llm) {
323
+ const original = stream?.[Symbol.asyncIterator];
324
+ if (typeof original !== "function" || original.__neatlogsObserved) return;
325
+ const observed = function() {
326
+ const iterator = original.call(this);
327
+ return {
328
+ next: async (...args) => {
329
+ const item = await iterator.next(...args);
330
+ if (!item.done && isContentDelta(item.value?.type)) markFirstStreamDelta(llm);
331
+ return item;
332
+ },
333
+ return: iterator.return?.bind(iterator),
334
+ throw: iterator.throw?.bind(iterator)
335
+ };
336
+ };
337
+ Object.defineProperty(observed, "__neatlogsObserved", { value: true });
338
+ try {
339
+ stream[Symbol.asyncIterator] = observed;
340
+ } catch {
341
+ }
342
+ }
343
+ function markFirstStreamDelta(llm) {
344
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
345
+ if (llm.ttftMs !== void 0) return;
346
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
347
+ }
348
+ function closeStreamFailure(llm, root, err) {
349
+ const message = err instanceof Error ? err.message : String(err);
350
+ try {
351
+ llm?.span.setAttribute("output.value", `[error] ${message}`);
352
+ llm?.span.setAttribute("neatlogs.error.message", message);
353
+ llm?.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message });
354
+ llm?.span.end();
355
+ } catch {
356
+ }
357
+ closeStreamRoot(root, void 0, message);
358
+ }
359
+ function isThenable(value) {
360
+ return value != null && typeof value.then === "function";
361
+ }
362
+ function closeStreamRoot(root, msg, error) {
363
+ if (!root) return;
364
+ try {
365
+ if (msg) {
366
+ const { text, toolCalls } = splitAssistantContent(msg.content);
367
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n") || terminalSummary(msg);
368
+ if (out) root.setAttribute("output.value", out);
369
+ applyStopReasonStatus(root, msg);
370
+ } else if (error) {
371
+ root.setAttribute("output.value", `[error] ${error}`);
372
+ root.setAttribute("neatlogs.error.message", error);
373
+ root.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: error });
374
+ }
375
+ root.end();
376
+ } catch {
377
+ }
378
+ }
379
+ function transcriptRows(messages) {
380
+ if (!Array.isArray(messages)) return [];
381
+ const rows = [];
382
+ for (const m of messages) {
383
+ const content = messageText(m);
384
+ if (!content) continue;
385
+ rows.push({ role: m?.role === "toolResult" ? "tool" : String(m?.role ?? "user"), content });
386
+ }
387
+ return rows;
388
+ }
389
+ function promptMessagesOf(context) {
390
+ const rows = [];
391
+ if (typeof context?.systemPrompt === "string" && context.systemPrompt) {
392
+ rows.push({ role: "system", content: context.systemPrompt });
393
+ }
394
+ if (Array.isArray(context?.messages)) {
395
+ for (const m of context.messages) {
396
+ const content = messageText(m);
397
+ if (content) rows.push({ role: String(m?.role ?? "user"), content });
398
+ }
399
+ }
400
+ return rows;
401
+ }
402
+ function handleEvent(tracer, state, event, getTranscript) {
81
403
  switch (event.type) {
82
404
  case "agent_start": {
83
405
  const base = getNeatlogsParentContext();
@@ -88,31 +410,98 @@ function handleEvent(tracer, state, event) {
88
410
  );
89
411
  state.agentSpan = span;
90
412
  state.agentCtx = import_api2.trace.setSpan(base, span);
91
- state.inputMessages = [];
413
+ state.inputMessages = transcriptRows(getTranscript?.());
414
+ state.turnIndex = 0;
415
+ state.runInput = void 0;
416
+ state.runError = void 0;
417
+ break;
418
+ }
419
+ case "turn_start": {
420
+ const parent = state.agentCtx ?? getNeatlogsParentContext();
421
+ state.turnIndex += 1;
422
+ const pending = state.inputMessages[state.inputMessages.length - 1];
423
+ const priorInput = pending && (pending.role === "user" || pending.role === "tool") ? pending.content : void 0;
424
+ const span = tracer.startSpan(
425
+ `pi_agent.turn.${state.turnIndex}`,
426
+ {
427
+ attributes: {
428
+ "neatlogs.span.kind": "CHAIN",
429
+ "neatlogs.chain.turn_index": state.turnIndex,
430
+ ...priorInput ? { "input.value": priorInput } : {}
431
+ }
432
+ },
433
+ parent
434
+ );
435
+ state.turnSpan = span;
436
+ state.turnCtx = import_api2.trace.setSpan(parent, span);
437
+ state.turnHasInput = !!priorInput;
438
+ state.turnStartEpochMs = Date.now();
439
+ break;
440
+ }
441
+ case "turn_end": {
442
+ if (!state.turnSpan) break;
443
+ const msg = event.message;
444
+ const { text, toolCalls } = splitAssistantContent(msg?.content);
445
+ const out = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n") || terminalSummary(msg);
446
+ if (out) state.turnSpan.setAttribute("output.value", out);
447
+ if (Array.isArray(event.toolResults) && event.toolResults.length) {
448
+ state.turnSpan.setAttribute("neatlogs.chain.tool_result_count", event.toolResults.length);
449
+ }
450
+ applyStopReasonStatus(state.turnSpan, msg);
451
+ state.turnSpan.end();
452
+ state.turnSpan = void 0;
453
+ state.turnCtx = void 0;
454
+ state.turnHasInput = false;
455
+ state.turnStartEpochMs = void 0;
456
+ break;
457
+ }
458
+ case "message_start": {
459
+ const msg = event.message;
460
+ if (!msg || msg.role !== "assistant") break;
461
+ state.llm = openLlmSpan(tracer, state, msg);
462
+ break;
463
+ }
464
+ case "message_update": {
465
+ const llm = state.llm;
466
+ if (!llm) break;
467
+ llm.span.setAttribute("neatlogs.llm.is_streaming", true);
468
+ if (llm.ttftMs === void 0 && isContentDelta(event.assistantMessageEvent?.type)) {
469
+ llm.ttftMs = llm.callStartEpochMs !== void 0 ? Date.now() - llm.callStartEpochMs : nowMs() - llm.startHr;
470
+ }
92
471
  break;
93
472
  }
94
473
  case "message_end": {
95
474
  const msg = event.message;
96
475
  if (!msg) return;
97
476
  if (msg.role === "assistant") {
98
- emitLlmSpan(tracer, state, msg);
477
+ const llm = state.llm ?? openLlmSpan(tracer, state, msg);
478
+ state.llm = void 0;
479
+ state.runError = finishLlmSpan(llm, msg) ?? state.runError;
99
480
  const { text } = splitAssistantContent(msg.content);
100
481
  if (text) state.inputMessages.push({ role: "assistant", content: text });
101
482
  } else {
102
483
  const role = msg.role === "toolResult" ? "tool" : String(msg.role || "user");
103
484
  const content = messageText(msg);
104
- if (content) state.inputMessages.push({ role, content });
485
+ if (content) {
486
+ state.inputMessages.push({ role, content });
487
+ if (role === "user" && state.runInput === void 0) state.runInput = content;
488
+ if (state.turnSpan && !state.turnHasInput) {
489
+ state.turnSpan.setAttribute("input.value", content);
490
+ state.turnHasInput = true;
491
+ }
492
+ }
105
493
  }
106
494
  break;
107
495
  }
108
496
  case "tool_execution_start": {
109
- const parent = state.agentCtx ?? getNeatlogsParentContext();
497
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
110
498
  const span = tracer.startSpan(
111
499
  `pi_agent.tool.${event.toolName ?? "tool"}`,
112
500
  {
113
501
  attributes: {
114
502
  "neatlogs.span.kind": "TOOL",
115
503
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
504
+ ...event.toolCallId ? { "neatlogs.tool.call_id": String(event.toolCallId) } : {},
116
505
  ...event.args !== void 0 ? { "input.value": safeStringify(event.args) } : {}
117
506
  }
118
507
  },
@@ -121,6 +510,12 @@ function handleEvent(tracer, state, event) {
121
510
  if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);
122
511
  break;
123
512
  }
513
+ case "tool_execution_update": {
514
+ const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
515
+ if (!span) break;
516
+ span.setAttribute("neatlogs.tool.is_streaming", true);
517
+ break;
518
+ }
124
519
  case "tool_execution_end": {
125
520
  const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
126
521
  if (!span) return;
@@ -128,7 +523,7 @@ function handleEvent(tracer, state, event) {
128
523
  span.setAttribute("output.value", safeStringify(event.result));
129
524
  }
130
525
  if (event.isError) {
131
- span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
526
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: toolErrorText(event.result) });
132
527
  span.setAttribute("neatlogs.tool.is_error", true);
133
528
  } else {
134
529
  span.setStatus({ code: import_api2.SpanStatusCode.OK });
@@ -140,17 +535,47 @@ function handleEvent(tracer, state, event) {
140
535
  case "agent_end": {
141
536
  for (const ts of state.toolSpans.values()) {
142
537
  try {
538
+ ts.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: "run ended before tool completed" });
143
539
  ts.end();
144
540
  } catch {
145
541
  }
146
542
  }
147
543
  state.toolSpans.clear();
544
+ if (state.llm) {
545
+ try {
546
+ state.llm.span.setAttribute("output.value", "[incomplete] run ended mid-stream");
547
+ state.llm.span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: "run ended mid-stream" });
548
+ state.llm.span.end();
549
+ } catch {
550
+ }
551
+ state.llm = void 0;
552
+ }
553
+ if (state.turnSpan) {
554
+ try {
555
+ state.turnSpan.end();
556
+ } catch {
557
+ }
558
+ state.turnSpan = void 0;
559
+ state.turnCtx = void 0;
560
+ state.turnStartEpochMs = void 0;
561
+ }
148
562
  if (state.agentSpan) {
149
- const firstUser = state.inputMessages.find((m) => m.role === "user");
150
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
563
+ const input = state.runInput ?? lastUserTextFrom(event.messages) ?? lastUserTextFrom(getTranscript?.());
564
+ if (input) state.agentSpan.setAttribute("input.value", input);
151
565
  const finalText = lastAssistantText(event.messages);
152
- if (finalText) state.agentSpan.setAttribute("output.value", finalText);
153
- state.agentSpan.setStatus({ code: import_api2.SpanStatusCode.OK });
566
+ const err = state.runError ?? terminalErrorFrom(event.messages);
567
+ if (finalText) {
568
+ state.agentSpan.setAttribute("output.value", finalText);
569
+ } else if (err) {
570
+ state.agentSpan.setAttribute("output.value", `[${err.stopReason}]${err.message ? ` ${err.message}` : ""}`);
571
+ }
572
+ if (err) {
573
+ state.agentSpan.setAttribute("neatlogs.agent.stop_reason", err.stopReason);
574
+ if (err.message) state.agentSpan.setAttribute("neatlogs.error.message", err.message);
575
+ state.agentSpan.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: err.message ?? err.stopReason });
576
+ } else {
577
+ state.agentSpan.setStatus({ code: import_api2.SpanStatusCode.OK });
578
+ }
154
579
  state.agentSpan.end();
155
580
  state.agentSpan = void 0;
156
581
  state.agentCtx = void 0;
@@ -161,55 +586,158 @@ function handleEvent(tracer, state, event) {
161
586
  break;
162
587
  }
163
588
  }
164
- function emitLlmSpan(tracer, state, msg) {
165
- const attrs = { "neatlogs.span.kind": "LLM" };
166
- if (msg.model) attrs["neatlogs.llm.model_name"] = String(msg.model);
167
- if (msg.provider) attrs["neatlogs.llm.provider"] = String(msg.provider);
168
- if (msg.stopReason) attrs["neatlogs.llm.stop_reason"] = String(msg.stopReason);
169
- const inMsgs = state.inputMessages;
589
+ function openLlmSpan(tracer, state, msg) {
590
+ const parent = state.turnCtx ?? state.agentCtx ?? getNeatlogsParentContext();
591
+ const startTime = callStartFrom(msg, state.turnStartEpochMs);
592
+ const span = tracer.startSpan(
593
+ `pi_agent.llm.${msg.model || "model"}`,
594
+ {
595
+ ...startTime !== void 0 ? { startTime } : {},
596
+ attributes: {
597
+ "neatlogs.span.kind": "LLM",
598
+ // Pi streams by default; message_update deltas confirm it per call.
599
+ "neatlogs.llm.is_streaming": false,
600
+ ...msg.model ? { "neatlogs.llm.model_name": String(msg.model) } : {},
601
+ ...msg.provider ? { "neatlogs.llm.provider": String(msg.provider) } : {}
602
+ }
603
+ },
604
+ parent
605
+ );
606
+ return {
607
+ span,
608
+ startHr: nowMs(),
609
+ callStartEpochMs: startTime,
610
+ inputMessages: state.inputMessages.slice()
611
+ };
612
+ }
613
+ function callStartFrom(msg, turnStart) {
614
+ const ts = msg.timestamp;
615
+ if (typeof ts !== "number" || !Number.isFinite(ts)) return void 0;
616
+ const now = Date.now();
617
+ const age = now - ts;
618
+ if (age < 0 || age > MAX_CALL_AGE_MS) return void 0;
619
+ return turnStart !== void 0 ? Math.max(ts, turnStart) : ts;
620
+ }
621
+ var MAX_CALL_AGE_MS = 30 * 6e4;
622
+ function terminalSummary(msg) {
623
+ const reason = msg?.stopReason;
624
+ if (reason !== "aborted" && reason !== "error") return "";
625
+ return `[${reason}]${msg?.errorMessage ? ` ${msg.errorMessage}` : ""}`;
626
+ }
627
+ function applyStopReasonStatus(span, msg) {
628
+ const reason = msg?.stopReason;
629
+ if (reason === "aborted" || reason === "error") {
630
+ span.setStatus({ code: import_api2.SpanStatusCode.ERROR, message: msg?.errorMessage ?? reason });
631
+ } else {
632
+ span.setStatus({ code: import_api2.SpanStatusCode.OK });
633
+ }
634
+ }
635
+ function isContentDelta(type) {
636
+ return type === "text_delta" || type === "thinking_delta" || type === "toolcall_delta";
637
+ }
638
+ function nowMs() {
639
+ const p = globalThis.performance;
640
+ return typeof p?.now === "function" ? p.now() : Date.now();
641
+ }
642
+ function toolErrorText(result) {
643
+ if (!result || typeof result !== "object") return "tool error";
644
+ const content = result.content;
645
+ if (Array.isArray(content)) {
646
+ const text = content.map((b) => typeof b?.text === "string" ? b.text : "").filter(Boolean).join(" ").trim();
647
+ if (text) return text;
648
+ }
649
+ return "tool error";
650
+ }
651
+ function lastUserTextFrom(messages) {
652
+ if (!Array.isArray(messages)) return void 0;
653
+ for (let i = messages.length - 1; i >= 0; i--) {
654
+ const m = messages[i];
655
+ if (m && m.role === "user") {
656
+ const text = messageText(m);
657
+ if (text) return text;
658
+ }
659
+ }
660
+ return void 0;
661
+ }
662
+ function terminalErrorFrom(messages) {
663
+ if (!Array.isArray(messages)) return void 0;
664
+ for (let i = messages.length - 1; i >= 0; i--) {
665
+ const m = messages[i];
666
+ if (m && m.role === "assistant") {
667
+ if (m.stopReason === "aborted" || m.stopReason === "error") {
668
+ return { stopReason: m.stopReason, message: m.errorMessage };
669
+ }
670
+ return void 0;
671
+ }
672
+ }
673
+ return void 0;
674
+ }
675
+ function finishLlmSpan(llm, msg, endTime) {
676
+ const span = llm.span;
677
+ if (msg.model) span.setAttribute("neatlogs.llm.model_name", String(msg.model));
678
+ if (msg.provider) span.setAttribute("neatlogs.llm.provider", String(msg.provider));
679
+ if (msg.responseModel) span.setAttribute("neatlogs.llm.response_model", String(msg.responseModel));
680
+ if (msg.api) span.setAttribute("neatlogs.llm.api", String(msg.api));
681
+ if (msg.stopReason) span.setAttribute("neatlogs.llm.stop_reason", String(msg.stopReason));
682
+ const inMsgs = llm.inputMessages;
170
683
  if (inMsgs.length) {
171
684
  inMsgs.forEach((m, i) => {
172
- attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
173
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
685
+ span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, m.role);
686
+ span.setAttribute(`neatlogs.llm.input_messages.${i}.content`, m.content);
174
687
  });
175
- attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs });
176
- attrs["input.value"] = safeStringify({ messages: inMsgs });
688
+ const inBlob = safeStringify({ messages: inMsgs });
689
+ span.setAttribute("neatlogs.llm.input", inBlob);
690
+ span.setAttribute("input.value", inBlob);
177
691
  }
178
692
  const { text, toolCalls } = splitAssistantContent(msg.content);
179
- const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n");
693
+ const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n") || terminalSummary(msg);
180
694
  if (outText || toolCalls.length) {
181
- attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
182
- attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
695
+ span.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
696
+ span.setAttribute("neatlogs.llm.output_messages.0.content", outText || "");
183
697
  const outBlob = { role: "assistant", content: outText || "" };
184
698
  if (toolCalls.length) {
185
699
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
186
700
  toolCalls.forEach((tc, j) => {
187
- if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;
701
+ if (tc.name) span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
188
702
  if (tc.arguments !== void 0)
189
- attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);
190
- if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
703
+ span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, safeStringify(tc.arguments));
704
+ if (tc.id) span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, String(tc.id));
191
705
  });
192
706
  }
193
- attrs["neatlogs.llm.output"] = safeStringify(outBlob);
194
- attrs["output.value"] = outText || "";
707
+ span.setAttribute("neatlogs.llm.output", safeStringify(outBlob));
708
+ span.setAttribute("output.value", outText || "");
195
709
  }
196
710
  const usage = msg.usage;
197
711
  if (usage) {
198
- if (usage.input != null) attrs["neatlogs.llm.token_count.prompt"] = usage.input;
199
- if (usage.output != null) attrs["neatlogs.llm.token_count.completion"] = usage.output;
712
+ if (usage.input != null) span.setAttribute("neatlogs.llm.token_count.prompt", usage.input);
713
+ if (usage.output != null) span.setAttribute("neatlogs.llm.token_count.completion", usage.output);
200
714
  const total = usage.totalTokens ?? (usage.input ?? 0) + (usage.output ?? 0);
201
- if (total) attrs["neatlogs.llm.token_count.total"] = total;
202
- if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
203
- if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
715
+ if (total) span.setAttribute("neatlogs.llm.token_count.total", total);
716
+ if (usage.cacheRead) span.setAttribute("neatlogs.llm.token_count.cache_read", usage.cacheRead);
717
+ if (usage.cacheWrite) span.setAttribute("neatlogs.llm.token_count.cache_write", usage.cacheWrite);
718
+ const cost = usage.cost;
719
+ if (cost) {
720
+ const totalCost = cost.total ?? [cost.input, cost.output, cacheReadOf(cost), cost.cacheWrite].reduce(
721
+ (sum, v) => sum + (typeof v === "number" ? v : 0),
722
+ 0
723
+ );
724
+ if (typeof totalCost === "number" && totalCost > 0) {
725
+ span.setAttribute("neatlogs.llm.cost_usd", totalCost);
726
+ }
727
+ if (typeof cost.input === "number") span.setAttribute("neatlogs.llm.cost.prompt", cost.input);
728
+ if (typeof cost.output === "number") span.setAttribute("neatlogs.llm.cost.completion", cost.output);
729
+ }
204
730
  }
205
- const parent = state.agentCtx ?? getNeatlogsParentContext();
206
- const span = tracer.startSpan(
207
- `pi_agent.llm.${msg.model || "model"}`,
208
- { attributes: attrs },
209
- parent
210
- );
211
- span.setStatus({ code: import_api2.SpanStatusCode.OK });
212
- span.end();
731
+ if (llm.ttftMs !== void 0) {
732
+ span.setAttribute("neatlogs.llm.metrics.ttft_ms", Math.round(llm.ttftMs));
733
+ }
734
+ if (msg.errorMessage) span.setAttribute("neatlogs.error.message", String(msg.errorMessage));
735
+ applyStopReasonStatus(span, msg);
736
+ span.end(endTime);
737
+ return msg.stopReason === "aborted" || msg.stopReason === "error" ? { stopReason: msg.stopReason, message: msg.errorMessage } : void 0;
738
+ }
739
+ function cacheReadOf(cost) {
740
+ return typeof cost.cacheRead === "number" ? cost.cacheRead : cost.cacheReads;
213
741
  }
214
742
  function splitAssistantContent(content) {
215
743
  const texts = [];
@@ -269,6 +797,8 @@ function safeStringify(value) {
269
797
  }
270
798
  // Annotate the CommonJS export names for ESM import in node:
271
799
  0 && (module.exports = {
272
- piAgentHooks
800
+ piAgentHooks,
801
+ tracePiAgentEvents,
802
+ tracePiStream
273
803
  });
274
804
  //# sourceMappingURL=pi-agent.cjs.map