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.
Files changed (57) hide show
  1. package/dist/azure-openai.cjs +344 -0
  2. package/dist/azure-openai.cjs.map +1 -0
  3. package/dist/azure-openai.d.ts +30 -0
  4. package/dist/azure-openai.mjs +318 -0
  5. package/dist/azure-openai.mjs.map +1 -0
  6. package/dist/bedrock.cjs +540 -0
  7. package/dist/bedrock.cjs.map +1 -0
  8. package/dist/bedrock.d.ts +29 -0
  9. package/dist/bedrock.mjs +514 -0
  10. package/dist/bedrock.mjs.map +1 -0
  11. package/dist/browser.cjs +121 -0
  12. package/dist/browser.cjs.map +1 -0
  13. package/dist/browser.d.ts +143 -0
  14. package/dist/browser.mjs +96 -0
  15. package/dist/browser.mjs.map +1 -0
  16. package/dist/claude-agent-sdk.cjs +406 -0
  17. package/dist/claude-agent-sdk.cjs.map +1 -0
  18. package/dist/claude-agent-sdk.d.ts +49 -0
  19. package/dist/claude-agent-sdk.mjs +381 -0
  20. package/dist/claude-agent-sdk.mjs.map +1 -0
  21. package/dist/index.cjs +3219 -1129
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.ts +7 -1
  24. package/dist/index.mjs +3210 -1131
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/langchain.cjs +9 -9
  27. package/dist/langchain.cjs.map +1 -1
  28. package/dist/langchain.mjs +9 -9
  29. package/dist/langchain.mjs.map +1 -1
  30. package/dist/mastra-wrap.cjs +25 -25
  31. package/dist/mastra-wrap.cjs.map +1 -1
  32. package/dist/mastra-wrap.mjs +25 -25
  33. package/dist/mastra-wrap.mjs.map +1 -1
  34. package/dist/openai-agents.cjs +6 -6
  35. package/dist/openai-agents.cjs.map +1 -1
  36. package/dist/openai-agents.mjs +6 -6
  37. package/dist/openai-agents.mjs.map +1 -1
  38. package/dist/opencode-plugin.cjs +5879 -0
  39. package/dist/opencode-plugin.cjs.map +1 -0
  40. package/dist/opencode-plugin.d.ts +38 -0
  41. package/dist/opencode-plugin.mjs +5854 -0
  42. package/dist/opencode-plugin.mjs.map +1 -0
  43. package/dist/openrouter-agent.cjs +273 -0
  44. package/dist/openrouter-agent.cjs.map +1 -0
  45. package/dist/openrouter-agent.d.ts +34 -0
  46. package/dist/openrouter-agent.mjs +247 -0
  47. package/dist/openrouter-agent.mjs.map +1 -0
  48. package/dist/pi-agent.cjs +10 -10
  49. package/dist/pi-agent.cjs.map +1 -1
  50. package/dist/pi-agent.mjs +10 -10
  51. package/dist/pi-agent.mjs.map +1 -1
  52. package/dist/vertex-ai.cjs +424 -0
  53. package/dist/vertex-ai.cjs.map +1 -0
  54. package/dist/vertex-ai.d.ts +39 -0
  55. package/dist/vertex-ai.mjs +397 -0
  56. package/dist/vertex-ai.mjs.map +1 -0
  57. package/package.json +76 -1
package/dist/pi-agent.cjs CHANGED
@@ -76,7 +76,7 @@ function handleEvent(tracer, state, event) {
76
76
  attributes: {
77
77
  "neatlogs.span.kind": "TOOL",
78
78
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
79
- ...event.args !== void 0 ? { "input.value": safeStringify(event.args).slice(0, 1e4) } : {}
79
+ ...event.args !== void 0 ? { "input.value": safeStringify(event.args) } : {}
80
80
  }
81
81
  },
82
82
  parent
