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/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.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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
|
134
|
-
if (
|
|
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
|
-
|
|
137
|
-
|
|
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
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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)
|
|
683
|
+
if (tc.name) span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc.name);
|
|
172
684
|
if (tc.arguments !== void 0)
|
|
173
|
-
|
|
174
|
-
if (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
|
-
|
|
178
|
-
|
|
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)
|
|
183
|
-
if (usage.output != null)
|
|
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)
|
|
186
|
-
if (usage.cacheRead)
|
|
187
|
-
if (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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
|
|
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
|