neatlogs 1.0.6 → 1.0.8
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 +3219 -1129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.mjs +3210 -1131
- 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 +5879 -0
- package/dist/opencode-plugin.cjs.map +1 -0
- package/dist/opencode-plugin.d.ts +38 -0
- package/dist/opencode-plugin.mjs +5854 -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 +76 -1
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/claude-agent-sdk.ts
|
|
21
|
+
var claude_agent_sdk_exports = {};
|
|
22
|
+
__export(claude_agent_sdk_exports, {
|
|
23
|
+
wrapClaudeAgentSDK: () => wrapClaudeAgentSDK
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(claude_agent_sdk_exports);
|
|
26
|
+
var import_api = require("@opentelemetry/api");
|
|
27
|
+
var TRACER_NAME = "neatlogs.claude_agent_sdk";
|
|
28
|
+
var ROOT_SCOPE = "__root__";
|
|
29
|
+
function wrapClaudeAgentSDK(sdk, options = {}) {
|
|
30
|
+
if (!sdk || typeof sdk.query !== "function") return sdk;
|
|
31
|
+
if (sdk._neatlogsWrapped) return sdk;
|
|
32
|
+
const wrapped = { ...sdk };
|
|
33
|
+
wrapped.query = wrapQuery(sdk.query.bind(sdk), options);
|
|
34
|
+
try {
|
|
35
|
+
Object.defineProperty(wrapped, "_neatlogsWrapped", {
|
|
36
|
+
value: true,
|
|
37
|
+
enumerable: false,
|
|
38
|
+
configurable: true
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
wrapped._neatlogsWrapped = true;
|
|
42
|
+
}
|
|
43
|
+
return wrapped;
|
|
44
|
+
}
|
|
45
|
+
function wrapQuery(original, options) {
|
|
46
|
+
return function(params, ...rest) {
|
|
47
|
+
const tracer = import_api.trace.getTracer(TRACER_NAME);
|
|
48
|
+
const workflowName = options.workflowName ?? "claude_agent.query";
|
|
49
|
+
const promptRef = { text: "" };
|
|
50
|
+
const agentSpan = tracer.startSpan(
|
|
51
|
+
"claude_agent.query",
|
|
52
|
+
{ attributes: { "neatlogs.span.kind": "AGENT", "neatlogs.workflow.name": workflowName } },
|
|
53
|
+
import_api.context.active()
|
|
54
|
+
);
|
|
55
|
+
const promptText = extractPromptText(params?.prompt);
|
|
56
|
+
if (promptText) {
|
|
57
|
+
promptRef.text = promptText;
|
|
58
|
+
agentSpan.setAttribute("input.value", promptText);
|
|
59
|
+
} else if (params && isAsyncIterable(params.prompt)) {
|
|
60
|
+
params = { ...params, prompt: tapPromptStream(params.prompt, promptRef) };
|
|
61
|
+
}
|
|
62
|
+
const agentCtx = import_api.trace.setSpan(import_api.context.active(), agentSpan);
|
|
63
|
+
const queryObj = import_api.context.with(agentCtx, () => original(params, ...rest));
|
|
64
|
+
return instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef);
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function isAsyncIterable(v) {
|
|
68
|
+
return Boolean(v) && typeof v[Symbol.asyncIterator] === "function";
|
|
69
|
+
}
|
|
70
|
+
async function* tapPromptStream(prompt, ref) {
|
|
71
|
+
for await (const message of prompt) {
|
|
72
|
+
if (!ref.text) {
|
|
73
|
+
const text = userMessageText(message);
|
|
74
|
+
if (text) ref.text = text;
|
|
75
|
+
}
|
|
76
|
+
yield message;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef) {
|
|
80
|
+
const originalAsyncIterator = queryObj?.[Symbol.asyncIterator]?.bind(queryObj);
|
|
81
|
+
if (!originalAsyncIterator) {
|
|
82
|
+
agentSpan.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
83
|
+
agentSpan.end();
|
|
84
|
+
return queryObj;
|
|
85
|
+
}
|
|
86
|
+
const rootScope = {
|
|
87
|
+
span: agentSpan,
|
|
88
|
+
ctx: agentCtx,
|
|
89
|
+
inputMessages: promptRef.text ? [{ role: "user", content: promptRef.text }] : [],
|
|
90
|
+
assistantBuffer: null,
|
|
91
|
+
finalText: "",
|
|
92
|
+
inputCaptured: Boolean(promptRef.text)
|
|
93
|
+
};
|
|
94
|
+
const state = {
|
|
95
|
+
toolSpans: /* @__PURE__ */ new Map(),
|
|
96
|
+
scopes: /* @__PURE__ */ new Map([[ROOT_SCOPE, rootScope]]),
|
|
97
|
+
finished: false,
|
|
98
|
+
promptRef
|
|
99
|
+
};
|
|
100
|
+
const finalizeAgent = (status, err) => {
|
|
101
|
+
if (state.finished) return;
|
|
102
|
+
state.finished = true;
|
|
103
|
+
for (const scope of state.scopes.values()) flushAssistantTurn(tracer, scope, state);
|
|
104
|
+
for (const ts of state.toolSpans.values()) {
|
|
105
|
+
try {
|
|
106
|
+
ts.end();
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
state.toolSpans.clear();
|
|
111
|
+
for (const [key, scope] of state.scopes) {
|
|
112
|
+
if (key === ROOT_SCOPE) continue;
|
|
113
|
+
closeScope(scope, "ok");
|
|
114
|
+
}
|
|
115
|
+
if (rootScope.finalText) agentSpan.setAttribute("output.value", rootScope.finalText);
|
|
116
|
+
if (status === "error") {
|
|
117
|
+
recordError(agentSpan, err);
|
|
118
|
+
} else {
|
|
119
|
+
agentSpan.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
120
|
+
agentSpan.end();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const wrapped = Object.create(Object.getPrototypeOf(queryObj));
|
|
124
|
+
Object.assign(wrapped, queryObj);
|
|
125
|
+
wrapped[Symbol.asyncIterator] = function() {
|
|
126
|
+
const iterator = originalAsyncIterator();
|
|
127
|
+
return {
|
|
128
|
+
async next() {
|
|
129
|
+
try {
|
|
130
|
+
const result = await import_api.context.with(agentCtx, () => iterator.next());
|
|
131
|
+
if (result.done) {
|
|
132
|
+
finalizeAgent("ok");
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
handleMessage(tracer, state, result.value, finalizeAgent);
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
finalizeAgent("error", err);
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
async return(value) {
|
|
146
|
+
finalizeAgent("ok");
|
|
147
|
+
return iterator.return?.(value) ?? { done: true, value: void 0 };
|
|
148
|
+
},
|
|
149
|
+
async throw(err) {
|
|
150
|
+
finalizeAgent("error", err);
|
|
151
|
+
if (iterator.throw) return iterator.throw(err);
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
return wrapped;
|
|
157
|
+
}
|
|
158
|
+
function closeScope(scope, status) {
|
|
159
|
+
try {
|
|
160
|
+
if (scope.finalText) scope.span.setAttribute("output.value", scope.finalText);
|
|
161
|
+
scope.span.setStatus({ code: status === "ok" ? import_api.SpanStatusCode.OK : import_api.SpanStatusCode.ERROR });
|
|
162
|
+
scope.span.end();
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function getScope(tracer, state, msg) {
|
|
167
|
+
const parentId = msg?.parent_tool_use_id ?? null;
|
|
168
|
+
if (!parentId) return state.scopes.get(ROOT_SCOPE);
|
|
169
|
+
const existing = state.scopes.get(parentId);
|
|
170
|
+
if (existing) return existing;
|
|
171
|
+
const root = state.scopes.get(ROOT_SCOPE);
|
|
172
|
+
if (!state.toolSpans.has(parentId) && root.assistantBuffer) {
|
|
173
|
+
flushAssistantTurn(tracer, root, state);
|
|
174
|
+
}
|
|
175
|
+
const parentToolSpan = state.toolSpans.get(parentId);
|
|
176
|
+
const parentCtx = parentToolSpan ? import_api.trace.setSpan(import_api.context.active(), parentToolSpan) : state.scopes.get(ROOT_SCOPE).ctx;
|
|
177
|
+
const subType = msg?.subagent_type ? String(msg.subagent_type) : "subagent";
|
|
178
|
+
const attrs = {
|
|
179
|
+
"neatlogs.span.kind": "AGENT",
|
|
180
|
+
"neatlogs.agent.name": subType
|
|
181
|
+
};
|
|
182
|
+
if (msg?.task_description) attrs["input.value"] = String(msg.task_description);
|
|
183
|
+
const span = tracer.startSpan(`claude_agent.subagent.${subType}`, { attributes: attrs }, parentCtx);
|
|
184
|
+
const scope = {
|
|
185
|
+
span,
|
|
186
|
+
ctx: import_api.trace.setSpan(import_api.context.active(), span),
|
|
187
|
+
inputMessages: msg?.task_description ? [{ role: "user", content: String(msg.task_description) }] : [],
|
|
188
|
+
assistantBuffer: null,
|
|
189
|
+
finalText: "",
|
|
190
|
+
inputCaptured: Boolean(msg?.task_description)
|
|
191
|
+
};
|
|
192
|
+
state.scopes.set(parentId, scope);
|
|
193
|
+
return scope;
|
|
194
|
+
}
|
|
195
|
+
function handleMessage(tracer, state, msg, finalizeAgent) {
|
|
196
|
+
if (!msg || typeof msg !== "object") return;
|
|
197
|
+
const rootScope = state.scopes.get(ROOT_SCOPE);
|
|
198
|
+
if (!rootScope.inputCaptured && state.promptRef.text) {
|
|
199
|
+
rootScope.inputCaptured = true;
|
|
200
|
+
rootScope.span.setAttribute("input.value", state.promptRef.text);
|
|
201
|
+
}
|
|
202
|
+
switch (msg.type) {
|
|
203
|
+
case "system": {
|
|
204
|
+
if (msg.session_id) {
|
|
205
|
+
state.sessionId = msg.session_id;
|
|
206
|
+
rootScope.span.setAttribute("neatlogs.conversation.id", String(msg.session_id));
|
|
207
|
+
}
|
|
208
|
+
if (msg.model) {
|
|
209
|
+
state.model = msg.model;
|
|
210
|
+
rootScope.span.setAttribute("neatlogs.agent.model", String(msg.model));
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "user": {
|
|
215
|
+
const scope = getScope(tracer, state, msg);
|
|
216
|
+
flushAssistantTurn(tracer, scope, state);
|
|
217
|
+
const userText = userMessageText(msg);
|
|
218
|
+
if (userText) {
|
|
219
|
+
if (!scope.inputCaptured) {
|
|
220
|
+
scope.inputCaptured = true;
|
|
221
|
+
scope.span.setAttribute("input.value", userText);
|
|
222
|
+
}
|
|
223
|
+
scope.inputMessages.push({ role: "user", content: userText });
|
|
224
|
+
}
|
|
225
|
+
closeToolSpansFromUser(state, scope, msg);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case "assistant": {
|
|
229
|
+
const scope = getScope(tracer, state, msg);
|
|
230
|
+
bufferAssistantMessage(scope, msg);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "result": {
|
|
234
|
+
flushAssistantTurn(tracer, rootScope, state);
|
|
235
|
+
const text = typeof msg.result === "string" ? msg.result : "";
|
|
236
|
+
if (text) rootScope.finalText = text;
|
|
237
|
+
if (msg.session_id && !state.sessionId) {
|
|
238
|
+
rootScope.span.setAttribute("neatlogs.conversation.id", String(msg.session_id));
|
|
239
|
+
}
|
|
240
|
+
const usage = msg.usage;
|
|
241
|
+
if (usage) setUsage(rootScope.span, usage);
|
|
242
|
+
if (msg.total_cost_usd != null) rootScope.span.setAttribute("neatlogs.agent.cost_usd", msg.total_cost_usd);
|
|
243
|
+
if (msg.num_turns != null) rootScope.span.setAttribute("neatlogs.agent.num_turns", msg.num_turns);
|
|
244
|
+
if (msg.is_error) rootScope.span.setAttribute("neatlogs.agent.is_error", true);
|
|
245
|
+
finalizeAgent(msg.is_error ? "error" : "ok", msg.is_error ? new Error(String(text || "agent run failed")) : void 0);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
default:
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function bufferAssistantMessage(scope, msg) {
|
|
253
|
+
const message = msg.message ?? msg;
|
|
254
|
+
const content = message?.content ?? [];
|
|
255
|
+
if (!scope.assistantBuffer) {
|
|
256
|
+
scope.assistantBuffer = { textParts: [], thinkingParts: [], toolCalls: [], usage: void 0 };
|
|
257
|
+
}
|
|
258
|
+
const buf = scope.assistantBuffer;
|
|
259
|
+
if (message?.model) buf.model = message.model;
|
|
260
|
+
if (message?.stop_reason) buf.stopReason = message.stop_reason;
|
|
261
|
+
if (message?.usage) buf.usage = message.usage;
|
|
262
|
+
for (const block of Array.isArray(content) ? content : []) {
|
|
263
|
+
if (!block || typeof block !== "object") continue;
|
|
264
|
+
if (block.type === "text" && typeof block.text === "string") buf.textParts.push(block.text);
|
|
265
|
+
else if (block.type === "thinking" && typeof block.thinking === "string") buf.thinkingParts.push(block.thinking);
|
|
266
|
+
else if (block.type === "tool_use") {
|
|
267
|
+
buf.toolCalls.push({ id: block.id ?? "", name: block.name ?? "", input: block.input });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function flushAssistantTurn(tracer, scope, state) {
|
|
272
|
+
const buf = scope.assistantBuffer;
|
|
273
|
+
if (!buf) return;
|
|
274
|
+
scope.assistantBuffer = null;
|
|
275
|
+
const model = buf.model ?? state.model ?? "";
|
|
276
|
+
const attrs = {
|
|
277
|
+
"neatlogs.span.kind": "LLM",
|
|
278
|
+
"neatlogs.llm.provider": "anthropic",
|
|
279
|
+
"neatlogs.llm.system": "anthropic"
|
|
280
|
+
};
|
|
281
|
+
if (model) attrs["neatlogs.llm.model_name"] = String(model);
|
|
282
|
+
if (scope.inputMessages.length === 0 && state.promptRef.text) {
|
|
283
|
+
scope.inputMessages.push({ role: "user", content: state.promptRef.text });
|
|
284
|
+
}
|
|
285
|
+
scope.inputMessages.forEach((m, i) => {
|
|
286
|
+
attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
|
|
287
|
+
attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
|
|
288
|
+
});
|
|
289
|
+
if (scope.inputMessages.length) {
|
|
290
|
+
attrs["input.value"] = safeStringify({ messages: scope.inputMessages });
|
|
291
|
+
}
|
|
292
|
+
const outText = buf.textParts.join("");
|
|
293
|
+
const outValue = outText || buf.toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.input ?? {})})`).join("\n");
|
|
294
|
+
if (outValue) {
|
|
295
|
+
attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
|
|
296
|
+
attrs["neatlogs.llm.output_messages.0.content"] = outValue;
|
|
297
|
+
attrs["output.value"] = outValue;
|
|
298
|
+
}
|
|
299
|
+
if (buf.thinkingParts.length) {
|
|
300
|
+
attrs["neatlogs.llm.output_messages.0.thinking"] = buf.thinkingParts.join("");
|
|
301
|
+
}
|
|
302
|
+
buf.toolCalls.forEach((tc, j) => {
|
|
303
|
+
attrs[`neatlogs.llm.tool_calls.${j}.id`] = tc.id;
|
|
304
|
+
attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;
|
|
305
|
+
attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.input ?? {});
|
|
306
|
+
});
|
|
307
|
+
if (buf.stopReason) attrs["neatlogs.llm.finish_reason"] = String(buf.stopReason);
|
|
308
|
+
const span = tracer.startSpan(`claude_agent.llm.${model || "model"}`, { attributes: attrs }, scope.ctx);
|
|
309
|
+
if (buf.usage) setUsage(span, buf.usage);
|
|
310
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
311
|
+
span.end();
|
|
312
|
+
if (outText) scope.finalText = outText;
|
|
313
|
+
const turnParts = [];
|
|
314
|
+
if (outText) turnParts.push(outText);
|
|
315
|
+
for (const tc of buf.toolCalls) turnParts.push(`[tool_call ${tc.name} ${safeStringify(tc.input ?? {})}]`);
|
|
316
|
+
if (turnParts.length) scope.inputMessages.push({ role: "assistant", content: turnParts.join("\n") });
|
|
317
|
+
for (const tc of buf.toolCalls) {
|
|
318
|
+
const toolSpan = tracer.startSpan(
|
|
319
|
+
`claude_agent.tool.${tc.name || "tool"}`,
|
|
320
|
+
{
|
|
321
|
+
attributes: {
|
|
322
|
+
"neatlogs.span.kind": "TOOL",
|
|
323
|
+
"neatlogs.tool.name": String(tc.name ?? ""),
|
|
324
|
+
...tc.id ? { "neatlogs.tool_call.id": String(tc.id) } : {},
|
|
325
|
+
"input.value": safeStringify(tc.input ?? {})
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
scope.ctx
|
|
329
|
+
);
|
|
330
|
+
if (tc.id) state.toolSpans.set(tc.id, toolSpan);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function closeToolSpansFromUser(state, scope, msg) {
|
|
334
|
+
const content = (msg.message ?? msg)?.content ?? [];
|
|
335
|
+
for (const block of Array.isArray(content) ? content : []) {
|
|
336
|
+
if (block?.type !== "tool_result") continue;
|
|
337
|
+
const id = block.tool_use_id ?? "";
|
|
338
|
+
const out = block.content;
|
|
339
|
+
const outText = typeof out === "string" ? out : safeStringify(out);
|
|
340
|
+
if (outText) scope.inputMessages.push({ role: "tool", content: outText });
|
|
341
|
+
const span = state.toolSpans.get(id);
|
|
342
|
+
if (!span) continue;
|
|
343
|
+
span.setAttribute("output.value", outText);
|
|
344
|
+
if (block.is_error) {
|
|
345
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR });
|
|
346
|
+
span.setAttribute("neatlogs.tool.is_error", true);
|
|
347
|
+
} else {
|
|
348
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
349
|
+
}
|
|
350
|
+
span.end();
|
|
351
|
+
state.toolSpans.delete(id);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function userMessageText(msg) {
|
|
355
|
+
const content = (msg.message ?? msg)?.content;
|
|
356
|
+
if (typeof content === "string") return content;
|
|
357
|
+
if (!Array.isArray(content)) return "";
|
|
358
|
+
const parts = [];
|
|
359
|
+
for (const block of content) {
|
|
360
|
+
if (typeof block === "string") parts.push(block);
|
|
361
|
+
else if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
362
|
+
parts.push(block.text);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return parts.join("\n");
|
|
366
|
+
}
|
|
367
|
+
function setUsage(span, usage) {
|
|
368
|
+
if (!usage) return;
|
|
369
|
+
if (usage.input_tokens != null) span.setAttribute("neatlogs.llm.token_count.prompt", usage.input_tokens);
|
|
370
|
+
if (usage.output_tokens != null) span.setAttribute("neatlogs.llm.token_count.completion", usage.output_tokens);
|
|
371
|
+
if (usage.input_tokens != null && usage.output_tokens != null) {
|
|
372
|
+
span.setAttribute("neatlogs.llm.token_count.total", usage.input_tokens + usage.output_tokens);
|
|
373
|
+
}
|
|
374
|
+
if (usage.cache_read_input_tokens != null) {
|
|
375
|
+
span.setAttribute("neatlogs.llm.token_count.cache_read", usage.cache_read_input_tokens);
|
|
376
|
+
}
|
|
377
|
+
if (usage.cache_creation_input_tokens != null) {
|
|
378
|
+
span.setAttribute("neatlogs.llm.token_count.cache_write", usage.cache_creation_input_tokens);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function extractPromptText(prompt) {
|
|
382
|
+
if (typeof prompt === "string") return prompt;
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
function safeStringify(value) {
|
|
386
|
+
if (typeof value === "string") return value;
|
|
387
|
+
try {
|
|
388
|
+
return JSON.stringify(value) ?? "";
|
|
389
|
+
} catch {
|
|
390
|
+
return "";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function recordError(span, err) {
|
|
394
|
+
if (err instanceof Error) {
|
|
395
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: err.message });
|
|
396
|
+
span.recordException(err);
|
|
397
|
+
} else {
|
|
398
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: String(err) });
|
|
399
|
+
}
|
|
400
|
+
span.end();
|
|
401
|
+
}
|
|
402
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
403
|
+
0 && (module.exports = {
|
|
404
|
+
wrapClaudeAgentSDK
|
|
405
|
+
});
|
|
406
|
+
//# sourceMappingURL=claude-agent-sdk.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/claude-agent-sdk.ts"],"sourcesContent":["/**\n * Neatlogs Claude Agent SDK integration.\n *\n * Wraps Anthropic's `@anthropic-ai/claude-agent-sdk` so every `query()` run is\n * traced. The SDK's `query()` returns an async-iterable of SDKMessages\n * (system init → assistant (text + tool_use) → user (tool_result) → … →\n * result). Each message carries `parent_tool_use_id`: null for the main\n * (orchestrator) agent, or the id of the spawning Task tool call for a subagent.\n * We translate that into a neatlogs span tree:\n *\n * AGENT claude_agent.query (orchestrator = trace root)\n * ↳ LLM orchestrator turn (one per model turn; text + tool_calls)\n * ↳ TOOL Read / Edit / Bash …\n * ↳ TOOL Task (spawns a subagent)\n * ↳ AGENT claude_agent.subagent.<type> (each subagent, nested)\n * ↳ LLM subagent turn\n * ↳ TOOL subagent tool call\n *\n * The orchestrator is the single root AGENT span — there is NO redundant WORKFLOW\n * wrapper. Subagents (Task-tool invocations, e.g. the wizard's parallel per-file\n * edits) get their own AGENT spans nested under the Task TOOL span that spawned\n * them, so a multi-agent run is represented faithfully and each agent's I/O is\n * distinct.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapClaudeAgentSDK } from 'neatlogs/claude-agent-sdk';\n * import * as claudeAgentSDK from '@anthropic-ai/claude-agent-sdk';\n *\n * await init({ apiKey, workflowName });\n * const { query } = wrapClaudeAgentSDK(claudeAgentSDK);\n * for await (const msg of query({ prompt: 'Hello', options: {...} })) { ... }\n *\n * Conversation tracking: Claude's `session_id` is captured on the root AGENT span\n * as `neatlogs.conversation.id`. Tool calls are traced only through the wrapped\n * `query` — calling the unwrapped SDK directly produces no tracing.\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span, type Context } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.claude_agent_sdk';\nconst ROOT_SCOPE = '__root__';\n\nexport interface WrapClaudeAgentSDKOptions {\n /** Logical grouping for traces (also settable globally via init({ workflowName })). */\n workflowName?: string;\n}\n\n/**\n * Wrap the Claude Agent SDK module. Returns a shallow copy of the module with an\n * instrumented `query`; all other exports (createSdkMcpServer, tool, the built-in\n * Tool helpers, etc.) are passed through unchanged.\n */\nexport function wrapClaudeAgentSDK<T extends Record<string, any>>(\n sdk: T,\n options: WrapClaudeAgentSDKOptions = {},\n): T {\n if (!sdk || typeof sdk.query !== 'function') return sdk;\n if ((sdk as any)._neatlogsWrapped) return sdk;\n\n const wrapped: Record<string, any> = { ...sdk };\n wrapped.query = wrapQuery(sdk.query.bind(sdk), options);\n\n try {\n Object.defineProperty(wrapped, '_neatlogsWrapped', {\n value: true,\n enumerable: false,\n configurable: true,\n });\n } catch {\n wrapped._neatlogsWrapped = true;\n }\n\n return wrapped as T;\n}\n\n// ---------------------------------------------------------------------------\n// query() wrapping\n// ---------------------------------------------------------------------------\n\nfunction wrapQuery(original: (...args: any[]) => any, options: WrapClaudeAgentSDKOptions) {\n return function (params: any, ...rest: any[]): any {\n const tracer = trace.getTracer(TRACER_NAME);\n const workflowName = options.workflowName ?? 'claude_agent.query';\n\n // Shared ref the input-tap fills with the first user-prompt text. In\n // streaming-input mode the prompt is an async generator fed into the SDK\n // subprocess and is NOT echoed back as a `user` output message before the\n // first assistant turn — so without tapping it, the first LLM span has no\n // input. The tap reads prompt text as the SDK pulls it.\n const promptRef: { text: string } = { text: '' };\n\n // The orchestrator AGENT is the trace ROOT (no WORKFLOW wrapper — a single\n // query() is one agent run; subagents nest below as their own AGENT spans).\n const agentSpan = tracer.startSpan(\n 'claude_agent.query',\n { attributes: { 'neatlogs.span.kind': 'AGENT', 'neatlogs.workflow.name': workflowName } },\n otelContext.active(),\n );\n\n // Input: a string prompt is captured directly. A streaming-input prompt (an\n // async iterable) is tapped — promptRef.text fills as the SDK pulls the\n // first user message — so the agent input and the first LLM span's input are\n // populated even before any `user` message echoes back through the output.\n const promptText = extractPromptText(params?.prompt);\n if (promptText) {\n promptRef.text = promptText;\n agentSpan.setAttribute('input.value', promptText);\n } else if (params && isAsyncIterable(params.prompt)) {\n params = { ...params, prompt: tapPromptStream(params.prompt, promptRef) };\n }\n\n const agentCtx = trace.setSpan(otelContext.active(), agentSpan);\n\n // Call the original query inside the AGENT context so any SDK-internal OTel\n // spans (and our child spans) nest under it.\n const queryObj = otelContext.with(agentCtx, () => original(params, ...rest));\n\n return instrumentQueryIterable(queryObj, agentSpan, agentCtx, tracer, promptRef);\n };\n}\n\n/** True for an async iterable (streaming-input prompt). */\nfunction isAsyncIterable(v: any): boolean {\n return Boolean(v) && typeof v[Symbol.asyncIterator] === 'function';\n}\n\n/**\n * Pass-through wrapper over a streaming-input prompt that records the first\n * user message's text into `ref` as the SDK consumes it. Never alters what the\n * SDK receives.\n */\nasync function* tapPromptStream(prompt: any, ref: { text: string }): AsyncGenerator<any> {\n // userMessageText is fully defensive (returns '' for any non-text shape), so\n // no try/catch is needed — the message is always yielded through untouched.\n for await (const message of prompt) {\n if (!ref.text) {\n const text = userMessageText(message);\n if (text) ref.text = text;\n }\n yield message;\n }\n}\n\ninterface ToolCallAccum {\n id: string;\n name: string;\n input: unknown;\n}\n\ninterface AssistantTurnBuffer {\n textParts: string[];\n thinkingParts: string[];\n toolCalls: ToolCallAccum[];\n usage: any;\n model?: string;\n stopReason?: string;\n}\n\n/**\n * One agent's tracing scope. There is always a root scope (the orchestrator,\n * keyed ROOT_SCOPE). Each subagent — identified by the `parent_tool_use_id` of\n * the Task tool call that spawned it — gets its own scope created lazily, with\n * its AGENT span nested under that Task TOOL span. Per-scope state keeps each\n * agent's conversation/turn buffer independent.\n */\ninterface AgentScope {\n /** The AGENT span for this scope (root = orchestrator; others = subagents). */\n span: Span;\n /** OTel context whose active span is this scope's AGENT span (children nest here). */\n ctx: Context;\n /** Running conversation (user/tool/assistant turns) — each LLM span's input. */\n inputMessages: Array<{ role: string; content: string }>;\n /** In-progress model turn, coalesced from multiple `assistant` messages. */\n assistantBuffer: AssistantTurnBuffer | null;\n /** Last assistant text seen (subagent output = its final text). */\n finalText: string;\n /** Whether input.value has been set on this scope's AGENT span. */\n inputCaptured: boolean;\n}\n\ninterface QueryState {\n /** TOOL spans keyed by tool_use_id, closed by the matching tool_result. */\n toolSpans: Map<string, Span>;\n /** Agent scopes keyed by parent_tool_use_id (ROOT_SCOPE for the orchestrator). */\n scopes: Map<string, AgentScope>;\n sessionId?: string;\n model?: string;\n finished: boolean;\n /** Lazily-filled prompt text from a streaming-input tap. */\n promptRef: { text: string };\n}\n\n/**\n * Wrap the Query object so iteration is instrumented while preserving its own\n * methods (interrupt, setPermissionMode, …). The SDK returns an async-iterable\n * object, not a bare generator.\n */\nfunction instrumentQueryIterable(\n queryObj: any,\n agentSpan: Span,\n agentCtx: Context,\n tracer: ReturnType<typeof trace.getTracer>,\n promptRef: { text: string },\n): any {\n const originalAsyncIterator = queryObj?.[Symbol.asyncIterator]?.bind(queryObj);\n if (!originalAsyncIterator) {\n // Not iterable — nothing to trace; close the root span immediately.\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n return queryObj;\n }\n\n // The root scope is the orchestrator agent. Seed its conversation with the\n // prompt (known up front for string prompts; filled by the tap otherwise).\n const rootScope: AgentScope = {\n span: agentSpan,\n ctx: agentCtx,\n inputMessages: promptRef.text ? [{ role: 'user', content: promptRef.text }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(promptRef.text),\n };\n const state: QueryState = {\n toolSpans: new Map(),\n scopes: new Map([[ROOT_SCOPE, rootScope]]),\n finished: false,\n promptRef,\n };\n\n const finalizeAgent = (status: 'ok' | 'error', err?: unknown) => {\n if (state.finished) return;\n state.finished = true;\n // Flush every scope's in-progress turn, then close subagent AGENT spans\n // (deepest-first) and finally the root.\n for (const scope of state.scopes.values()) flushAssistantTurn(tracer, scope, state);\n // Close any tool spans that never got a matching result.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n // Close subagent scopes first (any still open), then the root.\n for (const [key, scope] of state.scopes) {\n if (key === ROOT_SCOPE) continue;\n closeScope(scope, 'ok');\n }\n if (rootScope.finalText) agentSpan.setAttribute('output.value', rootScope.finalText);\n if (status === 'error') {\n recordError(agentSpan, err);\n } else {\n agentSpan.setStatus({ code: SpanStatusCode.OK });\n agentSpan.end();\n }\n };\n\n const wrapped = Object.create(Object.getPrototypeOf(queryObj));\n Object.assign(wrapped, queryObj);\n\n wrapped[Symbol.asyncIterator] = function () {\n const iterator = originalAsyncIterator();\n return {\n async next(): Promise<IteratorResult<any>> {\n try {\n const result = await otelContext.with(agentCtx, () => iterator.next());\n if (result.done) {\n finalizeAgent('ok');\n return result;\n }\n try {\n handleMessage(tracer, state, result.value, finalizeAgent);\n } catch {\n /* never let tracing break the run */\n }\n return result;\n } catch (err) {\n finalizeAgent('error', err);\n throw err;\n }\n },\n async return(value?: any): Promise<IteratorResult<any>> {\n finalizeAgent('ok');\n return iterator.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(err?: any): Promise<IteratorResult<any>> {\n finalizeAgent('error', err);\n if (iterator.throw) return iterator.throw(err);\n throw err;\n },\n };\n };\n\n return wrapped;\n}\n\n/** End a subagent scope's AGENT span, setting its output to the subagent's final text. */\nfunction closeScope(scope: AgentScope, status: 'ok' | 'error'): void {\n try {\n if (scope.finalText) scope.span.setAttribute('output.value', scope.finalText);\n scope.span.setStatus({ code: status === 'ok' ? SpanStatusCode.OK : SpanStatusCode.ERROR });\n scope.span.end();\n } catch {\n /* ignore */\n }\n}\n\n// ---------------------------------------------------------------------------\n// Message handling\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the agent scope for a message. `parent_tool_use_id` is null for the\n * orchestrator (root scope) and the spawning Task tool_use_id for a subagent.\n * Subagent scopes are created lazily, with their AGENT span nested under the\n * Task TOOL span (looked up by that id) so the hierarchy is\n * orchestrator → Task TOOL → subagent AGENT.\n */\nfunction getScope(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n): AgentScope {\n const parentId = msg?.parent_tool_use_id ?? null;\n if (!parentId) return state.scopes.get(ROOT_SCOPE)!;\n\n const existing = state.scopes.get(parentId);\n if (existing) return existing;\n\n // New subagent. Its spawning Task tool_use may still be buffered in the root\n // turn (the SDK emits subagent messages before the Task's tool_result closes\n // the parent turn). Flush the root turn first so the Task TOOL span exists and\n // this subagent AGENT can nest under it.\n const root = state.scopes.get(ROOT_SCOPE)!;\n if (!state.toolSpans.has(parentId) && root.assistantBuffer) {\n flushAssistantTurn(tracer, root, state);\n }\n\n // Nest the subagent AGENT span under the spawning Task TOOL span if we have it;\n // otherwise under the root agent.\n const parentToolSpan = state.toolSpans.get(parentId);\n const parentCtx = parentToolSpan\n ? trace.setSpan(otelContext.active(), parentToolSpan)\n : state.scopes.get(ROOT_SCOPE)!.ctx;\n\n const subType = msg?.subagent_type ? String(msg.subagent_type) : 'subagent';\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'AGENT',\n 'neatlogs.agent.name': subType,\n };\n if (msg?.task_description) attrs['input.value'] = String(msg.task_description);\n const span = tracer.startSpan(`claude_agent.subagent.${subType}`, { attributes: attrs }, parentCtx);\n\n const scope: AgentScope = {\n span,\n ctx: trace.setSpan(otelContext.active(), span),\n inputMessages: msg?.task_description ? [{ role: 'user', content: String(msg.task_description) }] : [],\n assistantBuffer: null,\n finalText: '',\n inputCaptured: Boolean(msg?.task_description),\n };\n state.scopes.set(parentId, scope);\n return scope;\n}\n\nfunction handleMessage(\n tracer: ReturnType<typeof trace.getTracer>,\n state: QueryState,\n msg: any,\n finalizeAgent: (status: 'ok' | 'error', err?: unknown) => void,\n): void {\n if (!msg || typeof msg !== 'object') return;\n\n const rootScope = state.scopes.get(ROOT_SCOPE)!;\n\n // Backfill root input from the tapped streaming-input prompt as soon as it's\n // available (it fills before the first assistant turn).\n if (!rootScope.inputCaptured && state.promptRef.text) {\n rootScope.inputCaptured = true;\n rootScope.span.setAttribute('input.value', state.promptRef.text);\n }\n\n switch (msg.type) {\n case 'system': {\n // init message — carries session_id, model, available tools.\n if (msg.session_id) {\n state.sessionId = msg.session_id;\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n if (msg.model) {\n state.model = msg.model;\n rootScope.span.setAttribute('neatlogs.agent.model', String(msg.model));\n }\n break;\n }\n\n case 'user': {\n // A `user` message (prompt, or tool_result turns) is a turn boundary for\n // its scope: flush the buffered turn as ONE LLM span first.\n const scope = getScope(tracer, state, msg);\n flushAssistantTurn(tracer, scope, state);\n\n const userText = userMessageText(msg);\n if (userText) {\n if (!scope.inputCaptured) {\n scope.inputCaptured = true;\n scope.span.setAttribute('input.value', userText);\n }\n scope.inputMessages.push({ role: 'user', content: userText });\n }\n closeToolSpansFromUser(state, scope, msg);\n break;\n }\n\n case 'assistant': {\n // Don't emit yet — the SDK delivers one model turn as multiple `assistant`\n // messages (text block, then tool_use block, …). Buffer them per scope;\n // the next user/result boundary flushes the turn as a single LLM span.\n const scope = getScope(tracer, state, msg);\n bufferAssistantMessage(scope, msg);\n break;\n }\n\n case 'result': {\n // The run is complete. Flush the root turn (a final text answer may have no\n // trailing user message), then finalize.\n flushAssistantTurn(tracer, rootScope, state);\n\n const text = typeof msg.result === 'string' ? msg.result : '';\n if (text) rootScope.finalText = text;\n if (msg.session_id && !state.sessionId) {\n rootScope.span.setAttribute('neatlogs.conversation.id', String(msg.session_id));\n }\n const usage = msg.usage;\n if (usage) setUsage(rootScope.span, usage);\n if (msg.total_cost_usd != null) rootScope.span.setAttribute('neatlogs.agent.cost_usd', msg.total_cost_usd);\n if (msg.num_turns != null) rootScope.span.setAttribute('neatlogs.agent.num_turns', msg.num_turns);\n if (msg.is_error) rootScope.span.setAttribute('neatlogs.agent.is_error', true);\n\n finalizeAgent(msg.is_error ? 'error' : 'ok', msg.is_error ? new Error(String(text || 'agent run failed')) : undefined);\n break;\n }\n\n default:\n break;\n }\n}\n\n/**\n * Append one `assistant` SDK message to the in-progress turn buffer. The SDK\n * splits a single model turn into multiple assistant messages (a text block,\n * then tool_use blocks); they share token usage. We merge their text, thinking,\n * and tool_use blocks so the turn becomes ONE LLM span on flush.\n */\nfunction bufferAssistantMessage(scope: AgentScope, msg: any): void {\n const message = msg.message ?? msg;\n const content = message?.content ?? [];\n\n if (!scope.assistantBuffer) {\n scope.assistantBuffer = { textParts: [], thinkingParts: [], toolCalls: [], usage: undefined };\n }\n const buf = scope.assistantBuffer;\n if (message?.model) buf.model = message.model;\n if (message?.stop_reason) buf.stopReason = message.stop_reason;\n // Usage is reported per assistant message but is the SAME turn total — keep\n // the largest/last non-empty one rather than summing (summing double-counts).\n if (message?.usage) buf.usage = message.usage;\n\n for (const block of Array.isArray(content) ? content : []) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') buf.textParts.push(block.text);\n else if (block.type === 'thinking' && typeof block.thinking === 'string') buf.thinkingParts.push(block.thinking);\n else if (block.type === 'tool_use') {\n buf.toolCalls.push({ id: block.id ?? '', name: block.name ?? '', input: block.input });\n }\n }\n}\n\n/**\n * Emit the buffered model turn as a SINGLE LLM span, then open TOOL spans for\n * its tool calls. No-op if no turn is buffered. This is what makes one LLM span\n * per real model turn (not per SDK assistant message).\n */\nfunction flushAssistantTurn(\n tracer: ReturnType<typeof trace.getTracer>,\n scope: AgentScope,\n state: QueryState,\n): void {\n const buf = scope.assistantBuffer;\n if (!buf) return;\n scope.assistantBuffer = null;\n\n const model = buf.model ?? state.model ?? '';\n\n const attrs: Record<string, any> = {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': 'anthropic',\n 'neatlogs.llm.system': 'anthropic',\n };\n if (model) attrs['neatlogs.llm.model_name'] = String(model);\n\n // If this is the root scope's first turn and no user message was recorded yet\n // (streaming-input mode), seed input from the tapped prompt text.\n if (scope.inputMessages.length === 0 && state.promptRef.text) {\n scope.inputMessages.push({ role: 'user', content: state.promptRef.text });\n }\n\n // Input = the exact accumulated conversation up to this turn. Emitted BOTH as\n // structured indexed input_messages.* AND as the flat `input.value` blob —\n // per neatlogs/config/attribute-mapping.json, the main UI panel renders\n // `neatlogs.{span_kind}.input` (mapped from `input.value`); the indexed\n // messages alone do NOT populate it. Without input.value the LLM Input is blank.\n scope.inputMessages.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 if (scope.inputMessages.length) {\n attrs['input.value'] = safeStringify({ messages: scope.inputMessages });\n }\n\n // Output = the turn's actual assistant content. Prefer the model's text; for a\n // tool-only turn (no text) the output IS the tool call(s), so render them as\n // the exact `name(arguments)` the model emitted (not a vague summary). The\n // structured tool_calls.* below still carry the same data for programmatic use.\n // `output.value` is the flat blob the UI maps to `neatlogs.{span_kind}.output`.\n const outText = buf.textParts.join('');\n const outValue =\n outText ||\n buf.toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.input ?? {})})`).join('\\n');\n if (outValue) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = outValue;\n attrs['output.value'] = outValue;\n }\n if (buf.thinkingParts.length) {\n attrs['neatlogs.llm.output_messages.0.thinking'] = buf.thinkingParts.join('');\n }\n buf.toolCalls.forEach((tc, j) => {\n attrs[`neatlogs.llm.tool_calls.${j}.id`] = tc.id;\n attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.input ?? {});\n });\n if (buf.stopReason) attrs['neatlogs.llm.finish_reason'] = String(buf.stopReason);\n\n // The LLM span nests under THIS scope's AGENT span.\n const span = tracer.startSpan(`claude_agent.llm.${model || 'model'}`, { attributes: attrs }, scope.ctx);\n if (buf.usage) setUsage(span, buf.usage);\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n\n // Track this scope's latest text as its output (the subagent/orchestrator\n // final answer is the last assistant text).\n if (outText) scope.finalText = outText;\n\n // Record this assistant turn so the NEXT turn's LLM span sees it as context.\n const turnParts: string[] = [];\n if (outText) turnParts.push(outText);\n for (const tc of buf.toolCalls) turnParts.push(`[tool_call ${tc.name} ${safeStringify(tc.input ?? {})}]`);\n if (turnParts.length) scope.inputMessages.push({ role: 'assistant', content: turnParts.join('\\n') });\n\n // Open TOOL spans for this turn's tool calls, nested under THIS scope's AGENT\n // span (closed by their tool_result). A Task tool's id becomes the key a\n // subagent's messages resolve to (see getScope).\n for (const tc of buf.toolCalls) {\n const toolSpan = tracer.startSpan(\n `claude_agent.tool.${tc.name || 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n 'neatlogs.tool.name': String(tc.name ?? ''),\n ...(tc.id ? { 'neatlogs.tool_call.id': String(tc.id) } : {}),\n 'input.value': safeStringify(tc.input ?? {}),\n },\n },\n scope.ctx,\n );\n if (tc.id) state.toolSpans.set(tc.id, toolSpan);\n }\n}\n\nfunction closeToolSpansFromUser(state: QueryState, scope: AgentScope, msg: any): void {\n const content = (msg.message ?? msg)?.content ?? [];\n for (const block of Array.isArray(content) ? content : []) {\n if (block?.type !== 'tool_result') continue;\n const id = block.tool_use_id ?? '';\n const out = block.content;\n const outText = (typeof out === 'string' ? out : safeStringify(out));\n // Feed the tool result into THIS scope's conversation so the next LLM span's\n // input reflects what the model actually saw.\n if (outText) scope.inputMessages.push({ role: 'tool', content: outText });\n const span = state.toolSpans.get(id);\n if (!span) continue;\n span.setAttribute('output.value', outText);\n if (block.is_error) {\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 state.toolSpans.delete(id);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the human/text content of a `user` SDK message — the prompt, not\n * tool_result blocks (those are handled separately as tool outputs).\n */\nfunction userMessageText(msg: any): string {\n const content = (msg.message ?? msg)?.content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object' && block.type === 'text' && typeof block.text === 'string') {\n parts.push(block.text);\n }\n }\n return parts.join('\\n');\n}\n\nfunction setUsage(span: Span, usage: any): void {\n if (!usage) return;\n if (usage.input_tokens != null) span.setAttribute('neatlogs.llm.token_count.prompt', usage.input_tokens);\n if (usage.output_tokens != null) span.setAttribute('neatlogs.llm.token_count.completion', usage.output_tokens);\n if (usage.input_tokens != null && usage.output_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.total', usage.input_tokens + usage.output_tokens);\n }\n if (usage.cache_read_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_read', usage.cache_read_input_tokens);\n }\n if (usage.cache_creation_input_tokens != null) {\n span.setAttribute('neatlogs.llm.token_count.cache_write', usage.cache_creation_input_tokens);\n }\n}\n\nfunction extractPromptText(prompt: any): string {\n if (typeof prompt === 'string') return prompt;\n // Streaming-input mode passes an async iterable of messages; we can't read it\n // synchronously without consuming it, so leave input.value unset in that case.\n return '';\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":["otelContext"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neatlogs Claude Agent SDK integration.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Anthropic's `@anthropic-ai/claude-agent-sdk` so every `query()` run is
|
|
5
|
+
* traced. The SDK's `query()` returns an async-iterable of SDKMessages
|
|
6
|
+
* (system init → assistant (text + tool_use) → user (tool_result) → … →
|
|
7
|
+
* result). Each message carries `parent_tool_use_id`: null for the main
|
|
8
|
+
* (orchestrator) agent, or the id of the spawning Task tool call for a subagent.
|
|
9
|
+
* We translate that into a neatlogs span tree:
|
|
10
|
+
*
|
|
11
|
+
* AGENT claude_agent.query (orchestrator = trace root)
|
|
12
|
+
* ↳ LLM orchestrator turn (one per model turn; text + tool_calls)
|
|
13
|
+
* ↳ TOOL Read / Edit / Bash …
|
|
14
|
+
* ↳ TOOL Task (spawns a subagent)
|
|
15
|
+
* ↳ AGENT claude_agent.subagent.<type> (each subagent, nested)
|
|
16
|
+
* ↳ LLM subagent turn
|
|
17
|
+
* ↳ TOOL subagent tool call
|
|
18
|
+
*
|
|
19
|
+
* The orchestrator is the single root AGENT span — there is NO redundant WORKFLOW
|
|
20
|
+
* wrapper. Subagents (Task-tool invocations, e.g. the wizard's parallel per-file
|
|
21
|
+
* edits) get their own AGENT spans nested under the Task TOOL span that spawned
|
|
22
|
+
* them, so a multi-agent run is represented faithfully and each agent's I/O is
|
|
23
|
+
* distinct.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* import { init } from 'neatlogs';
|
|
27
|
+
* import { wrapClaudeAgentSDK } from 'neatlogs/claude-agent-sdk';
|
|
28
|
+
* import * as claudeAgentSDK from '@anthropic-ai/claude-agent-sdk';
|
|
29
|
+
*
|
|
30
|
+
* await init({ apiKey, workflowName });
|
|
31
|
+
* const { query } = wrapClaudeAgentSDK(claudeAgentSDK);
|
|
32
|
+
* for await (const msg of query({ prompt: 'Hello', options: {...} })) { ... }
|
|
33
|
+
*
|
|
34
|
+
* Conversation tracking: Claude's `session_id` is captured on the root AGENT span
|
|
35
|
+
* as `neatlogs.conversation.id`. Tool calls are traced only through the wrapped
|
|
36
|
+
* `query` — calling the unwrapped SDK directly produces no tracing.
|
|
37
|
+
*/
|
|
38
|
+
interface WrapClaudeAgentSDKOptions {
|
|
39
|
+
/** Logical grouping for traces (also settable globally via init({ workflowName })). */
|
|
40
|
+
workflowName?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wrap the Claude Agent SDK module. Returns a shallow copy of the module with an
|
|
44
|
+
* instrumented `query`; all other exports (createSdkMcpServer, tool, the built-in
|
|
45
|
+
* Tool helpers, etc.) are passed through unchanged.
|
|
46
|
+
*/
|
|
47
|
+
declare function wrapClaudeAgentSDK<T extends Record<string, any>>(sdk: T, options?: WrapClaudeAgentSDKOptions): T;
|
|
48
|
+
|
|
49
|
+
export { type WrapClaudeAgentSDKOptions, wrapClaudeAgentSDK };
|