@@ -88,7 +88,7 @@ function handleEvent(tracer, state, event) {
88
88
  const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
89
89
  if (!span) return;
90
90
  if (event.result !== void 0) {
91
- span.setAttribute("output.value", safeStringify(event.result).slice(0, 1e4));
91
+ span.setAttribute("output.value", safeStringify(event.result));
92
92
  }
93
93
  if (event.isError) {
94
94
  span.setStatus({ code: import_api.SpanStatusCode.ERROR });
@@ -110,9 +110,9 @@ function handleEvent(tracer, state, event) {
110
110
  state.toolSpans.clear();
111
111
  if (state.agentSpan) {
112
112
  const firstUser = state.inputMessages.find((m) => m.role === "user");
113
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content.slice(0, 1e4));
113
+ if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
114
114
  const finalText = lastAssistantText(event.messages);
115
- if (finalText) state.agentSpan.setAttribute("output.value", finalText.slice(0, 1e4));
115
+ if (finalText) state.agentSpan.setAttribute("output.value", finalText);
116
116
  state.agentSpan.setStatus({ code: import_api.SpanStatusCode.OK });
117
117
  state.agentSpan.end();
118
118
  state.agentSpan = void 0;
@@ -133,16 +133,16 @@ function emitLlmSpan(tracer, state, msg) {
133
133
  if (inMsgs.length) {
134
134
  inMsgs.forEach((m, i) => {
135
135
  attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
136
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 1e4);
136
+ attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
137
137
  });
138
- attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs }).slice(0, 2e4);
139
- attrs["input.value"] = safeStringify({ messages: inMsgs }).slice(0, 1e4);
138
+ attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs });
139
+ attrs["input.value"] = safeStringify({ messages: inMsgs });
140
140
  }
141
141
  const { text, toolCalls } = splitAssistantContent(msg.content);
142
142
  const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n");
143
143
  if (outText || toolCalls.length) {
144
144
  attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
145
- attrs["neatlogs.llm.output_messages.0.content"] = (outText || "").slice(0, 1e4);
145
+ attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
146
146
  const outBlob = { role: "assistant", content: outText || "" };
147
147
  if (toolCalls.length) {
148
148
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
@@ -153,8 +153,8 @@ function emitLlmSpan(tracer, state, msg) {
153
153
  if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
154
154
  });
155
155
  }
156
- attrs["neatlogs.llm.output"] = safeStringify(outBlob).slice(0, 2e4);
157
- attrs["output.value"] = (outText || "").slice(0, 1e4);
156
+ attrs["neatlogs.llm.output"] = safeStringify(outBlob);
157
+ attrs["output.value"] = outText || "";
158
158
  }
159
159
  const usage = msg.usage;
160
160
  if (usage) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args).slice(0, 10000) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result).slice(0, 10000));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content.slice(0, 10000));\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText.slice(0, 10000));\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 10000);\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs }).slice(0, 20000);\n attrs['input.value'] = safeStringify({ messages: inMsgs }).slice(0, 10000);\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '').slice(0, 10000);\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob).slice(0, 20000);\n attrs['output.value'] = (outText || '').slice(0, 10000);\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":["otelContext"]}
