neatlogs 1.0.6 → 1.0.9
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/azure-openai.cjs +344 -0
- package/dist/azure-openai.cjs.map +1 -0
- package/dist/azure-openai.d.ts +30 -0
- package/dist/azure-openai.mjs +318 -0
- package/dist/azure-openai.mjs.map +1 -0
- package/dist/bedrock.cjs +540 -0
- package/dist/bedrock.cjs.map +1 -0
- package/dist/bedrock.d.ts +29 -0
- package/dist/bedrock.mjs +514 -0
- package/dist/bedrock.mjs.map +1 -0
- package/dist/browser.cjs +121 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +143 -0
- package/dist/browser.mjs +96 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/claude-agent-sdk.cjs +406 -0
- package/dist/claude-agent-sdk.cjs.map +1 -0
- package/dist/claude-agent-sdk.d.ts +49 -0
- package/dist/claude-agent-sdk.mjs +381 -0
- package/dist/claude-agent-sdk.mjs.map +1 -0
- package/dist/index.cjs +3642 -1126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.mjs +3637 -1132
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +9 -9
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.mjs +9 -9
- package/dist/langchain.mjs.map +1 -1
- package/dist/mastra-wrap.cjs +25 -25
- package/dist/mastra-wrap.cjs.map +1 -1
- package/dist/mastra-wrap.mjs +25 -25
- package/dist/mastra-wrap.mjs.map +1 -1
- package/dist/openai-agents.cjs +6 -6
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.mjs +6 -6
- package/dist/openai-agents.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +682 -0
- package/dist/opencode-plugin.cjs.map +1 -0
- package/dist/opencode-plugin.d.ts +39 -0
- package/dist/opencode-plugin.mjs +644 -0
- package/dist/opencode-plugin.mjs.map +1 -0
- package/dist/openrouter-agent.cjs +273 -0
- package/dist/openrouter-agent.cjs.map +1 -0
- package/dist/openrouter-agent.d.ts +34 -0
- package/dist/openrouter-agent.mjs +247 -0
- package/dist/openrouter-agent.mjs.map +1 -0
- package/dist/pi-agent.cjs +10 -10
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.mjs +10 -10
- package/dist/pi-agent.mjs.map +1 -1
- package/dist/vertex-ai.cjs +424 -0
- package/dist/vertex-ai.cjs.map +1 -0
- package/dist/vertex-ai.d.ts +39 -0
- package/dist/vertex-ai.mjs +397 -0
- package/dist/vertex-ai.mjs.map +1 -0
- package/package.json +78 -2
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/openrouter-agent.ts
|
|
2
|
+
import { trace, context as otelContext, SpanStatusCode } from "@opentelemetry/api";
|
|
3
|
+
var TRACER_NAME = "neatlogs.openrouter_agent";
|
|
4
|
+
var PROVIDER = "openrouter";
|
|
5
|
+
function wrapOpenRouterAgent(client) {
|
|
6
|
+
const c = client;
|
|
7
|
+
if (!c || c._neatlogsWrapped) return client;
|
|
8
|
+
return new Proxy(client, {
|
|
9
|
+
get(obj, prop, receiver) {
|
|
10
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
11
|
+
if (prop === "callModel" && typeof value === "function") {
|
|
12
|
+
return tracedCallModel(value.bind(obj));
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function wrapCallModel(callModel) {
|
|
19
|
+
return function(clientArg, opts, ...rest) {
|
|
20
|
+
const span = startLlmSpan(opts);
|
|
21
|
+
const ctx = trace.setSpan(otelContext.active(), span);
|
|
22
|
+
const result = otelContext.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));
|
|
23
|
+
return instrumentModelResult(result, span);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function tracedCallModel(original) {
|
|
27
|
+
return function(opts, ...rest) {
|
|
28
|
+
const span = startLlmSpan(opts);
|
|
29
|
+
const ctx = trace.setSpan(otelContext.active(), span);
|
|
30
|
+
const result = otelContext.with(ctx, () => original(opts, ...rest));
|
|
31
|
+
return instrumentModelResult(result, span);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function startLlmSpan(opts) {
|
|
35
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
36
|
+
const model = opts?.model ?? "";
|
|
37
|
+
const span = tracer.startSpan("openrouter.call_model", {
|
|
38
|
+
attributes: {
|
|
39
|
+
"neatlogs.span.kind": "LLM",
|
|
40
|
+
"neatlogs.llm.provider": PROVIDER,
|
|
41
|
+
"neatlogs.llm.system": PROVIDER,
|
|
42
|
+
"neatlogs.llm.model_name": model
|
|
43
|
+
}
|
|
44
|
+
}, otelContext.active());
|
|
45
|
+
const messages = Array.isArray(opts?.messages) ? opts.messages : Array.isArray(opts?.input) ? opts.input : [];
|
|
46
|
+
if (messages.length) {
|
|
47
|
+
messages.forEach((msg, i) => {
|
|
48
|
+
span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? "user");
|
|
49
|
+
const content = msg?.content;
|
|
50
|
+
span.setAttribute(
|
|
51
|
+
`neatlogs.llm.input_messages.${i}.content`,
|
|
52
|
+
typeof content === "string" ? content : safeStringify(content)
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
span.setAttribute("input.value", safeStringify({ messages }));
|
|
56
|
+
} else if (typeof opts?.input === "string") {
|
|
57
|
+
span.setAttribute("neatlogs.llm.input_messages.0.role", "user");
|
|
58
|
+
span.setAttribute("neatlogs.llm.input_messages.0.content", opts.input);
|
|
59
|
+
span.setAttribute("input.value", opts.input);
|
|
60
|
+
}
|
|
61
|
+
if (typeof opts?.instructions === "string" && opts.instructions) {
|
|
62
|
+
span.setAttribute("neatlogs.llm.system_prompt", opts.instructions);
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(opts?.tools)) {
|
|
65
|
+
for (let i = 0; i < opts.tools.length; i++) {
|
|
66
|
+
const t = opts.tools[i] ?? {};
|
|
67
|
+
const name = t.name ?? t.function?.name;
|
|
68
|
+
if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);
|
|
69
|
+
const desc = t.description ?? t.function?.description;
|
|
70
|
+
if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const temperature = opts?.temperature;
|
|
74
|
+
const topP = opts?.top_p ?? opts?.topP;
|
|
75
|
+
const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;
|
|
76
|
+
const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;
|
|
77
|
+
const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;
|
|
78
|
+
const topK = opts?.top_k ?? opts?.topK;
|
|
79
|
+
if (temperature != null) span.setAttribute("neatlogs.llm.temperature", temperature);
|
|
80
|
+
if (topP != null) span.setAttribute("neatlogs.llm.top_p", topP);
|
|
81
|
+
if (maxTokens != null) span.setAttribute("neatlogs.llm.max_tokens", maxTokens);
|
|
82
|
+
const params = {};
|
|
83
|
+
if (temperature != null) params.temperature = temperature;
|
|
84
|
+
if (topP != null) params.top_p = topP;
|
|
85
|
+
if (maxTokens != null) params.max_tokens = maxTokens;
|
|
86
|
+
if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;
|
|
87
|
+
if (presencePenalty != null) params.presence_penalty = presencePenalty;
|
|
88
|
+
if (topK != null) params.top_k = topK;
|
|
89
|
+
if (Object.keys(params).length) {
|
|
90
|
+
span.setAttribute("neatlogs.llm.invocation_parameters", JSON.stringify(params));
|
|
91
|
+
}
|
|
92
|
+
return span;
|
|
93
|
+
}
|
|
94
|
+
function instrumentModelResult(result, span) {
|
|
95
|
+
if (!result || typeof result !== "object" && typeof result !== "function") {
|
|
96
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
97
|
+
span.end();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
let finalized = false;
|
|
101
|
+
const finalizeFromResult = (resolved) => {
|
|
102
|
+
if (finalized) return;
|
|
103
|
+
finalized = true;
|
|
104
|
+
try {
|
|
105
|
+
finalizeLlm(span, resolved);
|
|
106
|
+
} catch {
|
|
107
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
108
|
+
span.end();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const finalizeError = (err) => {
|
|
112
|
+
if (finalized) return;
|
|
113
|
+
finalized = true;
|
|
114
|
+
recordError(span, err);
|
|
115
|
+
};
|
|
116
|
+
return new Proxy(result, {
|
|
117
|
+
get(obj, prop, receiver) {
|
|
118
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
119
|
+
if (prop === "getResponse" && typeof value === "function") {
|
|
120
|
+
return function(...args) {
|
|
121
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
122
|
+
(resp) => {
|
|
123
|
+
finalizeFromResult(resp);
|
|
124
|
+
return resp;
|
|
125
|
+
},
|
|
126
|
+
(err) => {
|
|
127
|
+
finalizeError(err);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (prop === "getText" && typeof value === "function") {
|
|
134
|
+
return function(...args) {
|
|
135
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
136
|
+
async (textStr) => {
|
|
137
|
+
let resp;
|
|
138
|
+
if (typeof obj?.getResponse === "function") {
|
|
139
|
+
try {
|
|
140
|
+
resp = await obj.getResponse();
|
|
141
|
+
} catch {
|
|
142
|
+
resp = void 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });
|
|
146
|
+
return textStr;
|
|
147
|
+
},
|
|
148
|
+
(err) => {
|
|
149
|
+
finalizeError(err);
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (prop === "then" && typeof value === "function") {
|
|
156
|
+
return function(onFulfilled, onRejected) {
|
|
157
|
+
return value.call(
|
|
158
|
+
obj,
|
|
159
|
+
(resolved) => {
|
|
160
|
+
finalizeFromResult(typeof resolved === "string" ? { text: resolved } : resolved ?? obj);
|
|
161
|
+
return onFulfilled ? onFulfilled(resolved) : resolved;
|
|
162
|
+
},
|
|
163
|
+
(err) => {
|
|
164
|
+
finalizeError(err);
|
|
165
|
+
return onRejected ? onRejected(err) : Promise.reject(err);
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function setOpenResponsesUsage(span, resp) {
|
|
175
|
+
const u = resp?.usage ?? resp;
|
|
176
|
+
if (!u) return;
|
|
177
|
+
const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;
|
|
178
|
+
const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;
|
|
179
|
+
const total = u.totalTokens ?? u.total_tokens;
|
|
180
|
+
const cached = u.cachedTokens ?? u.cached_tokens;
|
|
181
|
+
if (input != null) span.setAttribute("neatlogs.llm.token_count.prompt", input);
|
|
182
|
+
if (output != null) span.setAttribute("neatlogs.llm.token_count.completion", output);
|
|
183
|
+
if (total != null) span.setAttribute("neatlogs.llm.token_count.total", total);
|
|
184
|
+
else if (input != null && output != null) span.setAttribute("neatlogs.llm.token_count.total", input + output);
|
|
185
|
+
if (cached != null) span.setAttribute("neatlogs.llm.token_count.cache_read", cached);
|
|
186
|
+
}
|
|
187
|
+
function finalizeLlm(span, result) {
|
|
188
|
+
const text = result?.text ?? result?.output_text ?? result?.content ?? extractOpenResponsesText(result) ?? result?.choices?.[0]?.message?.content ?? result?.message?.content;
|
|
189
|
+
if (text) {
|
|
190
|
+
span.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
|
|
191
|
+
span.setAttribute("neatlogs.llm.output_messages.0.content", String(text));
|
|
192
|
+
span.setAttribute("output.value", String(text));
|
|
193
|
+
}
|
|
194
|
+
const toolCalls = result?.toolCalls ?? result?.tool_calls ?? result?.choices?.[0]?.message?.tool_calls ?? result?.message?.tool_calls;
|
|
195
|
+
if (Array.isArray(toolCalls)) {
|
|
196
|
+
toolCalls.forEach((tc, j) => {
|
|
197
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? "");
|
|
198
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? "");
|
|
199
|
+
const args = tc?.function?.arguments ?? tc?.arguments;
|
|
200
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === "string" ? args : safeStringify(args ?? {}));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const model = result?.model ?? result?.response?.model;
|
|
204
|
+
if (model) span.setAttribute("neatlogs.llm.model_name", String(model));
|
|
205
|
+
const responseId = result?.id ?? result?.response?.id;
|
|
206
|
+
if (responseId) span.setAttribute("neatlogs.llm.response_id", String(responseId));
|
|
207
|
+
const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;
|
|
208
|
+
if (finishReason) span.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
|
|
209
|
+
setOpenResponsesUsage(span, result);
|
|
210
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
211
|
+
span.end();
|
|
212
|
+
}
|
|
213
|
+
function extractOpenResponsesText(result) {
|
|
214
|
+
const output = result?.output;
|
|
215
|
+
if (!Array.isArray(output)) return void 0;
|
|
216
|
+
const parts = [];
|
|
217
|
+
for (const item of output) {
|
|
218
|
+
if (item?.type === "message" && Array.isArray(item.content)) {
|
|
219
|
+
for (const c of item.content) {
|
|
220
|
+
if ((c?.type === "output_text" || c?.type === "text") && typeof c.text === "string") parts.push(c.text);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return parts.length ? parts.join("") : void 0;
|
|
225
|
+
}
|
|
226
|
+
function safeStringify(value) {
|
|
227
|
+
if (typeof value === "string") return value;
|
|
228
|
+
try {
|
|
229
|
+
return JSON.stringify(value) ?? "";
|
|
230
|
+
} catch {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function recordError(span, err) {
|
|
235
|
+
if (err instanceof Error) {
|
|
236
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
237
|
+
span.recordException(err);
|
|
238
|
+
} else {
|
|
239
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
|
|
240
|
+
}
|
|
241
|
+
span.end();
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
wrapCallModel,
|
|
245
|
+
wrapOpenRouterAgent
|
|
246
|
+
};
|
|
247
|
+
//# sourceMappingURL=openrouter-agent.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openrouter-agent.ts"],"sourcesContent":["/**\n * Neatlogs OpenRouter Agent integration.\n *\n * Wraps the `@openrouter/agent` SDK so each `client.callModel(...)` is traced.\n * The OpenRouter client returns a `ModelResult`; telemetry is finalized when the\n * result is consumed (via `getText()` / awaiting it / iterating it), matching the\n * SDK's lazy-evaluation model — a result that is never consumed ships no span.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapOpenRouterAgent } from 'neatlogs/openrouter-agent';\n * import { OpenRouter } from '@openrouter/agent';\n *\n * await init({ apiKey, workflowName });\n * const openrouter = wrapOpenRouterAgent(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }));\n * const result = openrouter.callModel({ model: 'openai/gpt-4o', messages: [...] });\n * const text = await result.getText();\n *\n * Also exports wrapCallModel() for the standalone callModel helper:\n * const trackedCallModel = wrapCallModel(callModel);\n * const result = trackedCallModel(openrouter, { model, messages });\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.openrouter_agent';\nconst PROVIDER = 'openrouter';\n\n/**\n * Wrap an OpenRouter client instance so `callModel` emits LLM spans. Returns a\n * Proxy over the client; all other methods pass through unchanged.\n */\nexport function wrapOpenRouterAgent<T extends object>(client: T): T {\n const c = client as any;\n if (!c || c._neatlogsWrapped) return client;\n\n return new Proxy(client, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (prop === 'callModel' && typeof value === 'function') {\n return tracedCallModel(value.bind(obj));\n }\n return value;\n },\n }) as T;\n}\n\n/**\n * Wrap the standalone `callModel(client, opts)` helper. The wrapped function is\n * invoked with the client passed in: `trackedCallModel(openrouter, {...})`.\n */\nexport function wrapCallModel<F extends (...args: any[]) => any>(callModel: F): F {\n return function (this: any, clientArg: any, opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));\n return instrumentModelResult(result, span);\n } as unknown as F;\n}\n\n// ---------------------------------------------------------------------------\n// callModel wrapping\n// ---------------------------------------------------------------------------\n\nfunction tracedCallModel(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => original(opts, ...rest));\n return instrumentModelResult(result, span);\n };\n}\n\nfunction startLlmSpan(opts: any): Span {\n const tracer = trace.getTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n\n const span = tracer.startSpan('openrouter.call_model', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.llm.system': PROVIDER,\n 'neatlogs.llm.model_name': model,\n },\n }, otelContext.active());\n\n // The @openrouter/agent callModel input is `input` — either a string prompt or\n // a messages array (it also accepts `messages`/`instructions` in some shapes).\n // Capture whichever is present as indexed input messages + the flat input.value\n // blob (the canonical UI-rendered field, per attribute-mapping.json).\n const messages: any[] = Array.isArray(opts?.messages)\n ? opts.messages\n : Array.isArray(opts?.input)\n ? opts.input\n : [];\n if (messages.length) {\n messages.forEach((msg, i) => {\n span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? 'user');\n const content = msg?.content;\n span.setAttribute(\n `neatlogs.llm.input_messages.${i}.content`,\n typeof content === 'string' ? content : safeStringify(content),\n );\n });\n span.setAttribute('input.value', safeStringify({ messages }));\n } else if (typeof opts?.input === 'string') {\n span.setAttribute('neatlogs.llm.input_messages.0.role', 'user');\n span.setAttribute('neatlogs.llm.input_messages.0.content', opts.input);\n span.setAttribute('input.value', opts.input);\n }\n if (typeof opts?.instructions === 'string' && opts.instructions) {\n span.setAttribute('neatlogs.llm.system_prompt', opts.instructions);\n }\n\n if (Array.isArray(opts?.tools)) {\n for (let i = 0; i < opts.tools.length; i++) {\n const t = opts.tools[i] ?? {};\n const name = t.name ?? t.function?.name;\n if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);\n const desc = t.description ?? t.function?.description;\n if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);\n }\n }\n\n // @openrouter/agent's callModel request uses camelCase sampling params\n // (topP, maxOutputTokens, frequencyPenalty, presencePenalty, topK) derived\n // from the OpenResponses ResponsesRequest. Accept both camelCase and the\n // snake_case OpenAI-style variants so params are captured regardless of how\n // the caller spelled them.\n const temperature = opts?.temperature;\n const topP = opts?.top_p ?? opts?.topP;\n const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;\n const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;\n const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;\n const topK = opts?.top_k ?? opts?.topK;\n\n if (temperature != null) span.setAttribute('neatlogs.llm.temperature', temperature);\n if (topP != null) span.setAttribute('neatlogs.llm.top_p', topP);\n if (maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', maxTokens);\n\n // The backend reads invocation params ONLY from this JSON-string blob (parsed\n // into metadata.model_settings, which the UI renders). Individual attrs above\n // are kept for other consumers but are NOT what the UI shows. Only include\n // keys that are actually present so we never emit nulls.\n const params: Record<string, unknown> = {};\n if (temperature != null) params.temperature = temperature;\n if (topP != null) params.top_p = topP;\n if (maxTokens != null) params.max_tokens = maxTokens;\n if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;\n if (presencePenalty != null) params.presence_penalty = presencePenalty;\n if (topK != null) params.top_k = topK;\n if (Object.keys(params).length) {\n span.setAttribute('neatlogs.llm.invocation_parameters', JSON.stringify(params));\n }\n\n return span;\n}\n\n/**\n * Wrap a ModelResult so the span is finalized exactly once, when the caller\n * consumes the result. We patch the common consumption methods (getText,\n * getMessage, then) so finalization happens on first use; a result that is\n * never consumed never finalizes (matching SDK semantics).\n */\nfunction instrumentModelResult(result: any, span: Span): any {\n if (!result || (typeof result !== 'object' && typeof result !== 'function')) {\n // Synchronous/primitive return — nothing to defer; close immediately.\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return result;\n }\n\n let finalized = false;\n const finalizeFromResult = (resolved: any) => {\n if (finalized) return;\n finalized = true;\n try {\n finalizeLlm(span, resolved);\n } catch {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n };\n const finalizeError = (err: unknown) => {\n if (finalized) return;\n finalized = true;\n recordError(span, err);\n };\n\n // The @openrouter/agent ModelResult is consumed via getText()/getResponse()/\n // getTextStream(). Wrap with a Proxy to intercept whichever the caller uses\n // first; finalize the span from that, and grab usage via getResponse().\n return new Proxy(result, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n\n if (prop === 'getResponse' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n (resp: any) => {\n finalizeFromResult(resp); // full OpenResponsesResult (text + usage)\n return resp;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'getText' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n async (textStr: any) => {\n // getText() resolves to a bare string (no usage). Usage lives on\n // getResponse(); fetch it FIRST (the SDK supports concurrent\n // consumption) so the span carries tokens — then finalize ONCE.\n // Finalizing ends the span, so late attributes would be dropped.\n let resp: any;\n if (typeof obj?.getResponse === 'function') {\n try {\n resp = await obj.getResponse();\n } catch {\n resp = undefined;\n }\n }\n finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });\n return textStr;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'then' && typeof value === 'function') {\n // Result is awaitable directly: await result.\n return function (onFulfilled?: any, onRejected?: any) {\n return value.call(\n obj,\n (resolved: any) => {\n finalizeFromResult(typeof resolved === 'string' ? { text: resolved } : (resolved ?? obj));\n return onFulfilled ? onFulfilled(resolved) : resolved;\n },\n (err: any) => {\n finalizeError(err);\n return onRejected ? onRejected(err) : Promise.reject(err);\n },\n );\n };\n }\n\n return value;\n },\n });\n}\n\n/** Stamp usage from an @openrouter/agent OpenResponsesResult (inputTokens/outputTokens/...). */\nfunction setOpenResponsesUsage(span: Span, resp: any): void {\n const u = resp?.usage ?? resp;\n if (!u) return;\n const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;\n const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;\n const total = u.totalTokens ?? u.total_tokens;\n const cached = u.cachedTokens ?? u.cached_tokens;\n if (input != null) span.setAttribute('neatlogs.llm.token_count.prompt', input);\n if (output != null) span.setAttribute('neatlogs.llm.token_count.completion', output);\n if (total != null) span.setAttribute('neatlogs.llm.token_count.total', total);\n else if (input != null && output != null) span.setAttribute('neatlogs.llm.token_count.total', input + output);\n if (cached != null) span.setAttribute('neatlogs.llm.token_count.cache_read', cached);\n}\n\nfunction finalizeLlm(span: Span, result: any): void {\n // Text output. getText() gives a bare string (wrapped here as {text}); the\n // OpenResponsesResult from getResponse() carries text under output[]/output_text.\n const text =\n result?.text ??\n result?.output_text ??\n result?.content ??\n extractOpenResponsesText(result) ??\n result?.choices?.[0]?.message?.content ??\n result?.message?.content;\n if (text) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', String(text));\n span.setAttribute('output.value', String(text));\n }\n\n // Tool calls (OpenAI-compatible shape; OpenResponses uses output[] function_call items).\n const toolCalls =\n result?.toolCalls ??\n result?.tool_calls ??\n result?.choices?.[0]?.message?.tool_calls ??\n result?.message?.tool_calls;\n if (Array.isArray(toolCalls)) {\n toolCalls.forEach((tc: any, j: number) => {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? '');\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? '');\n const args = tc?.function?.arguments ?? tc?.arguments;\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === 'string' ? args : safeStringify(args ?? {}));\n });\n }\n\n const model = result?.model ?? result?.response?.model;\n if (model) span.setAttribute('neatlogs.llm.model_name', String(model));\n\n const responseId = result?.id ?? result?.response?.id;\n if (responseId) span.setAttribute('neatlogs.llm.response_id', String(responseId));\n\n const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n\n // Usage — OpenResponsesResult uses inputTokens/outputTokens; also handle the\n // OpenAI-compatible prompt_tokens/completion_tokens shape.\n setOpenResponsesUsage(span, result);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n/** Pull assistant text out of an OpenResponses-style result (output[] of message items). */\nfunction extractOpenResponsesText(result: any): string | undefined {\n const output = result?.output;\n if (!Array.isArray(output)) return undefined;\n const parts: string[] = [];\n for (const item of output) {\n if (item?.type === 'message' && Array.isArray(item.content)) {\n for (const c of item.content) {\n if ((c?.type === 'output_text' || c?.type === 'text') && typeof c.text === 'string') parts.push(c.text);\n }\n }\n }\n return parts.length ? parts.join('') : undefined;\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n span.end();\n}\n"],"mappings":"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","names":[]}
|
package/dist/pi-agent.cjs
CHANGED
|
@@ -76,7 +76,7 @@ function handleEvent(tracer, state, event) {
|
|
|
76
76
|
attributes: {
|
|
77
77
|
"neatlogs.span.kind": "TOOL",
|
|
78
78
|
...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
|
|
79
|
-
...event.args !== void 0 ? { "input.value": safeStringify(event.args)
|
|
79
|
+
...event.args !== void 0 ? { "input.value": safeStringify(event.args) } : {}
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
82
|
parent
|
|
@@ -88,7 +88,7 @@ function handleEvent(tracer, state, event) {
|
|
|
88
88
|
const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
|
|
89
89
|
if (!span) return;
|
|
90
90
|
if (event.result !== void 0) {
|
|
91
|
-
span.setAttribute("output.value", safeStringify(event.result)
|
|
91
|
+
span.setAttribute("output.value", safeStringify(event.result));
|
|
92
92
|
}
|
|
93
93
|
if (event.isError) {
|
|
94
94
|
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
@@ -110,9 +110,9 @@ function handleEvent(tracer, state, event) {
|
|
|
110
110
|
state.toolSpans.clear();
|
|
111
111
|
if (state.agentSpan) {
|
|
112
112
|
const firstUser = state.inputMessages.find((m) => m.role === "user");
|
|
113
|
-
if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content
|
|
113
|
+
if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
|
|
114
114
|
const finalText = lastAssistantText(event.messages);
|
|
115
|
-
if (finalText) state.agentSpan.setAttribute("output.value", finalText
|
|
115
|
+
if (finalText) state.agentSpan.setAttribute("output.value", finalText);
|
|
116
116
|
state.agentSpan.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
117
117
|
state.agentSpan.end();
|
|
118
118
|
state.agentSpan = void 0;
|
|
@@ -133,16 +133,16 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
133
133
|
if (inMsgs.length) {
|
|
134
134
|
inMsgs.forEach((m, i) => {
|
|
135
135
|
attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
|
|
136
|
-
attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content
|
|
136
|
+
attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
|
|
137
137
|
});
|
|
138
|
-
attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs })
|
|
139
|
-
attrs["input.value"] = safeStringify({ messages: inMsgs })
|
|
138
|
+
attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs });
|
|
139
|
+
attrs["input.value"] = safeStringify({ messages: inMsgs });
|
|
140
140
|
}
|
|
141
141
|
const { text, toolCalls } = splitAssistantContent(msg.content);
|
|
142
142
|
const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n");
|
|
143
143
|
if (outText || toolCalls.length) {
|
|
144
144
|
attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
|
|
145
|
-
attrs["neatlogs.llm.output_messages.0.content"] =
|
|
145
|
+
attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
|
|
146
146
|
const outBlob = { role: "assistant", content: outText || "" };
|
|
147
147
|
if (toolCalls.length) {
|
|
148
148
|
outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
|
|
@@ -153,8 +153,8 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
153
153
|
if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
|
-
attrs["neatlogs.llm.output"] = safeStringify(outBlob)
|
|
157
|
-
attrs["output.value"] =
|
|
156
|
+
attrs["neatlogs.llm.output"] = safeStringify(outBlob);
|
|
157
|
+
attrs["output.value"] = outText || "";
|
|
158
158
|
}
|
|
159
159
|
const usage = msg.usage;
|
|
160
160
|
if (usage) {
|
package/dist/pi-agent.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args).slice(0, 10000) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result).slice(0, 10000));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content.slice(0, 10000));\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText.slice(0, 10000));\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 10000);\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs }).slice(0, 20000);\n attrs['input.value'] = safeStringify({ messages: inMsgs }).slice(0, 10000);\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '').slice(0, 10000);\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob).slice(0, 20000);\n attrs['output.value'] = (outText || '').slice(0, 10000);\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":["otelContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content);\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText);\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs });\n attrs['input.value'] = safeStringify({ messages: inMsgs });\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '');\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob);\n attrs['output.value'] = (outText || '');\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,iBAMO;AAEP,IAAM,cAAc;AACpB,IAAM,aAAa;AAiDZ,SAAS,aAA+B,OAAa;AAC1D,MAAI,CAAC,SAAU,MAAc,UAAU,EAAG,QAAO;AACjD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,cAAc,WAAY,QAAO;AAE9C,QAAM,QAAuB,EAAE,WAAW,oBAAI,IAAI,GAAG,eAAe,CAAC,EAAE;AACvE,QAAM,SAAS,iBAAM,UAAU,WAAW;AAE1C,IAAE,UAAU,CAAC,UAAwB;AACnC,QAAI;AACF,kBAAY,QAAQ,OAAO,KAAK;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AAED,cAAY,CAAC;AACb,SAAO;AACT;AAEA,SAAS,YACP,QACA,OACA,OACM;AACN,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK,eAAe;AAElB,YAAM,OAAO,OAAO;AAAA,QAClB;AAAA,QACA,EAAE,YAAY,EAAE,sBAAsB,QAAQ,EAAE;AAAA,QAChD,WAAAA,QAAY,OAAO;AAAA,MACrB;AACA,YAAM,YAAY;AAClB,YAAM,WAAW,iBAAM,QAAQ,WAAAA,QAAY,OAAO,GAAG,IAAI;AACzD,YAAM,gBAAgB,CAAC;AACvB;AAAA,IACF;AAAA,IAEA,KAAK,eAAe;AAClB,YAAM,MAAM,MAAM;AAClB,UAAI,CAAC,IAAK;AACV,UAAI,IAAI,SAAS,aAAa;AAG5B,oBAAY,QAAQ,OAAO,GAAyB;AACpD,cAAM,EAAE,KAAK,IAAI,sBAAsB,IAAI,OAAO;AAClD,YAAI,KAAM,OAAM,cAAc,KAAK,EAAE,MAAM,aAAa,SAAS,KAAK,CAAC;AAAA,MACzE,OAAO;AAEL,cAAM,OAAO,IAAI,SAAS,eAAe,SAAS,OAAO,IAAI,QAAQ,MAAM;AAC3E,cAAM,UAAU,YAAY,GAAG;AAC/B,YAAI,QAAS,OAAM,cAAc,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,MACzD;AACA;AAAA,IACF;AAAA,IAEA,KAAK,wBAAwB;AAC3B,YAAM,SAAS,MAAM,YAAY,WAAAA,QAAY,OAAO;AACpD,YAAM,OAAO,OAAO;AAAA,QAClB,iBAAiB,MAAM,YAAY,MAAM;AAAA,QACzC;AAAA,UACE,YAAY;AAAA,YACV,sBAAsB;AAAA,YACtB,GAAI,MAAM,WAAW,EAAE,sBAAsB,OAAO,MAAM,QAAQ,EAAE,IAAI,CAAC;AAAA,YACzE,GAAI,MAAM,SAAS,SACf,EAAE,eAAe,cAAc,MAAM,IAAI,EAAE,IAC3C,CAAC;AAAA,UACP;AAAA,QACF;AAAA,QACA;AAAA,MACF;AACA,UAAI,MAAM,WAAY,OAAM,UAAU,IAAI,MAAM,YAAY,IAAI;AAChE;AAAA,IACF;AAAA,IAEA,KAAK,sBAAsB;AACzB,YAAM,OAAO,MAAM,aAAa,MAAM,UAAU,IAAI,MAAM,UAAU,IAAI;AACxE,UAAI,CAAC,KAAM;AACX,UAAI,MAAM,WAAW,QAAW;AAC9B,aAAK,aAAa,gBAAgB,cAAc,MAAM,MAAM,CAAC;AAAA,MAC/D;AACA,UAAI,MAAM,SAAS;AACjB,aAAK,UAAU,EAAE,MAAM,0BAAe,MAAM,CAAC;AAC7C,aAAK,aAAa,0BAA0B,IAAI;AAAA,MAClD,OAAO;AACL,aAAK,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AAAA,MAC5C;AACA,WAAK,IAAI;AACT,UAAI,MAAM,WAAY,OAAM,UAAU,OAAO,MAAM,UAAU;AAC7D;AAAA,IACF;AAAA,IAEA,KAAK,aAAa;AAEhB,iBAAW,MAAM,MAAM,UAAU,OAAO,GAAG;AACzC,YAAI;AACF,aAAG,IAAI;AAAA,QACT,QAAQ;AAAA,QAER;AAAA,MACF;AACA,YAAM,UAAU,MAAM;AACtB,UAAI,MAAM,WAAW;AAEnB,cAAM,YAAY,MAAM,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACnE,YAAI,UAAW,OAAM,UAAU,aAAa,eAAe,UAAU,OAAO;AAC5E,cAAM,YAAY,kBAAkB,MAAM,QAAQ;AAClD,YAAI,UAAW,OAAM,UAAU,aAAa,gBAAgB,SAAS;AACrE,cAAM,UAAU,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AACrD,cAAM,UAAU,IAAI;AACpB,cAAM,YAAY;AAClB,cAAM,WAAW;AAAA,MACnB;AACA;AAAA,IACF;AAAA,IAEA;AACE;AAAA,EACJ;AACF;AAEA,SAAS,YACP,QACA,OACA,KACM;AACN,QAAM,QAA6B,EAAE,sBAAsB,MAAM;AACjE,MAAI,IAAI,MAAO,OAAM,yBAAyB,IAAI,OAAO,IAAI,KAAK;AAClE,MAAI,IAAI,SAAU,OAAM,uBAAuB,IAAI,OAAO,IAAI,QAAQ;AACtE,MAAI,IAAI,WAAY,OAAM,0BAA0B,IAAI,OAAO,IAAI,UAAU;AAK7E,QAAM,SAAS,MAAM;AACrB,MAAI,OAAO,QAAQ;AACjB,WAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,YAAM,+BAA+B,CAAC,OAAO,IAAI,EAAE;AACnD,YAAM,+BAA+B,CAAC,UAAU,IAAI,EAAE;AAAA,IACxD,CAAC;AACD,UAAM,oBAAoB,IAAI,cAAc,EAAE,UAAU,OAAO,CAAC;AAChE,UAAM,aAAa,IAAI,cAAc,EAAE,UAAU,OAAO,CAAC;AAAA,EAC3D;AAEA,QAAM,EAAE,MAAM,UAAU,IAAI,sBAAsB,IAAI,OAAO;AAE7D,QAAM,UAAU,QAAQ,UAAU,IAAI,CAAC,OAAO,GAAG,GAAG,IAAI,IAAI,cAAc,GAAG,SAAS,CAAC,GAAG,EAAE,KAAK,IAAI;AACrG,MAAI,WAAW,UAAU,QAAQ;AAC/B,UAAM,qCAAqC,IAAI;AAC/C,UAAM,wCAAwC,IAAK,WAAW;AAC9D,UAAM,UAAmC,EAAE,MAAM,aAAa,SAAS,WAAW,GAAG;AACrF,QAAI,UAAU,QAAQ;AACpB,cAAQ,aAAa,UAAU,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,WAAW,GAAG,UAAU,EAAE;AACvF,gBAAU,QAAQ,CAAC,IAAI,MAAM;AAC3B,YAAI,GAAG,KAAM,OAAM,2BAA2B,CAAC,OAAO,IAAI,GAAG;AAC7D,YAAI,GAAG,cAAc;AACnB,gBAAM,2BAA2B,CAAC,YAAY,IAAI,cAAc,GAAG,SAAS;AAC9E,YAAI,GAAG,GAAI,OAAM,2BAA2B,CAAC,KAAK,IAAI,OAAO,GAAG,EAAE;AAAA,MACpE,CAAC;AAAA,IACH;AACA,UAAM,qBAAqB,IAAI,cAAc,OAAO;AACpD,UAAM,cAAc,IAAK,WAAW;AAAA,EACtC;AAEA,QAAM,QAAQ,IAAI;AAClB,MAAI,OAAO;AACT,QAAI,MAAM,SAAS,KAAM,OAAM,iCAAiC,IAAI,MAAM;AAC1E,QAAI,MAAM,UAAU,KAAM,OAAM,qCAAqC,IAAI,MAAM;AAC/E,UAAM,QAAQ,MAAM,gBAAiB,MAAM,SAAS,MAAM,MAAM,UAAU;AAC1E,QAAI,MAAO,OAAM,gCAAgC,IAAI;AACrD,QAAI,MAAM,UAAW,OAAM,qCAAqC,IAAI,MAAM;AAC1E,QAAI,MAAM,WAAY,OAAM,sCAAsC,IAAI,MAAM;AAAA,EAC9E;AAEA,QAAM,SAAS,MAAM,YAAY,WAAAA,QAAY,OAAO;AACpD,QAAM,OAAO,OAAO;AAAA,IAClB,gBAAgB,IAAI,SAAS,OAAO;AAAA,IACpC,EAAE,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACA,OAAK,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AAC1C,OAAK,IAAI;AACX;AAMA,SAAS,sBACP,SAC2C;AAC3C,QAAM,QAAkB,CAAC;AACzB,QAAM,YAA0B,CAAC;AACjC,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,UAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,SAAU,OAAM,KAAK,MAAM,IAAI;AAAA,eACzE,MAAM,SAAS;AACtB,kBAAU,KAAK,KAA8B;AAAA,IAEjD;AAAA,EACF,WAAW,OAAO,YAAY,UAAU;AACtC,UAAM,KAAK,OAAO;AAAA,EACpB;AACA,SAAO,EAAE,MAAM,MAAM,KAAK,EAAE,GAAG,UAAU;AAC3C;AAGA,SAAS,YAAY,KAAkB;AACrC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,IAAI;AACd,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC9B,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,GAAG;AACrB,QAAI,OAAO,UAAU,SAAU,OAAM,KAAK,KAAK;AAAA,aACtC,SAAS,OAAO,UAAU,UAAU;AAC3C,UAAI,OAAO,MAAM,SAAS,SAAU,OAAM,KAAK,MAAM,IAAI;AAAA,eAChD,MAAM,SAAS,WAAY,OAAM,KAAK,GAAG,MAAM,QAAQ,MAAM,IAAI,cAAc,MAAM,SAAS,CAAC,GAAG;AAAA,IAC7G;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,kBAAkB,UAAqC;AAC9D,MAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG,QAAO;AACrC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,KAAK,EAAE,SAAS,aAAa;AAC/B,YAAM,EAAE,KAAK,IAAI,sBAAsB,EAAE,OAAO;AAChD,UAAI,KAAM,QAAO;AAAA,IACnB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,GAAc;AACjC,MAAI;AACF,WAAO,eAAe,GAAG,YAAY,EAAE,OAAO,MAAM,YAAY,OAAO,cAAc,KAAK,CAAC;AAAA,EAC7F,QAAQ;AACN,MAAE,UAAU,IAAI;AAAA,EAClB;AACF;AAEA,SAAS,cAAc,OAAwB;AAC7C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":["otelContext"]}
|
package/dist/pi-agent.mjs
CHANGED
|
@@ -56,7 +56,7 @@ function handleEvent(tracer, state, event) {
|
|
|
56
56
|
attributes: {
|
|
57
57
|
"neatlogs.span.kind": "TOOL",
|
|
58
58
|
...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
|
|
59
|
-
...event.args !== void 0 ? { "input.value": safeStringify(event.args)
|
|
59
|
+
...event.args !== void 0 ? { "input.value": safeStringify(event.args) } : {}
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
parent
|
|
@@ -68,7 +68,7 @@ function handleEvent(tracer, state, event) {
|
|
|
68
68
|
const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
|
|
69
69
|
if (!span) return;
|
|
70
70
|
if (event.result !== void 0) {
|
|
71
|
-
span.setAttribute("output.value", safeStringify(event.result)
|
|
71
|
+
span.setAttribute("output.value", safeStringify(event.result));
|
|
72
72
|
}
|
|
73
73
|
if (event.isError) {
|
|
74
74
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
@@ -90,9 +90,9 @@ function handleEvent(tracer, state, event) {
|
|
|
90
90
|
state.toolSpans.clear();
|
|
91
91
|
if (state.agentSpan) {
|
|
92
92
|
const firstUser = state.inputMessages.find((m) => m.role === "user");
|
|
93
|
-
if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content
|
|
93
|
+
if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
|
|
94
94
|
const finalText = lastAssistantText(event.messages);
|
|
95
|
-
if (finalText) state.agentSpan.setAttribute("output.value", finalText
|
|
95
|
+
if (finalText) state.agentSpan.setAttribute("output.value", finalText);
|
|
96
96
|
state.agentSpan.setStatus({ code: SpanStatusCode.OK });
|
|
97
97
|
state.agentSpan.end();
|
|
98
98
|
state.agentSpan = void 0;
|
|
@@ -113,16 +113,16 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
113
113
|
if (inMsgs.length) {
|
|
114
114
|
inMsgs.forEach((m, i) => {
|
|
115
115
|
attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
|
|
116
|
-
attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content
|
|
116
|
+
attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
|
|
117
117
|
});
|
|
118
|
-
attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs })
|
|
119
|
-
attrs["input.value"] = safeStringify({ messages: inMsgs })
|
|
118
|
+
attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs });
|
|
119
|
+
attrs["input.value"] = safeStringify({ messages: inMsgs });
|
|
120
120
|
}
|
|
121
121
|
const { text, toolCalls } = splitAssistantContent(msg.content);
|
|
122
122
|
const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n");
|
|
123
123
|
if (outText || toolCalls.length) {
|
|
124
124
|
attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
|
|
125
|
-
attrs["neatlogs.llm.output_messages.0.content"] =
|
|
125
|
+
attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
|
|
126
126
|
const outBlob = { role: "assistant", content: outText || "" };
|
|
127
127
|
if (toolCalls.length) {
|
|
128
128
|
outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
|
|
@@ -133,8 +133,8 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
133
133
|
if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
|
|
134
134
|
});
|
|
135
135
|
}
|
|
136
|
-
attrs["neatlogs.llm.output"] = safeStringify(outBlob)
|
|
137
|
-
attrs["output.value"] =
|
|
136
|
+
attrs["neatlogs.llm.output"] = safeStringify(outBlob);
|
|
137
|
+
attrs["output.value"] = outText || "";
|
|
138
138
|
}
|
|
139
139
|
const usage = msg.usage;
|
|
140
140
|
if (usage) {
|
package/dist/pi-agent.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args).slice(0, 10000) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result).slice(0, 10000));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content.slice(0, 10000));\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText.slice(0, 10000));\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 10000);\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs }).slice(0, 20000);\n attrs['input.value'] = safeStringify({ messages: inMsgs }).slice(0, 10000);\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '').slice(0, 10000);\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob).slice(0, 20000);\n attrs['output.value'] = (outText || '').slice(0, 10000);\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content);\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText);\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs });\n attrs['input.value'] = safeStringify({ messages: inMsgs });\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '');\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob);\n attrs['output.value'] = (outText || '');\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":[]}
|