neatlogs 1.1.11 → 1.1.13

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