@agent-native/core 0.133.3 → 0.134.0

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 (169) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +56 -0
  3. package/corpus/core/docs/content/observability.mdx +41 -1
  4. package/corpus/core/docs/content/tracking.mdx +25 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/run-manager.ts +7 -0
  7. package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
  8. package/corpus/core/src/client/analytics.ts +105 -1
  9. package/corpus/core/src/deploy/build.ts +38 -1
  10. package/corpus/core/src/observability/posthog-ai.ts +294 -0
  11. package/corpus/core/src/observability/routes.ts +29 -6
  12. package/corpus/core/src/observability/traces.ts +287 -12
  13. package/corpus/core/src/observability/types.ts +11 -0
  14. package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
  15. package/corpus/core/src/server/agent-run-context.ts +21 -0
  16. package/corpus/core/src/server/capture-error.ts +8 -0
  17. package/corpus/core/src/server/core-routes-plugin.ts +75 -4
  18. package/corpus/core/src/server/deploy-environment.ts +57 -0
  19. package/corpus/core/src/server/error-noise-filter.ts +268 -0
  20. package/corpus/core/src/server/posthog-config.ts +75 -0
  21. package/corpus/core/src/server/request-context.ts +6 -0
  22. package/corpus/core/src/server/sentry-config.ts +4 -8
  23. package/corpus/core/src/server/sentry-plugin.ts +13 -29
  24. package/corpus/core/src/server/sentry.ts +13 -156
  25. package/corpus/core/src/server/ssr-handler.ts +6 -1
  26. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  27. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  29. package/corpus/core/src/tracking/error-capture.ts +44 -109
  30. package/corpus/core/src/tracking/index.ts +11 -0
  31. package/corpus/core/src/tracking/posthog-exception.ts +305 -0
  32. package/corpus/core/src/tracking/providers.ts +91 -14
  33. package/corpus/core/src/tracking/redaction.ts +105 -0
  34. package/dist/agent/run-manager.d.ts.map +1 -1
  35. package/dist/agent/run-manager.js +7 -0
  36. package/dist/agent/run-manager.js.map +1 -1
  37. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  38. package/dist/client/agent-chat-adapter.js +12 -0
  39. package/dist/client/agent-chat-adapter.js.map +1 -1
  40. package/dist/client/analytics.d.ts +8 -0
  41. package/dist/client/analytics.d.ts.map +1 -1
  42. package/dist/client/analytics.js +95 -1
  43. package/dist/client/analytics.js.map +1 -1
  44. package/dist/collab/routes.d.ts +1 -1
  45. package/dist/collab/struct-routes.d.ts +1 -1
  46. package/dist/deploy/build.d.ts.map +1 -1
  47. package/dist/deploy/build.js +38 -1
  48. package/dist/deploy/build.js.map +1 -1
  49. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  50. package/dist/notifications/routes.d.ts +2 -2
  51. package/dist/observability/posthog-ai.d.ts +126 -0
  52. package/dist/observability/posthog-ai.d.ts.map +1 -0
  53. package/dist/observability/posthog-ai.js +190 -0
  54. package/dist/observability/posthog-ai.js.map +1 -0
  55. package/dist/observability/routes.d.ts +3 -3
  56. package/dist/observability/routes.d.ts.map +1 -1
  57. package/dist/observability/routes.js +26 -6
  58. package/dist/observability/routes.js.map +1 -1
  59. package/dist/observability/traces.d.ts +9 -0
  60. package/dist/observability/traces.d.ts.map +1 -1
  61. package/dist/observability/traces.js +219 -10
  62. package/dist/observability/traces.js.map +1 -1
  63. package/dist/observability/types.d.ts +10 -0
  64. package/dist/observability/types.d.ts.map +1 -1
  65. package/dist/observability/types.js +1 -0
  66. package/dist/observability/types.js.map +1 -1
  67. package/dist/progress/routes.d.ts +1 -1
  68. package/dist/resources/handlers.d.ts +1 -1
  69. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  70. package/dist/secrets/register-framework-secrets.js +14 -0
  71. package/dist/secrets/register-framework-secrets.js.map +1 -1
  72. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  73. package/dist/server/agent-run-context.d.ts +8 -0
  74. package/dist/server/agent-run-context.d.ts.map +1 -1
  75. package/dist/server/agent-run-context.js +18 -0
  76. package/dist/server/agent-run-context.js.map +1 -1
  77. package/dist/server/capture-error.d.ts +8 -0
  78. package/dist/server/capture-error.d.ts.map +1 -1
  79. package/dist/server/capture-error.js.map +1 -1
  80. package/dist/server/core-routes-plugin.d.ts +0 -23
  81. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  82. package/dist/server/core-routes-plugin.js +71 -7
  83. package/dist/server/core-routes-plugin.js.map +1 -1
  84. package/dist/server/deploy-environment.d.ts +17 -0
  85. package/dist/server/deploy-environment.d.ts.map +1 -0
  86. package/dist/server/deploy-environment.js +47 -0
  87. package/dist/server/deploy-environment.js.map +1 -0
  88. package/dist/server/error-noise-filter.d.ts +80 -0
  89. package/dist/server/error-noise-filter.d.ts.map +1 -0
  90. package/dist/server/error-noise-filter.js +173 -0
  91. package/dist/server/error-noise-filter.js.map +1 -0
  92. package/dist/server/posthog-config.d.ts +28 -0
  93. package/dist/server/posthog-config.d.ts.map +1 -0
  94. package/dist/server/posthog-config.js +53 -0
  95. package/dist/server/posthog-config.js.map +1 -0
  96. package/dist/server/request-context.d.ts +6 -0
  97. package/dist/server/request-context.d.ts.map +1 -1
  98. package/dist/server/request-context.js.map +1 -1
  99. package/dist/server/sentry-config.d.ts +1 -0
  100. package/dist/server/sentry-config.d.ts.map +1 -1
  101. package/dist/server/sentry-config.js +3 -1
  102. package/dist/server/sentry-config.js.map +1 -1
  103. package/dist/server/sentry-plugin.d.ts.map +1 -1
  104. package/dist/server/sentry-plugin.js +12 -27
  105. package/dist/server/sentry-plugin.js.map +1 -1
  106. package/dist/server/sentry.d.ts.map +1 -1
  107. package/dist/server/sentry.js +8 -124
  108. package/dist/server/sentry.js.map +1 -1
  109. package/dist/server/ssr-handler.d.ts.map +1 -1
  110. package/dist/server/ssr-handler.js +6 -1
  111. package/dist/server/ssr-handler.js.map +1 -1
  112. package/dist/server/transcribe-voice.d.ts +1 -1
  113. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  114. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  115. package/dist/shared/mcp-embed-headers.js +1 -1
  116. package/dist/shared/mcp-embed-headers.js.map +1 -1
  117. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  118. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  119. package/dist/tracking/error-capture.d.ts +7 -0
  120. package/dist/tracking/error-capture.d.ts.map +1 -1
  121. package/dist/tracking/error-capture.js +10 -73
  122. package/dist/tracking/error-capture.js.map +1 -1
  123. package/dist/tracking/index.d.ts +1 -0
  124. package/dist/tracking/index.d.ts.map +1 -1
  125. package/dist/tracking/index.js +1 -0
  126. package/dist/tracking/index.js.map +1 -1
  127. package/dist/tracking/posthog-exception.d.ts +103 -0
  128. package/dist/tracking/posthog-exception.d.ts.map +1 -0
  129. package/dist/tracking/posthog-exception.js +181 -0
  130. package/dist/tracking/posthog-exception.js.map +1 -0
  131. package/dist/tracking/providers.d.ts +14 -0
  132. package/dist/tracking/providers.d.ts.map +1 -1
  133. package/dist/tracking/providers.js +63 -11
  134. package/dist/tracking/providers.js.map +1 -1
  135. package/dist/tracking/redaction.d.ts +25 -0
  136. package/dist/tracking/redaction.d.ts.map +1 -0
  137. package/dist/tracking/redaction.js +85 -0
  138. package/dist/tracking/redaction.js.map +1 -0
  139. package/docs/content/observability.mdx +41 -1
  140. package/docs/content/tracking.mdx +25 -0
  141. package/package.json +1 -1
  142. package/src/agent/run-manager.ts +7 -0
  143. package/src/client/agent-chat-adapter.ts +10 -0
  144. package/src/client/analytics.ts +105 -1
  145. package/src/deploy/build.ts +38 -1
  146. package/src/observability/posthog-ai.ts +294 -0
  147. package/src/observability/routes.ts +29 -6
  148. package/src/observability/traces.ts +287 -12
  149. package/src/observability/types.ts +11 -0
  150. package/src/secrets/register-framework-secrets.ts +16 -0
  151. package/src/server/agent-run-context.ts +21 -0
  152. package/src/server/capture-error.ts +8 -0
  153. package/src/server/core-routes-plugin.ts +75 -4
  154. package/src/server/deploy-environment.ts +57 -0
  155. package/src/server/error-noise-filter.ts +268 -0
  156. package/src/server/posthog-config.ts +75 -0
  157. package/src/server/request-context.ts +6 -0
  158. package/src/server/sentry-config.ts +4 -8
  159. package/src/server/sentry-plugin.ts +13 -29
  160. package/src/server/sentry.ts +13 -156
  161. package/src/server/ssr-handler.ts +6 -1
  162. package/src/shared/mcp-embed-headers.ts +1 -1
  163. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  164. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  165. package/src/tracking/error-capture.ts +44 -109
  166. package/src/tracking/index.ts +11 -0
  167. package/src/tracking/posthog-exception.ts +305 -0
  168. package/src/tracking/providers.ts +91 -14
  169. package/src/tracking/redaction.ts +105 -0