1
+ {"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content);\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText);\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs });\n attrs['input.value'] = safeStringify({ messages: inMsgs });\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '');\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob);\n attrs['output.value'] = (outText || '');\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBA,iBAMO;AAEP,IAAM,cAAc;AACpB,IAAM,aAAa;AAiDZ,SAAS,aAA+B,OAAa;AAC1D,MAAI,CAAC,SAAU,MAAc,UAAU,EAAG,QAAO;AACjD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,cAAc,WAAY,QAAO;AAE9C,QAAM,QAAuB,EAAE,WAAW,oBAAI,IAAI,GAAG,eAAe,CAAC,EAAE;AACvE,QAAM,SAAS,iBAAM,UAAU,WAAW;AAE1C,IAAE,UAAU,CAAC,UAAwB;AACnC,QAAI;AACF,kBAAY,QAAQ,OAAO,KAAK;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AAED,cAAY,CAAC;AACb,SAAO;AACT;AAEA,SAAS,YACP,QACA,OACA,OACM;AACN,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK,eAAe;AAElB,YAAM,OAAO,OAAO;AAAA,QAClB;AAAA,QACA,EAAE,YAAY,EAAE,sBAAsB,QAAQ,EAAE;AAAA,QAChD,WAAAA,QAAY,OAAO;AAAA,MACrB;AACA,YAAM,YAAY;AAClB,YAAM,WAAW,iBAAM,QAAQ,WAAAA,QAAY,OAAO,GAAG,IAAI;AACzD,YAAM,gBAAgB,CAAC;AACvB;AAAA,IACF;AAAA,IAEA,KAAK,eAAe;AAClB,YAAM,MAAM,MAAM;AAClB,UAAI,CAAC,IAAK;AACV,UAAI,IAAI,SAAS,aAAa;AAG5B,oBAAY,QAAQ,OAAO,GAAyB;AACpD,cAAM,EAAE,KAAK,IAAI,sBAAsB,IAAI,OAAO;AAClD,YAAI,KAAM,OAAM,cAAc,KAAK,EAAE,MAAM,aAAa,SAAS,KAAK,CAAC;AAAA,MACzE,OAAO;AAEL,cAAM,OAAO,IAAI,SAAS,eAAe,SAAS,OAAO,IAAI,QAAQ,MAAM;AAC3E,cAAM,UAAU,YAAY,GAAG;AAC/B,YAAI,QAAS,OAAM,cAAc,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,MACzD;AACA;AAAA,IACF;AAAA,IAEA,KAAK,wBAAwB;AAC3B,YAAM,SAAS,MAAM,YAAY,WAAAA,QAAY,OAAO;AACpD,YAAM,OAAO,OAAO;AAAA,QAClB,iBAAiB,MAAM,YAAY,MAAM;AAAA,QACzC;AAAA,UACE,YAAY;AAAA,YACV,sBAAsB;AAAA,YACtB,GAAI,MAAM,WAAW,EAAE,sBAAsB,OAAO,MAAM,QAAQ,EAAE,IAAI,CAAC;AAAA,YACzE,GAAI,MAAM,SAAS,SACf,EAAE,eAAe,cAAc,MAAM,IAAI,EAAE,IAC3C,CAAC;AAAA,UACP;AAAA,QACF;AAAA,QACA;AAAA,MACF;AACA,UAAI,MAAM,WAAY,OAAM,UAAU,IAAI,MAAM,YAAY,IAAI;AAChE;AAAA,IACF;AAAA,IAEA,KAAK,sBAAsB;AACzB,YAAM,OAAO,MAAM,aAAa,MAAM,UAAU,IAAI,MAAM,UAAU,IAAI;AACxE,UAAI,CAAC,KAAM;AACX,UAAI,MAAM,WAAW,QAAW;AAC9B,aAAK,aAAa,gBAAgB,cAAc,MAAM,MAAM,CAAC;AAAA,MAC/D;AACA,UAAI,MAAM,SAAS;AACjB,aAAK,UAAU,EAAE,MAAM,0BAAe,MAAM,CAAC;AAC7C,aAAK,aAAa,0BAA0B,IAAI;AAAA,MAClD,OAAO;AACL,aAAK,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AAAA,MAC5C;AACA,WAAK,IAAI;AACT,UAAI,MAAM,WAAY,OAAM,UAAU,OAAO,MAAM,UAAU;AAC7D;AAAA,IACF;AAAA,IAEA,KAAK,aAAa;AAEhB,iBAAW,MAAM,MAAM,UAAU,OAAO,GAAG;AACzC,YAAI;AACF,aAAG,IAAI;AAAA,QACT,QAAQ;AAAA,QAER;AAAA,MACF;AACA,YAAM,UAAU,MAAM;AACtB,UAAI,MAAM,WAAW;AAEnB,cAAM,YAAY,MAAM,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AACnE,YAAI,UAAW,OAAM,UAAU,aAAa,eAAe,UAAU,OAAO;AAC5E,cAAM,YAAY,kBAAkB,MAAM,QAAQ;AAClD,YAAI,UAAW,OAAM,UAAU,aAAa,gBAAgB,SAAS;AACrE,cAAM,UAAU,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AACrD,cAAM,UAAU,IAAI;AACpB,cAAM,YAAY;AAClB,cAAM,WAAW;AAAA,MACnB;AACA;AAAA,IACF;AAAA,IAEA;AACE;AAAA,EACJ;AACF;AAEA,SAAS,YACP,QACA,OACA,KACM;AACN,QAAM,QAA6B,EAAE,sBAAsB,MAAM;AACjE,MAAI,IAAI,MAAO,OAAM,yBAAyB,IAAI,OAAO,IAAI,KAAK;AAClE,MAAI,IAAI,SAAU,OAAM,uBAAuB,IAAI,OAAO,IAAI,QAAQ;AACtE,MAAI,IAAI,WAAY,OAAM,0BAA0B,IAAI,OAAO,IAAI,UAAU;AAK7E,QAAM,SAAS,MAAM;AACrB,MAAI,OAAO,QAAQ;AACjB,WAAO,QAAQ,CAAC,GAAG,MAAM;AACvB,YAAM,+BAA+B,CAAC,OAAO,IAAI,EAAE;AACnD,YAAM,+BAA+B,CAAC,UAAU,IAAI,EAAE;AAAA,IACxD,CAAC;AACD,UAAM,oBAAoB,IAAI,cAAc,EAAE,UAAU,OAAO,CAAC;AAChE,UAAM,aAAa,IAAI,cAAc,EAAE,UAAU,OAAO,CAAC;AAAA,EAC3D;AAEA,QAAM,EAAE,MAAM,UAAU,IAAI,sBAAsB,IAAI,OAAO;AAE7D,QAAM,UAAU,QAAQ,UAAU,IAAI,CAAC,OAAO,GAAG,GAAG,IAAI,IAAI,cAAc,GAAG,SAAS,CAAC,GAAG,EAAE,KAAK,IAAI;AACrG,MAAI,WAAW,UAAU,QAAQ;AAC/B,UAAM,qCAAqC,IAAI;AAC/C,UAAM,wCAAwC,IAAK,WAAW;AAC9D,UAAM,UAAmC,EAAE,MAAM,aAAa,SAAS,WAAW,GAAG;AACrF,QAAI,UAAU,QAAQ;AACpB,cAAQ,aAAa,UAAU,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,WAAW,GAAG,UAAU,EAAE;AACvF,gBAAU,QAAQ,CAAC,IAAI,MAAM;AAC3B,YAAI,GAAG,KAAM,OAAM,2BAA2B,CAAC,OAAO,IAAI,GAAG;AAC7D,YAAI,GAAG,cAAc;AACnB,gBAAM,2BAA2B,CAAC,YAAY,IAAI,cAAc,GAAG,SAAS;AAC9E,YAAI,GAAG,GAAI,OAAM,2BAA2B,CAAC,KAAK,IAAI,OAAO,GAAG,EAAE;AAAA,MACpE,CAAC;AAAA,IACH;AACA,UAAM,qBAAqB,IAAI,cAAc,OAAO;AACpD,UAAM,cAAc,IAAK,WAAW;AAAA,EACtC;AAEA,QAAM,QAAQ,IAAI;AAClB,MAAI,OAAO;AACT,QAAI,MAAM,SAAS,KAAM,OAAM,iCAAiC,IAAI,MAAM;AAC1E,QAAI,MAAM,UAAU,KAAM,OAAM,qCAAqC,IAAI,MAAM;AAC/E,UAAM,QAAQ,MAAM,gBAAiB,MAAM,SAAS,MAAM,MAAM,UAAU;AAC1E,QAAI,MAAO,OAAM,gCAAgC,IAAI;AACrD,QAAI,MAAM,UAAW,OAAM,qCAAqC,IAAI,MAAM;AAC1E,QAAI,MAAM,WAAY,OAAM,sCAAsC,IAAI,MAAM;AAAA,EAC9E;AAEA,QAAM,SAAS,MAAM,YAAY,WAAAA,QAAY,OAAO;AACpD,QAAM,OAAO,OAAO;AAAA,IAClB,gBAAgB,IAAI,SAAS,OAAO;AAAA,IACpC,EAAE,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AACA,OAAK,UAAU,EAAE,MAAM,0BAAe,GAAG,CAAC;AAC1C,OAAK,IAAI;AACX;AAMA,SAAS,sBACP,SAC2C;AAC3C,QAAM,QAAkB,CAAC;AACzB,QAAM,YAA0B,CAAC;AACjC,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,eAAW,SAAS,SAAS;AAC3B,UAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,UAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,SAAU,OAAM,KAAK,MAAM,IAAI;AAAA,eACzE,MAAM,SAAS;AACtB,kBAAU,KAAK,KAA8B;AAAA,IAEjD;AAAA,EACF,WAAW,OAAO,YAAY,UAAU;AACtC,UAAM,KAAK,OAAO;AAAA,EACpB;AACA,SAAO,EAAE,MAAM,MAAM,KAAK,EAAE,GAAG,UAAU;AAC3C;AAGA,SAAS,YAAY,KAAkB;AACrC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,IAAI;AACd,MAAI,OAAO,MAAM,SAAU,QAAO;AAClC,MAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC9B,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,GAAG;AACrB,QAAI,OAAO,UAAU,SAAU,OAAM,KAAK,KAAK;AAAA,aACtC,SAAS,OAAO,UAAU,UAAU;AAC3C,UAAI,OAAO,MAAM,SAAS,SAAU,OAAM,KAAK,MAAM,IAAI;AAAA,eAChD,MAAM,SAAS,WAAY,OAAM,KAAK,GAAG,MAAM,QAAQ,MAAM,IAAI,cAAc,MAAM,SAAS,CAAC,GAAG;AAAA,IAC7G;AAAA,EACF;AACA,SAAO,MAAM,KAAK,EAAE;AACtB;AAEA,SAAS,kBAAkB,UAAqC;AAC9D,MAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG,QAAO;AACrC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,KAAK,EAAE,SAAS,aAAa;AAC/B,YAAM,EAAE,KAAK,IAAI,sBAAsB,EAAE,OAAO;AAChD,UAAI,KAAM,QAAO;AAAA,IACnB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,GAAc;AACjC,MAAI;AACF,WAAO,eAAe,GAAG,YAAY,EAAE,OAAO,MAAM,YAAY,OAAO,cAAc,KAAK,CAAC;AAAA,EAC7F,QAAQ;AACN,MAAE,UAAU,IAAI;AAAA,EAClB;AACF;AAEA,SAAS,cAAc,OAAwB;AAC7C,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI;AACF,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":["otelContext"]}
package/dist/pi-agent.mjs CHANGED
@@ -56,7 +56,7 @@ function handleEvent(tracer, state, event) {
56
56
  attributes: {
57
57
  "neatlogs.span.kind": "TOOL",
58
58
  ...event.toolName ? { "neatlogs.tool.name": String(event.toolName) } : {},
59
- ...event.args !== void 0 ? { "input.value": safeStringify(event.args).slice(0, 1e4) } : {}
59
+ ...event.args !== void 0 ? { "input.value": safeStringify(event.args) } : {}
60
60
  }
61
61
  },
