neatlogs 1.1.12 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +593 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +591 -51
- package/dist/index.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +1 -1
- package/dist/opencode-plugin.cjs.map +1 -1
- package/dist/opencode-plugin.mjs +1 -1
- package/dist/opencode-plugin.mjs.map +1 -1
- package/dist/pi-agent.cjs +578 -48
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.d.ts +69 -7
- package/dist/pi-agent.mjs +575 -47
- package/dist/pi-agent.mjs.map +1 -1
- package/package.json +7 -1
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
|
150
|
-
if (
|
|
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
|
-
|
|
153
|
-
|
|
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
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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)
|
|
701
|
+
if (tc.name) span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
|
|
188
702
|
if (tc.arguments !== void 0)
|
|
189
|
-
|
|
190
|
-
if (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
|
-
|
|
194
|
-
|
|
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)
|
|
199
|
-
if (usage.output != null)
|
|
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)
|
|
202
|
-
if (usage.cacheRead)
|
|
203
|
-
if (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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
|
|
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
|