@@ -59,6 +59,15 @@ export declare function instrumentAgentLoop(opts: {
59
59
  };
60
60
  /** Raw user-authored message before prompt/context enrichment. */
61
61
  sentimentInput?: string;
62
+ /**
63
+ * Browser session id of the request that started this run, when it came from
64
+ * a page. Emitted as PostHog's `$session_id` so agent traces join to session
65
+ * replay — distinct from `$ai_session_id`, which is the thread.
66
+ *
67
+ * Defaults to the in-flight request context, which the agent-chat route
68
+ * populates from the `X-Agent-Native-Session-Id` header.
69
+ */
70
+ browserSessionId?: string;
62
71
  classifyError?: (error: unknown) => {
63
72
  status?: "success" | "error";
64
73
  errorMessage?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"traces.d.ts","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"traces.d.ts","sourceRoot":"","sources":["../../src/observability/traces.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { getRequestContext } from "../server/request-context.js";
2
+ import { MAX_AI_CONTENT_BYTES, MAX_AI_SPANS_PER_RUN, boundAiContent, emitAiSpanEvent, emitAiTraceEvent, toAiErrorDetail, } from "./posthog-ai.js";
1
3
  import { endAgentSpan, startAgentSpan } from "./tracing.js";
2
4
  import { trackingIdentityProperties } from "./tracking-identity.js";
3
5
  import { DEFAULT_OBSERVABILITY_CONFIG } from "./types.js";
@@ -47,6 +49,13 @@ function emitLlmGenerationTrackingEvent(args) {
47
49
  ? args.inputTokens + args.outputTokens
48
50
  : undefined;
49
51
  const error = args.errorMessage ?? undefined;
52
+ const terminalCode = args.terminalOutcome?.state === "failed" ||
53
+ args.terminalOutcome?.state === "input_required"
54
+ ? args.terminalOutcome.code
55
+ : undefined;
56
+ const terminalRetryable = args.terminalOutcome?.state === "failed"
57
+ ? args.terminalOutcome.retryable
58
+ : undefined;
50
59
  const properties = {
51
60
  ...trackingIdentityProperties(),
52
61
  source: "agent_observability",
@@ -73,13 +82,8 @@ function emitLlmGenerationTrackingEvent(args) {
73
82
  tools: args.tools,
74
83
  tools_truncated: args.toolsTruncated,
75
84
  terminal_state: args.terminalOutcome?.state,
76
- terminal_code: args.terminalOutcome?.state === "failed" ||
77
- args.terminalOutcome?.state === "input_required"
78
- ? args.terminalOutcome.code
79
- : undefined,
80
- terminal_retryable: args.terminalOutcome?.state === "failed"
81
- ? args.terminalOutcome.retryable
82
- : undefined,
85
+ terminal_code: terminalCode,
86
+ terminal_retryable: terminalRetryable,
83
87
  delegated: args.delegation ? true : undefined,
84
88
  delegation_protocol: args.delegation?.protocol,
85
89
  caller_app: args.delegation?.callerApp,
@@ -92,19 +96,35 @@ function emitLlmGenerationTrackingEvent(args) {
92
96
  $ai_trace_id: args.runId,
93
97
  $ai_session_id: args.threadId ?? undefined,
94
98
  $ai_span_id: args.llmSpanId,
95
- $ai_span_name: "agent_run",
96
- $ai_parent_id: args.parentSpanId,
99
+ $ai_span_name: args.model,
100
+ // Parent is the run's trace, not the internal `agent_run` span id — the
101
+ // latter is never emitted to PostHog, so pointing at it orphaned the
102
+ // generation and PostHog rendered a placeholder trace around it.
103
+ $ai_parent_id: args.runId,
97
104
  $ai_model: args.model,
98
105
  $ai_provider: provider,
99
106
  $ai_input_tokens: args.inputTokens,
100
107
  $ai_output_tokens: args.outputTokens,
101
108
  $ai_latency: Math.round((args.durationMs / 1000) * 1000) / 1000,
102
109
  $ai_is_error: args.status === "error",
103
- $ai_error: error,
110
+ $ai_error: args.status === "error"
111
+ ? toAiErrorDetail(error, {
112
+ state: args.terminalOutcome?.state,
113
+ code: terminalCode,
114
+ retryable: terminalRetryable,
115
+ })
116
+ : undefined,
104
117
  $ai_cache_read_input_tokens: args.cacheReadTokens,
105
118
  $ai_cache_creation_input_tokens: args.cacheWriteTokens,
106
119
  $ai_request_count: 1,
107
120
  $ai_total_cost_usd: costUsd,
121
+ $ai_input: args.aiInput,
122
+ $ai_output_choices: args.aiOutputChoices,
123
+ $ai_tools: args.aiTools,
124
+ $ai_input_truncated: args.aiInputTruncated || undefined,
125
+ $ai_output_truncated: args.aiOutputTruncated || undefined,
126
+ $ai_time_to_first_token: args.firstTokenMs,
127
+ $session_id: args.browserSessionId,
108
128
  };
109
129
  if (args.experimentAssignments?.length) {
110
130
  properties.experiment_ids = args.experimentAssignments
@@ -137,6 +157,75 @@ function emitLlmGenerationTrackingEvent(args) {
137
157
  // Tracking must never affect the agent run or trace persistence.
138
158
  }
139
159
  }
160
+ /**
161
+ * Build the PostHog content fields for a run's `$ai_generation`.
162
+ *
163
+ * This is one generation per run carrying the run's whole message list, not one
164
+ * per model round-trip: the engine layer reports aggregate usage only
165
+ * (`onUsage`) and exposes no per-step hook. Consequence to know when reading a
166
+ * trace — per-round-trip latency and intermediate assistant turns are not
167
+ * visible; a multi-step run collapses into a single generation node.
168
+ *
169
+ * `$ai_output_choices` is emitted whenever tool calls happened even with
170
+ * `capturePrompts` off, because PostHog derives `$ai_tools_called` /
171
+ * `$ai_tool_call_count` from tool-call blocks inside it and nothing else. The
172
+ * assistant's text content stays gated; only the structural call list ships.
173
+ */
174
+ function buildGenerationContent(args) {
175
+ const { config } = args;
176
+ const input = config.capturePrompts
177
+ ? boundAiContent(redactSensitiveFields(args.messages))
178
+ : undefined;
179
+ const toolCalls = args.toolSpans
180
+ .slice(0, MAX_TRACKED_GENERATION_TOOL_CALLS)
181
+ .map((span) => ({
182
+ type: "function",
183
+ id: span.id,
184
+ function: {
185
+ name: span.name,
186
+ // Already redacted at span construction, and only present when
187
+ // `captureToolArgs` is on.
188
+ ...(span.metadata?.input !== undefined
189
+ ? { arguments: span.metadata.input }
190
+ : {}),
191
+ },
192
+ }));
193
+ const hasChoice = config.capturePrompts || toolCalls.length > 0;
194
+ const output = hasChoice
195
+ ? boundAiContent([
196
+ {
197
+ role: "assistant",
198
+ ...(config.capturePrompts
199
+ ? { content: redactToolErrorMessage(args.assistantText) }
200
+ : {}),
201
+ ...(toolCalls.length ? { tool_calls: toolCalls } : {}),
202
+ },
203
+ ])
204
+ : undefined;
205
+ const toolList = Array.isArray(args.tools)
206
+ ? args.tools
207
+ .filter((tool) => !!tool && typeof tool.name === "string")
208
+ .map((tool) => ({
209
+ type: "function",
210
+ function: {
211
+ name: tool.name,
212
+ ...(typeof tool.description === "string"
213
+ ? { description: tool.description }
214
+ : {}),
215
+ },
216
+ }))
217
+ : [];
218
+ return {
219
+ aiInput: input?.value,
220
+ aiOutputChoices: output?.value,
221
+ // Tool definitions are app configuration rather than user content, so they
222
+ // are not gated — without them a trace shows calls to tools nobody can
223
+ // identify. Schemas are excluded to keep the event small.
224
+ aiTools: toolList.length ? toolList : undefined,
225
+ aiInputTruncated: input?.truncated,
226
+ aiOutputTruncated: output?.truncated,
227
+ };
228
+ }
140
229
  /** Keys whose values are stripped from persisted tool inputs when
141
230
  * `captureToolArgs` is enabled. Matched case-insensitively and tolerant
142
231
  * of `_` / `-` separators. M14 in the MCP/A2A audit: tool calls
@@ -198,6 +287,9 @@ export async function instrumentAgentLoop(opts) {
198
287
  }))
199
288
  .catch(() => null)
200
289
  : Promise.resolve(null);
290
+ // Falls back to the in-flight request so callers deep in the agent stack
291
+ // don't have to thread it down by hand.
292
+ const browserSessionId = opts.browserSessionId ?? getRequestContext()?.browserSessionId;
201
293
  // Optional OpenTelemetry root span for this run. No-ops unless a host has
202
294
  // installed `@opentelemetry/api` and registered a provider. The promise is
203
295
  // resolved before the loop runs so child tool/model spans can parent under
@@ -225,6 +317,10 @@ export async function instrumentAgentLoop(opts) {
225
317
  const toolNameToCounters = new Map();
226
318
  const toolCallIdToCounter = new Map();
227
319
  const generationToolCalls = new Map();
320
+ // Assistant text, accumulated only when prompt capture is on so a disabled
321
+ // config never holds message content in memory in the first place.
322
+ const assistantTextParts = [];
323
+ let assistantTextLength = 0;
228
324
  let toolCallCount = 0;
229
325
  let successfulTools = 0;
230
326
  let failedTools = 0;
@@ -266,6 +362,12 @@ export async function instrumentAgentLoop(opts) {
266
362
  };
267
363
  const instrumentedSend = (event) => {
268
364
  try {
365
+ if (config.capturePrompts &&
366
+ event.type === "text" &&
367
+ assistantTextLength < MAX_AI_CONTENT_BYTES) {
368
+ assistantTextParts.push(event.text);
369
+ assistantTextLength += event.text.length;
370
+ }
269
371
  // Some guardrails intentionally stop the loop by emitting a terminal
270
372
  // event and returning usage instead of throwing. Preserve that terminal
271
373
  // state in telemetry so a tripwire/loop-limit/provider error cannot be
@@ -552,6 +654,13 @@ export async function instrumentAgentLoop(opts) {
552
654
  ? Math.max(0, usage.firstEngineEventAtMs - runStart)
553
655
  : undefined;
554
656
  const llmSpanId = spanId();
657
+ const generationContent = buildGenerationContent({
658
+ config,
659
+ messages: loopOpts.messages,
660
+ tools: loopOpts.tools,
661
+ assistantText: assistantTextParts.join(""),
662
+ toolSpans: spans.filter((s) => s.spanType === "tool_call"),
663
+ });
555
664
  const llmSpan = {
556
665
  id: llmSpanId,
557
666
  runId,
@@ -607,6 +716,8 @@ export async function instrumentAgentLoop(opts) {
607
716
  createdAt: runStart,
608
717
  experimentAssignments: opts.experimentAssignments,
609
718
  modelSelectionSource: opts.modelSelectionSource,
719
+ browserSessionId,
720
+ ...generationContent,
610
721
  });
611
722
  }
612
723
  const parentSpan = {
@@ -629,6 +740,104 @@ export async function instrumentAgentLoop(opts) {
629
740
  createdAt: runStart,
630
741
  };
631
742
  spans.push(parentSpan);
743
+ // PostHog LLM analytics: the run is a `$ai_trace`, each tool call an
744
+ // `$ai_span` under it. Emitted from the collected spans rather than from a
745
+ // second instrumentation pass, so the tree PostHog shows and the tree we
746
+ // persist cannot drift apart.
747
+ try {
748
+ const aiError = runStatus === "error"
749
+ ? toAiErrorDetail(errorMessage, {
750
+ state: terminalOutcome?.state,
751
+ code: terminalOutcome?.state === "failed" ||
752
+ terminalOutcome?.state === "input_required"
753
+ ? terminalOutcome.code
754
+ : undefined,
755
+ retryable: terminalOutcome?.state === "failed"
756
+ ? terminalOutcome.retryable
757
+ : undefined,
758
+ })
759
+ : undefined;
760
+ const provider = llmProviderFromEngine(typeof loopOpts.engine?.name === "string"
761
+ ? loopOpts.engine.name
762
+ : undefined, usage?.model ?? loopOpts.model);
763
+ const toolSpans = config.captureLlmSpans
764
+ ? spans.filter((s) => s.spanType === "tool_call")
765
+ : [];
766
+ const emittedToolSpans = toolSpans.slice(0, MAX_AI_SPANS_PER_RUN);
767
+ const droppedToolSpans = toolSpans.length - emittedToolSpans.length;
768
+ emitAiTraceEvent({
769
+ runId,
770
+ threadId,
771
+ userId,
772
+ spanName: "agent_run",
773
+ model: usage?.model ?? loopOpts.model,
774
+ provider,
775
+ latencySeconds: Math.round(totalDurationMs) / 1000,
776
+ isError: runStatus === "error",
777
+ error: aiError,
778
+ inputTokens: usage?.usageReported ? usage.inputTokens : undefined,
779
+ outputTokens: usage?.usageReported ? usage.outputTokens : undefined,
780
+ costUsd: usage?.usageReported
781
+ ? costUsdFromCenticents(costCentsX100)
782
+ : undefined,
783
+ createdAt: runStart,
784
+ browserSessionId,
785
+ extraProperties: {
786
+ ...trackingIdentityProperties(),
787
+ source: "agent_observability",
788
+ run_id: runId,
789
+ thread_id: threadId,
790
+ // A truncated run must not read as a complete one.
791
+ ...(droppedToolSpans > 0
792
+ ? {
793
+ $ai_spans_dropped: droppedToolSpans,
794
+ $ai_spans_emitted: emittedToolSpans.length,
795
+ }
796
+ : {}),
797
+ },
798
+ });
799
+ for (const span of emittedToolSpans) {
800
+ // `span.errorMessage` is the raw tool result. It routinely contains
801
+ // upstream response bodies with Authorization headers and standalone
802
+ // API keys, so it gets the same redaction + bounding the generation
803
+ // event's `tools[].error_message` already applies, and the same
804
+ // `captureToolResults` gate — exporting it here otherwise reintroduced
805
+ // the leak that gate exists to prevent. `$ai_is_error` still marks the
806
+ // failure when the content is withheld.
807
+ const toolErrorMessage = span.status === "error" &&
808
+ span.errorMessage &&
809
+ config.captureToolResults
810
+ ? truncateToolErrorMessage(redactToolErrorMessage(span.errorMessage))
811
+ : undefined;
812
+ emitAiSpanEvent({
813
+ runId,
814
+ threadId,
815
+ userId,
816
+ spanId: span.id,
817
+ spanName: span.name,
818
+ latencySeconds: Math.round(span.durationMs) / 1000,
819
+ isError: span.status === "error",
820
+ error: toolErrorMessage
821
+ ? toAiErrorDetail(toolErrorMessage)
822
+ : undefined,
823
+ createdAt: span.createdAt,
824
+ browserSessionId,
825
+ // `metadata.input` is already redacted and only present when
826
+ // `captureToolArgs` is on; absent stays absent.
827
+ inputState: span.metadata?.input,
828
+ outputState: toolErrorMessage,
829
+ extraProperties: {
830
+ ...trackingIdentityProperties(),
831
+ source: "agent_observability",
832
+ span_type: "tool_call",
833
+ },
834
+ });
835
+ }
836
+ // coercion-ok: a throw here would skip trace persistence below
837
+ }
838
+ catch {
839
+ // LLM analytics must never affect the run or trace persistence.
840
+ }
632
841
  const summary = {
633
842
  runId,
634
843
  threadId,