62
62
  parent
@@ -68,7 +68,7 @@ function handleEvent(tracer, state, event) {
68
68
  const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : void 0;
69
69
  if (!span) return;
70
70
  if (event.result !== void 0) {
71
- span.setAttribute("output.value", safeStringify(event.result).slice(0, 1e4));
71
+ span.setAttribute("output.value", safeStringify(event.result));
72
72
  }
73
73
  if (event.isError) {
74
74
  span.setStatus({ code: SpanStatusCode.ERROR });
@@ -90,9 +90,9 @@ function handleEvent(tracer, state, event) {
90
90
  state.toolSpans.clear();
91
91
  if (state.agentSpan) {
92
92
  const firstUser = state.inputMessages.find((m) => m.role === "user");
93
- if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content.slice(0, 1e4));
93
+ if (firstUser) state.agentSpan.setAttribute("input.value", firstUser.content);
94
94
  const finalText = lastAssistantText(event.messages);
95
- if (finalText) state.agentSpan.setAttribute("output.value", finalText.slice(0, 1e4));
95
+ if (finalText) state.agentSpan.setAttribute("output.value", finalText);
96
96
  state.agentSpan.setStatus({ code: SpanStatusCode.OK });
97
97
  state.agentSpan.end();
98
98
  state.agentSpan = void 0;
@@ -113,16 +113,16 @@ function emitLlmSpan(tracer, state, msg) {
113
113
  if (inMsgs.length) {
114
114
  inMsgs.forEach((m, i) => {
115
115
  attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;
116
- attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 1e4);
116
+ attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;
117
117
  });
118
- attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs }).slice(0, 2e4);
119
- attrs["input.value"] = safeStringify({ messages: inMsgs }).slice(0, 1e4);
118
+ attrs["neatlogs.llm.input"] = safeStringify({ messages: inMsgs });
119
+ attrs["input.value"] = safeStringify({ messages: inMsgs });
120
120
  }
121
121
  const { text, toolCalls } = splitAssistantContent(msg.content);
122
122
  const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join("\n");
123
123
  if (outText || toolCalls.length) {
124
124
  attrs["neatlogs.llm.output_messages.0.role"] = "assistant";
125
- attrs["neatlogs.llm.output_messages.0.content"] = (outText || "").slice(0, 1e4);
125
+ attrs["neatlogs.llm.output_messages.0.content"] = outText || "";
126
126
  const outBlob = { role: "assistant", content: outText || "" };
127
127
  if (toolCalls.length) {
128
128
  outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));
@@ -133,8 +133,8 @@ function emitLlmSpan(tracer, state, msg) {
133
133
  if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);
134
134
  });
