neatlogs 1.1.6 → 1.1.7
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/README.md +16 -27
- package/dist/ai-sdk.cjs +142 -54
- package/dist/ai-sdk.cjs.map +1 -1
- package/dist/ai-sdk.mjs +145 -53
- package/dist/ai-sdk.mjs.map +1 -1
- package/dist/anthropic.cjs +65 -29
- package/dist/anthropic.cjs.map +1 -1
- package/dist/anthropic.mjs +73 -36
- package/dist/anthropic.mjs.map +1 -1
- package/dist/azure-openai.cjs +65 -29
- package/dist/azure-openai.cjs.map +1 -1
- package/dist/azure-openai.mjs +73 -36
- package/dist/azure-openai.mjs.map +1 -1
- package/dist/bedrock.cjs +65 -29
- package/dist/bedrock.cjs.map +1 -1
- package/dist/bedrock.mjs +73 -36
- package/dist/bedrock.mjs.map +1 -1
- package/dist/claude-agent-sdk.cjs +55 -15
- package/dist/claude-agent-sdk.cjs.map +1 -1
- package/dist/claude-agent-sdk.mjs +52 -8
- package/dist/claude-agent-sdk.mjs.map +1 -1
- package/dist/google-genai.cjs +69 -35
- package/dist/google-genai.cjs.map +1 -1
- package/dist/google-genai.mjs +77 -42
- package/dist/google-genai.mjs.map +1 -1
- package/dist/index.cjs +951 -682
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +44 -2
- package/dist/index.mjs +881 -609
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +46 -12
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.mjs +54 -19
- package/dist/langchain.mjs.map +1 -1
- package/dist/mastra-wrap.cjs +318 -46
- package/dist/mastra-wrap.cjs.map +1 -1
- package/dist/mastra-wrap.mjs +319 -41
- package/dist/mastra-wrap.mjs.map +1 -1
- package/dist/openai-agents.cjs +45 -9
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.mjs +45 -5
- package/dist/openai-agents.mjs.map +1 -1
- package/dist/openai.cjs +65 -29
- package/dist/openai.cjs.map +1 -1
- package/dist/openai.mjs +73 -36
- package/dist/openai.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +1 -1
- package/dist/opencode-plugin.cjs.map +1 -1
- package/dist/opencode-plugin.mjs +1 -1
- package/dist/opencode-plugin.mjs.map +1 -1
- package/dist/openrouter-agent.cjs +48 -12
- package/dist/openrouter-agent.cjs.map +1 -1
- package/dist/openrouter-agent.mjs +56 -19
- package/dist/openrouter-agent.mjs.map +1 -1
- package/dist/pi-agent.cjs +45 -9
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.mjs +45 -6
- package/dist/pi-agent.mjs.map +1 -1
- package/dist/strands.cjs +4 -136
- package/dist/strands.cjs.map +1 -1
- package/dist/strands.d.ts +5 -24
- package/dist/strands.mjs +4 -136
- package/dist/strands.mjs.map +1 -1
- package/dist/vertex-ai.cjs +69 -35
- package/dist/vertex-ai.cjs.map +1 -1
- package/dist/vertex-ai.mjs +77 -42
- package/dist/vertex-ai.mjs.map +1 -1
- package/package.json +2 -7
package/dist/pi-agent.cjs
CHANGED
|
@@ -23,7 +23,42 @@ __export(pi_agent_exports, {
|
|
|
23
23
|
piAgentHooks: () => piAgentHooks
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(pi_agent_exports);
|
|
26
|
+
var import_api2 = require("@opentelemetry/api");
|
|
27
|
+
|
|
28
|
+
// src/core/provider.ts
|
|
29
|
+
var import_node_async_hooks = require("async_hooks");
|
|
26
30
|
var import_api = require("@opentelemetry/api");
|
|
31
|
+
var PRIVATE_SPAN_STORAGE_KEY = /* @__PURE__ */ Symbol.for(
|
|
32
|
+
"neatlogs.private_span_async_local_storage"
|
|
33
|
+
);
|
|
34
|
+
var PRIVATE_PROVIDER_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
35
|
+
"neatlogs.private_provider_state"
|
|
36
|
+
);
|
|
37
|
+
var neatlogsGlobal = globalThis;
|
|
38
|
+
var privateContextStorage = neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ?? (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new import_node_async_hooks.AsyncLocalStorage());
|
|
39
|
+
var providerState = neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ?? (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {
|
|
40
|
+
provider: null
|
|
41
|
+
});
|
|
42
|
+
var preInitTracer = {
|
|
43
|
+
startSpan() {
|
|
44
|
+
return import_api.trace.wrapSpanContext(import_api.INVALID_SPAN_CONTEXT);
|
|
45
|
+
},
|
|
46
|
+
startActiveSpan(_name, arg2, arg3, arg4) {
|
|
47
|
+
const fn = typeof arg2 === "function" ? arg2 : typeof arg3 === "function" ? arg3 : arg4;
|
|
48
|
+
return fn(import_api.trace.wrapSpanContext(import_api.INVALID_SPAN_CONTEXT));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function getNeatlogsTracer(name) {
|
|
52
|
+
return providerState.provider?.getTracer(name) ?? preInitTracer;
|
|
53
|
+
}
|
|
54
|
+
function getNeatlogsActiveContext() {
|
|
55
|
+
return privateContextStorage.getStore() ?? import_api.ROOT_CONTEXT;
|
|
56
|
+
}
|
|
57
|
+
function getNeatlogsParentContext(baseContext) {
|
|
58
|
+
return baseContext ?? getNeatlogsActiveContext();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/pi-agent.ts
|
|
27
62
|
var TRACER_NAME = "neatlogs.pi-agent";
|
|
28
63
|
var PATCH_FLAG = "_neatlogs_patched";
|
|
29
64
|
function piAgentHooks(agent) {
|
|
@@ -31,7 +66,7 @@ function piAgentHooks(agent) {
|
|
|
31
66
|
const a = agent;
|
|
32
67
|
if (typeof a.subscribe !== "function") return agent;
|
|
33
68
|
const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
|
|
34
|
-
const tracer =
|
|
69
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
35
70
|
a.subscribe((event) => {
|
|
36
71
|
try {
|
|
37
72
|
handleEvent(tracer, state, event);
|
|
@@ -44,13 +79,14 @@ function piAgentHooks(agent) {
|
|
|
44
79
|
function handleEvent(tracer, state, event) {
|
|
45
80
|
switch (event.type) {
|
|
46
81
|
case "agent_start": {
|
|
82
|
+
const base = getNeatlogsParentContext();
|
|
47
83
|
const span = tracer.startSpan(
|
|
48
84
|
"pi_agent.run",
|
|
49
85
|
{ attributes: { "neatlogs.span.kind": "AGENT" } },
|
|
50
|
-
|
|
86
|
+
base
|
|
51
87
|
);
|
|
52
88
|
state.agentSpan = span;
|
|
53
|
-
state.agentCtx =
|
|
89
|
+
state.agentCtx = import_api2.trace.setSpan(base, span);
|
|
54
90
|
state.inputMessages = [];
|
|
55
91
|
break;
|
|
56
92
|
}
|
|
@@ -69,7 +105,7 @@ function handleEvent(tracer, state, event) {
|
|
|
69
105
|
break;
|
|
70
106
|
}
|
|
71
107
|
case "tool_execution_start": {
|
|
72
|
-
const parent = state.agentCtx ??
|
|
108
|
+
const parent = state.agentCtx ?? getNeatlogsParentContext();
|
|
73
109
|
const span = tracer.startSpan(
|
|
74
110
|
`pi_agent.tool.${event.toolName ?? "tool"}`,
|
|
75
111
|
{
|
|
@@ -91,10 +127,10 @@ function handleEvent(tracer, state, event) {
|
|
|
91
127
|
span.setAttribute("output.value", safeStringify(event.result));
|
|
92
128
|
}
|
|
93
129
|
if (event.isError) {
|
|
94
|
-
span.setStatus({ code:
|
|
130
|
+
span.setStatus({ code: import_api2.SpanStatusCode.ERROR });
|
|
95
131
|
span.setAttribute("neatlogs.tool.is_error", true);
|
|
96
132
|
} else {
|
|
97
|
-
span.setStatus({ code:
|
|
133
|
+
span.setStatus({ code: import_api2.SpanStatusCode.OK });
|
|
98
134
|
}
|
|
99
135
|
span.end();
|
|
100
136
|
if (event.toolCallId) state.toolSpans.delete(event.toolCallId);
|
|
@@ -113,7 +149,7 @@ function handleEvent(tracer, state, event) {
|
|
|
113
149
|
if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
|
|
114
150
|
const finalText = lastAssistantText(event.messages);
|
|
115
151
|
if (finalText) state.agentSpan.setAttribute("output.value", finalText);
|
|
116
|
-
state.agentSpan.setStatus({ code:
|
|
152
|
+
state.agentSpan.setStatus({ code: import_api2.SpanStatusCode.OK });
|
|
117
153
|
state.agentSpan.end();
|
|
118
154
|
state.agentSpan = void 0;
|
|
119
155
|
state.agentCtx = void 0;
|
|
@@ -165,13 +201,13 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
165
201
|
if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
|
|
166
202
|
if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
|
|
167
203
|
}
|
|
168
|
-
const parent = state.agentCtx ??
|
|
204
|
+
const parent = state.agentCtx ?? getNeatlogsParentContext();
|
|
169
205
|
const span = tracer.startSpan(
|
|
170
206
|
`pi_agent.llm.${msg.model || "model"}`,
|
|
171
207
|
{ attributes: attrs },
|
|
172
208
|
parent
|
|
173
209
|
);
|
|
174
|
-
span.setStatus({ code:
|
|
210
|
+
span.setStatus({ code: import_api2.SpanStatusCode.OK });
|
|
175
211
|
span.end();
|
|
176
212
|
}
|
|
177
213
|
function splitAssistantContent(content) {
|
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) }\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":["otelContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/pi-agent.ts","../src/core/provider.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 SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsParentContext } from './core/provider.js';\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 = getNeatlogsTracer(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 base = getNeatlogsParentContext();\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n base,\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(base, 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 ?? getNeatlogsParentContext();\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 ?? getNeatlogsParentContext();\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","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n return providerState.provider;\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n const ctx = otelTrace.setSpan(base, span);\n return privateContextStorage.run(ctx, fn);\n}\n"],"mappings":"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","names":["import_api","otelTrace"]}
|
package/dist/pi-agent.mjs
CHANGED
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
// src/pi-agent.ts
|
|
2
2
|
import {
|
|
3
3
|
trace,
|
|
4
|
-
context as otelContext,
|
|
5
4
|
SpanStatusCode
|
|
6
5
|
} from "@opentelemetry/api";
|
|
6
|
+
|
|
7
|
+
// src/core/provider.ts
|
|
8
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
9
|
+
import {
|
|
10
|
+
ROOT_CONTEXT,
|
|
11
|
+
INVALID_SPAN_CONTEXT,
|
|
12
|
+
trace as otelTrace
|
|
13
|
+
} from "@opentelemetry/api";
|
|
14
|
+
var PRIVATE_SPAN_STORAGE_KEY = /* @__PURE__ */ Symbol.for(
|
|
15
|
+
"neatlogs.private_span_async_local_storage"
|
|
16
|
+
);
|
|
17
|
+
var PRIVATE_PROVIDER_STATE_KEY = /* @__PURE__ */ Symbol.for(
|
|
18
|
+
"neatlogs.private_provider_state"
|
|
19
|
+
);
|
|
20
|
+
var neatlogsGlobal = globalThis;
|
|
21
|
+
var privateContextStorage = neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ?? (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage());
|
|
22
|
+
var providerState = neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ?? (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {
|
|
23
|
+
provider: null
|
|
24
|
+
});
|
|
25
|
+
var preInitTracer = {
|
|
26
|
+
startSpan() {
|
|
27
|
+
return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);
|
|
28
|
+
},
|
|
29
|
+
startActiveSpan(_name, arg2, arg3, arg4) {
|
|
30
|
+
const fn = typeof arg2 === "function" ? arg2 : typeof arg3 === "function" ? arg3 : arg4;
|
|
31
|
+
return fn(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function getNeatlogsTracer(name) {
|
|
35
|
+
return providerState.provider?.getTracer(name) ?? preInitTracer;
|
|
36
|
+
}
|
|
37
|
+
function getNeatlogsActiveContext() {
|
|
38
|
+
return privateContextStorage.getStore() ?? ROOT_CONTEXT;
|
|
39
|
+
}
|
|
40
|
+
function getNeatlogsParentContext(baseContext) {
|
|
41
|
+
return baseContext ?? getNeatlogsActiveContext();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/pi-agent.ts
|
|
7
45
|
var TRACER_NAME = "neatlogs.pi-agent";
|
|
8
46
|
var PATCH_FLAG = "_neatlogs_patched";
|
|
9
47
|
function piAgentHooks(agent) {
|
|
@@ -11,7 +49,7 @@ function piAgentHooks(agent) {
|
|
|
11
49
|
const a = agent;
|
|
12
50
|
if (typeof a.subscribe !== "function") return agent;
|
|
13
51
|
const state = { toolSpans: /* @__PURE__ */ new Map(), inputMessages: [] };
|
|
14
|
-
const tracer =
|
|
52
|
+
const tracer = getNeatlogsTracer(TRACER_NAME);
|
|
15
53
|
a.subscribe((event) => {
|
|
16
54
|
try {
|
|
17
55
|
handleEvent(tracer, state, event);
|
|
@@ -24,13 +62,14 @@ function piAgentHooks(agent) {
|
|
|
24
62
|
function handleEvent(tracer, state, event) {
|
|
25
63
|
switch (event.type) {
|
|
26
64
|
case "agent_start": {
|
|
65
|
+
const base = getNeatlogsParentContext();
|
|
27
66
|
const span = tracer.startSpan(
|
|
28
67
|
"pi_agent.run",
|
|
29
68
|
{ attributes: { "neatlogs.span.kind": "AGENT" } },
|
|
30
|
-
|
|
69
|
+
base
|
|
31
70
|
);
|
|
32
71
|
state.agentSpan = span;
|
|
33
|
-
state.agentCtx = trace.setSpan(
|
|
72
|
+
state.agentCtx = trace.setSpan(base, span);
|
|
34
73
|
state.inputMessages = [];
|
|
35
74
|
break;
|
|
36
75
|
}
|
|
@@ -49,7 +88,7 @@ function handleEvent(tracer, state, event) {
|
|
|
49
88
|
break;
|
|
50
89
|
}
|
|
51
90
|
case "tool_execution_start": {
|
|
52
|
-
const parent = state.agentCtx ??
|
|
91
|
+
const parent = state.agentCtx ?? getNeatlogsParentContext();
|
|
53
92
|
const span = tracer.startSpan(
|
|
54
93
|
`pi_agent.tool.${event.toolName ?? "tool"}`,
|
|
55
94
|
{
|
|
@@ -145,7 +184,7 @@ function emitLlmSpan(tracer, state, msg) {
|
|
|
145
184
|
if (usage.cacheRead) attrs["neatlogs.llm.token_count.cache_read"] = usage.cacheRead;
|
|
146
185
|
if (usage.cacheWrite) attrs["neatlogs.llm.token_count.cache_write"] = usage.cacheWrite;
|
|
147
186
|
}
|
|
148
|
-
const parent = state.agentCtx ??
|
|
187
|
+
const parent = state.agentCtx ?? getNeatlogsParentContext();
|
|
149
188
|
const span = tracer.startSpan(
|
|
150
189
|
`pi_agent.llm.${msg.model || "model"}`,
|
|
151
190
|
{ attributes: attrs },
|
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) }\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/pi-agent.ts","../src/core/provider.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 SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\nimport { getNeatlogsTracer, getNeatlogsParentContext } from './core/provider.js';\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 = getNeatlogsTracer(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 base = getNeatlogsParentContext();\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n base,\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(base, 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 ?? getNeatlogsParentContext();\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 ?? getNeatlogsParentContext();\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","/**\n * Neatlogs-owned tracing state.\n *\n * Spans are created by the private Neatlogs provider and their parent is carried\n * in a private context key. The process-global OpenTelemetry span is\n * deliberately left untouched so other observability SDKs cannot export or\n * become parents of Neatlogs spans.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n ROOT_CONTEXT,\n INVALID_SPAN_CONTEXT,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n type Tracer,\n type TracerProvider,\n} from '@opentelemetry/api';\n\n// Entry points are bundled independently (`neatlogs`, `neatlogs/openai`,\n// `neatlogs/ai`, `neatlogs/mastra`, … each in both CJS and ESM), so every piece\n// of shared tracing state — the private span store AND the resolved provider /\n// provider — must live on `globalThis` behind a `Symbol.for` key.\n// Otherwise `init()` (run from the `neatlogs` bundle) sets `_provider` in ITS\n// module copy while a wrapper imported from `neatlogs/openai` reads a different,\n// still-null copy and silently falls back to the foreign global provider.\nconst PRIVATE_SPAN_STORAGE_KEY = Symbol.for(\n 'neatlogs.private_span_async_local_storage',\n);\nconst PRIVATE_PROVIDER_STATE_KEY = Symbol.for(\n 'neatlogs.private_provider_state',\n);\ninterface PrivateProviderState {\n provider: TracerProvider | null;\n}\ntype NeatlogsGlobal = typeof globalThis & {\n [PRIVATE_SPAN_STORAGE_KEY]?: AsyncLocalStorage<Context>;\n [PRIVATE_PROVIDER_STATE_KEY]?: PrivateProviderState;\n};\nconst neatlogsGlobal = globalThis as NeatlogsGlobal;\n// Stores the full Neatlogs Context (parent span PLUS any threaded values such as\n// trace()'s prompt-template keys), not just the span. We never\n// activate the OTel global context, so this private store is the ONLY channel\n// through which those values propagate down to descendant spans.\nconst privateContextStorage =\n neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] ??\n (neatlogsGlobal[PRIVATE_SPAN_STORAGE_KEY] = new AsyncLocalStorage<Context>());\nconst providerState: PrivateProviderState =\n neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] ??\n (neatlogsGlobal[PRIVATE_PROVIDER_STATE_KEY] = {\n provider: null,\n });\n// Wrappers may be constructed or accidentally invoked before init(). They must\n// never fall back to a foreign process-global provider, so pre-init calls use a\n// local no-op tracer and safely emit no exported spans.\nconst preInitTracer: Tracer = {\n startSpan(): Span {\n return otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n _name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n const fn =\n typeof arg2 === 'function'\n ? arg2\n : typeof arg3 === 'function'\n ? arg3\n : arg4;\n return fn!(otelTrace.wrapSpanContext(INVALID_SPAN_CONTEXT)) as ReturnType<F>;\n },\n};\n\n/** @internal Configure the provider used by Neatlogs-created spans. */\nexport function _setNeatlogsProvider(provider: TracerProvider | null): void {\n providerState.provider = provider;\n}\n\n/** Resolve a tracer from the private provider when one is configured. */\nexport function getNeatlogsTracer(name: string): Tracer {\n return providerState.provider?.getTracer(name) ?? preInitTracer;\n}\n\n/**\n * @internal The private Neatlogs provider, or null before\n * init(). Used by integrations that must repoint a self-instrumenting library's\n * captured provider onto ours.\n */\nexport function getNeatlogsProvider(): TracerProvider | null {\n return providerState.provider;\n}\n\n/**\n * Wrap a tracer so that spans it creates parent from — and, for\n * `startActiveSpan`, activate on — the PRIVATE Neatlogs context instead of the\n * global one.\n *\n * We hand this to libraries that create their own spans off a tracer we give\n * them (the Vercel AI SDK's `experimental_telemetry.tracer`, which calls\n * `tracer.startActiveSpan()` internally). Without the facade the AI SDK's native\n * spans would parent from `context.active()` — the foreign co-tenant's context —\n * and `startActiveSpan` would push them onto the GLOBAL context, so a foreign\n * tracer's next span reads our native span as its parent. Both directions leak.\n *\n */\nexport function isolateTracer(tracer: Tracer): Tracer {\n const facade: Tracer = {\n startSpan(name: string, options?: SpanOptions, context?: Context): Span {\n const parent = context ?? getNeatlogsActiveContext();\n return tracer.startSpan(name, options, parent);\n },\n startActiveSpan<F extends (span: Span) => unknown>(\n name: string,\n arg2?: SpanOptions | Context | F,\n arg3?: Context | F,\n arg4?: F,\n ): ReturnType<F> {\n // Normalize the 2/3/4-arg overloads of startActiveSpan.\n let options: SpanOptions | undefined;\n let context: Context | undefined;\n let fn: F;\n if (typeof arg2 === 'function') {\n fn = arg2 as F;\n } else if (typeof arg3 === 'function') {\n options = arg2 as SpanOptions;\n fn = arg3 as F;\n } else {\n options = arg2 as SpanOptions;\n context = arg3 as Context;\n fn = arg4 as F;\n }\n const parent = context ?? getNeatlogsActiveContext();\n const span = tracer.startSpan(name, options, parent);\n return withNeatlogsSpan(span, () => fn(span), parent) as ReturnType<F>;\n },\n };\n return facade;\n}\n\n/**\n * The base context to build new Neatlogs spans and values on. NEVER contains a\n * foreign provider's span: it reads our private store, or ROOT_CONTEXT when\n * nothing is active.\n */\nexport function getNeatlogsActiveContext(): Context {\n return privateContextStorage.getStore() ?? ROOT_CONTEXT;\n}\n\n/** Return only the active Neatlogs span (never a foreign provider's span). */\nexport function getActiveNeatlogsSpan(): Span | undefined {\n return otelTrace.getSpan(getNeatlogsActiveContext());\n}\n\n/**\n * Build a parent context that cannot contain a foreign provider's span.\n *\n * The active Neatlogs context already carries the parent span AND any values\n * the caller threaded in upstream (e.g. `trace()`'s prompt-template values), so\n * those values reach the span processor via `onStart(parentContext)`. An\n * explicit `baseContext` (a caller's own value-carrying context) is honored as-is.\n */\nexport function getNeatlogsParentContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * A base context for callers that thread parent linkage themselves\n * (callback/event handlers that keep their own run-id → span map: LangChain,\n * OpenAI-Agents, Claude Agent SDK).\n *\n * This is the ACTIVE Neatlogs context — the private store's\n * context if a Neatlogs `trace()`/`span()` encloses this call, else\n * ROOT_CONTEXT. So a handler's own root/entry span nests under an enclosing\n * Neatlogs trace (preserving its session + end-user id) when one exists, and\n * auto-roots cleanly when one doesn't. A foreign provider's active span can\n * never leak in as an ancestor because the global context is never read.\n */\nexport function getNeatlogsBaseContext(baseContext?: Context): Context {\n return baseContext ?? getNeatlogsActiveContext();\n}\n\n/**\n * Build an execution context for a Neatlogs span while preserving the active\n * foreign context. The span rides our private context store.\n */\nexport function getNeatlogsExecutionContext(\n span: Span,\n baseContext: Context = ROOT_CONTEXT,\n): Context {\n return otelTrace.setSpan(baseContext, span);\n}\n\n/**\n * Run a callback with a Neatlogs span active under the appropriate policy.\n *\n * The stored context is `setSpan(base, span)` — carrying the span PLUS whatever\n * values `base` holds — so threaded values (prompt templates)\n * propagate to descendant spans through our private store instead of the global\n * OTel context we deliberately never touch.\n */\nexport function withNeatlogsSpan<T>(\n span: Span,\n fn: () => T,\n baseContext?: Context,\n): T {\n const base = baseContext ?? getNeatlogsActiveContext();\n const ctx = otelTrace.setSpan(base, span);\n return privateContextStorage.run(ctx, fn);\n}\n"],"mappings":"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","names":[]}
|
package/dist/strands.cjs
CHANGED
|
@@ -23,142 +23,10 @@ __export(strands_exports, {
|
|
|
23
23
|
strandsHooks: () => strandsHooks
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(strands_exports);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
Object.defineProperty(agent, PATCH_FLAG, {
|
|
32
|
-
value: true,
|
|
33
|
-
enumerable: false,
|
|
34
|
-
configurable: true
|
|
35
|
-
});
|
|
36
|
-
} catch {
|
|
37
|
-
agent[PATCH_FLAG] = true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return agent;
|
|
41
|
-
}
|
|
42
|
-
function installEventHookFromAgent(agent) {
|
|
43
|
-
const tracer = agent?._tracer;
|
|
44
|
-
const proto = tracer ? Object.getPrototypeOf(tracer) : void 0;
|
|
45
|
-
if (!proto || typeof proto._addEvent !== "function") return;
|
|
46
|
-
if (proto._addEvent[PATCH_FLAG]) return;
|
|
47
|
-
const orig = proto._addEvent;
|
|
48
|
-
function patchedAddEvent(span, eventName, eventAttributes) {
|
|
49
|
-
const result = orig.call(this, span, eventName, eventAttributes);
|
|
50
|
-
try {
|
|
51
|
-
enrichSpanFromEvent(span, eventName, eventAttributes || {});
|
|
52
|
-
} catch {
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
patchedAddEvent[PATCH_FLAG] = true;
|
|
57
|
-
proto._addEvent = patchedAddEvent;
|
|
58
|
-
}
|
|
59
|
-
function enrichSpanFromEvent(span, eventName, attrs) {
|
|
60
|
-
if (!span || typeof span.setAttribute !== "function") return;
|
|
61
|
-
if (typeof span.isRecording === "function" && !span.isRecording()) return;
|
|
62
|
-
const isTool = readSpanOp(span) === "execute_tool";
|
|
63
|
-
if (eventName.startsWith("gen_ai.") && eventName.endsWith(".message")) {
|
|
64
|
-
const role = eventName.slice("gen_ai.".length, -".message".length);
|
|
65
|
-
const content = flattenStrandsContent(attrs.content);
|
|
66
|
-
if (content) {
|
|
67
|
-
if (isTool) {
|
|
68
|
-
span.setAttribute("input.value", content);
|
|
69
|
-
span.setAttribute("neatlogs.tool.input", content);
|
|
70
|
-
} else {
|
|
71
|
-
appendInputMessage(span, role, content);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (eventName === "gen_ai.choice") {
|
|
77
|
-
const out = flattenStrandsContent(attrs.message);
|
|
78
|
-
if (out) setOutput(span, isTool, out);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (eventName === "gen_ai.client.inference.operation.details") {
|
|
82
|
-
const out = flattenStrandsContent(attrs["gen_ai.output.messages"]);
|
|
83
|
-
if (out) setOutput(span, isTool, out);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function setOutput(span, isTool, out) {
|
|
87
|
-
span.setAttribute("output.value", out);
|
|
88
|
-
if (isTool) {
|
|
89
|
-
span.setAttribute("neatlogs.tool.output", out);
|
|
90
|
-
} else {
|
|
91
|
-
span.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
|
|
92
|
-
span.setAttribute("neatlogs.llm.output_messages.0.content", out);
|
|
93
|
-
span.setAttribute("neatlogs.llm.output", safeStringify({ role: "assistant", content: out }));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
function appendInputMessage(span, role, content) {
|
|
97
|
-
const idx = span.__neatlogs_in_idx ?? 0;
|
|
98
|
-
span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, role);
|
|
99
|
-
span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, content);
|
|
100
|
-
span.__neatlogs_in_idx = idx + 1;
|
|
101
|
-
const existing = span.__neatlogs_in_msgs ?? [];
|
|
102
|
-
existing.push({ role, content });
|
|
103
|
-
span.__neatlogs_in_msgs = existing;
|
|
104
|
-
const blob = safeStringify({ messages: existing });
|
|
105
|
-
span.setAttribute("input.value", blob);
|
|
106
|
-
span.setAttribute("neatlogs.llm.input", blob);
|
|
107
|
-
}
|
|
108
|
-
function readSpanOp(span) {
|
|
109
|
-
try {
|
|
110
|
-
const a = span.attributes;
|
|
111
|
-
if (!a) return "";
|
|
112
|
-
const v = typeof a.get === "function" ? a.get("gen_ai.operation.name") : a["gen_ai.operation.name"];
|
|
113
|
-
return typeof v === "string" ? v.toLowerCase() : "";
|
|
114
|
-
} catch {
|
|
115
|
-
return "";
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function flattenStrandsContent(content) {
|
|
119
|
-
if (content == null) return "";
|
|
120
|
-
let val = content;
|
|
121
|
-
if (typeof val === "string") {
|
|
122
|
-
const s = val.trim();
|
|
123
|
-
if (!(s.startsWith("[") || s.startsWith("{"))) return val;
|
|
124
|
-
try {
|
|
125
|
-
val = JSON.parse(s);
|
|
126
|
-
} catch {
|
|
127
|
-
return val;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return flattenBlocks(val);
|
|
131
|
-
}
|
|
132
|
-
function flattenBlocks(val) {
|
|
133
|
-
const items = Array.isArray(val) ? val : [val];
|
|
134
|
-
const out = [];
|
|
135
|
-
for (const item of items) {
|
|
136
|
-
if (typeof item === "string") {
|
|
137
|
-
out.push(item);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (!item || typeof item !== "object") {
|
|
141
|
-
if (item != null) out.push(String(item));
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const o = item;
|
|
145
|
-
if (typeof o.text === "string") out.push(o.text);
|
|
146
|
-
else if (typeof o.content === "string") out.push(o.content);
|
|
147
|
-
else if (o.toolUse) out.push(`${o.toolUse.name ?? "tool"}(${safeStringify(o.toolUse.input ?? {})})`);
|
|
148
|
-
else if (o.toolResult) out.push(flattenBlocks(o.toolResult.content ?? o.toolResult));
|
|
149
|
-
else if (Array.isArray(o.parts)) out.push(flattenBlocks(o.parts));
|
|
150
|
-
else if (Array.isArray(o.content)) out.push(flattenBlocks(o.content));
|
|
151
|
-
else out.push(safeStringify(o));
|
|
152
|
-
}
|
|
153
|
-
return out.filter(Boolean).join("\n");
|
|
154
|
-
}
|
|
155
|
-
function safeStringify(value) {
|
|
156
|
-
if (typeof value === "string") return value;
|
|
157
|
-
try {
|
|
158
|
-
return JSON.stringify(value) ?? "";
|
|
159
|
-
} catch {
|
|
160
|
-
return "";
|
|
161
|
-
}
|
|
26
|
+
function strandsHooks(_agent) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"strandsHooks() is not supported because the Strands Agents tracer creates and activates spans on the global OpenTelemetry context (context.active() / context.with()), which cannot be isolated from other tracing SDKs (Datadog, etc.)."
|
|
29
|
+
);
|
|
162
30
|
}
|
|
163
31
|
// Annotate the CommonJS export names for ESM import in node:
|
|
164
32
|
0 && (module.exports = {
|
package/dist/strands.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/strands.ts"],"sourcesContent":["/**\n * Neatlogs Strands Agents integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { strandsHooks } from 'neatlogs/strands';\n * import { Agent } from '@strands-agents/sdk';\n *\n * await init({ apiKey, workflowName });\n * const agent = strandsHooks(new Agent({ model }));\n *\n * Strands self-instruments via native OpenTelemetry (gen_ai.* spans for agent /\n * model / tool calls), which `init()` captures and the attribute mapper classifies\n * (kind, model, token counts).\n *\n * BUT Strands records prompt/response CONTENT as OTel span EVENTS\n * (gen_ai.user.message, gen_ai.system.message, gen_ai.choice, gen_ai.tool.message,\n * gen_ai.client.inference.operation.details) — not as span attributes. neatlogs\n * renders I/O from attributes, so without help those spans show tokens but EMPTY\n * input/output. `strandsHooks()` installs a single class-level hook on Strands'\n * own `Tracer._addEvent` chokepoint: after Strands records a message/choice event,\n * we also copy its content onto the span as input.value / output.value (+ the LLM\n * message attributes). We do NOT create spans — Strands' native tracing stays the\n * source of truth; we only enrich it. Mirrors the Python SDK's strands hook.\n */\n\nconst PATCH_FLAG = '_neatlogs_patched';\n\nexport function strandsHooks<T extends object>(agent: T): T {\n // Each Strands Agent constructs its OWN private Tracer (`agent._tracer = new\n // Tracer(...)`), and the Tracer class is NOT publicly exported — so we reach it\n // through the agent instance and patch its `_addEvent` chokepoint. Patching the\n // PROTOTYPE (reached via the instance) covers every agent sharing that class and\n // is idempotent. This is the only access path: the package's exports map blocks\n // deep imports and the top-level index doesn't export Tracer.\n installEventHookFromAgent(agent);\n\n if (agent && typeof agent === 'object') {\n try {\n Object.defineProperty(agent, PATCH_FLAG, {\n value: true,\n enumerable: false,\n configurable: true,\n });\n } catch {\n (agent as any)[PATCH_FLAG] = true;\n }\n }\n return agent;\n}\n\nfunction installEventHookFromAgent(agent: any): void {\n const tracer = agent?._tracer;\n const proto = tracer ? Object.getPrototypeOf(tracer) : undefined;\n if (!proto || typeof proto._addEvent !== 'function') return;\n if (proto._addEvent[PATCH_FLAG]) return;\n\n const orig = proto._addEvent;\n function patchedAddEvent(this: any, span: any, eventName: string, eventAttributes?: Record<string, unknown>) {\n const result = orig.call(this, span, eventName, eventAttributes);\n try {\n enrichSpanFromEvent(span, eventName, eventAttributes || {});\n } catch {\n // never break the agent run over tracing\n }\n return result;\n }\n (patchedAddEvent as any)[PATCH_FLAG] = true;\n proto._addEvent = patchedAddEvent;\n}\n\nfunction enrichSpanFromEvent(\n span: any,\n eventName: string,\n attrs: Record<string, unknown>,\n): void {\n if (!span || typeof span.setAttribute !== 'function') return;\n if (typeof span.isRecording === 'function' && !span.isRecording()) return;\n\n // Classify from the span's own gen_ai.operation.name (set at creation): the same\n // message/choice events fire on chat (LLM), execute_tool (TOOL) and agent spans.\n // We don't set neatlogs.span.kind (the mapper does, from the span name); we only\n // route I/O to the correct namespace so a {span_kind}-templated key isn't needed.\n const isTool = readSpanOp(span) === 'execute_tool';\n\n // Input-side messages: gen_ai.{system,user,assistant,tool}.message\n if (eventName.startsWith('gen_ai.') && eventName.endsWith('.message')) {\n const role = eventName.slice('gen_ai.'.length, -'.message'.length);\n const content = flattenStrandsContent(attrs.content);\n if (content) {\n if (isTool) {\n span.setAttribute('input.value', content);\n span.setAttribute('neatlogs.tool.input', content);\n } else {\n appendInputMessage(span, role, content);\n }\n }\n return;\n }\n\n // Output: legacy gen_ai.choice or the latest-convention details event.\n if (eventName === 'gen_ai.choice') {\n const out = flattenStrandsContent(attrs.message);\n if (out) setOutput(span, isTool, out);\n return;\n }\n if (eventName === 'gen_ai.client.inference.operation.details') {\n const out = flattenStrandsContent(attrs['gen_ai.output.messages']);\n if (out) setOutput(span, isTool, out);\n }\n}\n\nfunction setOutput(span: any, isTool: boolean, out: string): void {\n span.setAttribute('output.value', out);\n if (isTool) {\n span.setAttribute('neatlogs.tool.output', out);\n } else {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', out);\n span.setAttribute('neatlogs.llm.output', safeStringify({ role: 'assistant', content: out }));\n }\n}\n\nfunction appendInputMessage(span: any, role: string, content: string): void {\n const idx = (span.__neatlogs_in_idx as number) ?? 0;\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.role`, role);\n span.setAttribute(`neatlogs.llm.input_messages.${idx}.content`, content);\n span.__neatlogs_in_idx = idx + 1;\n const existing: Array<{ role: string; content: string }> = span.__neatlogs_in_msgs ?? [];\n existing.push({ role, content });\n span.__neatlogs_in_msgs = existing;\n const blob = safeStringify({ messages: existing });\n span.setAttribute('input.value', blob);\n // Flat LLM input the backend reads directly (no reliance on {span_kind} mapping).\n span.setAttribute('neatlogs.llm.input', blob);\n}\n\nfunction readSpanOp(span: any): string {\n // OTel spans don't expose attributes publicly; Strands' span objects carry them\n // on `.attributes`. Best-effort read of gen_ai.operation.name.\n try {\n const a = span.attributes;\n if (!a) return '';\n const v = typeof a.get === 'function' ? a.get('gen_ai.operation.name') : a['gen_ai.operation.name'];\n return typeof v === 'string' ? v.toLowerCase() : '';\n } catch {\n return '';\n }\n}\n\n/**\n * Strands event content arrives as a JSON string of content blocks\n * ([{text}], [{toolUse|toolResult}], or {role,parts,...}) or a plain string.\n */\nfunction flattenStrandsContent(content: unknown): string {\n if (content == null) return '';\n let val: unknown = content;\n if (typeof val === 'string') {\n const s = val.trim();\n if (!(s.startsWith('[') || s.startsWith('{'))) return val; // already plain text\n try {\n val = JSON.parse(s);\n } catch {\n return val as string;\n }\n }\n return flattenBlocks(val);\n}\n\nfunction flattenBlocks(val: unknown): string {\n const items = Array.isArray(val) ? val : [val];\n const out: string[] = [];\n for (const item of items) {\n if (typeof item === 'string') {\n out.push(item);\n continue;\n }\n if (!item || typeof item !== 'object') {\n if (item != null) out.push(String(item));\n continue;\n }\n const o = item as Record<string, any>;\n if (typeof o.text === 'string') out.push(o.text);\n else if (typeof o.content === 'string') out.push(o.content);\n else if (o.toolUse) out.push(`${o.toolUse.name ?? 'tool'}(${safeStringify(o.toolUse.input ?? {})})`);\n else if (o.toolResult) out.push(flattenBlocks(o.toolResult.content ?? o.toolResult));\n else if (Array.isArray(o.parts)) out.push(flattenBlocks(o.parts));\n else if (Array.isArray(o.content)) out.push(flattenBlocks(o.content));\n else out.push(safeStringify(o));\n }\n return out.filter(Boolean).join('\\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/strands.ts"],"sourcesContent":["/**\n * Strands currently creates and activates spans through the process-global\n * OpenTelemetry context. Neatlogs always uses a private provider, so this\n * integration is rejected until Strands can accept an injected private-context\n * runtime or Neatlogs ships an explicit wrapper that owns span creation.\n */\nexport function strandsHooks<T extends object>(_agent: T): T {\n throw new Error(\n 'strandsHooks() is not supported because the Strands Agents tracer creates ' +\n 'and activates spans on the global OpenTelemetry context ' +\n '(context.active() / context.with()), which cannot be isolated from other ' +\n 'tracing SDKs (Datadog, etc.).',\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMO,SAAS,aAA+B,QAAc;AAC3D,QAAM,IAAI;AAAA,IACR;AAAA,EAIF;AACF;","names":[]}
|
package/dist/strands.d.ts
CHANGED
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* import { strandsHooks } from 'neatlogs/strands';
|
|
7
|
-
* import { Agent } from '@strands-agents/sdk';
|
|
8
|
-
*
|
|
9
|
-
* await init({ apiKey, workflowName });
|
|
10
|
-
* const agent = strandsHooks(new Agent({ model }));
|
|
11
|
-
*
|
|
12
|
-
* Strands self-instruments via native OpenTelemetry (gen_ai.* spans for agent /
|
|
13
|
-
* model / tool calls), which `init()` captures and the attribute mapper classifies
|
|
14
|
-
* (kind, model, token counts).
|
|
15
|
-
*
|
|
16
|
-
* BUT Strands records prompt/response CONTENT as OTel span EVENTS
|
|
17
|
-
* (gen_ai.user.message, gen_ai.system.message, gen_ai.choice, gen_ai.tool.message,
|
|
18
|
-
* gen_ai.client.inference.operation.details) — not as span attributes. neatlogs
|
|
19
|
-
* renders I/O from attributes, so without help those spans show tokens but EMPTY
|
|
20
|
-
* input/output. `strandsHooks()` installs a single class-level hook on Strands'
|
|
21
|
-
* own `Tracer._addEvent` chokepoint: after Strands records a message/choice event,
|
|
22
|
-
* we also copy its content onto the span as input.value / output.value (+ the LLM
|
|
23
|
-
* message attributes). We do NOT create spans — Strands' native tracing stays the
|
|
24
|
-
* source of truth; we only enrich it. Mirrors the Python SDK's strands hook.
|
|
2
|
+
* Strands currently creates and activates spans through the process-global
|
|
3
|
+
* OpenTelemetry context. Neatlogs always uses a private provider, so this
|
|
4
|
+
* integration is rejected until Strands can accept an injected private-context
|
|
5
|
+
* runtime or Neatlogs ships an explicit wrapper that owns span creation.
|
|
25
6
|
*/
|
|
26
|
-
declare function strandsHooks<T extends object>(
|
|
7
|
+
declare function strandsHooks<T extends object>(_agent: T): T;
|
|
27
8
|
|
|
28
9
|
export { strandsHooks };
|