135
135
  }
136
- attrs["neatlogs.llm.output"] = safeStringify(outBlob).slice(0, 2e4);
137
- attrs["output.value"] = (outText || "").slice(0, 1e4);
136
+ attrs["neatlogs.llm.output"] = safeStringify(outBlob);
137
+ attrs["output.value"] = outText || "";
138
138
  }
139
139
  const usage = msg.usage;
140
140
  if (usage) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args).slice(0, 10000) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result).slice(0, 10000));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content.slice(0, 10000));\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText.slice(0, 10000));\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content.slice(0, 10000);\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs }).slice(0, 20000);\n attrs['input.value'] = safeStringify({ messages: inMsgs }).slice(0, 10000);\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '').slice(0, 10000);\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob).slice(0, 20000);\n attrs['output.value'] = (outText || '').slice(0, 10000);\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/pi-agent.ts"],"sourcesContent":["/**\n * Neatlogs Pi Agent integration.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { piAgentHooks } from 'neatlogs/pi-agent';\n * import { Agent } from '@mariozechner/pi-agent-core';\n *\n * await init({ apiKey, workflowName });\n * const agent = piAgentHooks(new Agent({ initialState: { systemPrompt, model } }));\n * await agent.prompt('Hello');\n *\n * Pi Agent's `Agent` exposes a first-class `subscribe(listener)` API and emits\n * AgentEvents for its run lifecycle (message_*, tool_execution_*, turn_*,\n * agent_*). It does NOT emit its own OpenTelemetry spans — so we LISTEN to those\n * events (no monkey-patching) and translate them into neatlogs OTel spans:\n *\n * AGENT agent run (agent_start → agent_end)\n * ↳ LLM assistant message (each assistant message_end)\n * ↳ TOOL tool call (tool_execution_start → tool_execution_end)\n *\n * The AGENT span is opened as the active span so the LLM/TOOL children nest under\n * it (and under any user @span / trace() block active when prompt() is called).\n */\n\nimport {\n trace,\n context as otelContext,\n SpanStatusCode,\n type Span,\n type Context,\n} from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.pi-agent';\nconst PATCH_FLAG = '_neatlogs_patched';\n\n// Minimal structural types for the Pi Agent event surface (we duck-type — no\n// hard dependency on the pi-agent-core package).\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n}\ninterface PiToolCall {\n type: 'toolCall';\n id?: string;\n name?: string;\n arguments?: Record<string, unknown>;\n}\ninterface PiAssistantMessage {\n role: 'assistant';\n content?: Array<{ type: string; text?: string; name?: string; arguments?: unknown }>;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n stopReason?: string;\n}\ninterface PiAgentEvent {\n type: string;\n message?: any;\n messages?: any[];\n toolCallId?: string;\n toolName?: string;\n args?: unknown;\n result?: unknown;\n isError?: boolean;\n}\n\ninterface PerAgentState {\n agentSpan?: Span;\n agentCtx?: Context;\n toolSpans: Map<string, Span>;\n /** Running conversation (system/user/tool turns) to use as LLM-span input.\n * Pi Agent's assistant message_end carries only the response, not the prompt. */\n inputMessages: Array<{ role: string; content: string }>;\n}\n\n/**\n * Subscribe neatlogs tracing to a Pi Agent instance. Returns the same agent\n * (marked so re-subscribing is a no-op). Idempotent per agent.\n */\nexport function piAgentHooks<T extends object>(agent: T): T {\n if (!agent || (agent as any)[PATCH_FLAG]) return agent;\n const a = agent as any;\n if (typeof a.subscribe !== 'function') return agent; // not a Pi Agent — leave alone\n\n const state: PerAgentState = { toolSpans: new Map(), inputMessages: [] };\n const tracer = trace.getTracer(TRACER_NAME);\n\n a.subscribe((event: PiAgentEvent) => {\n try {\n handleEvent(tracer, state, event);\n } catch {\n // never let tracing break the agent run\n }\n });\n\n markPatched(a);\n return agent;\n}\n\nfunction handleEvent(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n event: PiAgentEvent,\n): void {\n switch (event.type) {\n case 'agent_start': {\n // Open the AGENT (run) span as the active span so children nest under it.\n const span = tracer.startSpan(\n 'pi_agent.run',\n { attributes: { 'neatlogs.span.kind': 'AGENT' } },\n otelContext.active(),\n );\n state.agentSpan = span;\n state.agentCtx = trace.setSpan(otelContext.active(), span);\n state.inputMessages = [];\n break;\n }\n\n case 'message_end': {\n const msg = event.message as any;\n if (!msg) return;\n if (msg.role === 'assistant') {\n // Assistant message = the LLM response. Emit an LLM span using the\n // accumulated conversation as input, then record the assistant turn too.\n emitLlmSpan(tracer, state, msg as PiAssistantMessage);\n const { text } = splitAssistantContent(msg.content);\n if (text) state.inputMessages.push({ role: 'assistant', content: text });\n } else {\n // user / toolResult turns — accumulate as input context for later LLM spans.\n const role = msg.role === 'toolResult' ? 'tool' : String(msg.role || 'user');\n const content = messageText(msg);\n if (content) state.inputMessages.push({ role, content });\n }\n break;\n }\n\n case 'tool_execution_start': {\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.tool.${event.toolName ?? 'tool'}`,\n {\n attributes: {\n 'neatlogs.span.kind': 'TOOL',\n ...(event.toolName ? { 'neatlogs.tool.name': String(event.toolName) } : {}),\n ...(event.args !== undefined\n ? { 'input.value': safeStringify(event.args) }\n : {}),\n },\n },\n parent,\n );\n if (event.toolCallId) state.toolSpans.set(event.toolCallId, span);\n break;\n }\n\n case 'tool_execution_end': {\n const span = event.toolCallId ? state.toolSpans.get(event.toolCallId) : undefined;\n if (!span) return;\n if (event.result !== undefined) {\n span.setAttribute('output.value', safeStringify(event.result));\n }\n if (event.isError) {\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.setAttribute('neatlogs.tool.is_error', true);\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n span.end();\n if (event.toolCallId) state.toolSpans.delete(event.toolCallId);\n break;\n }\n\n case 'agent_end': {\n // Close any tool spans that never received an end event, then the agent span.\n for (const ts of state.toolSpans.values()) {\n try {\n ts.end();\n } catch {\n /* ignore */\n }\n }\n state.toolSpans.clear();\n if (state.agentSpan) {\n // Agent input = the first user message of the run; output = final answer.\n const firstUser = state.inputMessages.find((m) => m.role === 'user');\n if (firstUser) state.agentSpan.setAttribute('input.value', firstUser.content);\n const finalText = lastAssistantText(event.messages);\n if (finalText) state.agentSpan.setAttribute('output.value', finalText);\n state.agentSpan.setStatus({ code: SpanStatusCode.OK });\n state.agentSpan.end();\n state.agentSpan = undefined;\n state.agentCtx = undefined;\n }\n break;\n }\n\n default:\n break;\n }\n}\n\nfunction emitLlmSpan(\n tracer: ReturnType<typeof trace.getTracer>,\n state: PerAgentState,\n msg: PiAssistantMessage,\n): void {\n const attrs: Record<string, any> = { 'neatlogs.span.kind': 'LLM' };\n if (msg.model) attrs['neatlogs.llm.model_name'] = String(msg.model);\n if (msg.provider) attrs['neatlogs.llm.provider'] = String(msg.provider);\n if (msg.stopReason) attrs['neatlogs.llm.stop_reason'] = String(msg.stopReason);\n\n // Input = the conversation accumulated up to this assistant turn (system +\n // user + prior assistant/tool messages). Pi Agent's message_end doesn't carry\n // the prompt, so we reconstruct it from the running inputMessages list.\n const inMsgs = state.inputMessages;\n if (inMsgs.length) {\n inMsgs.forEach((m, i) => {\n attrs[`neatlogs.llm.input_messages.${i}.role`] = m.role;\n attrs[`neatlogs.llm.input_messages.${i}.content`] = m.content;\n });\n attrs['neatlogs.llm.input'] = safeStringify({ messages: inMsgs });\n attrs['input.value'] = safeStringify({ messages: inMsgs });\n }\n\n const { text, toolCalls } = splitAssistantContent(msg.content);\n // Output: text if present, else a readable tool-call summary so the span isn't blank.\n const outText = text || toolCalls.map((tc) => `${tc.name}(${safeStringify(tc.arguments)})`).join('\\n');\n if (outText || toolCalls.length) {\n attrs['neatlogs.llm.output_messages.0.role'] = 'assistant';\n attrs['neatlogs.llm.output_messages.0.content'] = (outText || '');\n const outBlob: Record<string, unknown> = { role: 'assistant', content: outText || '' };\n if (toolCalls.length) {\n outBlob.tool_calls = toolCalls.map((tc) => ({ name: tc.name, arguments: tc.arguments }));\n toolCalls.forEach((tc, j) => {\n if (tc.name) attrs[`neatlogs.llm.tool_calls.${j}.name`] = tc.name;\n if (tc.arguments !== undefined)\n attrs[`neatlogs.llm.tool_calls.${j}.arguments`] = safeStringify(tc.arguments);\n if (tc.id) attrs[`neatlogs.llm.tool_calls.${j}.id`] = String(tc.id);\n });\n }\n attrs['neatlogs.llm.output'] = safeStringify(outBlob);\n attrs['output.value'] = (outText || '');\n }\n\n const usage = msg.usage;\n if (usage) {\n if (usage.input != null) attrs['neatlogs.llm.token_count.prompt'] = usage.input;\n if (usage.output != null) attrs['neatlogs.llm.token_count.completion'] = usage.output;\n const total = usage.totalTokens ?? ((usage.input ?? 0) + (usage.output ?? 0));\n if (total) attrs['neatlogs.llm.token_count.total'] = total;\n if (usage.cacheRead) attrs['neatlogs.llm.token_count.cache_read'] = usage.cacheRead;\n if (usage.cacheWrite) attrs['neatlogs.llm.token_count.cache_write'] = usage.cacheWrite;\n }\n\n const parent = state.agentCtx ?? otelContext.active();\n const span = tracer.startSpan(\n `pi_agent.llm.${msg.model || 'model'}`,\n { attributes: attrs },\n parent,\n );\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n// ---------------------------------------------------------------------------\n// Content helpers\n// ---------------------------------------------------------------------------\n\nfunction splitAssistantContent(\n content: PiAssistantMessage['content'],\n): { text: string; toolCalls: PiToolCall[] } {\n const texts: string[] = [];\n const toolCalls: PiToolCall[] = [];\n if (Array.isArray(content)) {\n for (const block of content) {\n if (!block || typeof block !== 'object') continue;\n if (block.type === 'text' && typeof block.text === 'string') texts.push(block.text);\n else if (block.type === 'toolCall')\n toolCalls.push(block as unknown as PiToolCall);\n // thinking blocks intentionally omitted from the main output text\n }\n } else if (typeof content === 'string') {\n texts.push(content);\n }\n return { text: texts.join(''), toolCalls };\n}\n\n/** Flatten any message's content (string or block array) to readable text. */\nfunction messageText(msg: any): string {\n if (!msg) return '';\n const c = msg.content;\n if (typeof c === 'string') return c;\n if (!Array.isArray(c)) return '';\n const parts: string[] = [];\n for (const block of c) {\n if (typeof block === 'string') parts.push(block);\n else if (block && typeof block === 'object') {\n if (typeof block.text === 'string') parts.push(block.text);\n else if (block.type === 'toolCall') parts.push(`${block.name ?? 'tool'}(${safeStringify(block.arguments)})`);\n }\n }\n return parts.join('');\n}\n\nfunction lastAssistantText(messages: any[] | undefined): string {\n if (!Array.isArray(messages)) return '';\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i];\n if (m && m.role === 'assistant') {\n const { text } = splitAssistantContent(m.content);\n if (text) return text;\n }\n }\n return '';\n}\n\nfunction markPatched(e: any): void {\n try {\n Object.defineProperty(e, PATCH_FLAG, { value: true, enumerable: false, configurable: true });\n } catch {\n e[PATCH_FLAG] = true;\n }\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n"],"mappings":"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","names":[]}