autotel-genai 0.3.4 → 0.3.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"guard.cjs","names":["GEN_AI","GEN_AI_GUARD_EVENT"],"sources":["../src/guard.ts"],"sourcesContent":["/**\n * GenAI guard / budget runtime — an in-process kill-switch for agent runs.\n *\n * Most tracing tells you what an agent _did_, after the fact. A guard runs\n * _during_ the run: you feed it each step (an LLM call, a tool call, a\n * delegation), it accumulates cost / tokens / loop state, and evaluates a set\n * of rules. When a rule crosses its threshold the guard either records a\n * warning and lets the run continue, or stops the run — aborting an\n * {@link AbortSignal} and (by default) throwing a structured error so the agent\n * loop unwinds. No prompt, no retry, no runaway bill.\n *\n * This is the inline, OpenTelemetry-native analogue of a `watch` kill-switch:\n * cost ceilings, token ceilings, max tool calls, wall-clock timeouts, and\n * spin-loop / error-loop detection — all pure, deterministic logic with no LLM\n * in the loop.\n *\n * @example Budget ceiling that halts a runaway agent\n * ```typescript\n * import { createGenAiBudget } from 'autotel-genai/guard';\n * import { estimateLLMCost } from 'autotel-genai/cost';\n *\n * const budget = createGenAiBudget({ maxCostUsd: 5, warnAtUsd: 4 });\n *\n * for (const task of tasks) {\n * if (budget.stopped) break;\n * const res = await model.chat(task);\n * budget.record(\n * {\n * kind: 'llm',\n * usage: {\n * costUsd: estimateLLMCost('gpt-4o', {\n * inputTokens: res.usage.input,\n * outputTokens: res.usage.output,\n * }),\n * },\n * },\n * ctx, // optional TraceContext — records gen_ai.guard.* telemetry\n * ); // throws GEN_AI_GUARD_STOP once total cost > $5\n * }\n * ```\n *\n * @example Loop detection from a shorthand rule string\n * ```typescript\n * import { createGenAiGuard, parseGuardRules } from 'autotel-genai/guard';\n *\n * const guard = createGenAiGuard({\n * rules: parseGuardRules('budget:$2,loop:3/10,max-tools:50,timeout:5m'),\n * });\n *\n * guard.record({ kind: 'tool', name: 'search', signature: JSON.stringify(args) });\n * ```\n */\n\nimport { createStructuredError, type TraceContext } from 'autotel';\nimport { securityEvent } from 'autotel-audit';\nimport { GEN_AI, GEN_AI_GUARD_EVENT } from './semconv.js';\n\n/** What a rule does when it crosses its threshold. */\nexport type GuardAction = 'warn' | 'stop';\n\n/** Usage contribution for a single step. */\nexport interface GuardUsage {\n /**\n * Estimated USD cost for this step. Pair with\n * {@link import('./cost.js').estimateLLMCost} to compute it from token usage.\n */\n costUsd?: number;\n /** Input (prompt) tokens for this step. */\n inputTokens?: number;\n /** Output (completion) tokens for this step. */\n outputTokens?: number;\n}\n\n/** A single supervised step: an LLM call, a tool call, a delegation, … */\nexport interface GenAiGuardStep {\n /**\n * Step kind. `tool` increments the tool-call counter; any value is allowed.\n */\n kind?: 'llm' | 'tool' | 'agent' | 'workflow' | (string & {});\n /** Operation or tool name — used together with {@link signature} for loops. */\n name?: string;\n /**\n * Stable signature of the call inputs (e.g. `JSON.stringify(args)`). Two\n * steps with the same `name` + `signature` count as identical for spin-loop\n * detection. Hash or truncate it yourself if the raw args are large.\n */\n signature?: string;\n /** Whether this step failed. Drives error-loop detection. */\n error?: boolean;\n /** Cost / token contribution to the running totals. */\n usage?: GuardUsage;\n}\n\n/** Read-only snapshot of the accumulated session state. */\nexport interface GuardState {\n /** Epoch ms when the guard was created. */\n readonly startedAt: number;\n /** Epoch ms at evaluation time. */\n readonly now: number;\n /** Wall-clock ms since {@link startedAt}. */\n readonly elapsedMs: number;\n /** Accumulated estimated cost in USD. */\n readonly costUsd: number;\n /** Accumulated input tokens. */\n readonly inputTokens: number;\n /** Accumulated output tokens. */\n readonly outputTokens: number;\n /** Total steps recorded. */\n readonly stepCount: number;\n /** Steps with `kind === 'tool'`. */\n readonly toolCallCount: number;\n /** Steps with `error === true`. */\n readonly errorCount: number;\n /** Bounded recent history (newest last) for loop detection. */\n readonly history: ReadonlyArray<{ key: string | undefined; error: boolean }>;\n}\n\n/** A fired rule. */\nexport interface GuardViolation {\n /** Rule name, e.g. `cost-ceiling:$5`. */\n rule: string;\n /** Whether the run continued (`warn`) or was halted (`stop`). */\n action: GuardAction;\n /** Human-readable explanation. */\n message: string;\n /** The observed value that crossed the threshold. */\n observed: number;\n /** The configured threshold. */\n limit: number;\n}\n\n/** A guard rule: inspect the state after a step and optionally fire. */\nexport interface GenAiGuardRule {\n /** Stable identifier, surfaced on {@link GuardViolation.rule}. */\n name: string;\n /** Default {@link GuardAction} when the rule fires. Defaults to `stop`. */\n action?: GuardAction;\n /**\n * Evaluate against the post-step state and the step just recorded (absent on\n * a bare {@link GenAiGuard.check}). Return a partial violation to fire, or\n * `undefined` to pass.\n */\n evaluate: (\n state: GuardState,\n step: GenAiGuardStep | undefined,\n ) => { message: string; observed: number; limit: number } | undefined;\n}\n\n/** Minimal telemetry sink — the subset of {@link TraceContext} the guard uses. */\nexport type GuardSink = Pick<TraceContext, 'setAttributes' | 'track'>;\n\n/** What happens when a `stop` rule fires. */\nexport type GuardStopBehavior =\n /** Abort the signal and throw a structured error (default). */\n | 'throw'\n /** Abort the signal but do not throw — inspect `guard.stopped` yourself. */\n | 'abort'\n /** Record the violation only; neither abort nor throw. */\n | 'silent';\n\nexport interface GenAiGuardOptions {\n /** Rules to evaluate after each step. */\n rules: GenAiGuardRule[];\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /**\n * Max recent steps retained for loop detection. Defaults to 256; raise it if\n * a spin-loop rule uses a larger window.\n */\n historyLimit?: number;\n /** Clock injection point, for tests. Defaults to {@link Date.now}. */\n now?: () => number;\n}\n\n/** A live guard supervising an agent run. */\nexport interface GenAiGuard {\n /**\n * Record a step, accumulate its usage, and evaluate every rule. Returns the\n * violations that fired _this call_ (each rule fires at most once). When a\n * `stop` rule fires, aborts {@link signal} and — unless `onStop` is `abort` /\n * `silent` — throws a structured `GEN_AI_GUARD_STOP` error.\n */\n record(step: GenAiGuardStep, ctx?: GuardSink): GuardViolation[];\n /**\n * Re-evaluate the rules without recording a step. Useful for time-based\n * rules (e.g. a timeout) between LLM calls.\n */\n check(ctx?: GuardSink): GuardViolation[];\n /** Current accumulated state. */\n readonly state: GuardState;\n /** Fires once a `stop` rule has crossed its threshold. */\n readonly signal: AbortSignal;\n /** `true` after a `stop` rule has fired. */\n readonly stopped: boolean;\n /** Every violation fired so far, in order. */\n readonly violations: ReadonlyArray<GuardViolation>;\n}\n\n// --- Rule factories --------------------------------------------------------\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6;\n}\n\n/** Stop / warn when accumulated cost exceeds `limitUsd`. */\nexport function costCeiling(\n limitUsd: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `cost-ceiling:$${limitUsd}`,\n action,\n evaluate: (s) =>\n s.costUsd > limitUsd\n ? {\n message: `session cost $${round(s.costUsd)} exceeds $${limitUsd}`,\n observed: round(s.costUsd),\n limit: limitUsd,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when accumulated input + output tokens exceed `limit`. */\nexport function tokenCeiling(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `token-ceiling:${limit}`,\n action,\n evaluate: (s) => {\n const total = s.inputTokens + s.outputTokens;\n return total > limit\n ? {\n message: `session tokens ${total} exceed ${limit}`,\n observed: total,\n limit,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn when the number of tool calls exceeds `limit`. */\nexport function maxToolCalls(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-tool-calls:${limit}`,\n action,\n evaluate: (s) =>\n s.toolCallCount > limit\n ? {\n message: `tool calls ${s.toolCallCount} exceed ${limit}`,\n observed: s.toolCallCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when the total number of steps exceeds `limit`. */\nexport function maxSteps(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-steps:${limit}`,\n action,\n evaluate: (s) =>\n s.stepCount > limit\n ? {\n message: `steps ${s.stepCount} exceed ${limit}`,\n observed: s.stepCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when wall-clock time since start exceeds `limitMs`. */\nexport function maxDuration(\n limitMs: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-duration:${limitMs}ms`,\n action,\n evaluate: (s) =>\n s.elapsedMs > limitMs\n ? {\n message: `elapsed ${s.elapsedMs}ms exceeds ${limitMs}ms`,\n observed: s.elapsedMs,\n limit: limitMs,\n }\n : undefined,\n };\n}\n\n/**\n * Stop / warn on a spin loop: the same step (`name` + `signature`) repeated\n * `count` or more times within the last `window` steps. Steps without a `name`\n * are ignored (no identity to compare).\n */\nexport function spinLoop(\n options: { count: number; window: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count, window } = options;\n return {\n name: `spin-loop:${count}/${window}`,\n action,\n evaluate: (s, step) => {\n const key = stepKey(step);\n if (key === undefined) return undefined;\n const recent = s.history.slice(-window);\n let occurrences = 0;\n for (const h of recent) if (h.key === key) occurrences++;\n return occurrences >= count\n ? {\n message: `step \"${step?.name}\" repeated ${occurrences}× in last ${window}`,\n observed: occurrences,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn on `count` or more consecutive failing steps. */\nexport function errorLoop(\n options: { count: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count } = options;\n return {\n name: `error-loop:${count}`,\n action,\n evaluate: (s) => {\n let streak = 0;\n for (let i = s.history.length - 1; i >= 0; i--) {\n if (s.history[i].error) streak++;\n else break;\n }\n return streak >= count\n ? {\n message: `${streak} consecutive errors`,\n observed: streak,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/**\n * Approximate context-window sizes (in input tokens) for common models.\n * Matched exactly first, then by longest key prefix. Override via the `limit`\n * option on {@link contextBudget}.\n */\nexport const CONTEXT_LIMITS: Record<string, number> = {\n 'gpt-4o': 128_000,\n 'gpt-4.1': 1_047_576,\n 'gpt-4': 8192,\n 'o3-mini': 200_000,\n 'claude-opus-4': 200_000,\n 'claude-sonnet-4': 200_000,\n 'claude-3-5-sonnet': 200_000,\n 'claude-3-5-haiku': 200_000,\n 'claude-3': 200_000,\n 'gemini-1.5-pro': 2_097_152,\n 'gemini-1.5-flash': 1_048_576,\n 'gemini-2.0-flash': 1_048_576,\n};\n\nfunction resolveContextLimit(model: string): number | undefined {\n const exact = CONTEXT_LIMITS[model];\n if (exact) return exact;\n let best: number | undefined;\n let bestLength = 0;\n for (const key of Object.keys(CONTEXT_LIMITS)) {\n if (model.startsWith(key) && key.length > bestLength) {\n best = CONTEXT_LIMITS[key];\n bestLength = key.length;\n }\n }\n return best;\n}\n\n/**\n * Stop / warn when cumulative input tokens reach `threshold` (default 0.9) of\n * the model's context window. Provide an explicit `limit`, or a `model` to look\n * one up from {@link CONTEXT_LIMITS}.\n */\nexport function contextBudget(\n options: { model?: string; limit?: number; threshold?: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const threshold = options.threshold ?? 0.9;\n const limit =\n options.limit ?? (options.model ? resolveContextLimit(options.model) : undefined);\n return {\n name: `context-budget:${Math.round(threshold * 100)}%`,\n action,\n evaluate: (s) => {\n if (!limit) return undefined;\n const ratio = s.inputTokens / limit;\n return ratio >= threshold\n ? {\n message: `context ${Math.round(ratio * 100)}% full (${s.inputTokens}/${limit})`,\n observed: round(ratio),\n limit: threshold,\n }\n : undefined;\n },\n };\n}\n\nfunction stepKey(step: GenAiGuardStep | undefined): string | undefined {\n if (!step || step.name === undefined) return undefined;\n return `${step.name}\u0000${step.signature ?? ''}`;\n}\n\n// --- Shorthand rule parser -------------------------------------------------\n\nfunction parseScaledNumber(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*([kmb])?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid number: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const scale = { k: 1e3, m: 1e6, b: 1e9 }[m[2]?.toLowerCase() ?? ''] ?? 1;\n return value * scale;\n}\n\nfunction parseDurationMs(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h)?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid duration: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const unit = (m[2] ?? 's').toLowerCase();\n const scale = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[unit] ?? 1000;\n return value * scale;\n}\n\n/**\n * Parse a comma-separated shorthand rule string into {@link GenAiGuardRule}s.\n * All parsed rules default to the `stop` action — pass `defaultAction: 'warn'`\n * to warn instead, or build typed rules directly for per-rule actions.\n *\n * Supported tokens:\n * - `budget:$5` / `cost:$5` — {@link costCeiling}\n * - `tokens:100k` — {@link tokenCeiling} (`k`/`m`/`b` suffixes)\n * - `loop:3/10` — {@link spinLoop} (count / window)\n * - `errors:3` — {@link errorLoop}\n * - `max-tools:50` — {@link maxToolCalls}\n * - `max-steps:100` — {@link maxSteps}\n * - `timeout:30m` — {@link maxDuration} (`ms`/`s`/`m`/`h`, default seconds)\n * - `context:0.9` / `context:0.9@gpt-4o` — {@link contextBudget}\n */\nexport function parseGuardRules(\n shorthand: string,\n options?: { defaultAction?: GuardAction },\n): GenAiGuardRule[] {\n const action = options?.defaultAction ?? 'stop';\n const rules: GenAiGuardRule[] = [];\n for (const token of shorthand.split(',')) {\n const trimmed = token.trim();\n if (!trimmed) continue;\n const sep = trimmed.indexOf(':');\n if (sep === -1) throw new Error(`invalid guard rule: \"${trimmed}\"`);\n const key = trimmed.slice(0, sep).trim().toLowerCase();\n const value = trimmed.slice(sep + 1).trim();\n\n switch (key) {\n case 'budget':\n case 'cost': {\n rules.push(costCeiling(parseScaledNumber(value.replace(/^\\$/, '')), action));\n break;\n }\n case 'tokens': {\n rules.push(tokenCeiling(parseScaledNumber(value), action));\n break;\n }\n case 'loop': {\n const [count, window] = value.split('/');\n rules.push(\n spinLoop(\n { count: parseScaledNumber(count), window: parseScaledNumber(window) },\n action,\n ),\n );\n break;\n }\n case 'errors': {\n rules.push(errorLoop({ count: parseScaledNumber(value) }, action));\n break;\n }\n case 'max-tools': {\n rules.push(maxToolCalls(parseScaledNumber(value), action));\n break;\n }\n case 'max-steps': {\n rules.push(maxSteps(parseScaledNumber(value), action));\n break;\n }\n case 'timeout': {\n rules.push(maxDuration(parseDurationMs(value), action));\n break;\n }\n case 'context': {\n const [thr, model] = value.split('@');\n rules.push(\n contextBudget(\n { threshold: Number.parseFloat(thr), model: model?.trim() || undefined },\n action,\n ),\n );\n break;\n }\n default: {\n throw new Error(`unknown guard rule: \"${key}\"`);\n }\n }\n }\n return rules;\n}\n\n// --- Guard implementation --------------------------------------------------\n\nfunction recordTelemetry(\n ctx: GuardSink,\n state: GuardState,\n newViolations: GuardViolation[],\n stopped: boolean,\n): void {\n ctx.setAttributes({\n [GEN_AI.SESSION_COST_USD]: round(state.costUsd),\n [GEN_AI.SESSION_INPUT_TOKENS]: state.inputTokens,\n [GEN_AI.SESSION_OUTPUT_TOKENS]: state.outputTokens,\n [GEN_AI.SESSION_STEP_COUNT]: state.stepCount,\n [GEN_AI.SESSION_TOOL_CALL_COUNT]: state.toolCallCount,\n [GEN_AI.SESSION_ERROR_COUNT]: state.errorCount,\n });\n if (newViolations.length === 0) return;\n if (stopped) ctx.setAttributes({ [GEN_AI.GUARD_STOPPED]: true });\n for (const violation of newViolations) {\n ctx.track(\n violation.action === 'stop'\n ? GEN_AI_GUARD_EVENT.STOP\n : GEN_AI_GUARD_EVENT.WARNING,\n {\n [GEN_AI.GUARD_RULE]: violation.rule,\n [GEN_AI.GUARD_ACTION]: violation.action,\n [GEN_AI.GUARD_MESSAGE]: violation.message,\n [GEN_AI.GUARD_OBSERVED]: violation.observed,\n [GEN_AI.GUARD_LIMIT]: violation.limit,\n },\n );\n if (violation.action === 'stop') {\n securityEvent(\n {\n name: 'llm.guard.triggered',\n category: 'llm',\n outcome: 'blocked',\n severity: 'error',\n reason: violation.rule,\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n },\n { onMissingContext: 'warn' },\n );\n }\n }\n}\n\n/**\n * Create a guard that supervises an in-process agent run against `rules`.\n */\nexport function createGenAiGuard(options: GenAiGuardOptions): GenAiGuard {\n const { rules } = options;\n const onStop = options.onStop ?? 'throw';\n const historyLimit = options.historyLimit ?? 256;\n const now = options.now ?? Date.now;\n const startedAt = now();\n\n const controller = new AbortController();\n const history: { key: string | undefined; error: boolean }[] = [];\n const fired = new Set<string>();\n const violations: GuardViolation[] = [];\n\n let costUsd = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let stepCount = 0;\n let toolCallCount = 0;\n let errorCount = 0;\n let stopped = false;\n\n function snapshot(): GuardState {\n const at = now();\n return {\n startedAt,\n now: at,\n elapsedMs: at - startedAt,\n costUsd,\n inputTokens,\n outputTokens,\n stepCount,\n toolCallCount,\n errorCount,\n history,\n };\n }\n\n function evaluate(\n step: GenAiGuardStep | undefined,\n ctx: GuardSink | undefined,\n ): GuardViolation[] {\n const state = snapshot();\n const newViolations: GuardViolation[] = [];\n\n for (const rule of rules) {\n if (fired.has(rule.name)) continue;\n const hit = rule.evaluate(state, step);\n if (!hit) continue;\n fired.add(rule.name);\n const violation: GuardViolation = {\n rule: rule.name,\n action: rule.action ?? 'stop',\n message: hit.message,\n observed: hit.observed,\n limit: hit.limit,\n };\n violations.push(violation);\n newViolations.push(violation);\n }\n\n const stopViolation = newViolations.find((v) => v.action === 'stop');\n if (stopViolation && !stopped) {\n stopped = true;\n if (onStop !== 'silent') controller.abort(toStopError(stopViolation, state));\n }\n\n if (ctx) recordTelemetry(ctx, state, newViolations, stopped);\n\n if (stopViolation && onStop === 'throw') {\n throw toStopError(stopViolation, snapshot());\n }\n return newViolations;\n }\n\n return {\n record(step, ctx) {\n stepCount++;\n if (step.kind === 'tool') toolCallCount++;\n if (step.error) errorCount++;\n if (step.usage) {\n costUsd += step.usage.costUsd ?? 0;\n inputTokens += step.usage.inputTokens ?? 0;\n outputTokens += step.usage.outputTokens ?? 0;\n }\n history.push({ key: stepKey(step), error: step.error === true });\n if (history.length > historyLimit) history.shift();\n return evaluate(step, ctx);\n },\n check(ctx) {\n return evaluate(undefined, ctx);\n },\n get state() {\n return snapshot();\n },\n get signal() {\n return controller.signal;\n },\n get stopped() {\n return stopped;\n },\n get violations() {\n return violations;\n },\n };\n}\n\nfunction toStopError(violation: GuardViolation, state: GuardState): Error {\n return createStructuredError({\n name: 'GenAiGuardStop',\n code: 'GEN_AI_GUARD_STOP',\n status: 429,\n message: `Guard rule \"${violation.rule}\" halted the run: ${violation.message}`,\n why: 'A stop-action guard rule crossed its threshold during the agent run.',\n fix: 'Raise the limit, fix the loop, or handle GEN_AI_GUARD_STOP to wind the run down cleanly.',\n details: {\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n costUsd: round(state.costUsd),\n stepCount: state.stepCount,\n },\n });\n}\n\n// --- Budget preset ---------------------------------------------------------\n\nexport interface GenAiBudgetOptions {\n /** Hard cost ceiling in USD — a `stop` rule. */\n maxCostUsd?: number;\n /** Soft cost threshold in USD — a `warn` rule. */\n warnAtUsd?: number;\n /** Hard token ceiling (input + output) — a `stop` rule. */\n maxTokens?: number;\n /** Hard tool-call ceiling — a `stop` rule. */\n maxToolCalls?: number;\n /** Hard wall-clock ceiling in ms — a `stop` rule. */\n maxDurationMs?: number;\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /** Clock injection point, for tests. */\n now?: () => number;\n}\n\n/**\n * Convenience preset that wires the common cost / token / tool-call / duration\n * ceilings into a {@link createGenAiGuard}. Returns the same {@link GenAiGuard}.\n */\nexport function createGenAiBudget(options: GenAiBudgetOptions): GenAiGuard {\n const rules: GenAiGuardRule[] = [];\n if (options.warnAtUsd !== undefined) {\n rules.push(costCeiling(options.warnAtUsd, 'warn'));\n }\n if (options.maxCostUsd !== undefined) {\n rules.push(costCeiling(options.maxCostUsd, 'stop'));\n }\n if (options.maxTokens !== undefined) {\n rules.push(tokenCeiling(options.maxTokens, 'stop'));\n }\n if (options.maxToolCalls !== undefined) {\n rules.push(maxToolCalls(options.maxToolCalls, 'stop'));\n }\n if (options.maxDurationMs !== undefined) {\n rules.push(maxDuration(options.maxDurationMs, 'stop'));\n }\n return createGenAiGuard({\n rules,\n onStop: options.onStop,\n now: options.now,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"guard.cjs","names":["GEN_AI","GEN_AI_GUARD_EVENT"],"sources":["../src/guard.ts"],"sourcesContent":["/**\n * GenAI guard / budget runtime — an in-process kill-switch for agent runs.\n *\n * Most tracing tells you what an agent _did_, after the fact. A guard runs\n * _during_ the run: you feed it each step (an LLM call, a tool call, a\n * delegation), it accumulates cost / tokens / loop state, and evaluates a set\n * of rules. When a rule crosses its threshold the guard either records a\n * warning and lets the run continue, or stops the run — aborting an\n * {@link AbortSignal} and (by default) throwing a structured error so the agent\n * loop unwinds. No prompt, no retry, no runaway bill.\n *\n * This is the inline, OpenTelemetry-native analogue of a `watch` kill-switch:\n * cost ceilings, token ceilings, max tool calls, wall-clock timeouts, and\n * spin-loop / error-loop detection — all pure, deterministic logic with no LLM\n * in the loop.\n *\n * @example Budget ceiling that halts a runaway agent\n * ```typescript\n * import { createGenAiBudget } from 'autotel-genai/guard';\n * import { estimateLLMCost } from 'autotel-genai/cost';\n *\n * const budget = createGenAiBudget({ maxCostUsd: 5, warnAtUsd: 4 });\n *\n * for (const task of tasks) {\n * if (budget.stopped) break;\n * const res = await model.chat(task);\n * budget.record(\n * {\n * kind: 'llm',\n * usage: {\n * costUsd: estimateLLMCost('gpt-4o', {\n * inputTokens: res.usage.input,\n * outputTokens: res.usage.output,\n * }),\n * },\n * },\n * ctx, // optional TraceContext — records gen_ai.guard.* telemetry\n * ); // throws GEN_AI_GUARD_STOP once total cost > $5\n * }\n * ```\n *\n * @example Loop detection from a shorthand rule string\n * ```typescript\n * import { createGenAiGuard, parseGuardRules } from 'autotel-genai/guard';\n *\n * const guard = createGenAiGuard({\n * rules: parseGuardRules('budget:$2,loop:3/10,max-tools:50,timeout:5m'),\n * });\n *\n * guard.record({ kind: 'tool', name: 'search', signature: JSON.stringify(args) });\n * ```\n */\n\nimport { createStructuredError, type TraceContext } from 'autotel';\nimport { securityEvent } from 'autotel-audit';\nimport { GEN_AI, GEN_AI_GUARD_EVENT } from './semconv.js';\n\n/** What a rule does when it crosses its threshold. */\nexport type GuardAction = 'warn' | 'stop';\n\n/** Usage contribution for a single step. */\nexport interface GuardUsage {\n /**\n * Estimated USD cost for this step. Pair with\n * {@link import('./cost.js').estimateLLMCost} to compute it from token usage.\n */\n costUsd?: number;\n /** Input (prompt) tokens for this step. */\n inputTokens?: number;\n /** Output (completion) tokens for this step. */\n outputTokens?: number;\n}\n\n/** A single supervised step: an LLM call, a tool call, a delegation, … */\nexport interface GenAiGuardStep {\n /**\n * Step kind. `tool` increments the tool-call counter; any value is allowed.\n */\n kind?: 'llm' | 'tool' | 'agent' | 'workflow' | (string & {});\n /** Operation or tool name — used together with {@link signature} for loops. */\n name?: string;\n /**\n * Stable signature of the call inputs (e.g. `JSON.stringify(args)`). Two\n * steps with the same `name` + `signature` count as identical for spin-loop\n * detection. Hash or truncate it yourself if the raw args are large.\n */\n signature?: string;\n /** Whether this step failed. Drives error-loop detection. */\n error?: boolean;\n /** Cost / token contribution to the running totals. */\n usage?: GuardUsage;\n}\n\n/** Read-only snapshot of the accumulated session state. */\nexport interface GuardState {\n /** Epoch ms when the guard was created. */\n readonly startedAt: number;\n /** Epoch ms at evaluation time. */\n readonly now: number;\n /** Wall-clock ms since {@link startedAt}. */\n readonly elapsedMs: number;\n /** Accumulated estimated cost in USD. */\n readonly costUsd: number;\n /** Accumulated input tokens. */\n readonly inputTokens: number;\n /** Accumulated output tokens. */\n readonly outputTokens: number;\n /** Total steps recorded. */\n readonly stepCount: number;\n /** Steps with `kind === 'tool'`. */\n readonly toolCallCount: number;\n /** Steps with `error === true`. */\n readonly errorCount: number;\n /** Bounded recent history (newest last) for loop detection. */\n readonly history: ReadonlyArray<{ key: string | undefined; error: boolean }>;\n}\n\n/** A fired rule. */\nexport interface GuardViolation {\n /** Rule name, e.g. `cost-ceiling:$5`. */\n rule: string;\n /** Whether the run continued (`warn`) or was halted (`stop`). */\n action: GuardAction;\n /** Human-readable explanation. */\n message: string;\n /** The observed value that crossed the threshold. */\n observed: number;\n /** The configured threshold. */\n limit: number;\n}\n\n/** A guard rule: inspect the state after a step and optionally fire. */\nexport interface GenAiGuardRule {\n /** Stable identifier, surfaced on {@link GuardViolation.rule}. */\n name: string;\n /** Default {@link GuardAction} when the rule fires. Defaults to `stop`. */\n action?: GuardAction;\n /**\n * Evaluate against the post-step state and the step just recorded (absent on\n * a bare {@link GenAiGuard.check}). Return a partial violation to fire, or\n * `undefined` to pass.\n */\n evaluate: (\n state: GuardState,\n step: GenAiGuardStep | undefined,\n ) => { message: string; observed: number; limit: number } | undefined;\n}\n\n/** Minimal telemetry sink — the subset of {@link TraceContext} the guard uses. */\nexport type GuardSink = Pick<TraceContext, 'setAttributes' | 'track'>;\n\n/** What happens when a `stop` rule fires. */\nexport type GuardStopBehavior =\n /** Abort the signal and throw a structured error (default). */\n | 'throw'\n /** Abort the signal but do not throw — inspect `guard.stopped` yourself. */\n | 'abort'\n /** Record the violation only; neither abort nor throw. */\n | 'silent';\n\nexport interface GenAiGuardOptions {\n /** Rules to evaluate after each step. */\n rules: GenAiGuardRule[];\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /**\n * Max recent steps retained for loop detection. Defaults to 256; raise it if\n * a spin-loop rule uses a larger window.\n */\n historyLimit?: number;\n /** Clock injection point, for tests. Defaults to {@link Date.now}. */\n now?: () => number;\n}\n\n/** A live guard supervising an agent run. */\nexport interface GenAiGuard {\n /**\n * Record a step, accumulate its usage, and evaluate every rule. Returns the\n * violations that fired _this call_ (each rule fires at most once). When a\n * `stop` rule fires, aborts {@link signal} and — unless `onStop` is `abort` /\n * `silent` — throws a structured `GEN_AI_GUARD_STOP` error.\n */\n record(step: GenAiGuardStep, ctx?: GuardSink): GuardViolation[];\n /**\n * Re-evaluate the rules without recording a step. Useful for time-based\n * rules (e.g. a timeout) between LLM calls.\n */\n check(ctx?: GuardSink): GuardViolation[];\n /** Current accumulated state. */\n readonly state: GuardState;\n /** Fires once a `stop` rule has crossed its threshold. */\n readonly signal: AbortSignal;\n /** `true` after a `stop` rule has fired. */\n readonly stopped: boolean;\n /** Every violation fired so far, in order. */\n readonly violations: ReadonlyArray<GuardViolation>;\n}\n\n// --- Rule factories --------------------------------------------------------\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6;\n}\n\n/** Stop / warn when accumulated cost exceeds `limitUsd`. */\nexport function costCeiling(\n limitUsd: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `cost-ceiling:$${limitUsd}`,\n action,\n evaluate: (s) =>\n s.costUsd > limitUsd\n ? {\n message: `session cost $${round(s.costUsd)} exceeds $${limitUsd}`,\n observed: round(s.costUsd),\n limit: limitUsd,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when accumulated input + output tokens exceed `limit`. */\nexport function tokenCeiling(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `token-ceiling:${limit}`,\n action,\n evaluate: (s) => {\n const total = s.inputTokens + s.outputTokens;\n return total > limit\n ? {\n message: `session tokens ${total} exceed ${limit}`,\n observed: total,\n limit,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn when the number of tool calls exceeds `limit`. */\nexport function maxToolCalls(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-tool-calls:${limit}`,\n action,\n evaluate: (s) =>\n s.toolCallCount > limit\n ? {\n message: `tool calls ${s.toolCallCount} exceed ${limit}`,\n observed: s.toolCallCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when the total number of steps exceeds `limit`. */\nexport function maxSteps(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-steps:${limit}`,\n action,\n evaluate: (s) =>\n s.stepCount > limit\n ? {\n message: `steps ${s.stepCount} exceed ${limit}`,\n observed: s.stepCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when wall-clock time since start exceeds `limitMs`. */\nexport function maxDuration(\n limitMs: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-duration:${limitMs}ms`,\n action,\n evaluate: (s) =>\n s.elapsedMs > limitMs\n ? {\n message: `elapsed ${s.elapsedMs}ms exceeds ${limitMs}ms`,\n observed: s.elapsedMs,\n limit: limitMs,\n }\n : undefined,\n };\n}\n\n/**\n * Stop / warn on a spin loop: the same step (`name` + `signature`) repeated\n * `count` or more times within the last `window` steps. Steps without a `name`\n * are ignored (no identity to compare).\n */\nexport function spinLoop(\n options: { count: number; window: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count, window } = options;\n return {\n name: `spin-loop:${count}/${window}`,\n action,\n evaluate: (s, step) => {\n const key = stepKey(step);\n if (key === undefined) return undefined;\n const recent = s.history.slice(-window);\n let occurrences = 0;\n for (const h of recent) if (h.key === key) occurrences++;\n return occurrences >= count\n ? {\n message: `step \"${step?.name}\" repeated ${occurrences}× in last ${window}`,\n observed: occurrences,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn on `count` or more consecutive failing steps. */\nexport function errorLoop(\n options: { count: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count } = options;\n return {\n name: `error-loop:${count}`,\n action,\n evaluate: (s) => {\n let streak = 0;\n for (let i = s.history.length - 1; i >= 0; i--) {\n if (s.history[i].error) streak++;\n else break;\n }\n return streak >= count\n ? {\n message: `${streak} consecutive errors`,\n observed: streak,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/**\n * Approximate context-window sizes (in input tokens) for common models.\n * Matched exactly first, then by longest key prefix. Override via the `limit`\n * option on {@link contextBudget}.\n */\nexport const CONTEXT_LIMITS: Record<string, number> = {\n 'gpt-4o': 128_000,\n 'gpt-4.1': 1_047_576,\n 'gpt-4': 8192,\n 'o3-mini': 200_000,\n 'claude-opus-4': 200_000,\n 'claude-sonnet-4': 200_000,\n 'claude-3-5-sonnet': 200_000,\n 'claude-3-5-haiku': 200_000,\n 'claude-3': 200_000,\n 'gemini-1.5-pro': 2_097_152,\n 'gemini-1.5-flash': 1_048_576,\n 'gemini-2.0-flash': 1_048_576,\n};\n\nfunction resolveContextLimit(model: string): number | undefined {\n const exact = CONTEXT_LIMITS[model];\n if (exact) return exact;\n let best: number | undefined;\n let bestLength = 0;\n for (const key of Object.keys(CONTEXT_LIMITS)) {\n if (model.startsWith(key) && key.length > bestLength) {\n best = CONTEXT_LIMITS[key];\n bestLength = key.length;\n }\n }\n return best;\n}\n\n/**\n * Stop / warn when cumulative input tokens reach `threshold` (default 0.9) of\n * the model's context window. Provide an explicit `limit`, or a `model` to look\n * one up from {@link CONTEXT_LIMITS}.\n */\nexport function contextBudget(\n options: { model?: string; limit?: number; threshold?: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const threshold = options.threshold ?? 0.9;\n const limit =\n options.limit ?? (options.model ? resolveContextLimit(options.model) : undefined);\n return {\n name: `context-budget:${Math.round(threshold * 100)}%`,\n action,\n evaluate: (s) => {\n if (!limit) return undefined;\n const ratio = s.inputTokens / limit;\n return ratio >= threshold\n ? {\n message: `context ${Math.round(ratio * 100)}% full (${s.inputTokens}/${limit})`,\n observed: round(ratio),\n limit: threshold,\n }\n : undefined;\n },\n };\n}\n\nfunction stepKey(step: GenAiGuardStep | undefined): string | undefined {\n if (!step || step.name === undefined) return undefined;\n return `${step.name}\u0000${step.signature ?? ''}`;\n}\n\n// --- Shorthand rule parser -------------------------------------------------\n\nfunction parseScaledNumber(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*([kmb])?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid number: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const scale = { k: 1e3, m: 1e6, b: 1e9 }[m[2]?.toLowerCase() ?? ''] ?? 1;\n return value * scale;\n}\n\nfunction parseDurationMs(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h)?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid duration: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const unit = (m[2] ?? 's').toLowerCase();\n const scale = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[unit] ?? 1000;\n return value * scale;\n}\n\n/**\n * Parse a comma-separated shorthand rule string into {@link GenAiGuardRule}s.\n * All parsed rules default to the `stop` action — pass `defaultAction: 'warn'`\n * to warn instead, or build typed rules directly for per-rule actions.\n *\n * Supported tokens:\n * - `budget:$5` / `cost:$5` — {@link costCeiling}\n * - `tokens:100k` — {@link tokenCeiling} (`k`/`m`/`b` suffixes)\n * - `loop:3/10` — {@link spinLoop} (count / window)\n * - `errors:3` — {@link errorLoop}\n * - `max-tools:50` — {@link maxToolCalls}\n * - `max-steps:100` — {@link maxSteps}\n * - `timeout:30m` — {@link maxDuration} (`ms`/`s`/`m`/`h`, default seconds)\n * - `context:0.9` / `context:0.9@gpt-4o` — {@link contextBudget}\n */\nexport function parseGuardRules(\n shorthand: string,\n options?: { defaultAction?: GuardAction },\n): GenAiGuardRule[] {\n const action = options?.defaultAction ?? 'stop';\n const rules: GenAiGuardRule[] = [];\n for (const token of shorthand.split(',')) {\n const trimmed = token.trim();\n if (!trimmed) continue;\n const sep = trimmed.indexOf(':');\n if (sep === -1) throw new Error(`invalid guard rule: \"${trimmed}\"`);\n const key = trimmed.slice(0, sep).trim().toLowerCase();\n const value = trimmed.slice(sep + 1).trim();\n\n switch (key) {\n case 'budget':\n case 'cost': {\n rules.push(costCeiling(parseScaledNumber(value.replace(/^\\$/, '')), action));\n break;\n }\n case 'tokens': {\n rules.push(tokenCeiling(parseScaledNumber(value), action));\n break;\n }\n case 'loop': {\n const [count, window] = value.split('/');\n rules.push(\n spinLoop(\n { count: parseScaledNumber(count), window: parseScaledNumber(window) },\n action,\n ),\n );\n break;\n }\n case 'errors': {\n rules.push(errorLoop({ count: parseScaledNumber(value) }, action));\n break;\n }\n case 'max-tools': {\n rules.push(maxToolCalls(parseScaledNumber(value), action));\n break;\n }\n case 'max-steps': {\n rules.push(maxSteps(parseScaledNumber(value), action));\n break;\n }\n case 'timeout': {\n rules.push(maxDuration(parseDurationMs(value), action));\n break;\n }\n case 'context': {\n const [thr, model] = value.split('@');\n rules.push(\n contextBudget(\n { threshold: Number.parseFloat(thr), model: model?.trim() || undefined },\n action,\n ),\n );\n break;\n }\n default: {\n throw new Error(`unknown guard rule: \"${key}\"`);\n }\n }\n }\n return rules;\n}\n\n// --- Guard implementation --------------------------------------------------\n\nfunction recordTelemetry(\n ctx: GuardSink,\n state: GuardState,\n newViolations: GuardViolation[],\n stopped: boolean,\n): void {\n ctx.setAttributes({\n [GEN_AI.SESSION_COST_USD]: round(state.costUsd),\n [GEN_AI.SESSION_INPUT_TOKENS]: state.inputTokens,\n [GEN_AI.SESSION_OUTPUT_TOKENS]: state.outputTokens,\n [GEN_AI.SESSION_STEP_COUNT]: state.stepCount,\n [GEN_AI.SESSION_TOOL_CALL_COUNT]: state.toolCallCount,\n [GEN_AI.SESSION_ERROR_COUNT]: state.errorCount,\n });\n if (newViolations.length === 0) return;\n if (stopped) ctx.setAttributes({ [GEN_AI.GUARD_STOPPED]: true });\n for (const violation of newViolations) {\n ctx.track(\n violation.action === 'stop'\n ? GEN_AI_GUARD_EVENT.STOP\n : GEN_AI_GUARD_EVENT.WARNING,\n {\n [GEN_AI.GUARD_RULE]: violation.rule,\n [GEN_AI.GUARD_ACTION]: violation.action,\n [GEN_AI.GUARD_MESSAGE]: violation.message,\n [GEN_AI.GUARD_OBSERVED]: violation.observed,\n [GEN_AI.GUARD_LIMIT]: violation.limit,\n },\n );\n if (violation.action === 'stop') {\n securityEvent(\n {\n name: 'llm.guard.triggered',\n category: 'llm',\n outcome: 'blocked',\n severity: 'error',\n reason: violation.rule,\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n },\n { onMissingContext: 'warn' },\n );\n }\n }\n}\n\n/**\n * Create a guard that supervises an in-process agent run against `rules`.\n */\nexport function createGenAiGuard(options: GenAiGuardOptions): GenAiGuard {\n const { rules } = options;\n const onStop = options.onStop ?? 'throw';\n const historyLimit = options.historyLimit ?? 256;\n const now = options.now ?? Date.now;\n const startedAt = now();\n\n const controller = new AbortController();\n const history: { key: string | undefined; error: boolean }[] = [];\n const fired = new Set<string>();\n const violations: GuardViolation[] = [];\n\n let costUsd = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let stepCount = 0;\n let toolCallCount = 0;\n let errorCount = 0;\n let stopped = false;\n\n function snapshot(): GuardState {\n const at = now();\n return {\n startedAt,\n now: at,\n elapsedMs: at - startedAt,\n costUsd,\n inputTokens,\n outputTokens,\n stepCount,\n toolCallCount,\n errorCount,\n history,\n };\n }\n\n function evaluate(\n step: GenAiGuardStep | undefined,\n ctx: GuardSink | undefined,\n ): GuardViolation[] {\n const state = snapshot();\n const newViolations: GuardViolation[] = [];\n\n for (const rule of rules) {\n if (fired.has(rule.name)) continue;\n const hit = rule.evaluate(state, step);\n if (!hit) continue;\n fired.add(rule.name);\n const violation: GuardViolation = {\n rule: rule.name,\n action: rule.action ?? 'stop',\n message: hit.message,\n observed: hit.observed,\n limit: hit.limit,\n };\n violations.push(violation);\n newViolations.push(violation);\n }\n\n const stopViolation = newViolations.find((v) => v.action === 'stop');\n if (stopViolation && !stopped) {\n stopped = true;\n if (onStop !== 'silent') controller.abort(toStopError(stopViolation, state));\n }\n\n if (ctx) recordTelemetry(ctx, state, newViolations, stopped);\n\n if (stopViolation && onStop === 'throw') {\n throw toStopError(stopViolation, snapshot());\n }\n return newViolations;\n }\n\n return {\n record(step, ctx) {\n stepCount++;\n if (step.kind === 'tool') toolCallCount++;\n if (step.error) errorCount++;\n if (step.usage) {\n costUsd += step.usage.costUsd ?? 0;\n inputTokens += step.usage.inputTokens ?? 0;\n outputTokens += step.usage.outputTokens ?? 0;\n }\n history.push({ key: stepKey(step), error: step.error === true });\n if (history.length > historyLimit) history.shift();\n return evaluate(step, ctx);\n },\n check(ctx) {\n return evaluate(undefined, ctx);\n },\n get state() {\n return snapshot();\n },\n get signal() {\n return controller.signal;\n },\n get stopped() {\n return stopped;\n },\n get violations() {\n return violations;\n },\n };\n}\n\nfunction toStopError(violation: GuardViolation, state: GuardState): Error {\n return createStructuredError({\n name: 'GenAiGuardStop',\n code: 'GEN_AI_GUARD_STOP',\n status: 429,\n message: `Guard rule \"${violation.rule}\" halted the run: ${violation.message}`,\n why: 'A stop-action guard rule crossed its threshold during the agent run.',\n fix: 'Raise the limit, fix the loop, or handle GEN_AI_GUARD_STOP to wind the run down cleanly.',\n details: {\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n costUsd: round(state.costUsd),\n stepCount: state.stepCount,\n },\n });\n}\n\n// --- Budget preset ---------------------------------------------------------\n\nexport interface GenAiBudgetOptions {\n /** Hard cost ceiling in USD — a `stop` rule. */\n maxCostUsd?: number;\n /** Soft cost threshold in USD — a `warn` rule. */\n warnAtUsd?: number;\n /** Hard token ceiling (input + output) — a `stop` rule. */\n maxTokens?: number;\n /** Hard tool-call ceiling — a `stop` rule. */\n maxToolCalls?: number;\n /** Hard wall-clock ceiling in ms — a `stop` rule. */\n maxDurationMs?: number;\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /** Clock injection point, for tests. */\n now?: () => number;\n}\n\n/**\n * Convenience preset that wires the common cost / token / tool-call / duration\n * ceilings into a {@link createGenAiGuard}. Returns the same {@link GenAiGuard}.\n */\nexport function createGenAiBudget(options: GenAiBudgetOptions): GenAiGuard {\n const rules: GenAiGuardRule[] = [];\n if (options.warnAtUsd !== undefined) {\n rules.push(costCeiling(options.warnAtUsd, 'warn'));\n }\n if (options.maxCostUsd !== undefined) {\n rules.push(costCeiling(options.maxCostUsd, 'stop'));\n }\n if (options.maxTokens !== undefined) {\n rules.push(tokenCeiling(options.maxTokens, 'stop'));\n }\n if (options.maxToolCalls !== undefined) {\n rules.push(maxToolCalls(options.maxToolCalls, 'stop'));\n }\n if (options.maxDurationMs !== undefined) {\n rules.push(maxDuration(options.maxDurationMs, 'stop'));\n }\n return createGenAiGuard({\n rules,\n onStop: options.onStop,\n now: options.now,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwMA,SAAS,MAAM,GAAmB;CAChC,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;AAGA,SAAgB,YACd,UACA,SAAsB,QACN;CAChB,OAAO;EACL,MAAM,iBAAiB;EACvB;EACA,WAAW,MACT,EAAE,UAAU,WACR;GACE,SAAS,iBAAiB,MAAM,EAAE,OAAO,EAAE,YAAY;GACvD,UAAU,MAAM,EAAE,OAAO;GACzB,OAAO;EACT,IACA;CACR;AACF;;AAGA,SAAgB,aACd,OACA,SAAsB,QACN;CAChB,OAAO;EACL,MAAM,iBAAiB;EACvB;EACA,WAAW,MAAM;GACf,MAAM,QAAQ,EAAE,cAAc,EAAE;GAChC,OAAO,QAAQ,QACX;IACE,SAAS,kBAAkB,MAAM,UAAU;IAC3C,UAAU;IACV;GACF,IACA;EACN;CACF;AACF;;AAGA,SAAgB,aACd,OACA,SAAsB,QACN;CAChB,OAAO;EACL,MAAM,kBAAkB;EACxB;EACA,WAAW,MACT,EAAE,gBAAgB,QACd;GACE,SAAS,cAAc,EAAE,cAAc,UAAU;GACjD,UAAU,EAAE;GACZ;EACF,IACA;CACR;AACF;;AAGA,SAAgB,SACd,OACA,SAAsB,QACN;CAChB,OAAO;EACL,MAAM,aAAa;EACnB;EACA,WAAW,MACT,EAAE,YAAY,QACV;GACE,SAAS,SAAS,EAAE,UAAU,UAAU;GACxC,UAAU,EAAE;GACZ;EACF,IACA;CACR;AACF;;AAGA,SAAgB,YACd,SACA,SAAsB,QACN;CAChB,OAAO;EACL,MAAM,gBAAgB,QAAQ;EAC9B;EACA,WAAW,MACT,EAAE,YAAY,UACV;GACE,SAAS,WAAW,EAAE,UAAU,aAAa,QAAQ;GACrD,UAAU,EAAE;GACZ,OAAO;EACT,IACA;CACR;AACF;;;;;;AAOA,SAAgB,SACd,SACA,SAAsB,QACN;CAChB,MAAM,EAAE,OAAO,WAAW;CAC1B,OAAO;EACL,MAAM,aAAa,MAAM,GAAG;EAC5B;EACA,WAAW,GAAG,SAAS;GACrB,MAAM,MAAM,QAAQ,IAAI;GACxB,IAAI,QAAQ,WAAW,OAAO;GAC9B,MAAM,SAAS,EAAE,QAAQ,MAAM,CAAC,MAAM;GACtC,IAAI,cAAc;GAClB,KAAK,MAAM,KAAK,QAAQ,IAAI,EAAE,QAAQ,KAAK;GAC3C,OAAO,eAAe,QAClB;IACE,SAAS,SAAS,MAAM,KAAK,aAAa,YAAY,YAAY;IAClE,UAAU;IACV,OAAO;GACT,IACA;EACN;CACF;AACF;;AAGA,SAAgB,UACd,SACA,SAAsB,QACN;CAChB,MAAM,EAAE,UAAU;CAClB,OAAO;EACL,MAAM,cAAc;EACpB;EACA,WAAW,MAAM;GACf,IAAI,SAAS;GACb,KAAK,IAAI,IAAI,EAAE,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;IAC9C,IAAI,EAAE,QAAQ,EAAE,CAAC,OAAO;SACnB;GACP;GACA,OAAO,UAAU,QACb;IACE,SAAS,GAAG,OAAO;IACnB,UAAU;IACV,OAAO;GACT,IACA;EACN;CACF;AACF;;;;;;AAOA,MAAa,iBAAyC;CACpD,UAAU;CACV,WAAW;CACX,SAAS;CACT,WAAW;CACX,iBAAiB;CACjB,mBAAmB;CACnB,qBAAqB;CACrB,oBAAoB;CACpB,YAAY;CACZ,kBAAkB;CAClB,oBAAoB;CACpB,oBAAoB;AACtB;AAEA,SAAS,oBAAoB,OAAmC;CAC9D,MAAM,QAAQ,eAAe;CAC7B,IAAI,OAAO,OAAO;CAClB,IAAI;CACJ,IAAI,aAAa;CACjB,KAAK,MAAM,OAAO,OAAO,KAAK,cAAc,GAAG;EAC7C,IAAI,MAAM,WAAW,GAAG,KAAK,IAAI,SAAS,YAAY;GACpD,OAAO,eAAe;GACtB,aAAa,IAAI;EACnB;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,cACd,SACA,SAAsB,QACN;CAChB,MAAM,YAAY,QAAQ,aAAa;CACvC,MAAM,QACJ,QAAQ,UAAU,QAAQ,QAAQ,oBAAoB,QAAQ,KAAK,IAAI;CACzE,OAAO;EACL,MAAM,kBAAkB,KAAK,MAAM,YAAY,GAAG,EAAE;EACpD;EACA,WAAW,MAAM;GACf,IAAI,CAAC,OAAO,OAAO;GACnB,MAAM,QAAQ,EAAE,cAAc;GAC9B,OAAO,SAAS,YACZ;IACE,SAAS,WAAW,KAAK,MAAM,QAAQ,GAAG,EAAE,UAAU,EAAE,YAAY,GAAG,MAAM;IAC7E,UAAU,MAAM,KAAK;IACrB,OAAO;GACT,IACA;EACN;CACF;AACF;AAEA,SAAS,QAAQ,MAAsD;CACrE,IAAI,CAAC,QAAQ,KAAK,SAAS,WAAW,OAAO;CAC7C,OAAO,GAAG,KAAK,KAAK,GAAG,KAAK,aAAa;AAC3C;AAIA,SAAS,kBAAkB,KAAqB;CAC9C,MAAM,IAAI,gCAAgC,KAAK,IAAI,KAAK,CAAC;CACzD,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,oBAAoB,IAAI,EAAE;CAClD,MAAM,QAAQ,OAAO,WAAW,EAAE,EAAE;CACpC,MAAM,QAAQ;EAAE,GAAG;EAAK,GAAG;EAAK,GAAG;CAAI,EAAE,EAAE,EAAE,EAAE,YAAY,KAAK,OAAO;CACvE,OAAO,QAAQ;AACjB;AAEA,SAAS,gBAAgB,KAAqB;CAC5C,MAAM,IAAI,mCAAmC,KAAK,IAAI,KAAK,CAAC;CAC5D,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,sBAAsB,IAAI,EAAE;CACpD,MAAM,QAAQ,OAAO,WAAW,EAAE,EAAE;CACpC,MAAM,QAAQ,EAAE,MAAM,IAAG,CAAE,YAAY;CACvC,MAAM,QAAQ;EAAE,IAAI;EAAG,GAAG;EAAM,GAAG;EAAQ,GAAG;CAAU,EAAE,SAAS;CACnE,OAAO,QAAQ;AACjB;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBACd,WACA,SACkB;CAClB,MAAM,SAAS,SAAS,iBAAiB;CACzC,MAAM,QAA0B,CAAC;CACjC,KAAK,MAAM,SAAS,UAAU,MAAM,GAAG,GAAG;EACxC,MAAM,UAAU,MAAM,KAAK;EAC3B,IAAI,CAAC,SAAS;EACd,MAAM,MAAM,QAAQ,QAAQ,GAAG;EAC/B,IAAI,QAAQ,CAAC,GAAG,MAAM,IAAI,MAAM,wBAAwB,QAAQ,EAAE;EAClE,MAAM,MAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;EACrD,MAAM,QAAQ,QAAQ,MAAM,MAAM,CAAC,CAAC,CAAC,KAAK;EAE1C,QAAQ,KAAR;GACE,KAAK;GACL,KAAK,QAAQ;IACX,MAAM,KAAK,YAAY,kBAAkB,MAAM,QAAQ,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC;IAC3E;GACF;GACA,KAAK,UAAU;IACb,MAAM,KAAK,aAAa,kBAAkB,KAAK,GAAG,MAAM,CAAC;IACzD;GACF;GACA,KAAK,QAAQ;IACX,MAAM,CAAC,OAAO,UAAU,MAAM,MAAM,GAAG;IACvC,MAAM,KACJ,SACE;KAAE,OAAO,kBAAkB,KAAK;KAAG,QAAQ,kBAAkB,MAAM;IAAE,GACrE,MACF,CACF;IACA;GACF;GACA,KAAK,UAAU;IACb,MAAM,KAAK,UAAU,EAAE,OAAO,kBAAkB,KAAK,EAAE,GAAG,MAAM,CAAC;IACjE;GACF;GACA,KAAK,aAAa;IAChB,MAAM,KAAK,aAAa,kBAAkB,KAAK,GAAG,MAAM,CAAC;IACzD;GACF;GACA,KAAK,aAAa;IAChB,MAAM,KAAK,SAAS,kBAAkB,KAAK,GAAG,MAAM,CAAC;IACrD;GACF;GACA,KAAK,WAAW;IACd,MAAM,KAAK,YAAY,gBAAgB,KAAK,GAAG,MAAM,CAAC;IACtD;GACF;GACA,KAAK,WAAW;IACd,MAAM,CAAC,KAAK,SAAS,MAAM,MAAM,GAAG;IACpC,MAAM,KACJ,cACE;KAAE,WAAW,OAAO,WAAW,GAAG;KAAG,OAAO,OAAO,KAAK,KAAK;IAAU,GACvE,MACF,CACF;IACA;GACF;GACA,SAAS;IACP,MAAM,IAAI,MAAM,wBAAwB,IAAI,EAAE;GAChD;EACF;CACF;CACA,OAAO;AACT;AAIA,SAAS,gBACP,KACA,OACA,eACA,SACM;CACN,IAAI,cAAc;GACfA,uBAAO,mBAAmB,MAAM,MAAM,OAAO;GAC7CA,uBAAO,uBAAuB,MAAM;GACpCA,uBAAO,wBAAwB,MAAM;GACrCA,uBAAO,qBAAqB,MAAM;GAClCA,uBAAO,0BAA0B,MAAM;GACvCA,uBAAO,sBAAsB,MAAM;CACtC,CAAC;CACD,IAAI,cAAc,WAAW,GAAG;CAChC,IAAI,SAAS,IAAI,cAAc,GAAGA,uBAAO,gBAAgB,KAAK,CAAC;CAC/D,KAAK,MAAM,aAAa,eAAe;EACrC,IAAI,MACF,UAAU,WAAW,SACjBC,mCAAmB,OACnBA,mCAAmB,SACvB;IACGD,uBAAO,aAAa,UAAU;IAC9BA,uBAAO,eAAe,UAAU;IAChCA,uBAAO,gBAAgB,UAAU;IACjCA,uBAAO,iBAAiB,UAAU;IAClCA,uBAAO,cAAc,UAAU;EAClC,CACF;EACA,IAAI,UAAU,WAAW,QAAQ;GAC/B,iCACE;IACE,MAAM;IACN,UAAU;IACV,SAAS;IACT,UAAU;IACV,QAAQ,UAAU;IAClB,MAAM,UAAU;IAChB,UAAU,UAAU;IACpB,OAAO,UAAU;GACnB,GACA,EAAE,kBAAkB,OAAO,CAC7B;EACF;CACF;AACF;;;;AAKA,SAAgB,iBAAiB,SAAwC;CACvE,MAAM,EAAE,UAAU;CAClB,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,MAAM,QAAQ,OAAO,KAAK;CAChC,MAAM,YAAY,IAAI;CAEtB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAyD,CAAC;CAChE,MAAM,QAAQ,IAAI,IAAY;CAC9B,MAAM,aAA+B,CAAC;CAEtC,IAAI,UAAU;CACd,IAAI,cAAc;CAClB,IAAI,eAAe;CACnB,IAAI,YAAY;CAChB,IAAI,gBAAgB;CACpB,IAAI,aAAa;CACjB,IAAI,UAAU;CAEd,SAAS,WAAuB;EAC9B,MAAM,KAAK,IAAI;EACf,OAAO;GACL;GACA,KAAK;GACL,WAAW,KAAK;GAChB;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CAEA,SAAS,SACP,MACA,KACkB;EAClB,MAAM,QAAQ,SAAS;EACvB,MAAM,gBAAkC,CAAC;EAEzC,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,MAAM,IAAI,KAAK,IAAI,GAAG;GAC1B,MAAM,MAAM,KAAK,SAAS,OAAO,IAAI;GACrC,IAAI,CAAC,KAAK;GACV,MAAM,IAAI,KAAK,IAAI;GACnB,MAAM,YAA4B;IAChC,MAAM,KAAK;IACX,QAAQ,KAAK,UAAU;IACvB,SAAS,IAAI;IACb,UAAU,IAAI;IACd,OAAO,IAAI;GACb;GACA,WAAW,KAAK,SAAS;GACzB,cAAc,KAAK,SAAS;EAC9B;EAEA,MAAM,gBAAgB,cAAc,MAAM,MAAM,EAAE,WAAW,MAAM;EACnE,IAAI,iBAAiB,CAAC,SAAS;GAC7B,UAAU;GACV,IAAI,WAAW,UAAU,WAAW,MAAM,YAAY,eAAe,KAAK,CAAC;EAC7E;EAEA,IAAI,KAAK,gBAAgB,KAAK,OAAO,eAAe,OAAO;EAE3D,IAAI,iBAAiB,WAAW,SAAS;GACvC,MAAM,YAAY,eAAe,SAAS,CAAC;EAC7C;EACA,OAAO;CACT;CAEA,OAAO;EACL,OAAO,MAAM,KAAK;GAChB;GACA,IAAI,KAAK,SAAS,QAAQ;GAC1B,IAAI,KAAK,OAAO;GAChB,IAAI,KAAK,OAAO;IACd,WAAW,KAAK,MAAM,WAAW;IACjC,eAAe,KAAK,MAAM,eAAe;IACzC,gBAAgB,KAAK,MAAM,gBAAgB;GAC7C;GACA,QAAQ,KAAK;IAAE,KAAK,QAAQ,IAAI;IAAG,OAAO,KAAK,UAAU;GAAK,CAAC;GAC/D,IAAI,QAAQ,SAAS,cAAc,QAAQ,MAAM;GACjD,OAAO,SAAS,MAAM,GAAG;EAC3B;EACA,MAAM,KAAK;GACT,OAAO,SAAS,WAAW,GAAG;EAChC;EACA,IAAI,QAAQ;GACV,OAAO,SAAS;EAClB;EACA,IAAI,SAAS;GACX,OAAO,WAAW;EACpB;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,IAAI,aAAa;GACf,OAAO;EACT;CACF;AACF;AAEA,SAAS,YAAY,WAA2B,OAA0B;CACxE,0CAA6B;EAC3B,MAAM;EACN,MAAM;EACN,QAAQ;EACR,SAAS,eAAe,UAAU,KAAK,oBAAoB,UAAU;EACrE,KAAK;EACL,KAAK;EACL,SAAS;GACP,MAAM,UAAU;GAChB,UAAU,UAAU;GACpB,OAAO,UAAU;GACjB,SAAS,MAAM,MAAM,OAAO;GAC5B,WAAW,MAAM;EACnB;CACF,CAAC;AACH;;;;;AAyBA,SAAgB,kBAAkB,SAAyC;CACzE,MAAM,QAA0B,CAAC;CACjC,IAAI,QAAQ,cAAc,WAAW;EACnC,MAAM,KAAK,YAAY,QAAQ,WAAW,MAAM,CAAC;CACnD;CACA,IAAI,QAAQ,eAAe,WAAW;EACpC,MAAM,KAAK,YAAY,QAAQ,YAAY,MAAM,CAAC;CACpD;CACA,IAAI,QAAQ,cAAc,WAAW;EACnC,MAAM,KAAK,aAAa,QAAQ,WAAW,MAAM,CAAC;CACpD;CACA,IAAI,QAAQ,iBAAiB,WAAW;EACtC,MAAM,KAAK,aAAa,QAAQ,cAAc,MAAM,CAAC;CACvD;CACA,IAAI,QAAQ,kBAAkB,WAAW;EACvC,MAAM,KAAK,YAAY,QAAQ,eAAe,MAAM,CAAC;CACvD;CACA,OAAO,iBAAiB;EACtB;EACA,QAAQ,QAAQ;EAChB,KAAK,QAAQ;CACf,CAAC;AACH"}
package/dist/guard.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"guard.js","names":[],"sources":["../src/guard.ts"],"sourcesContent":["/**\n * GenAI guard / budget runtime — an in-process kill-switch for agent runs.\n *\n * Most tracing tells you what an agent _did_, after the fact. A guard runs\n * _during_ the run: you feed it each step (an LLM call, a tool call, a\n * delegation), it accumulates cost / tokens / loop state, and evaluates a set\n * of rules. When a rule crosses its threshold the guard either records a\n * warning and lets the run continue, or stops the run — aborting an\n * {@link AbortSignal} and (by default) throwing a structured error so the agent\n * loop unwinds. No prompt, no retry, no runaway bill.\n *\n * This is the inline, OpenTelemetry-native analogue of a `watch` kill-switch:\n * cost ceilings, token ceilings, max tool calls, wall-clock timeouts, and\n * spin-loop / error-loop detection — all pure, deterministic logic with no LLM\n * in the loop.\n *\n * @example Budget ceiling that halts a runaway agent\n * ```typescript\n * import { createGenAiBudget } from 'autotel-genai/guard';\n * import { estimateLLMCost } from 'autotel-genai/cost';\n *\n * const budget = createGenAiBudget({ maxCostUsd: 5, warnAtUsd: 4 });\n *\n * for (const task of tasks) {\n * if (budget.stopped) break;\n * const res = await model.chat(task);\n * budget.record(\n * {\n * kind: 'llm',\n * usage: {\n * costUsd: estimateLLMCost('gpt-4o', {\n * inputTokens: res.usage.input,\n * outputTokens: res.usage.output,\n * }),\n * },\n * },\n * ctx, // optional TraceContext — records gen_ai.guard.* telemetry\n * ); // throws GEN_AI_GUARD_STOP once total cost > $5\n * }\n * ```\n *\n * @example Loop detection from a shorthand rule string\n * ```typescript\n * import { createGenAiGuard, parseGuardRules } from 'autotel-genai/guard';\n *\n * const guard = createGenAiGuard({\n * rules: parseGuardRules('budget:$2,loop:3/10,max-tools:50,timeout:5m'),\n * });\n *\n * guard.record({ kind: 'tool', name: 'search', signature: JSON.stringify(args) });\n * ```\n */\n\nimport { createStructuredError, type TraceContext } from 'autotel';\nimport { securityEvent } from 'autotel-audit';\nimport { GEN_AI, GEN_AI_GUARD_EVENT } from './semconv.js';\n\n/** What a rule does when it crosses its threshold. */\nexport type GuardAction = 'warn' | 'stop';\n\n/** Usage contribution for a single step. */\nexport interface GuardUsage {\n /**\n * Estimated USD cost for this step. Pair with\n * {@link import('./cost.js').estimateLLMCost} to compute it from token usage.\n */\n costUsd?: number;\n /** Input (prompt) tokens for this step. */\n inputTokens?: number;\n /** Output (completion) tokens for this step. */\n outputTokens?: number;\n}\n\n/** A single supervised step: an LLM call, a tool call, a delegation, … */\nexport interface GenAiGuardStep {\n /**\n * Step kind. `tool` increments the tool-call counter; any value is allowed.\n */\n kind?: 'llm' | 'tool' | 'agent' | 'workflow' | (string & {});\n /** Operation or tool name — used together with {@link signature} for loops. */\n name?: string;\n /**\n * Stable signature of the call inputs (e.g. `JSON.stringify(args)`). Two\n * steps with the same `name` + `signature` count as identical for spin-loop\n * detection. Hash or truncate it yourself if the raw args are large.\n */\n signature?: string;\n /** Whether this step failed. Drives error-loop detection. */\n error?: boolean;\n /** Cost / token contribution to the running totals. */\n usage?: GuardUsage;\n}\n\n/** Read-only snapshot of the accumulated session state. */\nexport interface GuardState {\n /** Epoch ms when the guard was created. */\n readonly startedAt: number;\n /** Epoch ms at evaluation time. */\n readonly now: number;\n /** Wall-clock ms since {@link startedAt}. */\n readonly elapsedMs: number;\n /** Accumulated estimated cost in USD. */\n readonly costUsd: number;\n /** Accumulated input tokens. */\n readonly inputTokens: number;\n /** Accumulated output tokens. */\n readonly outputTokens: number;\n /** Total steps recorded. */\n readonly stepCount: number;\n /** Steps with `kind === 'tool'`. */\n readonly toolCallCount: number;\n /** Steps with `error === true`. */\n readonly errorCount: number;\n /** Bounded recent history (newest last) for loop detection. */\n readonly history: ReadonlyArray<{ key: string | undefined; error: boolean }>;\n}\n\n/** A fired rule. */\nexport interface GuardViolation {\n /** Rule name, e.g. `cost-ceiling:$5`. */\n rule: string;\n /** Whether the run continued (`warn`) or was halted (`stop`). */\n action: GuardAction;\n /** Human-readable explanation. */\n message: string;\n /** The observed value that crossed the threshold. */\n observed: number;\n /** The configured threshold. */\n limit: number;\n}\n\n/** A guard rule: inspect the state after a step and optionally fire. */\nexport interface GenAiGuardRule {\n /** Stable identifier, surfaced on {@link GuardViolation.rule}. */\n name: string;\n /** Default {@link GuardAction} when the rule fires. Defaults to `stop`. */\n action?: GuardAction;\n /**\n * Evaluate against the post-step state and the step just recorded (absent on\n * a bare {@link GenAiGuard.check}). Return a partial violation to fire, or\n * `undefined` to pass.\n */\n evaluate: (\n state: GuardState,\n step: GenAiGuardStep | undefined,\n ) => { message: string; observed: number; limit: number } | undefined;\n}\n\n/** Minimal telemetry sink — the subset of {@link TraceContext} the guard uses. */\nexport type GuardSink = Pick<TraceContext, 'setAttributes' | 'track'>;\n\n/** What happens when a `stop` rule fires. */\nexport type GuardStopBehavior =\n /** Abort the signal and throw a structured error (default). */\n | 'throw'\n /** Abort the signal but do not throw — inspect `guard.stopped` yourself. */\n | 'abort'\n /** Record the violation only; neither abort nor throw. */\n | 'silent';\n\nexport interface GenAiGuardOptions {\n /** Rules to evaluate after each step. */\n rules: GenAiGuardRule[];\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /**\n * Max recent steps retained for loop detection. Defaults to 256; raise it if\n * a spin-loop rule uses a larger window.\n */\n historyLimit?: number;\n /** Clock injection point, for tests. Defaults to {@link Date.now}. */\n now?: () => number;\n}\n\n/** A live guard supervising an agent run. */\nexport interface GenAiGuard {\n /**\n * Record a step, accumulate its usage, and evaluate every rule. Returns the\n * violations that fired _this call_ (each rule fires at most once). When a\n * `stop` rule fires, aborts {@link signal} and — unless `onStop` is `abort` /\n * `silent` — throws a structured `GEN_AI_GUARD_STOP` error.\n */\n record(step: GenAiGuardStep, ctx?: GuardSink): GuardViolation[];\n /**\n * Re-evaluate the rules without recording a step. Useful for time-based\n * rules (e.g. a timeout) between LLM calls.\n */\n check(ctx?: GuardSink): GuardViolation[];\n /** Current accumulated state. */\n readonly state: GuardState;\n /** Fires once a `stop` rule has crossed its threshold. */\n readonly signal: AbortSignal;\n /** `true` after a `stop` rule has fired. */\n readonly stopped: boolean;\n /** Every violation fired so far, in order. */\n readonly violations: ReadonlyArray<GuardViolation>;\n}\n\n// --- Rule factories --------------------------------------------------------\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6;\n}\n\n/** Stop / warn when accumulated cost exceeds `limitUsd`. */\nexport function costCeiling(\n limitUsd: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `cost-ceiling:$${limitUsd}`,\n action,\n evaluate: (s) =>\n s.costUsd > limitUsd\n ? {\n message: `session cost $${round(s.costUsd)} exceeds $${limitUsd}`,\n observed: round(s.costUsd),\n limit: limitUsd,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when accumulated input + output tokens exceed `limit`. */\nexport function tokenCeiling(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `token-ceiling:${limit}`,\n action,\n evaluate: (s) => {\n const total = s.inputTokens + s.outputTokens;\n return total > limit\n ? {\n message: `session tokens ${total} exceed ${limit}`,\n observed: total,\n limit,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn when the number of tool calls exceeds `limit`. */\nexport function maxToolCalls(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-tool-calls:${limit}`,\n action,\n evaluate: (s) =>\n s.toolCallCount > limit\n ? {\n message: `tool calls ${s.toolCallCount} exceed ${limit}`,\n observed: s.toolCallCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when the total number of steps exceeds `limit`. */\nexport function maxSteps(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-steps:${limit}`,\n action,\n evaluate: (s) =>\n s.stepCount > limit\n ? {\n message: `steps ${s.stepCount} exceed ${limit}`,\n observed: s.stepCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when wall-clock time since start exceeds `limitMs`. */\nexport function maxDuration(\n limitMs: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-duration:${limitMs}ms`,\n action,\n evaluate: (s) =>\n s.elapsedMs > limitMs\n ? {\n message: `elapsed ${s.elapsedMs}ms exceeds ${limitMs}ms`,\n observed: s.elapsedMs,\n limit: limitMs,\n }\n : undefined,\n };\n}\n\n/**\n * Stop / warn on a spin loop: the same step (`name` + `signature`) repeated\n * `count` or more times within the last `window` steps. Steps without a `name`\n * are ignored (no identity to compare).\n */\nexport function spinLoop(\n options: { count: number; window: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count, window } = options;\n return {\n name: `spin-loop:${count}/${window}`,\n action,\n evaluate: (s, step) => {\n const key = stepKey(step);\n if (key === undefined) return undefined;\n const recent = s.history.slice(-window);\n let occurrences = 0;\n for (const h of recent) if (h.key === key) occurrences++;\n return occurrences >= count\n ? {\n message: `step \"${step?.name}\" repeated ${occurrences}× in last ${window}`,\n observed: occurrences,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn on `count` or more consecutive failing steps. */\nexport function errorLoop(\n options: { count: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count } = options;\n return {\n name: `error-loop:${count}`,\n action,\n evaluate: (s) => {\n let streak = 0;\n for (let i = s.history.length - 1; i >= 0; i--) {\n if (s.history[i].error) streak++;\n else break;\n }\n return streak >= count\n ? {\n message: `${streak} consecutive errors`,\n observed: streak,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/**\n * Approximate context-window sizes (in input tokens) for common models.\n * Matched exactly first, then by longest key prefix. Override via the `limit`\n * option on {@link contextBudget}.\n */\nexport const CONTEXT_LIMITS: Record<string, number> = {\n 'gpt-4o': 128_000,\n 'gpt-4.1': 1_047_576,\n 'gpt-4': 8192,\n 'o3-mini': 200_000,\n 'claude-opus-4': 200_000,\n 'claude-sonnet-4': 200_000,\n 'claude-3-5-sonnet': 200_000,\n 'claude-3-5-haiku': 200_000,\n 'claude-3': 200_000,\n 'gemini-1.5-pro': 2_097_152,\n 'gemini-1.5-flash': 1_048_576,\n 'gemini-2.0-flash': 1_048_576,\n};\n\nfunction resolveContextLimit(model: string): number | undefined {\n const exact = CONTEXT_LIMITS[model];\n if (exact) return exact;\n let best: number | undefined;\n let bestLength = 0;\n for (const key of Object.keys(CONTEXT_LIMITS)) {\n if (model.startsWith(key) && key.length > bestLength) {\n best = CONTEXT_LIMITS[key];\n bestLength = key.length;\n }\n }\n return best;\n}\n\n/**\n * Stop / warn when cumulative input tokens reach `threshold` (default 0.9) of\n * the model's context window. Provide an explicit `limit`, or a `model` to look\n * one up from {@link CONTEXT_LIMITS}.\n */\nexport function contextBudget(\n options: { model?: string; limit?: number; threshold?: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const threshold = options.threshold ?? 0.9;\n const limit =\n options.limit ?? (options.model ? resolveContextLimit(options.model) : undefined);\n return {\n name: `context-budget:${Math.round(threshold * 100)}%`,\n action,\n evaluate: (s) => {\n if (!limit) return undefined;\n const ratio = s.inputTokens / limit;\n return ratio >= threshold\n ? {\n message: `context ${Math.round(ratio * 100)}% full (${s.inputTokens}/${limit})`,\n observed: round(ratio),\n limit: threshold,\n }\n : undefined;\n },\n };\n}\n\nfunction stepKey(step: GenAiGuardStep | undefined): string | undefined {\n if (!step || step.name === undefined) return undefined;\n return `${step.name}\u0000${step.signature ?? ''}`;\n}\n\n// --- Shorthand rule parser -------------------------------------------------\n\nfunction parseScaledNumber(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*([kmb])?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid number: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const scale = { k: 1e3, m: 1e6, b: 1e9 }[m[2]?.toLowerCase() ?? ''] ?? 1;\n return value * scale;\n}\n\nfunction parseDurationMs(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h)?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid duration: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const unit = (m[2] ?? 's').toLowerCase();\n const scale = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[unit] ?? 1000;\n return value * scale;\n}\n\n/**\n * Parse a comma-separated shorthand rule string into {@link GenAiGuardRule}s.\n * All parsed rules default to the `stop` action — pass `defaultAction: 'warn'`\n * to warn instead, or build typed rules directly for per-rule actions.\n *\n * Supported tokens:\n * - `budget:$5` / `cost:$5` — {@link costCeiling}\n * - `tokens:100k` — {@link tokenCeiling} (`k`/`m`/`b` suffixes)\n * - `loop:3/10` — {@link spinLoop} (count / window)\n * - `errors:3` — {@link errorLoop}\n * - `max-tools:50` — {@link maxToolCalls}\n * - `max-steps:100` — {@link maxSteps}\n * - `timeout:30m` — {@link maxDuration} (`ms`/`s`/`m`/`h`, default seconds)\n * - `context:0.9` / `context:0.9@gpt-4o` — {@link contextBudget}\n */\nexport function parseGuardRules(\n shorthand: string,\n options?: { defaultAction?: GuardAction },\n): GenAiGuardRule[] {\n const action = options?.defaultAction ?? 'stop';\n const rules: GenAiGuardRule[] = [];\n for (const token of shorthand.split(',')) {\n const trimmed = token.trim();\n if (!trimmed) continue;\n const sep = trimmed.indexOf(':');\n if (sep === -1) throw new Error(`invalid guard rule: \"${trimmed}\"`);\n const key = trimmed.slice(0, sep).trim().toLowerCase();\n const value = trimmed.slice(sep + 1).trim();\n\n switch (key) {\n case 'budget':\n case 'cost': {\n rules.push(costCeiling(parseScaledNumber(value.replace(/^\\$/, '')), action));\n break;\n }\n case 'tokens': {\n rules.push(tokenCeiling(parseScaledNumber(value), action));\n break;\n }\n case 'loop': {\n const [count, window] = value.split('/');\n rules.push(\n spinLoop(\n { count: parseScaledNumber(count), window: parseScaledNumber(window) },\n action,\n ),\n );\n break;\n }\n case 'errors': {\n rules.push(errorLoop({ count: parseScaledNumber(value) }, action));\n break;\n }\n case 'max-tools': {\n rules.push(maxToolCalls(parseScaledNumber(value), action));\n break;\n }\n case 'max-steps': {\n rules.push(maxSteps(parseScaledNumber(value), action));\n break;\n }\n case 'timeout': {\n rules.push(maxDuration(parseDurationMs(value), action));\n break;\n }\n case 'context': {\n const [thr, model] = value.split('@');\n rules.push(\n contextBudget(\n { threshold: Number.parseFloat(thr), model: model?.trim() || undefined },\n action,\n ),\n );\n break;\n }\n default: {\n throw new Error(`unknown guard rule: \"${key}\"`);\n }\n }\n }\n return rules;\n}\n\n// --- Guard implementation --------------------------------------------------\n\nfunction recordTelemetry(\n ctx: GuardSink,\n state: GuardState,\n newViolations: GuardViolation[],\n stopped: boolean,\n): void {\n ctx.setAttributes({\n [GEN_AI.SESSION_COST_USD]: round(state.costUsd),\n [GEN_AI.SESSION_INPUT_TOKENS]: state.inputTokens,\n [GEN_AI.SESSION_OUTPUT_TOKENS]: state.outputTokens,\n [GEN_AI.SESSION_STEP_COUNT]: state.stepCount,\n [GEN_AI.SESSION_TOOL_CALL_COUNT]: state.toolCallCount,\n [GEN_AI.SESSION_ERROR_COUNT]: state.errorCount,\n });\n if (newViolations.length === 0) return;\n if (stopped) ctx.setAttributes({ [GEN_AI.GUARD_STOPPED]: true });\n for (const violation of newViolations) {\n ctx.track(\n violation.action === 'stop'\n ? GEN_AI_GUARD_EVENT.STOP\n : GEN_AI_GUARD_EVENT.WARNING,\n {\n [GEN_AI.GUARD_RULE]: violation.rule,\n [GEN_AI.GUARD_ACTION]: violation.action,\n [GEN_AI.GUARD_MESSAGE]: violation.message,\n [GEN_AI.GUARD_OBSERVED]: violation.observed,\n [GEN_AI.GUARD_LIMIT]: violation.limit,\n },\n );\n if (violation.action === 'stop') {\n securityEvent(\n {\n name: 'llm.guard.triggered',\n category: 'llm',\n outcome: 'blocked',\n severity: 'error',\n reason: violation.rule,\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n },\n { onMissingContext: 'warn' },\n );\n }\n }\n}\n\n/**\n * Create a guard that supervises an in-process agent run against `rules`.\n */\nexport function createGenAiGuard(options: GenAiGuardOptions): GenAiGuard {\n const { rules } = options;\n const onStop = options.onStop ?? 'throw';\n const historyLimit = options.historyLimit ?? 256;\n const now = options.now ?? Date.now;\n const startedAt = now();\n\n const controller = new AbortController();\n const history: { key: string | undefined; error: boolean }[] = [];\n const fired = new Set<string>();\n const violations: GuardViolation[] = [];\n\n let costUsd = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let stepCount = 0;\n let toolCallCount = 0;\n let errorCount = 0;\n let stopped = false;\n\n function snapshot(): GuardState {\n const at = now();\n return {\n startedAt,\n now: at,\n elapsedMs: at - startedAt,\n costUsd,\n inputTokens,\n outputTokens,\n stepCount,\n toolCallCount,\n errorCount,\n history,\n };\n }\n\n function evaluate(\n step: GenAiGuardStep | undefined,\n ctx: GuardSink | undefined,\n ): GuardViolation[] {\n const state = snapshot();\n const newViolations: GuardViolation[] = [];\n\n for (const rule of rules) {\n if (fired.has(rule.name)) continue;\n const hit = rule.evaluate(state, step);\n if (!hit) continue;\n fired.add(rule.name);\n const violation: GuardViolation = {\n rule: rule.name,\n action: rule.action ?? 'stop',\n message: hit.message,\n observed: hit.observed,\n limit: hit.limit,\n };\n violations.push(violation);\n newViolations.push(violation);\n }\n\n const stopViolation = newViolations.find((v) => v.action === 'stop');\n if (stopViolation && !stopped) {\n stopped = true;\n if (onStop !== 'silent') controller.abort(toStopError(stopViolation, state));\n }\n\n if (ctx) recordTelemetry(ctx, state, newViolations, stopped);\n\n if (stopViolation && onStop === 'throw') {\n throw toStopError(stopViolation, snapshot());\n }\n return newViolations;\n }\n\n return {\n record(step, ctx) {\n stepCount++;\n if (step.kind === 'tool') toolCallCount++;\n if (step.error) errorCount++;\n if (step.usage) {\n costUsd += step.usage.costUsd ?? 0;\n inputTokens += step.usage.inputTokens ?? 0;\n outputTokens += step.usage.outputTokens ?? 0;\n }\n history.push({ key: stepKey(step), error: step.error === true });\n if (history.length > historyLimit) history.shift();\n return evaluate(step, ctx);\n },\n check(ctx) {\n return evaluate(undefined, ctx);\n },\n get state() {\n return snapshot();\n },\n get signal() {\n return controller.signal;\n },\n get stopped() {\n return stopped;\n },\n get violations() {\n return violations;\n },\n };\n}\n\nfunction toStopError(violation: GuardViolation, state: GuardState): Error {\n return createStructuredError({\n name: 'GenAiGuardStop',\n code: 'GEN_AI_GUARD_STOP',\n status: 429,\n message: `Guard rule \"${violation.rule}\" halted the run: ${violation.message}`,\n why: 'A stop-action guard rule crossed its threshold during the agent run.',\n fix: 'Raise the limit, fix the loop, or handle GEN_AI_GUARD_STOP to wind the run down cleanly.',\n details: {\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n costUsd: round(state.costUsd),\n stepCount: state.stepCount,\n },\n });\n}\n\n// --- Budget preset ---------------------------------------------------------\n\nexport interface GenAiBudgetOptions {\n /** Hard cost ceiling in USD — a `stop` rule. */\n maxCostUsd?: number;\n /** Soft cost threshold in USD — a `warn` rule. */\n warnAtUsd?: number;\n /** Hard token ceiling (input + output) — a `stop` rule. */\n maxTokens?: number;\n /** Hard tool-call ceiling — a `stop` rule. */\n maxToolCalls?: number;\n /** Hard wall-clock ceiling in ms — a `stop` rule. */\n maxDurationMs?: number;\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /** Clock injection point, for tests. */\n now?: () => number;\n}\n\n/**\n * Convenience preset that wires the common cost / token / tool-call / duration\n * ceilings into a {@link createGenAiGuard}. Returns the same {@link GenAiGuard}.\n */\nexport function createGenAiBudget(options: GenAiBudgetOptions): GenAiGuard {\n const rules: GenAiGuardRule[] = [];\n if (options.warnAtUsd !== undefined) {\n rules.push(costCeiling(options.warnAtUsd, 'warn'));\n }\n if (options.maxCostUsd !== undefined) {\n rules.push(costCeiling(options.maxCostUsd, 'stop'));\n }\n if (options.maxTokens !== undefined) {\n rules.push(tokenCeiling(options.maxTokens, 'stop'));\n }\n if (options.maxToolCalls !== undefined) {\n rules.push(maxToolCalls(options.maxToolCalls, 'stop'));\n }\n if (options.maxDurationMs !== undefined) {\n rules.push(maxDuration(options.maxDurationMs, 'stop'));\n }\n return createGenAiGuard({\n rules,\n onStop: options.onStop,\n now: options.now,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"guard.js","names":[],"sources":["../src/guard.ts"],"sourcesContent":["/**\n * GenAI guard / budget runtime — an in-process kill-switch for agent runs.\n *\n * Most tracing tells you what an agent _did_, after the fact. A guard runs\n * _during_ the run: you feed it each step (an LLM call, a tool call, a\n * delegation), it accumulates cost / tokens / loop state, and evaluates a set\n * of rules. When a rule crosses its threshold the guard either records a\n * warning and lets the run continue, or stops the run — aborting an\n * {@link AbortSignal} and (by default) throwing a structured error so the agent\n * loop unwinds. No prompt, no retry, no runaway bill.\n *\n * This is the inline, OpenTelemetry-native analogue of a `watch` kill-switch:\n * cost ceilings, token ceilings, max tool calls, wall-clock timeouts, and\n * spin-loop / error-loop detection — all pure, deterministic logic with no LLM\n * in the loop.\n *\n * @example Budget ceiling that halts a runaway agent\n * ```typescript\n * import { createGenAiBudget } from 'autotel-genai/guard';\n * import { estimateLLMCost } from 'autotel-genai/cost';\n *\n * const budget = createGenAiBudget({ maxCostUsd: 5, warnAtUsd: 4 });\n *\n * for (const task of tasks) {\n * if (budget.stopped) break;\n * const res = await model.chat(task);\n * budget.record(\n * {\n * kind: 'llm',\n * usage: {\n * costUsd: estimateLLMCost('gpt-4o', {\n * inputTokens: res.usage.input,\n * outputTokens: res.usage.output,\n * }),\n * },\n * },\n * ctx, // optional TraceContext — records gen_ai.guard.* telemetry\n * ); // throws GEN_AI_GUARD_STOP once total cost > $5\n * }\n * ```\n *\n * @example Loop detection from a shorthand rule string\n * ```typescript\n * import { createGenAiGuard, parseGuardRules } from 'autotel-genai/guard';\n *\n * const guard = createGenAiGuard({\n * rules: parseGuardRules('budget:$2,loop:3/10,max-tools:50,timeout:5m'),\n * });\n *\n * guard.record({ kind: 'tool', name: 'search', signature: JSON.stringify(args) });\n * ```\n */\n\nimport { createStructuredError, type TraceContext } from 'autotel';\nimport { securityEvent } from 'autotel-audit';\nimport { GEN_AI, GEN_AI_GUARD_EVENT } from './semconv.js';\n\n/** What a rule does when it crosses its threshold. */\nexport type GuardAction = 'warn' | 'stop';\n\n/** Usage contribution for a single step. */\nexport interface GuardUsage {\n /**\n * Estimated USD cost for this step. Pair with\n * {@link import('./cost.js').estimateLLMCost} to compute it from token usage.\n */\n costUsd?: number;\n /** Input (prompt) tokens for this step. */\n inputTokens?: number;\n /** Output (completion) tokens for this step. */\n outputTokens?: number;\n}\n\n/** A single supervised step: an LLM call, a tool call, a delegation, … */\nexport interface GenAiGuardStep {\n /**\n * Step kind. `tool` increments the tool-call counter; any value is allowed.\n */\n kind?: 'llm' | 'tool' | 'agent' | 'workflow' | (string & {});\n /** Operation or tool name — used together with {@link signature} for loops. */\n name?: string;\n /**\n * Stable signature of the call inputs (e.g. `JSON.stringify(args)`). Two\n * steps with the same `name` + `signature` count as identical for spin-loop\n * detection. Hash or truncate it yourself if the raw args are large.\n */\n signature?: string;\n /** Whether this step failed. Drives error-loop detection. */\n error?: boolean;\n /** Cost / token contribution to the running totals. */\n usage?: GuardUsage;\n}\n\n/** Read-only snapshot of the accumulated session state. */\nexport interface GuardState {\n /** Epoch ms when the guard was created. */\n readonly startedAt: number;\n /** Epoch ms at evaluation time. */\n readonly now: number;\n /** Wall-clock ms since {@link startedAt}. */\n readonly elapsedMs: number;\n /** Accumulated estimated cost in USD. */\n readonly costUsd: number;\n /** Accumulated input tokens. */\n readonly inputTokens: number;\n /** Accumulated output tokens. */\n readonly outputTokens: number;\n /** Total steps recorded. */\n readonly stepCount: number;\n /** Steps with `kind === 'tool'`. */\n readonly toolCallCount: number;\n /** Steps with `error === true`. */\n readonly errorCount: number;\n /** Bounded recent history (newest last) for loop detection. */\n readonly history: ReadonlyArray<{ key: string | undefined; error: boolean }>;\n}\n\n/** A fired rule. */\nexport interface GuardViolation {\n /** Rule name, e.g. `cost-ceiling:$5`. */\n rule: string;\n /** Whether the run continued (`warn`) or was halted (`stop`). */\n action: GuardAction;\n /** Human-readable explanation. */\n message: string;\n /** The observed value that crossed the threshold. */\n observed: number;\n /** The configured threshold. */\n limit: number;\n}\n\n/** A guard rule: inspect the state after a step and optionally fire. */\nexport interface GenAiGuardRule {\n /** Stable identifier, surfaced on {@link GuardViolation.rule}. */\n name: string;\n /** Default {@link GuardAction} when the rule fires. Defaults to `stop`. */\n action?: GuardAction;\n /**\n * Evaluate against the post-step state and the step just recorded (absent on\n * a bare {@link GenAiGuard.check}). Return a partial violation to fire, or\n * `undefined` to pass.\n */\n evaluate: (\n state: GuardState,\n step: GenAiGuardStep | undefined,\n ) => { message: string; observed: number; limit: number } | undefined;\n}\n\n/** Minimal telemetry sink — the subset of {@link TraceContext} the guard uses. */\nexport type GuardSink = Pick<TraceContext, 'setAttributes' | 'track'>;\n\n/** What happens when a `stop` rule fires. */\nexport type GuardStopBehavior =\n /** Abort the signal and throw a structured error (default). */\n | 'throw'\n /** Abort the signal but do not throw — inspect `guard.stopped` yourself. */\n | 'abort'\n /** Record the violation only; neither abort nor throw. */\n | 'silent';\n\nexport interface GenAiGuardOptions {\n /** Rules to evaluate after each step. */\n rules: GenAiGuardRule[];\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /**\n * Max recent steps retained for loop detection. Defaults to 256; raise it if\n * a spin-loop rule uses a larger window.\n */\n historyLimit?: number;\n /** Clock injection point, for tests. Defaults to {@link Date.now}. */\n now?: () => number;\n}\n\n/** A live guard supervising an agent run. */\nexport interface GenAiGuard {\n /**\n * Record a step, accumulate its usage, and evaluate every rule. Returns the\n * violations that fired _this call_ (each rule fires at most once). When a\n * `stop` rule fires, aborts {@link signal} and — unless `onStop` is `abort` /\n * `silent` — throws a structured `GEN_AI_GUARD_STOP` error.\n */\n record(step: GenAiGuardStep, ctx?: GuardSink): GuardViolation[];\n /**\n * Re-evaluate the rules without recording a step. Useful for time-based\n * rules (e.g. a timeout) between LLM calls.\n */\n check(ctx?: GuardSink): GuardViolation[];\n /** Current accumulated state. */\n readonly state: GuardState;\n /** Fires once a `stop` rule has crossed its threshold. */\n readonly signal: AbortSignal;\n /** `true` after a `stop` rule has fired. */\n readonly stopped: boolean;\n /** Every violation fired so far, in order. */\n readonly violations: ReadonlyArray<GuardViolation>;\n}\n\n// --- Rule factories --------------------------------------------------------\n\nfunction round(n: number): number {\n return Math.round(n * 1e6) / 1e6;\n}\n\n/** Stop / warn when accumulated cost exceeds `limitUsd`. */\nexport function costCeiling(\n limitUsd: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `cost-ceiling:$${limitUsd}`,\n action,\n evaluate: (s) =>\n s.costUsd > limitUsd\n ? {\n message: `session cost $${round(s.costUsd)} exceeds $${limitUsd}`,\n observed: round(s.costUsd),\n limit: limitUsd,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when accumulated input + output tokens exceed `limit`. */\nexport function tokenCeiling(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `token-ceiling:${limit}`,\n action,\n evaluate: (s) => {\n const total = s.inputTokens + s.outputTokens;\n return total > limit\n ? {\n message: `session tokens ${total} exceed ${limit}`,\n observed: total,\n limit,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn when the number of tool calls exceeds `limit`. */\nexport function maxToolCalls(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-tool-calls:${limit}`,\n action,\n evaluate: (s) =>\n s.toolCallCount > limit\n ? {\n message: `tool calls ${s.toolCallCount} exceed ${limit}`,\n observed: s.toolCallCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when the total number of steps exceeds `limit`. */\nexport function maxSteps(\n limit: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-steps:${limit}`,\n action,\n evaluate: (s) =>\n s.stepCount > limit\n ? {\n message: `steps ${s.stepCount} exceed ${limit}`,\n observed: s.stepCount,\n limit,\n }\n : undefined,\n };\n}\n\n/** Stop / warn when wall-clock time since start exceeds `limitMs`. */\nexport function maxDuration(\n limitMs: number,\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n return {\n name: `max-duration:${limitMs}ms`,\n action,\n evaluate: (s) =>\n s.elapsedMs > limitMs\n ? {\n message: `elapsed ${s.elapsedMs}ms exceeds ${limitMs}ms`,\n observed: s.elapsedMs,\n limit: limitMs,\n }\n : undefined,\n };\n}\n\n/**\n * Stop / warn on a spin loop: the same step (`name` + `signature`) repeated\n * `count` or more times within the last `window` steps. Steps without a `name`\n * are ignored (no identity to compare).\n */\nexport function spinLoop(\n options: { count: number; window: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count, window } = options;\n return {\n name: `spin-loop:${count}/${window}`,\n action,\n evaluate: (s, step) => {\n const key = stepKey(step);\n if (key === undefined) return undefined;\n const recent = s.history.slice(-window);\n let occurrences = 0;\n for (const h of recent) if (h.key === key) occurrences++;\n return occurrences >= count\n ? {\n message: `step \"${step?.name}\" repeated ${occurrences}× in last ${window}`,\n observed: occurrences,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/** Stop / warn on `count` or more consecutive failing steps. */\nexport function errorLoop(\n options: { count: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const { count } = options;\n return {\n name: `error-loop:${count}`,\n action,\n evaluate: (s) => {\n let streak = 0;\n for (let i = s.history.length - 1; i >= 0; i--) {\n if (s.history[i].error) streak++;\n else break;\n }\n return streak >= count\n ? {\n message: `${streak} consecutive errors`,\n observed: streak,\n limit: count,\n }\n : undefined;\n },\n };\n}\n\n/**\n * Approximate context-window sizes (in input tokens) for common models.\n * Matched exactly first, then by longest key prefix. Override via the `limit`\n * option on {@link contextBudget}.\n */\nexport const CONTEXT_LIMITS: Record<string, number> = {\n 'gpt-4o': 128_000,\n 'gpt-4.1': 1_047_576,\n 'gpt-4': 8192,\n 'o3-mini': 200_000,\n 'claude-opus-4': 200_000,\n 'claude-sonnet-4': 200_000,\n 'claude-3-5-sonnet': 200_000,\n 'claude-3-5-haiku': 200_000,\n 'claude-3': 200_000,\n 'gemini-1.5-pro': 2_097_152,\n 'gemini-1.5-flash': 1_048_576,\n 'gemini-2.0-flash': 1_048_576,\n};\n\nfunction resolveContextLimit(model: string): number | undefined {\n const exact = CONTEXT_LIMITS[model];\n if (exact) return exact;\n let best: number | undefined;\n let bestLength = 0;\n for (const key of Object.keys(CONTEXT_LIMITS)) {\n if (model.startsWith(key) && key.length > bestLength) {\n best = CONTEXT_LIMITS[key];\n bestLength = key.length;\n }\n }\n return best;\n}\n\n/**\n * Stop / warn when cumulative input tokens reach `threshold` (default 0.9) of\n * the model's context window. Provide an explicit `limit`, or a `model` to look\n * one up from {@link CONTEXT_LIMITS}.\n */\nexport function contextBudget(\n options: { model?: string; limit?: number; threshold?: number },\n action: GuardAction = 'stop',\n): GenAiGuardRule {\n const threshold = options.threshold ?? 0.9;\n const limit =\n options.limit ?? (options.model ? resolveContextLimit(options.model) : undefined);\n return {\n name: `context-budget:${Math.round(threshold * 100)}%`,\n action,\n evaluate: (s) => {\n if (!limit) return undefined;\n const ratio = s.inputTokens / limit;\n return ratio >= threshold\n ? {\n message: `context ${Math.round(ratio * 100)}% full (${s.inputTokens}/${limit})`,\n observed: round(ratio),\n limit: threshold,\n }\n : undefined;\n },\n };\n}\n\nfunction stepKey(step: GenAiGuardStep | undefined): string | undefined {\n if (!step || step.name === undefined) return undefined;\n return `${step.name}\u0000${step.signature ?? ''}`;\n}\n\n// --- Shorthand rule parser -------------------------------------------------\n\nfunction parseScaledNumber(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*([kmb])?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid number: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const scale = { k: 1e3, m: 1e6, b: 1e9 }[m[2]?.toLowerCase() ?? ''] ?? 1;\n return value * scale;\n}\n\nfunction parseDurationMs(raw: string): number {\n const m = /^(\\d+(?:\\.\\d+)?)\\s*(ms|s|m|h)?$/i.exec(raw.trim());\n if (!m) throw new Error(`invalid duration: \"${raw}\"`);\n const value = Number.parseFloat(m[1]);\n const unit = (m[2] ?? 's').toLowerCase();\n const scale = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 }[unit] ?? 1000;\n return value * scale;\n}\n\n/**\n * Parse a comma-separated shorthand rule string into {@link GenAiGuardRule}s.\n * All parsed rules default to the `stop` action — pass `defaultAction: 'warn'`\n * to warn instead, or build typed rules directly for per-rule actions.\n *\n * Supported tokens:\n * - `budget:$5` / `cost:$5` — {@link costCeiling}\n * - `tokens:100k` — {@link tokenCeiling} (`k`/`m`/`b` suffixes)\n * - `loop:3/10` — {@link spinLoop} (count / window)\n * - `errors:3` — {@link errorLoop}\n * - `max-tools:50` — {@link maxToolCalls}\n * - `max-steps:100` — {@link maxSteps}\n * - `timeout:30m` — {@link maxDuration} (`ms`/`s`/`m`/`h`, default seconds)\n * - `context:0.9` / `context:0.9@gpt-4o` — {@link contextBudget}\n */\nexport function parseGuardRules(\n shorthand: string,\n options?: { defaultAction?: GuardAction },\n): GenAiGuardRule[] {\n const action = options?.defaultAction ?? 'stop';\n const rules: GenAiGuardRule[] = [];\n for (const token of shorthand.split(',')) {\n const trimmed = token.trim();\n if (!trimmed) continue;\n const sep = trimmed.indexOf(':');\n if (sep === -1) throw new Error(`invalid guard rule: \"${trimmed}\"`);\n const key = trimmed.slice(0, sep).trim().toLowerCase();\n const value = trimmed.slice(sep + 1).trim();\n\n switch (key) {\n case 'budget':\n case 'cost': {\n rules.push(costCeiling(parseScaledNumber(value.replace(/^\\$/, '')), action));\n break;\n }\n case 'tokens': {\n rules.push(tokenCeiling(parseScaledNumber(value), action));\n break;\n }\n case 'loop': {\n const [count, window] = value.split('/');\n rules.push(\n spinLoop(\n { count: parseScaledNumber(count), window: parseScaledNumber(window) },\n action,\n ),\n );\n break;\n }\n case 'errors': {\n rules.push(errorLoop({ count: parseScaledNumber(value) }, action));\n break;\n }\n case 'max-tools': {\n rules.push(maxToolCalls(parseScaledNumber(value), action));\n break;\n }\n case 'max-steps': {\n rules.push(maxSteps(parseScaledNumber(value), action));\n break;\n }\n case 'timeout': {\n rules.push(maxDuration(parseDurationMs(value), action));\n break;\n }\n case 'context': {\n const [thr, model] = value.split('@');\n rules.push(\n contextBudget(\n { threshold: Number.parseFloat(thr), model: model?.trim() || undefined },\n action,\n ),\n );\n break;\n }\n default: {\n throw new Error(`unknown guard rule: \"${key}\"`);\n }\n }\n }\n return rules;\n}\n\n// --- Guard implementation --------------------------------------------------\n\nfunction recordTelemetry(\n ctx: GuardSink,\n state: GuardState,\n newViolations: GuardViolation[],\n stopped: boolean,\n): void {\n ctx.setAttributes({\n [GEN_AI.SESSION_COST_USD]: round(state.costUsd),\n [GEN_AI.SESSION_INPUT_TOKENS]: state.inputTokens,\n [GEN_AI.SESSION_OUTPUT_TOKENS]: state.outputTokens,\n [GEN_AI.SESSION_STEP_COUNT]: state.stepCount,\n [GEN_AI.SESSION_TOOL_CALL_COUNT]: state.toolCallCount,\n [GEN_AI.SESSION_ERROR_COUNT]: state.errorCount,\n });\n if (newViolations.length === 0) return;\n if (stopped) ctx.setAttributes({ [GEN_AI.GUARD_STOPPED]: true });\n for (const violation of newViolations) {\n ctx.track(\n violation.action === 'stop'\n ? GEN_AI_GUARD_EVENT.STOP\n : GEN_AI_GUARD_EVENT.WARNING,\n {\n [GEN_AI.GUARD_RULE]: violation.rule,\n [GEN_AI.GUARD_ACTION]: violation.action,\n [GEN_AI.GUARD_MESSAGE]: violation.message,\n [GEN_AI.GUARD_OBSERVED]: violation.observed,\n [GEN_AI.GUARD_LIMIT]: violation.limit,\n },\n );\n if (violation.action === 'stop') {\n securityEvent(\n {\n name: 'llm.guard.triggered',\n category: 'llm',\n outcome: 'blocked',\n severity: 'error',\n reason: violation.rule,\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n },\n { onMissingContext: 'warn' },\n );\n }\n }\n}\n\n/**\n * Create a guard that supervises an in-process agent run against `rules`.\n */\nexport function createGenAiGuard(options: GenAiGuardOptions): GenAiGuard {\n const { rules } = options;\n const onStop = options.onStop ?? 'throw';\n const historyLimit = options.historyLimit ?? 256;\n const now = options.now ?? Date.now;\n const startedAt = now();\n\n const controller = new AbortController();\n const history: { key: string | undefined; error: boolean }[] = [];\n const fired = new Set<string>();\n const violations: GuardViolation[] = [];\n\n let costUsd = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let stepCount = 0;\n let toolCallCount = 0;\n let errorCount = 0;\n let stopped = false;\n\n function snapshot(): GuardState {\n const at = now();\n return {\n startedAt,\n now: at,\n elapsedMs: at - startedAt,\n costUsd,\n inputTokens,\n outputTokens,\n stepCount,\n toolCallCount,\n errorCount,\n history,\n };\n }\n\n function evaluate(\n step: GenAiGuardStep | undefined,\n ctx: GuardSink | undefined,\n ): GuardViolation[] {\n const state = snapshot();\n const newViolations: GuardViolation[] = [];\n\n for (const rule of rules) {\n if (fired.has(rule.name)) continue;\n const hit = rule.evaluate(state, step);\n if (!hit) continue;\n fired.add(rule.name);\n const violation: GuardViolation = {\n rule: rule.name,\n action: rule.action ?? 'stop',\n message: hit.message,\n observed: hit.observed,\n limit: hit.limit,\n };\n violations.push(violation);\n newViolations.push(violation);\n }\n\n const stopViolation = newViolations.find((v) => v.action === 'stop');\n if (stopViolation && !stopped) {\n stopped = true;\n if (onStop !== 'silent') controller.abort(toStopError(stopViolation, state));\n }\n\n if (ctx) recordTelemetry(ctx, state, newViolations, stopped);\n\n if (stopViolation && onStop === 'throw') {\n throw toStopError(stopViolation, snapshot());\n }\n return newViolations;\n }\n\n return {\n record(step, ctx) {\n stepCount++;\n if (step.kind === 'tool') toolCallCount++;\n if (step.error) errorCount++;\n if (step.usage) {\n costUsd += step.usage.costUsd ?? 0;\n inputTokens += step.usage.inputTokens ?? 0;\n outputTokens += step.usage.outputTokens ?? 0;\n }\n history.push({ key: stepKey(step), error: step.error === true });\n if (history.length > historyLimit) history.shift();\n return evaluate(step, ctx);\n },\n check(ctx) {\n return evaluate(undefined, ctx);\n },\n get state() {\n return snapshot();\n },\n get signal() {\n return controller.signal;\n },\n get stopped() {\n return stopped;\n },\n get violations() {\n return violations;\n },\n };\n}\n\nfunction toStopError(violation: GuardViolation, state: GuardState): Error {\n return createStructuredError({\n name: 'GenAiGuardStop',\n code: 'GEN_AI_GUARD_STOP',\n status: 429,\n message: `Guard rule \"${violation.rule}\" halted the run: ${violation.message}`,\n why: 'A stop-action guard rule crossed its threshold during the agent run.',\n fix: 'Raise the limit, fix the loop, or handle GEN_AI_GUARD_STOP to wind the run down cleanly.',\n details: {\n rule: violation.rule,\n observed: violation.observed,\n limit: violation.limit,\n costUsd: round(state.costUsd),\n stepCount: state.stepCount,\n },\n });\n}\n\n// --- Budget preset ---------------------------------------------------------\n\nexport interface GenAiBudgetOptions {\n /** Hard cost ceiling in USD — a `stop` rule. */\n maxCostUsd?: number;\n /** Soft cost threshold in USD — a `warn` rule. */\n warnAtUsd?: number;\n /** Hard token ceiling (input + output) — a `stop` rule. */\n maxTokens?: number;\n /** Hard tool-call ceiling — a `stop` rule. */\n maxToolCalls?: number;\n /** Hard wall-clock ceiling in ms — a `stop` rule. */\n maxDurationMs?: number;\n /** What to do when a `stop` rule fires. Defaults to `throw`. */\n onStop?: GuardStopBehavior;\n /** Clock injection point, for tests. */\n now?: () => number;\n}\n\n/**\n * Convenience preset that wires the common cost / token / tool-call / duration\n * ceilings into a {@link createGenAiGuard}. Returns the same {@link GenAiGuard}.\n */\nexport function createGenAiBudget(options: GenAiBudgetOptions): GenAiGuard {\n const rules: GenAiGuardRule[] = [];\n if (options.warnAtUsd !== undefined) {\n rules.push(costCeiling(options.warnAtUsd, 'warn'));\n }\n if (options.maxCostUsd !== undefined) {\n rules.push(costCeiling(options.maxCostUsd, 'stop'));\n }\n if (options.maxTokens !== undefined) {\n rules.push(tokenCeiling(options.maxTokens, 'stop'));\n }\n if (options.maxToolCalls !== undefined) {\n rules.push(maxToolCalls(options.maxToolCalls, 'stop'));\n }\n if (options.maxDurationMs !== undefined) {\n rules.push(maxDuration(options.maxDurationMs, 'stop'));\n }\n return createGenAiGuard({\n rules,\n onStop: options.onStop,\n now: options.now,\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"observer-BoQNdbkg.cjs","names":["SpanStatusCode","otelTrace","otelContext","SpanStatusCode","genAiResponseAttributes","estimateLLMCost","genAiUsageAttributes","GEN_AI","genAiToolAttributes","agentContextFromSpan","genAiSpanName","GEN_AI_OPERATION","SpanKind","genAiWorkflowAttributes","genAiAgentAttributes","genAiRequestAttributes","recordPlanStep","recordInputProvenance","recordMemoryAccess","recordRenderOutput","normalizeAiSdkProvider","GEN_AI_OPERATION","AI_SDK_AGENT_NAME","normalizeAiSdkProvider","AI_SDK_AGENT_NAME","otelContext","otelTrace","GEN_AI_TOOL_TYPE","GEN_AI_OPERATION","GEN_AI_TOOL_TYPE","GEN_AI_OPERATION","normalizeAiSdkProvider"],"sources":["../src/observer/span-registry.ts","../src/observer/observer.ts","../src/observer/ai-sdk-shapes.ts","../src/observer/ai-sdk.ts","../src/observer/ai-sdk-messages.ts","../src/observer/ai-sdk-telemetry.ts","../src/observer/ai-sdk-channel.ts","../src/observer/langchain.ts"],"sourcesContent":["/**\n * Tracks the spans currently open in a {@link createGenAiObserver} stream and\n * the parent each was started under, so the observer can:\n *\n * - look up a parent span when a child starts, and\n * - force-close any descendant whose terminal event never arrived because its\n * parent ended first (an aborted tool call, a cancelled turn, a run that was\n * interrupted). Without this, abandoned spans leak open forever.\n */\n\nimport { SpanStatusCode, type Span, type TimeInput } from '@opentelemetry/api';\n\ninterface Entry {\n span: Span;\n parentId: string | undefined;\n}\n\nexport class SpanRegistry {\n private readonly entries = new Map<string, Entry>();\n\n /** Record an open span and the parent it was started under. */\n add(id: string, span: Span, parentId: string | undefined): void {\n this.entries.set(id, { span, parentId });\n }\n\n /** The open span for `id`, if any (used to parent a starting child). */\n spanFor(id: string): Span | undefined {\n return this.entries.get(id)?.span;\n }\n\n /** Remove and return the open span for `id`. */\n take(id: string): Span | undefined {\n const entry = this.entries.get(id);\n if (!entry) return undefined;\n this.entries.delete(id);\n return entry.span;\n }\n\n /**\n * Close every still-open descendant of `parentId` as ERROR — their terminal\n * event never arrived because the parent ended first. Depth-first, so the\n * deepest leaks close before their own parents.\n */\n reapDescendants(\n parentId: string,\n message: string,\n endTime?: TimeInput,\n ): void {\n // Deleting from a Map during its own for…of is well-defined; recursion only\n // removes deeper descendants (parentId === id), never an unvisited sibling.\n for (const [id, entry] of this.entries) {\n if (entry.parentId !== parentId) continue;\n this.entries.delete(id);\n this.reapDescendants(id, message, endTime);\n entry.span.setStatus({ code: SpanStatusCode.ERROR, message });\n entry.span.end(endTime);\n }\n }\n\n /** Count of spans still open — for tests and leak assertions. */\n get openCount(): number {\n return this.entries.size;\n }\n}\n","/**\n * `createGenAiObserver` — turn a stream of GenAI lifecycle events into a\n * canonical `gen_ai.*` span tree.\n *\n * A complement to {@link traceGenAI}: where the wrapper instruments code you\n * own, the observer instruments a framework that emits its own event stream.\n * Subscribe it once and feed every event through it; it reconstructs the span\n * hierarchy, estimates cost, force-closes abandoned children, and keeps\n * sensitive content off spans unless you opt in.\n *\n * Scope: this adapter records span attributes only. It deliberately does not\n * emit the `gen_ai.*` log events (`inference.operation.details`,\n * `operation.exception`) that `recordInferenceDetails` provides — the source\n * event stream already carries that detail.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * observe({ type: 'agent.start', id: 'a1', agent: { name: 'planner' } });\n * observe({ type: 'chat.start', id: 'c1', parentId: 'a1',\n * request: { provider: 'openai', model: 'gpt-4o' } });\n * observe({ type: 'chat.end', id: 'c1',\n * usage: { inputTokens: 412, outputTokens: 87 } });\n * observe({ type: 'agent.end', id: 'a1' });\n * ```\n */\n\nimport {\n context as otelContext,\n SpanKind,\n SpanStatusCode,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n} from '@opentelemetry/api';\nimport {\n genAiAgentAttributes,\n genAiRequestAttributes,\n genAiResponseAttributes,\n genAiToolAttributes,\n genAiUsageAttributes,\n genAiWorkflowAttributes,\n type GenAiAttributeMap,\n} from '../attributes.js';\nimport { estimateLLMCost } from '../cost.js';\nimport { setGenAiContent, type GenAiContentSink } from '../events.js';\nimport { GEN_AI, GEN_AI_OPERATION, genAiSpanName } from '../semconv.js';\nimport {\n recordInputProvenance,\n recordMemoryAccess,\n recordPlanStep,\n recordRenderOutput,\n} from '../agent/agent-security.js';\nimport { agentContextFromSpan } from '../agent/context.js';\nimport { SpanRegistry } from './span-registry.js';\nimport type {\n ChatEndEvent,\n ChatStartEvent,\n GenAiObserver,\n GenAiObserverEvent,\n GenAiObserverOptions,\n SpanEnd,\n InputProvenanceEvent,\n MemoryAccessEvent,\n PlanStepEvent,\n RenderOutputEvent,\n ToolEndEvent,\n ToolStartEvent,\n} from './types.js';\n\nconst ORPHAN_MESSAGE =\n 'Parent ended before this span received its terminal event.';\n\n/** The `*.start` half of the event union — the events that open a span. */\ntype StartEvent = Extract<GenAiObserverEvent, { type: `${string}.start` }>;\n\nexport function createGenAiObserver(\n options: GenAiObserverOptions = {},\n): GenAiObserver {\n const tracer =\n options.tracer ?? otelTrace.getTracer('autotel-genai/observer');\n const registry = new SpanRegistry();\n\n /** Parent context: a tracked parent span, else the resolver, else root. */\n function parentContext(event: StartEvent): Context | undefined {\n const parentSpan = event.parentId\n ? registry.spanFor(event.parentId)\n : undefined;\n if (parentSpan) return otelTrace.setSpan(otelContext.active(), parentSpan);\n return options.resolveParentContext?.(event);\n }\n\n /** Start a span, register it under its parent, and return it. */\n function start(\n event: StartEvent,\n name: string,\n kind: SpanKind,\n attributes: GenAiAttributeMap,\n ): Span {\n const parent = parentContext(event);\n const spanOptions: SpanOptions = {\n kind,\n attributes,\n startTime: event.startTime,\n links: event.links,\n root: parent === undefined,\n };\n const span = tracer.startSpan(name, spanOptions, parent);\n registry.add(event.id, span, event.parentId);\n options.onSpanStart?.(event.id, span);\n return span;\n }\n\n /**\n * Close the span for `event.id`: first force-close any descendant whose\n * terminal event never arrived, then decorate, set status, and end.\n */\n function end(event: SpanEnd, decorate?: (span: Span) => void): void {\n const span = registry.take(event.id);\n if (!span) return;\n registry.reapDescendants(event.id, ORPHAN_MESSAGE, event.endTime);\n decorate?.(span);\n if (event.error !== undefined) {\n const message = errorMessage(event.error);\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n // The SDK normalizes a string or Error; hand it the original Error when\n // we have one, otherwise the message we already derived.\n span.recordException(\n event.error instanceof Error ? event.error : (message ?? 'error'),\n );\n }\n span.end(event.endTime);\n }\n\n /** Run the privacy gate; without it, content is always omitted. */\n function approvedContent(\n event: GenAiObserverEvent,\n ): GenAiObserverEvent | undefined {\n if (!options.exportContent) return undefined;\n try {\n return options.exportContent({ ...event });\n } catch (error) {\n console.error('[autotel-genai:observer] exportContent failed:', error);\n return undefined;\n }\n }\n\n function applyChatStart(span: Span, event: ChatStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'chat.start') return;\n setGenAiContent(spanSink(span), {\n inputMessages: content.inputMessages,\n systemInstructions: content.systemInstructions,\n });\n }\n\n function applyChatEnd(span: Span, event: ChatEndEvent): void {\n if (event.response) {\n span.setAttributes(genAiResponseAttributes(event.response));\n }\n if (event.usage) {\n const costModel = event.costModel ?? event.response?.model;\n const costUsd = costModel\n ? estimateLLMCost(costModel, event.usage)\n : undefined;\n span.setAttributes(genAiUsageAttributes({ ...event.usage, costUsd }));\n }\n if (event.streaming) {\n const { timeToFinish, outputTokensPerSecond, timePerOutputChunk } =\n event.streaming;\n const streamingAttrs: GenAiAttributeMap = {};\n if (timeToFinish !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_TO_FINISH] = timeToFinish;\n }\n if (outputTokensPerSecond !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_OUTPUT_TOKENS_PER_SECOND] =\n outputTokensPerSecond;\n }\n if (timePerOutputChunk !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_PER_OUTPUT_CHUNK] =\n timePerOutputChunk;\n }\n span.setAttributes(streamingAttrs);\n }\n const content = approvedContent(event);\n if (content?.type === 'chat.end') {\n setGenAiContent(spanSink(span), {\n outputMessages: content.outputMessages,\n });\n }\n }\n\n function applyToolStart(span: Span, event: ToolStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.start') return;\n span.setAttributes(\n genAiToolAttributes({ callArguments: content.callArguments }),\n );\n }\n\n function applyToolEnd(span: Span, event: ToolEndEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.end') return;\n span.setAttributes(genAiToolAttributes({ callResult: content.callResult }));\n }\n\n function stampOnParent(\n parentId: string,\n apply: (ctx: ReturnType<typeof agentContextFromSpan>) => void,\n ): void {\n const parent = registry.spanFor(parentId);\n if (!parent) return;\n apply(agentContextFromSpan(parent));\n }\n\n return (event: GenAiObserverEvent): void => {\n switch (event.type) {\n case 'workflow.start': {\n start(\n event,\n genAiSpanName(\n GEN_AI_OPERATION.INVOKE_WORKFLOW,\n event.workflow.workflowName,\n ),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_WORKFLOW,\n ...genAiWorkflowAttributes(event.workflow),\n },\n );\n return;\n }\n case 'agent.start': {\n const internal = !event.remote;\n start(\n event,\n genAiSpanName(GEN_AI_OPERATION.INVOKE_AGENT, event.agent.name),\n internal ? SpanKind.INTERNAL : SpanKind.CLIENT,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_AGENT,\n ...(event.provider\n ? { [GEN_AI.PROVIDER_NAME]: event.provider }\n : {}),\n ...genAiAgentAttributes(event.agent, { internal }),\n },\n );\n return;\n }\n case 'chat.start': {\n const operation = event.request.operation ?? GEN_AI_OPERATION.CHAT;\n const span = start(\n event,\n genAiSpanName(operation, event.request.model),\n SpanKind.CLIENT,\n genAiRequestAttributes({ ...event.request, operation }),\n );\n applyChatStart(span, event);\n return;\n }\n case 'tool.start': {\n const span = start(\n event,\n genAiSpanName(GEN_AI_OPERATION.EXECUTE_TOOL, event.tool.name),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.EXECUTE_TOOL,\n ...genAiToolAttributes(event.tool),\n },\n );\n applyToolStart(span, event);\n return;\n }\n case 'workflow.end':\n case 'agent.end': {\n end(event);\n return;\n }\n case 'chat.end': {\n end(event, (span) => applyChatEnd(span, event));\n return;\n }\n case 'tool.end': {\n end(event, (span) => applyToolEnd(span, event));\n return;\n }\n case 'plan.step': {\n stampOnParent(event.parentId, (ctx) =>\n recordPlanStep({\n ctx,\n stepIndex: event.stepIndex,\n toolIntents: event.toolIntents,\n policyIds: event.policyIds,\n summary: event.summary,\n }),\n );\n return;\n }\n case 'input.provenance': {\n stampOnParent(event.parentId, (ctx) =>\n recordInputProvenance({ ctx, provenance: event.provenance }),\n );\n return;\n }\n case 'memory.access': {\n stampOnParent(event.parentId, (ctx) =>\n recordMemoryAccess({\n ctx,\n operation: event.operation,\n isolationKey: event.isolationKey,\n contentHash: event.contentHash,\n }),\n );\n return;\n }\n case 'render.output': {\n stampOnParent(event.parentId, (ctx) =>\n recordRenderOutput({\n ctx,\n format: event.format,\n containsUrl: event.containsUrl,\n urlCount: event.urlCount,\n }),\n );\n return;\n }\n default: {\n const unexpected: never = event;\n void unexpected;\n }\n }\n };\n}\n\n/** A {@link GenAiContentSink} backed by a raw span (only `setAttributes` is used). */\nfunction spanSink(span: Span): GenAiContentSink {\n return {\n setAttributes: (attrs) => {\n span.setAttributes(attrs);\n },\n track: () => {},\n };\n}\n\nfunction errorMessage(error: unknown): string | undefined {\n if (error === undefined) return undefined;\n if (typeof error === 'string') return error || undefined;\n if (error instanceof Error) return error.message;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n","/**\n * Shared structural converters from Vercel AI SDK shapes to autotel inputs.\n *\n * The AI SDK reports the same data three ways — the lifecycle `Telemetry`\n * events ({@link autotelTelemetry}), the `ai:telemetry` channel messages\n * ({@link subscribeAiTelemetry}), and a finished call result\n * ({@link observeAiSdkResult}) — and across v4/v5 with differing field names\n * (`promptTokens` vs `inputTokens`, flat vs nested token details). These helpers\n * are the single place that normalizes those shapes, so the three entry points\n * stay consistent and none of them re-derive the mapping.\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiRequestInput } from '../attributes.js';\nimport type { TokenUsage } from '../cost.js';\nimport { GEN_AI_OPERATION } from '../semconv.js';\n\n/** Every AI SDK token-usage field name we understand (v4 + v5, flat + nested). */\nexport interface AiSdkUsageShape {\n inputTokens?: number;\n outputTokens?: number;\n /** v4 aliases. */\n promptTokens?: number;\n completionTokens?: number;\n /** Embedding usage. */\n tokens?: number;\n /** Flat v5 detail fields. */\n reasoningTokens?: number;\n cachedInputTokens?: number;\n /** Nested v5 detail fields. */\n inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n}\n\n/** Map any AI SDK usage shape to {@link TokenUsage}; `undefined` if all empty. */\nexport function toTokenUsage(\n usage: AiSdkUsageShape | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined;\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens ?? usage.promptTokens ?? usage.tokens,\n outputTokens: usage.outputTokens ?? usage.completionTokens,\n reasoningOutputTokens:\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens,\n cacheReadInputTokens:\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens,\n cacheCreationInputTokens: usage.inputTokenDetails?.cacheWriteTokens,\n };\n return Object.values(tokenUsage).some((v) => v !== undefined)\n ? tokenUsage\n : undefined;\n}\n\n/** Normalize an optional AI SDK provider id to a canonical `gen_ai` provider. */\nexport function normalizeProvider(provider: string | undefined) {\n return provider ? normalizeAiSdkProvider(provider) : undefined;\n}\n\n/** Request-side fields shared by the lifecycle and channel model-call events. */\nexport interface AiSdkRequestShape {\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n}\n\n/** Build the `chat` request attributes from a model-call start event. */\nexport function toChatRequest(event: AiSdkRequestShape): GenAiRequestInput {\n return {\n operation: GEN_AI_OPERATION.CHAT,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n temperature: event.temperature,\n maxTokens: event.maxOutputTokens,\n topP: event.topP,\n topK: event.topK,\n frequencyPenalty: event.frequencyPenalty,\n presencePenalty: event.presencePenalty,\n stopSequences: event.stopSequences,\n seed: event.seed,\n };\n}\n","/**\n * Vercel AI SDK → {@link createGenAiObserver} glue.\n *\n * The AI SDK has no global event stream; its stable surface is the result of a\n * `generateText`/`streamText` call (`steps`, each with `usage`, `toolCalls`,\n * `toolResults`, `response`). {@link observeAiSdkResult} walks that result and\n * emits observer events:\n *\n * - a trivial single-step call with no tools → one `chat` span;\n * - a multi-step or tool-using call → an `invoke_agent` wrapper (mirroring the\n * AI SDK's own `invoke_agent › chat › execute_tool` hierarchy) with a `chat`\n * per step and an `execute_tool` per tool call.\n *\n * Typed structurally against the AI SDK result shape so it pulls in no\n * dependency and tolerates v4/v5 field differences (`promptTokens` vs\n * `inputTokens`). Cost is left to the observer, which prices `chat` usage.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * const result = await generateText({ model: openai('gpt-4o'), prompt });\n * observeAiSdkResult(observe, result, { id: 'gen-1', provider: 'openai', model: 'gpt-4o' });\n * ```\n */\n\nimport type { TimeInput } from '@opentelemetry/api';\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiProviderName } from '../semconv.js';\nimport { toTokenUsage } from './ai-sdk-shapes.js';\nimport type { GenAiObserver } from './types.js';\n\n/** AI SDK usage object — canonical (v5) or legacy (v4) field names. */\nexport interface AiSdkUsage {\n inputTokens?: number;\n outputTokens?: number;\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AiSdkToolCall {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n}\n\nexport interface AiSdkToolResult {\n toolCallId?: string;\n toolName?: string;\n output?: unknown;\n result?: unknown;\n}\n\n/** One AI SDK step (also the shape of a single-call result). */\nexport interface AiSdkStep {\n usage?: AiSdkUsage;\n finishReason?: string;\n response?: { id?: string; modelId?: string; timestamp?: Date | number };\n toolCalls?: AiSdkToolCall[];\n toolResults?: AiSdkToolResult[];\n}\n\n/** An AI SDK `generateText`/`streamText` result. */\nexport interface AiSdkResult extends AiSdkStep {\n steps?: AiSdkStep[];\n}\n\nexport interface ObserveAiSdkOptions {\n /** Base span id; per-step and per-tool ids derive from it. */\n id: string;\n parentId?: string;\n /** AI SDK provider id (e.g. `openai`, `amazon-bedrock`); normalized for you. */\n provider?: string;\n /** Request model — used for the span name and as the cost model. */\n model?: string;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\nexport function observeAiSdkResult(\n observe: GenAiObserver,\n result: AiSdkResult,\n options: ObserveAiSdkOptions,\n): void {\n const provider: GenAiProviderName | undefined = options.provider\n ? normalizeAiSdkProvider(options.provider)\n : undefined;\n const steps = result.steps ?? [result];\n const toolCount = steps.reduce(\n (total, step) => total + (step.toolCalls?.length ?? 0),\n 0,\n );\n\n if (steps.length <= 1 && toolCount === 0) {\n emitChat(observe, steps[0] ?? result, {\n id: options.id,\n parentId: options.parentId,\n provider,\n model: options.model,\n });\n return;\n }\n\n observe({\n type: 'agent.start',\n id: options.id,\n parentId: options.parentId,\n provider,\n agent: { name: options.model ?? AI_SDK_AGENT_NAME },\n });\n steps.forEach((step, index) => {\n emitChat(observe, step, {\n id: `${options.id}:step:${index}`,\n parentId: options.id,\n provider,\n model: options.model,\n });\n emitTools(observe, step, options.id, index);\n });\n observe({ type: 'agent.end', id: options.id });\n}\n\ninterface ChatContext {\n id: string;\n parentId?: string;\n provider?: GenAiProviderName;\n model?: string;\n}\n\nfunction emitChat(\n observe: GenAiObserver,\n step: AiSdkStep,\n ctx: ChatContext,\n): void {\n observe({\n type: 'chat.start',\n id: ctx.id,\n parentId: ctx.parentId,\n request: { provider: ctx.provider, model: ctx.model },\n });\n const responseModel = step.response?.modelId;\n observe({\n type: 'chat.end',\n id: ctx.id,\n response: {\n model: responseModel,\n id: step.response?.id,\n finishReasons: step.finishReason ? [step.finishReason] : undefined,\n },\n usage: toTokenUsage(step.usage),\n costModel: ctx.model ?? responseModel,\n endTime: asTimeInput(step.response?.timestamp),\n });\n}\n\nfunction emitTools(\n observe: GenAiObserver,\n step: AiSdkStep,\n parentId: string,\n stepIndex: number,\n): void {\n const results = step.toolResults ?? [];\n (step.toolCalls ?? []).forEach((call, callIndex) => {\n const id = `${parentId}:tool:${call.toolCallId ?? `${stepIndex}.${callIndex}`}`;\n observe({\n type: 'tool.start',\n id,\n parentId,\n tool: { name: call.toolName, callId: call.toolCallId },\n callArguments: call.input ?? call.args,\n });\n const result = call.toolCallId\n ? results.find((r) => r.toolCallId === call.toolCallId)\n : results[callIndex];\n observe({\n type: 'tool.end',\n id,\n callResult: result?.output ?? result?.result,\n });\n });\n}\n\nfunction asTimeInput(value: Date | number | undefined): TimeInput | undefined {\n return value instanceof Date || typeof value === 'number' ? value : undefined;\n}\n","/**\n * Vercel AI SDK messages → GenAI SemConv message format.\n *\n * The AI SDK carries prompts as `ModelMessage[]` (role + string-or-parts) and\n * model output as `ContentPart[]`. The GenAI semantic conventions instead model\n * `gen_ai.input.messages` / `gen_ai.output.messages` as `{ role, parts }` where\n * each part is `{ type, ...typeFields }`:\n *\n * - text: `{ type: 'text', content }`\n * - reasoning: `{ type: 'reasoning', content }`\n * - tool_call: `{ type: 'tool_call', id, name, arguments }`\n * - tool_call_response: `{ type: 'tool_call_response', id, response }`\n * - blob/uri: media, referenced by `mime_type` only (never inlined)\n *\n * Typed structurally against the AI SDK shapes (no `ai` / `@ai-sdk/*` import).\n * Media bytes are deliberately not copied onto spans — only the modality and\n * mime type — so content capture never balloons span size or leaks raw blobs.\n */\n\nimport type { GenAiMessage, GenAiMessagePart } from '../events.js';\n\n// --- Structural views of the AI SDK message shapes -------------------------\n// Exported so the telemetry/channel event views can type their `messages` /\n// `content` fields against these directly — no per-call-site casts, and (with no\n// index signature) the real SDK `ModelMessage[]` / `ContentPart[]` stay\n// assignable to them.\n\nexport interface ModelMessageView {\n role?: string;\n content?: string | readonly ContentPartView[];\n}\n\nexport interface ContentPartView {\n type?: string;\n /** text / reasoning */\n text?: string;\n /** tool-call */\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n /** tool-result */\n output?: unknown;\n /** image / file */\n mediaType?: string;\n}\n\n/** Result of converting an AI SDK prompt: messages plus split-out system parts. */\nexport interface ConvertedPrompt {\n messages: GenAiMessage[];\n systemInstructions?: GenAiMessagePart[];\n}\n\n/**\n * Convert an AI SDK `ModelMessage[]` prompt to GenAI input messages, splitting\n * `system` messages out into `systemInstructions` (recorded separately per the\n * conventions).\n */\nexport function promptToGenAiMessages(\n messages: readonly ModelMessageView[] | undefined,\n): ConvertedPrompt {\n if (!messages?.length) return { messages: [] };\n\n const out: GenAiMessage[] = [];\n const system: GenAiMessagePart[] = [];\n\n for (const message of messages) {\n const role = message.role ?? 'user';\n const parts = contentToParts(message.content);\n if (role === 'system') {\n system.push(...parts);\n } else {\n out.push({ role, parts });\n }\n }\n\n return system.length > 0\n ? { messages: out, systemInstructions: system }\n : { messages: out };\n}\n\n/**\n * Convert the `ContentPart[]` of a model response into a single GenAI output\n * message (always `assistant`), optionally tagged with a `finish_reason`.\n */\nexport function contentToGenAiMessage(\n content: readonly ContentPartView[] | undefined,\n finishReason?: string,\n): GenAiMessage | undefined {\n if (!content?.length) return undefined;\n const message: GenAiMessage = {\n role: 'assistant',\n parts: content.flatMap((part) => partToGenAi(part)),\n };\n if (finishReason) message.finish_reason = finishReason;\n return message;\n}\n\nfunction contentToParts(\n content: string | readonly ContentPartView[] | undefined,\n): GenAiMessagePart[] {\n if (content === undefined) return [];\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : [];\n }\n return content.flatMap((part) => partToGenAi(part));\n}\n\n/** Map one AI SDK content part to zero or more GenAI parts. */\nfunction partToGenAi(part: ContentPartView): GenAiMessagePart[] {\n switch (part.type) {\n case 'text': {\n return part.text ? [{ type: 'text', content: part.text }] : [];\n }\n case 'reasoning': {\n return part.text ? [{ type: 'reasoning', content: part.text }] : [];\n }\n case 'tool-call': {\n return [\n {\n type: 'tool_call',\n id: part.toolCallId,\n name: part.toolName,\n arguments: part.input,\n },\n ];\n }\n case 'tool-result': {\n return [\n {\n type: 'tool_call_response',\n id: part.toolCallId,\n response: part.output,\n },\n ];\n }\n case 'image':\n case 'file': {\n // Reference media by modality + mime type only — never inline bytes.\n return [\n {\n type: 'blob',\n modality: part.type === 'image' ? 'image' : 'file',\n mime_type: part.mediaType,\n },\n ];\n }\n default: {\n return part.type ? [{ type: part.type }] : [];\n }\n }\n}\n","/**\n * `autotelTelemetry` — a Vercel AI SDK telemetry integration.\n *\n * Implements the AI SDK's `Telemetry` lifecycle interface (stable since ai v7)\n * so you register it once and every `generateText` / `streamText` / `embed`\n * call streams a canonical `gen_ai.*` span tree — live, as the operation runs,\n * not reconstructed after the fact:\n *\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n *\n * Where the built-in `@ai-sdk/otel` `OpenTelemetry` integration emits the same\n * `gen_ai.*` spans, this one additionally:\n *\n * - prices every model call (`gen_ai.usage.cost.usd`) from {@link MODEL_PRICING};\n * - records streaming throughput (`time_to_first_chunk`, `time_to_finish`,\n * `output_tokens_per_second`) on each `chat` span;\n * - keeps token usage on leaf `chat` spans only, so the `invoke_agent` root\n * never double-counts.\n *\n * It is push-based and concurrency-safe: every lifecycle event carries the\n * AI SDK `callId`, so interleaved concurrent generations never cross wires. It\n * is the live counterpart to {@link observeAiSdkResult}, which walks a finished\n * result instead.\n *\n * Typed structurally against the AI SDK event shapes — it pulls in **no**\n * dependency on `ai` / `@ai-sdk/*`. The returned object satisfies the AI SDK\n * `Telemetry` interface by structural compatibility (every event the SDK passes\n * is a superset of the fields read here), so `registerTelemetry(autotelTelemetry())`\n * type-checks without importing the interface.\n *\n * Scope: `generateText` / `streamText` (incl. tool loops and `output`) and\n * `embed` / `embedMany`. A tool's nested `generateText` and the provider's own\n * HTTP spans parent correctly via the `executeTool` / `executeLanguageModelCall`\n * context runners. `rerank` has no canonical `gen_ai` operation in the v1.42.0\n * registry and is intentionally not mapped.\n *\n * Message content (prompts, responses, tool arguments/results) is **off by\n * default**; enable it with `captureContent`, gated by the SDK's `recordInputs`\n * / `recordOutputs` and the optional `exportContent` privacy callback.\n */\n\nimport {\n context as otelContext,\n trace as otelTrace,\n type Context,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type {\n ChatStreamTiming,\n GenAiObserverEvent,\n GenAiObserver,\n} from './types.js';\n\n// --- Structural views of the AI SDK telemetry event shapes -----------------\n// Only the fields this integration reads. The real events are supersets, so a\n// callback typed against these is assignable to the AI SDK `Telemetry` methods.\n\ninterface OperationStartEvent {\n callId: string;\n /** e.g. `'ai.generateText'`, `'ai.streamText'`, `'ai.embed'`. */\n operationId?: string;\n provider?: string;\n modelId?: string;\n /** From telemetry settings — used as the agent name. */\n functionId?: string;\n}\n\ninterface LanguageModelCallStartEventView {\n callId: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n /** Standardized prompt messages (when content capture is on). */\n messages?: readonly ModelMessageView[];\n /** Whether the SDK call permits recording inputs (default true). */\n recordInputs?: boolean;\n}\n\ninterface LanguageModelCallEndEventView {\n callId: string;\n modelId?: string;\n responseId?: string;\n finishReason?: string;\n usage?: AiSdkUsageShape;\n /** Output content parts (when content capture is on). */\n content?: readonly ContentPartView[];\n /** Whether the SDK call permits recording outputs (default true). */\n recordOutputs?: boolean;\n performance?: {\n responseTimeMs?: number;\n effectiveOutputTokensPerSecond?: number;\n timeToFirstOutputMs?: number;\n };\n}\n\ninterface ToolCallView {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\ninterface ToolExecutionStartEventView {\n callId: string;\n toolCall: ToolCallView;\n recordInputs?: boolean;\n}\n\ninterface ToolOutputView {\n type?: string;\n output?: unknown;\n error?: unknown;\n}\n\ninterface ToolExecutionEndEventView {\n callId: string;\n toolCall: ToolCallView;\n toolOutput: ToolOutputView;\n recordOutputs?: boolean;\n}\n\ninterface OperationEndEventView {\n callId: string;\n}\n\ninterface AbortEventView {\n callId: string;\n reason?: unknown;\n}\n\ninterface EmbeddingModelCallEndEventView {\n callId: string;\n embedCallId?: string;\n provider?: string;\n modelId?: string;\n usage?: { tokens?: number };\n}\n\n/**\n * The subset of the AI SDK `Telemetry` interface this integration implements.\n * The returned object is assignable to the SDK's `Telemetry` type.\n */\nexport interface AutotelTelemetryIntegration {\n onStart(event: OperationStartEvent): void;\n onLanguageModelCallStart(event: LanguageModelCallStartEventView): void;\n onLanguageModelCallEnd(event: LanguageModelCallEndEventView): void;\n onToolExecutionStart(event: ToolExecutionStartEventView): void;\n onToolExecutionEnd(event: ToolExecutionEndEventView): void;\n onEmbedEnd(event: EmbeddingModelCallEndEventView): void;\n onEnd(event: OperationEndEventView): void;\n onAbort(event: AbortEventView): void;\n onError(event: unknown): void;\n executeLanguageModelCall<T>(\n options: { callId: string; execute: () => PromiseLike<T> },\n ): PromiseLike<T>;\n executeTool<T>(\n options: {\n callId: string;\n toolCallId: string;\n execute: () => PromiseLike<T>;\n },\n ): PromiseLike<T>;\n}\n\nexport interface AutotelTelemetryOptions {\n /**\n * Tracer to emit spans on. Defaults to the global `autotel-genai/observer`\n * tracer. Pass an application-owned tracer to share one configuration.\n */\n tracer?: Tracer;\n /**\n * Privacy gate applied to all captured content (prompts, responses, tool\n * arguments/results) before it is written to a span. Receives each\n * content-bearing observer event; return it (optionally redacted) to keep its\n * content, or `undefined` to drop that event's content. Only consulted when\n * {@link AutotelTelemetryOptions.captureContent} is enabled.\n */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /**\n * Capture prompt/response message content (`gen_ai.input.messages`,\n * `gen_ai.output.messages`, `gen_ai.system_instructions`) and tool\n * arguments/results. **Off by default** for privacy. When `true`, content is\n * still filtered through {@link AutotelTelemetryOptions.exportContent} if you\n * provide one (redact there); with no `exportContent`, all approved content is\n * written. The AI SDK's per-call `recordInputs` / `recordOutputs` are still\n * honored — either being `false` suppresses that side.\n */\n captureContent?: boolean;\n /**\n * Resolve an OpenTelemetry parent context for an operation that has no\n * tracked parent — e.g. attach the `invoke_agent` root to the incoming\n * request span. Return `undefined` to keep it a root.\n */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/** Per-call correlation state, keyed by the AI SDK `callId`. */\ninterface CallState {\n /** Whether an `invoke_agent` root span was opened for this call. */\n hasAgent: boolean;\n /** Whether the root operation is streaming (`streamText`) or not. */\n stream?: boolean;\n /** Stack of open `chat` span ids (one per in-flight model call). */\n openLm: string[];\n /** Monotonic counter for unique model-call span ids within the call. */\n lmSeq: number;\n /** FIFO of anonymous tool span ids when the SDK omits `toolCallId`. */\n anonymousToolIds: string[];\n /** Monotonic counter for unique anonymous tool span ids within the call. */\n toolSeq: number;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/**\n * Create an AI SDK telemetry integration that streams canonical `gen_ai.*`\n * spans (with cost + streaming timing) for every AI SDK operation.\n *\n * @example\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n */\nexport function autotelTelemetry(\n options: AutotelTelemetryOptions = {},\n): AutotelTelemetryIntegration {\n const captureContent = options.captureContent ?? false;\n // When content capture is on with no explicit gate, allow all approved\n // content; an `exportContent` callback (redaction) always wins if provided.\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n // Live spans by observer id, so the context runners can re-enter a span's\n // OTel context to nest provider HTTP calls / a tool's inner `generateText`.\n const spans = new Map<string, Span>();\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n onSpanStart: (id, span) => spans.set(id, span),\n });\n const calls = new Map<string, CallState>();\n\n function startToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n const seq = state?.toolSeq ?? 0;\n if (state) state.toolSeq += 1;\n const id = `${callId}:tool:anon:${seq}`;\n state?.anonymousToolIds.push(id);\n return id;\n }\n\n function endToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n return state?.anonymousToolIds.shift() ?? `${callId}:tool:anon:0`;\n }\n\n /** Run `execute` within `span`'s OTel context so nested work parents to it. */\n function runInSpan<T>(\n span: Span | undefined,\n execute: () => PromiseLike<T>,\n ): PromiseLike<T> {\n if (!span) return execute();\n return otelContext.with(\n otelTrace.setSpan(otelContext.active(), span),\n execute,\n );\n }\n\n return {\n onStart(event) {\n // Embedding / rerank operations are not `invoke_agent` roots; they are\n // handled as leaf spans by `onEmbedEnd` (rerank is unsupported).\n if (isNonAgentOperation(event.operationId)) return;\n const state: CallState = {\n hasAgent: true,\n stream: event.operationId?.includes('stream'),\n openLm: [],\n lmSeq: 0,\n anonymousToolIds: [],\n toolSeq: 0,\n };\n calls.set(event.callId, state);\n observe({\n type: 'agent.start',\n id: event.callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.functionId ?? event.modelId ?? AI_SDK_AGENT_NAME },\n });\n },\n\n onLanguageModelCallStart(event) {\n const state = calls.get(event.callId);\n const id = `${event.callId}:lm:${state ? state.lmSeq++ : 0}`;\n if (state) state.openLm.push(id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n request: { ...toChatRequest(event), stream: state?.stream },\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n },\n\n onLanguageModelCallEnd(event) {\n const state = calls.get(event.callId);\n const id = state?.openLm.pop() ?? `${event.callId}:lm:0`;\n const outputMessage =\n captureContent && event.recordOutputs !== false\n ? contentToGenAiMessage(event.content, event.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: event.modelId,\n id: event.responseId,\n finishReasons: event.finishReason ? [event.finishReason] : undefined,\n timeToFirstChunk: msToSeconds(event.performance?.timeToFirstOutputMs),\n },\n usage: toTokenUsage(event.usage),\n costModel: event.modelId,\n streaming: mapStreaming(event.performance),\n outputMessages: outputMessage ? [outputMessage] : undefined,\n });\n spans.delete(id);\n },\n\n onToolExecutionStart(event) {\n const state = calls.get(event.callId);\n const id = startToolSpanId(event.callId, state, event.toolCall);\n observe({\n type: 'tool.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n tool: {\n name: event.toolCall.toolName,\n callId: event.toolCall.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall.input\n : undefined,\n });\n },\n\n onToolExecutionEnd(event) {\n const state = calls.get(event.callId);\n const id = endToolSpanId(event.callId, state, event.toolCall);\n const isError = event.toolOutput.type === 'tool-error';\n observe({\n type: 'tool.end',\n id,\n error: isError ? event.toolOutput.error : undefined,\n callResult:\n !isError && captureContent && event.recordOutputs !== false\n ? event.toolOutput.output\n : undefined,\n });\n spans.delete(id);\n },\n\n onEmbedEnd(event) {\n const id = `${event.callId}:embed:${event.embedCallId ?? '0'}`;\n observe({\n type: 'chat.start',\n id,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n observe({\n type: 'chat.end',\n id,\n response: { model: event.modelId },\n usage:\n event.usage?.tokens === undefined\n ? undefined\n : { inputTokens: event.usage.tokens },\n costModel: event.modelId,\n });\n spans.delete(id);\n },\n\n onEnd(event) {\n if (!calls.delete(event.callId)) return;\n observe({ type: 'agent.end', id: event.callId });\n spans.delete(event.callId);\n },\n\n onAbort(event) {\n if (!calls.delete(event.callId)) return;\n observe({\n type: 'agent.end',\n id: event.callId,\n error: event.reason ?? 'aborted',\n });\n spans.delete(event.callId);\n },\n\n onError(event) {\n // The dispatcher invokes `onError` with `{ callId, error }`. Close the\n // matching `invoke_agent` root when we can correlate it; otherwise leave\n // it for `onEnd`/`onAbort` (the observer reaps any open children on close).\n const callId = errorCallId(event);\n if (callId === undefined || !calls.delete(callId)) return;\n observe({\n type: 'agent.end',\n id: callId,\n error: errorValue(event) ?? event,\n });\n spans.delete(callId);\n },\n\n executeLanguageModelCall(options) {\n // `onLanguageModelCallStart` already opened this call's `chat` span (it is\n // the top of the stack). Run the provider call inside it so any\n // auto-instrumented HTTP spans become its children.\n const state = calls.get(options.callId);\n const id = state?.openLm.at(-1);\n return runInSpan(id ? spans.get(id) : undefined, options.execute);\n },\n\n executeTool(options) {\n // `onToolExecutionStart` already opened the tool span; run the tool's\n // `execute` inside it so a nested `generateText` nests under the tool.\n const id = `${options.callId}:tool:${options.toolCallId}`;\n return runInSpan(spans.get(id), options.execute);\n },\n };\n}\n\n/** Operations that are not `invoke_agent` roots (embeddings, rerank). */\nfunction isNonAgentOperation(operationId: string | undefined): boolean {\n if (!operationId) return false;\n return operationId.includes('embed') || operationId.includes('rerank');\n}\n\nfunction mapStreaming(\n performance: LanguageModelCallEndEventView['performance'],\n): ChatStreamTiming | undefined {\n if (!performance) return undefined;\n const timing: ChatStreamTiming = {\n timeToFinish: msToSeconds(performance.responseTimeMs),\n outputTokensPerSecond: performance.effectiveOutputTokensPerSecond,\n };\n return timing.timeToFinish !== undefined ||\n timing.outputTokensPerSecond !== undefined\n ? timing\n : undefined;\n}\n\nfunction msToSeconds(ms: number | undefined): number | undefined {\n return ms === undefined ? undefined : ms / 1000;\n}\n\nfunction errorCallId(event: unknown): string | undefined {\n if (\n typeof event === 'object' &&\n event !== null &&\n 'callId' in event &&\n typeof (event as { callId: unknown }).callId === 'string'\n ) {\n return (event as { callId: string }).callId;\n }\n return undefined;\n}\n\nfunction errorValue(event: unknown): unknown {\n if (typeof event === 'object' && event !== null && 'error' in event) {\n return (event as { error: unknown }).error;\n }\n return undefined;\n}\n","/**\n * `subscribeAiTelemetry` — the zero-config Vercel AI SDK path.\n *\n * Instead of `registerTelemetry(autotelTelemetry())`, subscribe to the AI SDK's\n * `ai:telemetry` Node [tracing channel](https://nodejs.org/api/diagnostics_channel.html).\n * The SDK publishes an operation span per `generateText` / `streamText` /\n * `languageModelCall` / `executeTool` / `embed` as long as the channel has a\n * subscriber — no registration call, and it binds async context across provider\n * calls and tool executions:\n *\n * ```ts\n * import { subscribeAiTelemetry } from 'autotel-genai/observer';\n *\n * const unsubscribe = subscribeAiTelemetry(); // once, at startup\n * ```\n *\n * Fidelity note: the channel exposes coarser data than the registered\n * {@link autotelTelemetry} integration. You get the canonical\n * `invoke_agent › chat › execute_tool` tree with token usage and cost (read\n * from each operation's result), but **not** the per-call streaming timing\n * (`time_to_first_chunk` etc.) that only the lifecycle `onLanguageModelCallEnd`\n * event carries. Prefer `registerTelemetry(autotelTelemetry())` when you can;\n * use this when you cannot add a registration call.\n *\n * No-ops (returning an unsubscribe that does nothing) on runtimes without Node\n * diagnostics-channel support, so it is safe to call unconditionally.\n */\n\nimport type { Context, Tracer } from '@opentelemetry/api';\nimport { subscribeTracingChannel } from 'autotel/diagnostics';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type { GenAiObserver, GenAiObserverEvent } from './types.js';\n\nconst AI_SDK_TELEMETRY_TRACING_CHANNEL = 'ai:telemetry';\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/** A published tracing-channel message: `{ type, event }` plus the settled result. */\ninterface ChannelMessage {\n type?: string;\n event?: ChannelEvent;\n result?: unknown;\n error?: unknown;\n}\n\ninterface ChannelEvent {\n callId?: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n toolCall?: { toolCallId?: string; toolName?: string; input?: unknown };\n messages?: readonly ModelMessageView[];\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\nexport interface SubscribeAiTelemetryOptions {\n /** Tracer to emit spans on. Defaults to the global observer tracer. */\n tracer?: Tracer;\n /**\n * Capture prompt/response content and tool arguments/results. Off by default.\n * @see {@link import('./ai-sdk-telemetry.js').AutotelTelemetryOptions.captureContent}\n */\n captureContent?: boolean;\n /** Privacy gate for captured content (redact or drop per event). */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /** Resolve an OTel parent context for otherwise-root operations. */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/**\n * Subscribe to the `ai:telemetry` tracing channel and emit canonical `gen_ai.*`\n * spans. Returns an unsubscribe function (a no-op on unsupported runtimes).\n */\nexport function subscribeAiTelemetry(\n options: SubscribeAiTelemetryOptions = {},\n): () => void {\n const captureContent = options.captureContent ?? false;\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n });\n\n // Pair each `start` with its `asyncEnd`/`error` by message-object identity,\n // recording the observer span id we assigned at start.\n const ids = new WeakMap<ChannelMessage, string>();\n let seq = 0;\n\n const handlers = {\n start(message: ChannelMessage) {\n const event = message.event ?? {};\n const callId = event.callId ?? `op:${seq}`;\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n ids.set(message, callId);\n observe({\n type: 'agent.start',\n id: callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.modelId ?? AI_SDK_AGENT_NAME },\n });\n return;\n }\n case 'languageModelCall': {\n const id = `${callId}:lm:${seq++}`;\n ids.set(message, id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: callId,\n request: toChatRequest(event),\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n return;\n }\n case 'executeTool': {\n const id = `${callId}:tool:${event.toolCall?.toolCallId ?? seq++}`;\n ids.set(message, id);\n observe({\n type: 'tool.start',\n id,\n parentId: callId,\n tool: {\n name: event.toolCall?.toolName,\n callId: event.toolCall?.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall?.input\n : undefined,\n });\n return;\n }\n case 'embed': {\n ids.set(message, callId);\n observe({\n type: 'chat.start',\n id: callId,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n return;\n }\n // 'step' and 'rerank' are intentionally not mapped.\n }\n },\n\n asyncEnd(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n closeSpan(observe, message, id, captureContent);\n },\n\n error(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n // `closeSpan` reads `message.error`; route by type to the right end event.\n closeSpan(observe, message, id, captureContent);\n },\n };\n\n return subscribeTracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL, handlers);\n}\n\nfunction closeSpan(\n observe: GenAiObserver,\n message: ChannelMessage,\n id: string,\n captureContent: boolean,\n): void {\n const error = message.error;\n const result = message.result as ResultView | undefined;\n const event = message.event;\n\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n observe({ type: 'agent.end', id, error });\n return;\n }\n case 'embed': {\n observe({\n type: 'chat.end',\n id,\n response: { model: event?.modelId },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n error,\n });\n return;\n }\n case 'languageModelCall': {\n const outputMessage =\n captureContent && event?.recordOutputs !== false\n ? contentToGenAiMessage(result?.content, result?.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: result?.response?.modelId ?? event?.modelId,\n id: result?.response?.id,\n finishReasons: result?.finishReason ? [result.finishReason] : undefined,\n },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n outputMessages: outputMessage ? [outputMessage] : undefined,\n error,\n });\n return;\n }\n case 'executeTool': {\n observe({\n type: 'tool.end',\n id,\n callResult:\n captureContent &&\n event?.recordOutputs !== false &&\n result?.output !== undefined\n ? result.output\n : undefined,\n error,\n });\n return;\n }\n }\n}\n\ninterface ResultView {\n content?: readonly ContentPartView[];\n finishReason?: string;\n output?: unknown;\n response?: { id?: string; modelId?: string };\n usage?: AiSdkUsageShape;\n}\n\n","/**\n * LangChain / LangGraph → {@link createGenAiObserver} glue.\n *\n * LangChain's callback system is a real event stream: register one handler and\n * it receives every run's start/end/error with a `runId` and `parentRunId` —\n * which map directly onto the observer's `id` / `parentId`. {@link\n * createLangChainObserver} returns a handler object you pass to `callbacks`:\n *\n * - chain runs (LangGraph nodes, agents) → `invoke_agent` spans;\n * - LLM / chat-model runs → `chat` spans (with usage and estimated cost);\n * - tool runs → `execute_tool` spans.\n *\n * Typed structurally against the callback payloads so it pulls in no LangChain\n * dependency. Pass it via `{ callbacks: [createLangChainObserver(observe)] }`.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * await graph.invoke(input, { callbacks: [createLangChainObserver(observe)] });\n * ```\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { TokenUsage } from '../cost.js';\nimport type { GenAiObserver } from './types.js';\n\n/** LangChain `Serialized` — the identity of a chain/LLM/tool. */\ninterface Serialized {\n id?: string[];\n name?: string;\n kwargs?: Record<string, unknown>;\n}\n\ninterface LLMGeneration {\n generationInfo?: Record<string, unknown>;\n message?: {\n usage_metadata?: Record<string, unknown>;\n response_metadata?: Record<string, unknown>;\n };\n}\n\n/** LangChain `LLMResult` — what an LLM/chat-model run returns. */\ninterface LLMResult {\n generations?: LLMGeneration[][];\n llmOutput?: Record<string, unknown>;\n}\n\n/** The subset of LangChain's `CallbackHandlerMethods` this adapter implements. */\nexport interface LangChainObserverHandler {\n handleChainStart(\n chain: Serialized,\n inputs: unknown,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runType?: string,\n runName?: string,\n ): void;\n handleChainEnd(outputs: unknown, runId: string): void;\n handleChainError(error: unknown, runId: string): void;\n handleLLMStart(\n llm: Serialized,\n prompts: string[],\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleChatModelStart(\n llm: Serialized,\n messages: unknown,\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleLLMEnd(output: LLMResult, runId: string): void;\n handleLLMError(error: unknown, runId: string): void;\n handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runName?: string,\n ): void;\n handleToolEnd(output: unknown, runId: string): void;\n handleToolError(error: unknown, runId: string): void;\n}\n\nexport interface LangChainObserverOptions {\n /**\n * Decide whether a chain run becomes an `invoke_agent` span. The default\n * skips LangChain/LangGraph plumbing (Runnable*, ChannelWrite, Branch,\n * prompts, `__start__`/`__end__`) so only the graph and its named nodes show;\n * children of a skipped chain reparent to the nearest kept ancestor.\n */\n skipChain?: (name: string) => boolean;\n}\n\n/** LangChain/LangGraph structural runnables that are noise, not real steps. */\nconst PLUMBING_CHAIN = /^(Runnable|Channel|Branch|Prompt|ChatPrompt|__)/i;\n\nexport function createLangChainObserver(\n observe: GenAiObserver,\n options: LangChainObserverOptions = {},\n): LangChainObserverHandler {\n const skipChain = options.skipChain ?? ((name) => PLUMBING_CHAIN.test(name));\n // Remember each LLM run's request model so `chat.end` can price it — the end\n // payload doesn't reliably carry the model. Cleared on end and error.\n const models = new Map<string, string>();\n // Skipped chains record the kept ancestor their children should parent to.\n const reparented = new Map<string, string | undefined>();\n\n /** The span id a child of `runId` should use as its parent. */\n function keptParent(runId: string | undefined): string | undefined {\n if (runId === undefined) return undefined;\n return reparented.has(runId) ? reparented.get(runId) : runId;\n }\n\n function startChat(\n llm: Serialized,\n runId: string,\n parentRunId: string | undefined,\n extraParams: Record<string, unknown> | undefined,\n ): void {\n const model = langChainModel(llm, extraParams);\n if (model) models.set(runId, model);\n observe({\n type: 'chat.start',\n id: runId,\n parentId: keptParent(parentRunId),\n request: { provider: langChainProvider(llm), model },\n });\n }\n\n return {\n handleChainStart(\n chain,\n _inputs,\n runId,\n parentRunId,\n _tags,\n _metadata,\n _runType,\n runName,\n ) {\n const name = runName ?? lastIdSegment(chain) ?? 'chain';\n const parentId = keptParent(parentRunId);\n if (skipChain(name)) {\n reparented.set(runId, parentId);\n return;\n }\n observe({ type: 'agent.start', id: runId, parentId, agent: { name } });\n },\n handleChainEnd(_outputs, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId });\n },\n handleChainError(error, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId, error });\n },\n\n handleLLMStart(llm, _prompts, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleChatModelStart(llm, _messages, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleLLMEnd(output, runId) {\n const costModel = models.get(runId);\n models.delete(runId);\n observe({\n type: 'chat.end',\n id: runId,\n response: { finishReasons: finishReasons(output) },\n usage: langChainUsage(output),\n costModel,\n });\n },\n handleLLMError(error, runId) {\n models.delete(runId);\n observe({ type: 'chat.end', id: runId, error });\n },\n\n handleToolStart(\n tool,\n input,\n runId,\n parentRunId,\n _tags,\n _metadata,\n runName,\n ) {\n observe({\n type: 'tool.start',\n id: runId,\n parentId: keptParent(parentRunId),\n tool: { name: runName ?? lastIdSegment(tool) },\n callArguments: input,\n });\n },\n handleToolEnd(output, runId) {\n observe({ type: 'tool.end', id: runId, callResult: output });\n },\n handleToolError(error, runId) {\n observe({ type: 'tool.end', id: runId, error });\n },\n };\n}\n\n/**\n * Provider from a `Serialized.id` path. LangChain serializes a chat model as\n * `[...namespace, provider, ClassName]` (e.g. `…, 'openai', 'ChatOpenAI'`), so\n * the provider is the lowercase module segment before the PascalCase class.\n */\nfunction langChainProvider(serialized: Serialized): string | undefined {\n const candidate = serialized.id?.at(-2);\n return candidate && candidate === candidate.toLowerCase()\n ? normalizeAiSdkProvider(candidate)\n : undefined;\n}\n\nfunction langChainModel(\n serialized: Serialized,\n extraParams: Record<string, unknown> | undefined,\n): string | undefined {\n const invocation = asRecord(extraParams?.invocation_params);\n return (\n str(invocation?.model) ??\n str(invocation?.model_name) ??\n str(serialized.kwargs?.model) ??\n str(serialized.kwargs?.model_name)\n );\n}\n\nfunction langChainUsage(output: LLMResult): TokenUsage | undefined {\n // `usage_metadata` is LangChain's canonical cross-provider shape (Ollama,\n // newer integrations); `llmOutput.tokenUsage` is OpenAI; `llmOutput.usage`\n // is Anthropic-style.\n const usageMetadata = asRecord(\n firstGeneration(output)?.message?.usage_metadata,\n );\n const tokenUsage = asRecord(output.llmOutput?.tokenUsage);\n const usage = asRecord(output.llmOutput?.usage);\n const inputTokens =\n num(usageMetadata?.input_tokens) ??\n num(tokenUsage?.promptTokens) ??\n num(usage?.input_tokens);\n const outputTokens =\n num(usageMetadata?.output_tokens) ??\n num(tokenUsage?.completionTokens) ??\n num(usage?.output_tokens);\n if (inputTokens === undefined && outputTokens === undefined) return undefined;\n return { inputTokens, outputTokens };\n}\n\nfunction finishReasons(output: LLMResult): string[] | undefined {\n const generation = firstGeneration(output);\n const reason =\n str(generation?.generationInfo?.finish_reason) ??\n str(asRecord(generation?.message?.response_metadata)?.done_reason);\n return reason ? [reason] : undefined;\n}\n\nfunction firstGeneration(output: LLMResult): LLMGeneration | undefined {\n return output.generations?.[0]?.[0];\n}\n\nfunction lastIdSegment(serialized: Serialized): string | undefined {\n return serialized.name ?? serialized.id?.at(-1);\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value)\n ? value\n : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"observer-BoQNdbkg.cjs","names":["SpanStatusCode","otelTrace","otelContext","SpanStatusCode","genAiResponseAttributes","estimateLLMCost","genAiUsageAttributes","GEN_AI","genAiToolAttributes","agentContextFromSpan","genAiSpanName","GEN_AI_OPERATION","SpanKind","genAiWorkflowAttributes","genAiAgentAttributes","genAiRequestAttributes","recordPlanStep","recordInputProvenance","recordMemoryAccess","recordRenderOutput","normalizeAiSdkProvider","GEN_AI_OPERATION","AI_SDK_AGENT_NAME","normalizeAiSdkProvider","AI_SDK_AGENT_NAME","otelContext","otelTrace","GEN_AI_TOOL_TYPE","GEN_AI_OPERATION","GEN_AI_TOOL_TYPE","GEN_AI_OPERATION","normalizeAiSdkProvider"],"sources":["../src/observer/span-registry.ts","../src/observer/observer.ts","../src/observer/ai-sdk-shapes.ts","../src/observer/ai-sdk.ts","../src/observer/ai-sdk-messages.ts","../src/observer/ai-sdk-telemetry.ts","../src/observer/ai-sdk-channel.ts","../src/observer/langchain.ts"],"sourcesContent":["/**\n * Tracks the spans currently open in a {@link createGenAiObserver} stream and\n * the parent each was started under, so the observer can:\n *\n * - look up a parent span when a child starts, and\n * - force-close any descendant whose terminal event never arrived because its\n * parent ended first (an aborted tool call, a cancelled turn, a run that was\n * interrupted). Without this, abandoned spans leak open forever.\n */\n\nimport { SpanStatusCode, type Span, type TimeInput } from '@opentelemetry/api';\n\ninterface Entry {\n span: Span;\n parentId: string | undefined;\n}\n\nexport class SpanRegistry {\n private readonly entries = new Map<string, Entry>();\n\n /** Record an open span and the parent it was started under. */\n add(id: string, span: Span, parentId: string | undefined): void {\n this.entries.set(id, { span, parentId });\n }\n\n /** The open span for `id`, if any (used to parent a starting child). */\n spanFor(id: string): Span | undefined {\n return this.entries.get(id)?.span;\n }\n\n /** Remove and return the open span for `id`. */\n take(id: string): Span | undefined {\n const entry = this.entries.get(id);\n if (!entry) return undefined;\n this.entries.delete(id);\n return entry.span;\n }\n\n /**\n * Close every still-open descendant of `parentId` as ERROR — their terminal\n * event never arrived because the parent ended first. Depth-first, so the\n * deepest leaks close before their own parents.\n */\n reapDescendants(\n parentId: string,\n message: string,\n endTime?: TimeInput,\n ): void {\n // Deleting from a Map during its own for…of is well-defined; recursion only\n // removes deeper descendants (parentId === id), never an unvisited sibling.\n for (const [id, entry] of this.entries) {\n if (entry.parentId !== parentId) continue;\n this.entries.delete(id);\n this.reapDescendants(id, message, endTime);\n entry.span.setStatus({ code: SpanStatusCode.ERROR, message });\n entry.span.end(endTime);\n }\n }\n\n /** Count of spans still open — for tests and leak assertions. */\n get openCount(): number {\n return this.entries.size;\n }\n}\n","/**\n * `createGenAiObserver` — turn a stream of GenAI lifecycle events into a\n * canonical `gen_ai.*` span tree.\n *\n * A complement to {@link traceGenAI}: where the wrapper instruments code you\n * own, the observer instruments a framework that emits its own event stream.\n * Subscribe it once and feed every event through it; it reconstructs the span\n * hierarchy, estimates cost, force-closes abandoned children, and keeps\n * sensitive content off spans unless you opt in.\n *\n * Scope: this adapter records span attributes only. It deliberately does not\n * emit the `gen_ai.*` log events (`inference.operation.details`,\n * `operation.exception`) that `recordInferenceDetails` provides — the source\n * event stream already carries that detail.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * observe({ type: 'agent.start', id: 'a1', agent: { name: 'planner' } });\n * observe({ type: 'chat.start', id: 'c1', parentId: 'a1',\n * request: { provider: 'openai', model: 'gpt-4o' } });\n * observe({ type: 'chat.end', id: 'c1',\n * usage: { inputTokens: 412, outputTokens: 87 } });\n * observe({ type: 'agent.end', id: 'a1' });\n * ```\n */\n\nimport {\n context as otelContext,\n SpanKind,\n SpanStatusCode,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n} from '@opentelemetry/api';\nimport {\n genAiAgentAttributes,\n genAiRequestAttributes,\n genAiResponseAttributes,\n genAiToolAttributes,\n genAiUsageAttributes,\n genAiWorkflowAttributes,\n type GenAiAttributeMap,\n} from '../attributes.js';\nimport { estimateLLMCost } from '../cost.js';\nimport { setGenAiContent, type GenAiContentSink } from '../events.js';\nimport { GEN_AI, GEN_AI_OPERATION, genAiSpanName } from '../semconv.js';\nimport {\n recordInputProvenance,\n recordMemoryAccess,\n recordPlanStep,\n recordRenderOutput,\n} from '../agent/agent-security.js';\nimport { agentContextFromSpan } from '../agent/context.js';\nimport { SpanRegistry } from './span-registry.js';\nimport type {\n ChatEndEvent,\n ChatStartEvent,\n GenAiObserver,\n GenAiObserverEvent,\n GenAiObserverOptions,\n SpanEnd,\n InputProvenanceEvent,\n MemoryAccessEvent,\n PlanStepEvent,\n RenderOutputEvent,\n ToolEndEvent,\n ToolStartEvent,\n} from './types.js';\n\nconst ORPHAN_MESSAGE =\n 'Parent ended before this span received its terminal event.';\n\n/** The `*.start` half of the event union — the events that open a span. */\ntype StartEvent = Extract<GenAiObserverEvent, { type: `${string}.start` }>;\n\nexport function createGenAiObserver(\n options: GenAiObserverOptions = {},\n): GenAiObserver {\n const tracer =\n options.tracer ?? otelTrace.getTracer('autotel-genai/observer');\n const registry = new SpanRegistry();\n\n /** Parent context: a tracked parent span, else the resolver, else root. */\n function parentContext(event: StartEvent): Context | undefined {\n const parentSpan = event.parentId\n ? registry.spanFor(event.parentId)\n : undefined;\n if (parentSpan) return otelTrace.setSpan(otelContext.active(), parentSpan);\n return options.resolveParentContext?.(event);\n }\n\n /** Start a span, register it under its parent, and return it. */\n function start(\n event: StartEvent,\n name: string,\n kind: SpanKind,\n attributes: GenAiAttributeMap,\n ): Span {\n const parent = parentContext(event);\n const spanOptions: SpanOptions = {\n kind,\n attributes,\n startTime: event.startTime,\n links: event.links,\n root: parent === undefined,\n };\n const span = tracer.startSpan(name, spanOptions, parent);\n registry.add(event.id, span, event.parentId);\n options.onSpanStart?.(event.id, span);\n return span;\n }\n\n /**\n * Close the span for `event.id`: first force-close any descendant whose\n * terminal event never arrived, then decorate, set status, and end.\n */\n function end(event: SpanEnd, decorate?: (span: Span) => void): void {\n const span = registry.take(event.id);\n if (!span) return;\n registry.reapDescendants(event.id, ORPHAN_MESSAGE, event.endTime);\n decorate?.(span);\n if (event.error !== undefined) {\n const message = errorMessage(event.error);\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n // The SDK normalizes a string or Error; hand it the original Error when\n // we have one, otherwise the message we already derived.\n span.recordException(\n event.error instanceof Error ? event.error : (message ?? 'error'),\n );\n }\n span.end(event.endTime);\n }\n\n /** Run the privacy gate; without it, content is always omitted. */\n function approvedContent(\n event: GenAiObserverEvent,\n ): GenAiObserverEvent | undefined {\n if (!options.exportContent) return undefined;\n try {\n return options.exportContent({ ...event });\n } catch (error) {\n console.error('[autotel-genai:observer] exportContent failed:', error);\n return undefined;\n }\n }\n\n function applyChatStart(span: Span, event: ChatStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'chat.start') return;\n setGenAiContent(spanSink(span), {\n inputMessages: content.inputMessages,\n systemInstructions: content.systemInstructions,\n });\n }\n\n function applyChatEnd(span: Span, event: ChatEndEvent): void {\n if (event.response) {\n span.setAttributes(genAiResponseAttributes(event.response));\n }\n if (event.usage) {\n const costModel = event.costModel ?? event.response?.model;\n const costUsd = costModel\n ? estimateLLMCost(costModel, event.usage)\n : undefined;\n span.setAttributes(genAiUsageAttributes({ ...event.usage, costUsd }));\n }\n if (event.streaming) {\n const { timeToFinish, outputTokensPerSecond, timePerOutputChunk } =\n event.streaming;\n const streamingAttrs: GenAiAttributeMap = {};\n if (timeToFinish !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_TO_FINISH] = timeToFinish;\n }\n if (outputTokensPerSecond !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_OUTPUT_TOKENS_PER_SECOND] =\n outputTokensPerSecond;\n }\n if (timePerOutputChunk !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_PER_OUTPUT_CHUNK] =\n timePerOutputChunk;\n }\n span.setAttributes(streamingAttrs);\n }\n const content = approvedContent(event);\n if (content?.type === 'chat.end') {\n setGenAiContent(spanSink(span), {\n outputMessages: content.outputMessages,\n });\n }\n }\n\n function applyToolStart(span: Span, event: ToolStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.start') return;\n span.setAttributes(\n genAiToolAttributes({ callArguments: content.callArguments }),\n );\n }\n\n function applyToolEnd(span: Span, event: ToolEndEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.end') return;\n span.setAttributes(genAiToolAttributes({ callResult: content.callResult }));\n }\n\n function stampOnParent(\n parentId: string,\n apply: (ctx: ReturnType<typeof agentContextFromSpan>) => void,\n ): void {\n const parent = registry.spanFor(parentId);\n if (!parent) return;\n apply(agentContextFromSpan(parent));\n }\n\n return (event: GenAiObserverEvent): void => {\n switch (event.type) {\n case 'workflow.start': {\n start(\n event,\n genAiSpanName(\n GEN_AI_OPERATION.INVOKE_WORKFLOW,\n event.workflow.workflowName,\n ),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_WORKFLOW,\n ...genAiWorkflowAttributes(event.workflow),\n },\n );\n return;\n }\n case 'agent.start': {\n const internal = !event.remote;\n start(\n event,\n genAiSpanName(GEN_AI_OPERATION.INVOKE_AGENT, event.agent.name),\n internal ? SpanKind.INTERNAL : SpanKind.CLIENT,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_AGENT,\n ...(event.provider\n ? { [GEN_AI.PROVIDER_NAME]: event.provider }\n : {}),\n ...genAiAgentAttributes(event.agent, { internal }),\n },\n );\n return;\n }\n case 'chat.start': {\n const operation = event.request.operation ?? GEN_AI_OPERATION.CHAT;\n const span = start(\n event,\n genAiSpanName(operation, event.request.model),\n SpanKind.CLIENT,\n genAiRequestAttributes({ ...event.request, operation }),\n );\n applyChatStart(span, event);\n return;\n }\n case 'tool.start': {\n const span = start(\n event,\n genAiSpanName(GEN_AI_OPERATION.EXECUTE_TOOL, event.tool.name),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.EXECUTE_TOOL,\n ...genAiToolAttributes(event.tool),\n },\n );\n applyToolStart(span, event);\n return;\n }\n case 'workflow.end':\n case 'agent.end': {\n end(event);\n return;\n }\n case 'chat.end': {\n end(event, (span) => applyChatEnd(span, event));\n return;\n }\n case 'tool.end': {\n end(event, (span) => applyToolEnd(span, event));\n return;\n }\n case 'plan.step': {\n stampOnParent(event.parentId, (ctx) =>\n recordPlanStep({\n ctx,\n stepIndex: event.stepIndex,\n toolIntents: event.toolIntents,\n policyIds: event.policyIds,\n summary: event.summary,\n }),\n );\n return;\n }\n case 'input.provenance': {\n stampOnParent(event.parentId, (ctx) =>\n recordInputProvenance({ ctx, provenance: event.provenance }),\n );\n return;\n }\n case 'memory.access': {\n stampOnParent(event.parentId, (ctx) =>\n recordMemoryAccess({\n ctx,\n operation: event.operation,\n isolationKey: event.isolationKey,\n contentHash: event.contentHash,\n }),\n );\n return;\n }\n case 'render.output': {\n stampOnParent(event.parentId, (ctx) =>\n recordRenderOutput({\n ctx,\n format: event.format,\n containsUrl: event.containsUrl,\n urlCount: event.urlCount,\n }),\n );\n return;\n }\n default: {\n const unexpected: never = event;\n void unexpected;\n }\n }\n };\n}\n\n/** A {@link GenAiContentSink} backed by a raw span (only `setAttributes` is used). */\nfunction spanSink(span: Span): GenAiContentSink {\n return {\n setAttributes: (attrs) => {\n span.setAttributes(attrs);\n },\n track: () => {},\n };\n}\n\nfunction errorMessage(error: unknown): string | undefined {\n if (error === undefined) return undefined;\n if (typeof error === 'string') return error || undefined;\n if (error instanceof Error) return error.message;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n","/**\n * Shared structural converters from Vercel AI SDK shapes to autotel inputs.\n *\n * The AI SDK reports the same data three ways — the lifecycle `Telemetry`\n * events ({@link autotelTelemetry}), the `ai:telemetry` channel messages\n * ({@link subscribeAiTelemetry}), and a finished call result\n * ({@link observeAiSdkResult}) — and across v4/v5 with differing field names\n * (`promptTokens` vs `inputTokens`, flat vs nested token details). These helpers\n * are the single place that normalizes those shapes, so the three entry points\n * stay consistent and none of them re-derive the mapping.\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiRequestInput } from '../attributes.js';\nimport type { TokenUsage } from '../cost.js';\nimport { GEN_AI_OPERATION } from '../semconv.js';\n\n/** Every AI SDK token-usage field name we understand (v4 + v5, flat + nested). */\nexport interface AiSdkUsageShape {\n inputTokens?: number;\n outputTokens?: number;\n /** v4 aliases. */\n promptTokens?: number;\n completionTokens?: number;\n /** Embedding usage. */\n tokens?: number;\n /** Flat v5 detail fields. */\n reasoningTokens?: number;\n cachedInputTokens?: number;\n /** Nested v5 detail fields. */\n inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n}\n\n/** Map any AI SDK usage shape to {@link TokenUsage}; `undefined` if all empty. */\nexport function toTokenUsage(\n usage: AiSdkUsageShape | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined;\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens ?? usage.promptTokens ?? usage.tokens,\n outputTokens: usage.outputTokens ?? usage.completionTokens,\n reasoningOutputTokens:\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens,\n cacheReadInputTokens:\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens,\n cacheCreationInputTokens: usage.inputTokenDetails?.cacheWriteTokens,\n };\n return Object.values(tokenUsage).some((v) => v !== undefined)\n ? tokenUsage\n : undefined;\n}\n\n/** Normalize an optional AI SDK provider id to a canonical `gen_ai` provider. */\nexport function normalizeProvider(provider: string | undefined) {\n return provider ? normalizeAiSdkProvider(provider) : undefined;\n}\n\n/** Request-side fields shared by the lifecycle and channel model-call events. */\nexport interface AiSdkRequestShape {\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n}\n\n/** Build the `chat` request attributes from a model-call start event. */\nexport function toChatRequest(event: AiSdkRequestShape): GenAiRequestInput {\n return {\n operation: GEN_AI_OPERATION.CHAT,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n temperature: event.temperature,\n maxTokens: event.maxOutputTokens,\n topP: event.topP,\n topK: event.topK,\n frequencyPenalty: event.frequencyPenalty,\n presencePenalty: event.presencePenalty,\n stopSequences: event.stopSequences,\n seed: event.seed,\n };\n}\n","/**\n * Vercel AI SDK → {@link createGenAiObserver} glue.\n *\n * The AI SDK has no global event stream; its stable surface is the result of a\n * `generateText`/`streamText` call (`steps`, each with `usage`, `toolCalls`,\n * `toolResults`, `response`). {@link observeAiSdkResult} walks that result and\n * emits observer events:\n *\n * - a trivial single-step call with no tools → one `chat` span;\n * - a multi-step or tool-using call → an `invoke_agent` wrapper (mirroring the\n * AI SDK's own `invoke_agent › chat › execute_tool` hierarchy) with a `chat`\n * per step and an `execute_tool` per tool call.\n *\n * Typed structurally against the AI SDK result shape so it pulls in no\n * dependency and tolerates v4/v5 field differences (`promptTokens` vs\n * `inputTokens`). Cost is left to the observer, which prices `chat` usage.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * const result = await generateText({ model: openai('gpt-4o'), prompt });\n * observeAiSdkResult(observe, result, { id: 'gen-1', provider: 'openai', model: 'gpt-4o' });\n * ```\n */\n\nimport type { TimeInput } from '@opentelemetry/api';\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiProviderName } from '../semconv.js';\nimport { toTokenUsage } from './ai-sdk-shapes.js';\nimport type { GenAiObserver } from './types.js';\n\n/** AI SDK usage object — canonical (v5) or legacy (v4) field names. */\nexport interface AiSdkUsage {\n inputTokens?: number;\n outputTokens?: number;\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AiSdkToolCall {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n}\n\nexport interface AiSdkToolResult {\n toolCallId?: string;\n toolName?: string;\n output?: unknown;\n result?: unknown;\n}\n\n/** One AI SDK step (also the shape of a single-call result). */\nexport interface AiSdkStep {\n usage?: AiSdkUsage;\n finishReason?: string;\n response?: { id?: string; modelId?: string; timestamp?: Date | number };\n toolCalls?: AiSdkToolCall[];\n toolResults?: AiSdkToolResult[];\n}\n\n/** An AI SDK `generateText`/`streamText` result. */\nexport interface AiSdkResult extends AiSdkStep {\n steps?: AiSdkStep[];\n}\n\nexport interface ObserveAiSdkOptions {\n /** Base span id; per-step and per-tool ids derive from it. */\n id: string;\n parentId?: string;\n /** AI SDK provider id (e.g. `openai`, `amazon-bedrock`); normalized for you. */\n provider?: string;\n /** Request model — used for the span name and as the cost model. */\n model?: string;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\nexport function observeAiSdkResult(\n observe: GenAiObserver,\n result: AiSdkResult,\n options: ObserveAiSdkOptions,\n): void {\n const provider: GenAiProviderName | undefined = options.provider\n ? normalizeAiSdkProvider(options.provider)\n : undefined;\n const steps = result.steps ?? [result];\n const toolCount = steps.reduce(\n (total, step) => total + (step.toolCalls?.length ?? 0),\n 0,\n );\n\n if (steps.length <= 1 && toolCount === 0) {\n emitChat(observe, steps[0] ?? result, {\n id: options.id,\n parentId: options.parentId,\n provider,\n model: options.model,\n });\n return;\n }\n\n observe({\n type: 'agent.start',\n id: options.id,\n parentId: options.parentId,\n provider,\n agent: { name: options.model ?? AI_SDK_AGENT_NAME },\n });\n steps.forEach((step, index) => {\n emitChat(observe, step, {\n id: `${options.id}:step:${index}`,\n parentId: options.id,\n provider,\n model: options.model,\n });\n emitTools(observe, step, options.id, index);\n });\n observe({ type: 'agent.end', id: options.id });\n}\n\ninterface ChatContext {\n id: string;\n parentId?: string;\n provider?: GenAiProviderName;\n model?: string;\n}\n\nfunction emitChat(\n observe: GenAiObserver,\n step: AiSdkStep,\n ctx: ChatContext,\n): void {\n observe({\n type: 'chat.start',\n id: ctx.id,\n parentId: ctx.parentId,\n request: { provider: ctx.provider, model: ctx.model },\n });\n const responseModel = step.response?.modelId;\n observe({\n type: 'chat.end',\n id: ctx.id,\n response: {\n model: responseModel,\n id: step.response?.id,\n finishReasons: step.finishReason ? [step.finishReason] : undefined,\n },\n usage: toTokenUsage(step.usage),\n costModel: ctx.model ?? responseModel,\n endTime: asTimeInput(step.response?.timestamp),\n });\n}\n\nfunction emitTools(\n observe: GenAiObserver,\n step: AiSdkStep,\n parentId: string,\n stepIndex: number,\n): void {\n const results = step.toolResults ?? [];\n (step.toolCalls ?? []).forEach((call, callIndex) => {\n const id = `${parentId}:tool:${call.toolCallId ?? `${stepIndex}.${callIndex}`}`;\n observe({\n type: 'tool.start',\n id,\n parentId,\n tool: { name: call.toolName, callId: call.toolCallId },\n callArguments: call.input ?? call.args,\n });\n const result = call.toolCallId\n ? results.find((r) => r.toolCallId === call.toolCallId)\n : results[callIndex];\n observe({\n type: 'tool.end',\n id,\n callResult: result?.output ?? result?.result,\n });\n });\n}\n\nfunction asTimeInput(value: Date | number | undefined): TimeInput | undefined {\n return value instanceof Date || typeof value === 'number' ? value : undefined;\n}\n","/**\n * Vercel AI SDK messages → GenAI SemConv message format.\n *\n * The AI SDK carries prompts as `ModelMessage[]` (role + string-or-parts) and\n * model output as `ContentPart[]`. The GenAI semantic conventions instead model\n * `gen_ai.input.messages` / `gen_ai.output.messages` as `{ role, parts }` where\n * each part is `{ type, ...typeFields }`:\n *\n * - text: `{ type: 'text', content }`\n * - reasoning: `{ type: 'reasoning', content }`\n * - tool_call: `{ type: 'tool_call', id, name, arguments }`\n * - tool_call_response: `{ type: 'tool_call_response', id, response }`\n * - blob/uri: media, referenced by `mime_type` only (never inlined)\n *\n * Typed structurally against the AI SDK shapes (no `ai` / `@ai-sdk/*` import).\n * Media bytes are deliberately not copied onto spans — only the modality and\n * mime type — so content capture never balloons span size or leaks raw blobs.\n */\n\nimport type { GenAiMessage, GenAiMessagePart } from '../events.js';\n\n// --- Structural views of the AI SDK message shapes -------------------------\n// Exported so the telemetry/channel event views can type their `messages` /\n// `content` fields against these directly — no per-call-site casts, and (with no\n// index signature) the real SDK `ModelMessage[]` / `ContentPart[]` stay\n// assignable to them.\n\nexport interface ModelMessageView {\n role?: string;\n content?: string | readonly ContentPartView[];\n}\n\nexport interface ContentPartView {\n type?: string;\n /** text / reasoning */\n text?: string;\n /** tool-call */\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n /** tool-result */\n output?: unknown;\n /** image / file */\n mediaType?: string;\n}\n\n/** Result of converting an AI SDK prompt: messages plus split-out system parts. */\nexport interface ConvertedPrompt {\n messages: GenAiMessage[];\n systemInstructions?: GenAiMessagePart[];\n}\n\n/**\n * Convert an AI SDK `ModelMessage[]` prompt to GenAI input messages, splitting\n * `system` messages out into `systemInstructions` (recorded separately per the\n * conventions).\n */\nexport function promptToGenAiMessages(\n messages: readonly ModelMessageView[] | undefined,\n): ConvertedPrompt {\n if (!messages?.length) return { messages: [] };\n\n const out: GenAiMessage[] = [];\n const system: GenAiMessagePart[] = [];\n\n for (const message of messages) {\n const role = message.role ?? 'user';\n const parts = contentToParts(message.content);\n if (role === 'system') {\n system.push(...parts);\n } else {\n out.push({ role, parts });\n }\n }\n\n return system.length > 0\n ? { messages: out, systemInstructions: system }\n : { messages: out };\n}\n\n/**\n * Convert the `ContentPart[]` of a model response into a single GenAI output\n * message (always `assistant`), optionally tagged with a `finish_reason`.\n */\nexport function contentToGenAiMessage(\n content: readonly ContentPartView[] | undefined,\n finishReason?: string,\n): GenAiMessage | undefined {\n if (!content?.length) return undefined;\n const message: GenAiMessage = {\n role: 'assistant',\n parts: content.flatMap((part) => partToGenAi(part)),\n };\n if (finishReason) message.finish_reason = finishReason;\n return message;\n}\n\nfunction contentToParts(\n content: string | readonly ContentPartView[] | undefined,\n): GenAiMessagePart[] {\n if (content === undefined) return [];\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : [];\n }\n return content.flatMap((part) => partToGenAi(part));\n}\n\n/** Map one AI SDK content part to zero or more GenAI parts. */\nfunction partToGenAi(part: ContentPartView): GenAiMessagePart[] {\n switch (part.type) {\n case 'text': {\n return part.text ? [{ type: 'text', content: part.text }] : [];\n }\n case 'reasoning': {\n return part.text ? [{ type: 'reasoning', content: part.text }] : [];\n }\n case 'tool-call': {\n return [\n {\n type: 'tool_call',\n id: part.toolCallId,\n name: part.toolName,\n arguments: part.input,\n },\n ];\n }\n case 'tool-result': {\n return [\n {\n type: 'tool_call_response',\n id: part.toolCallId,\n response: part.output,\n },\n ];\n }\n case 'image':\n case 'file': {\n // Reference media by modality + mime type only — never inline bytes.\n return [\n {\n type: 'blob',\n modality: part.type === 'image' ? 'image' : 'file',\n mime_type: part.mediaType,\n },\n ];\n }\n default: {\n return part.type ? [{ type: part.type }] : [];\n }\n }\n}\n","/**\n * `autotelTelemetry` — a Vercel AI SDK telemetry integration.\n *\n * Implements the AI SDK's `Telemetry` lifecycle interface (stable since ai v7)\n * so you register it once and every `generateText` / `streamText` / `embed`\n * call streams a canonical `gen_ai.*` span tree — live, as the operation runs,\n * not reconstructed after the fact:\n *\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n *\n * Where the built-in `@ai-sdk/otel` `OpenTelemetry` integration emits the same\n * `gen_ai.*` spans, this one additionally:\n *\n * - prices every model call (`gen_ai.usage.cost.usd`) from {@link MODEL_PRICING};\n * - records streaming throughput (`time_to_first_chunk`, `time_to_finish`,\n * `output_tokens_per_second`) on each `chat` span;\n * - keeps token usage on leaf `chat` spans only, so the `invoke_agent` root\n * never double-counts.\n *\n * It is push-based and concurrency-safe: every lifecycle event carries the\n * AI SDK `callId`, so interleaved concurrent generations never cross wires. It\n * is the live counterpart to {@link observeAiSdkResult}, which walks a finished\n * result instead.\n *\n * Typed structurally against the AI SDK event shapes — it pulls in **no**\n * dependency on `ai` / `@ai-sdk/*`. The returned object satisfies the AI SDK\n * `Telemetry` interface by structural compatibility (every event the SDK passes\n * is a superset of the fields read here), so `registerTelemetry(autotelTelemetry())`\n * type-checks without importing the interface.\n *\n * Scope: `generateText` / `streamText` (incl. tool loops and `output`) and\n * `embed` / `embedMany`. A tool's nested `generateText` and the provider's own\n * HTTP spans parent correctly via the `executeTool` / `executeLanguageModelCall`\n * context runners. `rerank` has no canonical `gen_ai` operation in the v1.42.0\n * registry and is intentionally not mapped.\n *\n * Message content (prompts, responses, tool arguments/results) is **off by\n * default**; enable it with `captureContent`, gated by the SDK's `recordInputs`\n * / `recordOutputs` and the optional `exportContent` privacy callback.\n */\n\nimport {\n context as otelContext,\n trace as otelTrace,\n type Context,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type {\n ChatStreamTiming,\n GenAiObserverEvent,\n GenAiObserver,\n} from './types.js';\n\n// --- Structural views of the AI SDK telemetry event shapes -----------------\n// Only the fields this integration reads. The real events are supersets, so a\n// callback typed against these is assignable to the AI SDK `Telemetry` methods.\n\ninterface OperationStartEvent {\n callId: string;\n /** e.g. `'ai.generateText'`, `'ai.streamText'`, `'ai.embed'`. */\n operationId?: string;\n provider?: string;\n modelId?: string;\n /** From telemetry settings — used as the agent name. */\n functionId?: string;\n}\n\ninterface LanguageModelCallStartEventView {\n callId: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n /** Standardized prompt messages (when content capture is on). */\n messages?: readonly ModelMessageView[];\n /** Whether the SDK call permits recording inputs (default true). */\n recordInputs?: boolean;\n}\n\ninterface LanguageModelCallEndEventView {\n callId: string;\n modelId?: string;\n responseId?: string;\n finishReason?: string;\n usage?: AiSdkUsageShape;\n /** Output content parts (when content capture is on). */\n content?: readonly ContentPartView[];\n /** Whether the SDK call permits recording outputs (default true). */\n recordOutputs?: boolean;\n performance?: {\n responseTimeMs?: number;\n effectiveOutputTokensPerSecond?: number;\n timeToFirstOutputMs?: number;\n };\n}\n\ninterface ToolCallView {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\ninterface ToolExecutionStartEventView {\n callId: string;\n toolCall: ToolCallView;\n recordInputs?: boolean;\n}\n\ninterface ToolOutputView {\n type?: string;\n output?: unknown;\n error?: unknown;\n}\n\ninterface ToolExecutionEndEventView {\n callId: string;\n toolCall: ToolCallView;\n toolOutput: ToolOutputView;\n recordOutputs?: boolean;\n}\n\ninterface OperationEndEventView {\n callId: string;\n}\n\ninterface AbortEventView {\n callId: string;\n reason?: unknown;\n}\n\ninterface EmbeddingModelCallEndEventView {\n callId: string;\n embedCallId?: string;\n provider?: string;\n modelId?: string;\n usage?: { tokens?: number };\n}\n\n/**\n * The subset of the AI SDK `Telemetry` interface this integration implements.\n * The returned object is assignable to the SDK's `Telemetry` type.\n */\nexport interface AutotelTelemetryIntegration {\n onStart(event: OperationStartEvent): void;\n onLanguageModelCallStart(event: LanguageModelCallStartEventView): void;\n onLanguageModelCallEnd(event: LanguageModelCallEndEventView): void;\n onToolExecutionStart(event: ToolExecutionStartEventView): void;\n onToolExecutionEnd(event: ToolExecutionEndEventView): void;\n onEmbedEnd(event: EmbeddingModelCallEndEventView): void;\n onEnd(event: OperationEndEventView): void;\n onAbort(event: AbortEventView): void;\n onError(event: unknown): void;\n executeLanguageModelCall<T>(\n options: { callId: string; execute: () => PromiseLike<T> },\n ): PromiseLike<T>;\n executeTool<T>(\n options: {\n callId: string;\n toolCallId: string;\n execute: () => PromiseLike<T>;\n },\n ): PromiseLike<T>;\n}\n\nexport interface AutotelTelemetryOptions {\n /**\n * Tracer to emit spans on. Defaults to the global `autotel-genai/observer`\n * tracer. Pass an application-owned tracer to share one configuration.\n */\n tracer?: Tracer;\n /**\n * Privacy gate applied to all captured content (prompts, responses, tool\n * arguments/results) before it is written to a span. Receives each\n * content-bearing observer event; return it (optionally redacted) to keep its\n * content, or `undefined` to drop that event's content. Only consulted when\n * {@link AutotelTelemetryOptions.captureContent} is enabled.\n */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /**\n * Capture prompt/response message content (`gen_ai.input.messages`,\n * `gen_ai.output.messages`, `gen_ai.system_instructions`) and tool\n * arguments/results. **Off by default** for privacy. When `true`, content is\n * still filtered through {@link AutotelTelemetryOptions.exportContent} if you\n * provide one (redact there); with no `exportContent`, all approved content is\n * written. The AI SDK's per-call `recordInputs` / `recordOutputs` are still\n * honored — either being `false` suppresses that side.\n */\n captureContent?: boolean;\n /**\n * Resolve an OpenTelemetry parent context for an operation that has no\n * tracked parent — e.g. attach the `invoke_agent` root to the incoming\n * request span. Return `undefined` to keep it a root.\n */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/** Per-call correlation state, keyed by the AI SDK `callId`. */\ninterface CallState {\n /** Whether an `invoke_agent` root span was opened for this call. */\n hasAgent: boolean;\n /** Whether the root operation is streaming (`streamText`) or not. */\n stream?: boolean;\n /** Stack of open `chat` span ids (one per in-flight model call). */\n openLm: string[];\n /** Monotonic counter for unique model-call span ids within the call. */\n lmSeq: number;\n /** FIFO of anonymous tool span ids when the SDK omits `toolCallId`. */\n anonymousToolIds: string[];\n /** Monotonic counter for unique anonymous tool span ids within the call. */\n toolSeq: number;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/**\n * Create an AI SDK telemetry integration that streams canonical `gen_ai.*`\n * spans (with cost + streaming timing) for every AI SDK operation.\n *\n * @example\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n */\nexport function autotelTelemetry(\n options: AutotelTelemetryOptions = {},\n): AutotelTelemetryIntegration {\n const captureContent = options.captureContent ?? false;\n // When content capture is on with no explicit gate, allow all approved\n // content; an `exportContent` callback (redaction) always wins if provided.\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n // Live spans by observer id, so the context runners can re-enter a span's\n // OTel context to nest provider HTTP calls / a tool's inner `generateText`.\n const spans = new Map<string, Span>();\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n onSpanStart: (id, span) => spans.set(id, span),\n });\n const calls = new Map<string, CallState>();\n\n function startToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n const seq = state?.toolSeq ?? 0;\n if (state) state.toolSeq += 1;\n const id = `${callId}:tool:anon:${seq}`;\n state?.anonymousToolIds.push(id);\n return id;\n }\n\n function endToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n return state?.anonymousToolIds.shift() ?? `${callId}:tool:anon:0`;\n }\n\n /** Run `execute` within `span`'s OTel context so nested work parents to it. */\n function runInSpan<T>(\n span: Span | undefined,\n execute: () => PromiseLike<T>,\n ): PromiseLike<T> {\n if (!span) return execute();\n return otelContext.with(\n otelTrace.setSpan(otelContext.active(), span),\n execute,\n );\n }\n\n return {\n onStart(event) {\n // Embedding / rerank operations are not `invoke_agent` roots; they are\n // handled as leaf spans by `onEmbedEnd` (rerank is unsupported).\n if (isNonAgentOperation(event.operationId)) return;\n const state: CallState = {\n hasAgent: true,\n stream: event.operationId?.includes('stream'),\n openLm: [],\n lmSeq: 0,\n anonymousToolIds: [],\n toolSeq: 0,\n };\n calls.set(event.callId, state);\n observe({\n type: 'agent.start',\n id: event.callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.functionId ?? event.modelId ?? AI_SDK_AGENT_NAME },\n });\n },\n\n onLanguageModelCallStart(event) {\n const state = calls.get(event.callId);\n const id = `${event.callId}:lm:${state ? state.lmSeq++ : 0}`;\n if (state) state.openLm.push(id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n request: { ...toChatRequest(event), stream: state?.stream },\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n },\n\n onLanguageModelCallEnd(event) {\n const state = calls.get(event.callId);\n const id = state?.openLm.pop() ?? `${event.callId}:lm:0`;\n const outputMessage =\n captureContent && event.recordOutputs !== false\n ? contentToGenAiMessage(event.content, event.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: event.modelId,\n id: event.responseId,\n finishReasons: event.finishReason ? [event.finishReason] : undefined,\n timeToFirstChunk: msToSeconds(event.performance?.timeToFirstOutputMs),\n },\n usage: toTokenUsage(event.usage),\n costModel: event.modelId,\n streaming: mapStreaming(event.performance),\n outputMessages: outputMessage ? [outputMessage] : undefined,\n });\n spans.delete(id);\n },\n\n onToolExecutionStart(event) {\n const state = calls.get(event.callId);\n const id = startToolSpanId(event.callId, state, event.toolCall);\n observe({\n type: 'tool.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n tool: {\n name: event.toolCall.toolName,\n callId: event.toolCall.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall.input\n : undefined,\n });\n },\n\n onToolExecutionEnd(event) {\n const state = calls.get(event.callId);\n const id = endToolSpanId(event.callId, state, event.toolCall);\n const isError = event.toolOutput.type === 'tool-error';\n observe({\n type: 'tool.end',\n id,\n error: isError ? event.toolOutput.error : undefined,\n callResult:\n !isError && captureContent && event.recordOutputs !== false\n ? event.toolOutput.output\n : undefined,\n });\n spans.delete(id);\n },\n\n onEmbedEnd(event) {\n const id = `${event.callId}:embed:${event.embedCallId ?? '0'}`;\n observe({\n type: 'chat.start',\n id,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n observe({\n type: 'chat.end',\n id,\n response: { model: event.modelId },\n usage:\n event.usage?.tokens === undefined\n ? undefined\n : { inputTokens: event.usage.tokens },\n costModel: event.modelId,\n });\n spans.delete(id);\n },\n\n onEnd(event) {\n if (!calls.delete(event.callId)) return;\n observe({ type: 'agent.end', id: event.callId });\n spans.delete(event.callId);\n },\n\n onAbort(event) {\n if (!calls.delete(event.callId)) return;\n observe({\n type: 'agent.end',\n id: event.callId,\n error: event.reason ?? 'aborted',\n });\n spans.delete(event.callId);\n },\n\n onError(event) {\n // The dispatcher invokes `onError` with `{ callId, error }`. Close the\n // matching `invoke_agent` root when we can correlate it; otherwise leave\n // it for `onEnd`/`onAbort` (the observer reaps any open children on close).\n const callId = errorCallId(event);\n if (callId === undefined || !calls.delete(callId)) return;\n observe({\n type: 'agent.end',\n id: callId,\n error: errorValue(event) ?? event,\n });\n spans.delete(callId);\n },\n\n executeLanguageModelCall(options) {\n // `onLanguageModelCallStart` already opened this call's `chat` span (it is\n // the top of the stack). Run the provider call inside it so any\n // auto-instrumented HTTP spans become its children.\n const state = calls.get(options.callId);\n const id = state?.openLm.at(-1);\n return runInSpan(id ? spans.get(id) : undefined, options.execute);\n },\n\n executeTool(options) {\n // `onToolExecutionStart` already opened the tool span; run the tool's\n // `execute` inside it so a nested `generateText` nests under the tool.\n const id = `${options.callId}:tool:${options.toolCallId}`;\n return runInSpan(spans.get(id), options.execute);\n },\n };\n}\n\n/** Operations that are not `invoke_agent` roots (embeddings, rerank). */\nfunction isNonAgentOperation(operationId: string | undefined): boolean {\n if (!operationId) return false;\n return operationId.includes('embed') || operationId.includes('rerank');\n}\n\nfunction mapStreaming(\n performance: LanguageModelCallEndEventView['performance'],\n): ChatStreamTiming | undefined {\n if (!performance) return undefined;\n const timing: ChatStreamTiming = {\n timeToFinish: msToSeconds(performance.responseTimeMs),\n outputTokensPerSecond: performance.effectiveOutputTokensPerSecond,\n };\n return timing.timeToFinish !== undefined ||\n timing.outputTokensPerSecond !== undefined\n ? timing\n : undefined;\n}\n\nfunction msToSeconds(ms: number | undefined): number | undefined {\n return ms === undefined ? undefined : ms / 1000;\n}\n\nfunction errorCallId(event: unknown): string | undefined {\n if (\n typeof event === 'object' &&\n event !== null &&\n 'callId' in event &&\n typeof (event as { callId: unknown }).callId === 'string'\n ) {\n return (event as { callId: string }).callId;\n }\n return undefined;\n}\n\nfunction errorValue(event: unknown): unknown {\n if (typeof event === 'object' && event !== null && 'error' in event) {\n return (event as { error: unknown }).error;\n }\n return undefined;\n}\n","/**\n * `subscribeAiTelemetry` — the zero-config Vercel AI SDK path.\n *\n * Instead of `registerTelemetry(autotelTelemetry())`, subscribe to the AI SDK's\n * `ai:telemetry` Node [tracing channel](https://nodejs.org/api/diagnostics_channel.html).\n * The SDK publishes an operation span per `generateText` / `streamText` /\n * `languageModelCall` / `executeTool` / `embed` as long as the channel has a\n * subscriber — no registration call, and it binds async context across provider\n * calls and tool executions:\n *\n * ```ts\n * import { subscribeAiTelemetry } from 'autotel-genai/observer';\n *\n * const unsubscribe = subscribeAiTelemetry(); // once, at startup\n * ```\n *\n * Fidelity note: the channel exposes coarser data than the registered\n * {@link autotelTelemetry} integration. You get the canonical\n * `invoke_agent › chat › execute_tool` tree with token usage and cost (read\n * from each operation's result), but **not** the per-call streaming timing\n * (`time_to_first_chunk` etc.) that only the lifecycle `onLanguageModelCallEnd`\n * event carries. Prefer `registerTelemetry(autotelTelemetry())` when you can;\n * use this when you cannot add a registration call.\n *\n * No-ops (returning an unsubscribe that does nothing) on runtimes without Node\n * diagnostics-channel support, so it is safe to call unconditionally.\n */\n\nimport type { Context, Tracer } from '@opentelemetry/api';\nimport { subscribeTracingChannel } from 'autotel/diagnostics';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type { GenAiObserver, GenAiObserverEvent } from './types.js';\n\nconst AI_SDK_TELEMETRY_TRACING_CHANNEL = 'ai:telemetry';\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/** A published tracing-channel message: `{ type, event }` plus the settled result. */\ninterface ChannelMessage {\n type?: string;\n event?: ChannelEvent;\n result?: unknown;\n error?: unknown;\n}\n\ninterface ChannelEvent {\n callId?: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n toolCall?: { toolCallId?: string; toolName?: string; input?: unknown };\n messages?: readonly ModelMessageView[];\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\nexport interface SubscribeAiTelemetryOptions {\n /** Tracer to emit spans on. Defaults to the global observer tracer. */\n tracer?: Tracer;\n /**\n * Capture prompt/response content and tool arguments/results. Off by default.\n * @see {@link import('./ai-sdk-telemetry.js').AutotelTelemetryOptions.captureContent}\n */\n captureContent?: boolean;\n /** Privacy gate for captured content (redact or drop per event). */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /** Resolve an OTel parent context for otherwise-root operations. */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/**\n * Subscribe to the `ai:telemetry` tracing channel and emit canonical `gen_ai.*`\n * spans. Returns an unsubscribe function (a no-op on unsupported runtimes).\n */\nexport function subscribeAiTelemetry(\n options: SubscribeAiTelemetryOptions = {},\n): () => void {\n const captureContent = options.captureContent ?? false;\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n });\n\n // Pair each `start` with its `asyncEnd`/`error` by message-object identity,\n // recording the observer span id we assigned at start.\n const ids = new WeakMap<ChannelMessage, string>();\n let seq = 0;\n\n const handlers = {\n start(message: ChannelMessage) {\n const event = message.event ?? {};\n const callId = event.callId ?? `op:${seq}`;\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n ids.set(message, callId);\n observe({\n type: 'agent.start',\n id: callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.modelId ?? AI_SDK_AGENT_NAME },\n });\n return;\n }\n case 'languageModelCall': {\n const id = `${callId}:lm:${seq++}`;\n ids.set(message, id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: callId,\n request: toChatRequest(event),\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n return;\n }\n case 'executeTool': {\n const id = `${callId}:tool:${event.toolCall?.toolCallId ?? seq++}`;\n ids.set(message, id);\n observe({\n type: 'tool.start',\n id,\n parentId: callId,\n tool: {\n name: event.toolCall?.toolName,\n callId: event.toolCall?.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall?.input\n : undefined,\n });\n return;\n }\n case 'embed': {\n ids.set(message, callId);\n observe({\n type: 'chat.start',\n id: callId,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n return;\n }\n // 'step' and 'rerank' are intentionally not mapped.\n }\n },\n\n asyncEnd(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n closeSpan(observe, message, id, captureContent);\n },\n\n error(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n // `closeSpan` reads `message.error`; route by type to the right end event.\n closeSpan(observe, message, id, captureContent);\n },\n };\n\n return subscribeTracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL, handlers);\n}\n\nfunction closeSpan(\n observe: GenAiObserver,\n message: ChannelMessage,\n id: string,\n captureContent: boolean,\n): void {\n const error = message.error;\n const result = message.result as ResultView | undefined;\n const event = message.event;\n\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n observe({ type: 'agent.end', id, error });\n return;\n }\n case 'embed': {\n observe({\n type: 'chat.end',\n id,\n response: { model: event?.modelId },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n error,\n });\n return;\n }\n case 'languageModelCall': {\n const outputMessage =\n captureContent && event?.recordOutputs !== false\n ? contentToGenAiMessage(result?.content, result?.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: result?.response?.modelId ?? event?.modelId,\n id: result?.response?.id,\n finishReasons: result?.finishReason ? [result.finishReason] : undefined,\n },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n outputMessages: outputMessage ? [outputMessage] : undefined,\n error,\n });\n return;\n }\n case 'executeTool': {\n observe({\n type: 'tool.end',\n id,\n callResult:\n captureContent &&\n event?.recordOutputs !== false &&\n result?.output !== undefined\n ? result.output\n : undefined,\n error,\n });\n return;\n }\n }\n}\n\ninterface ResultView {\n content?: readonly ContentPartView[];\n finishReason?: string;\n output?: unknown;\n response?: { id?: string; modelId?: string };\n usage?: AiSdkUsageShape;\n}\n\n","/**\n * LangChain / LangGraph → {@link createGenAiObserver} glue.\n *\n * LangChain's callback system is a real event stream: register one handler and\n * it receives every run's start/end/error with a `runId` and `parentRunId` —\n * which map directly onto the observer's `id` / `parentId`. {@link\n * createLangChainObserver} returns a handler object you pass to `callbacks`:\n *\n * - chain runs (LangGraph nodes, agents) → `invoke_agent` spans;\n * - LLM / chat-model runs → `chat` spans (with usage and estimated cost);\n * - tool runs → `execute_tool` spans.\n *\n * Typed structurally against the callback payloads so it pulls in no LangChain\n * dependency. Pass it via `{ callbacks: [createLangChainObserver(observe)] }`.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * await graph.invoke(input, { callbacks: [createLangChainObserver(observe)] });\n * ```\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { TokenUsage } from '../cost.js';\nimport type { GenAiObserver } from './types.js';\n\n/** LangChain `Serialized` — the identity of a chain/LLM/tool. */\ninterface Serialized {\n id?: string[];\n name?: string;\n kwargs?: Record<string, unknown>;\n}\n\ninterface LLMGeneration {\n generationInfo?: Record<string, unknown>;\n message?: {\n usage_metadata?: Record<string, unknown>;\n response_metadata?: Record<string, unknown>;\n };\n}\n\n/** LangChain `LLMResult` — what an LLM/chat-model run returns. */\ninterface LLMResult {\n generations?: LLMGeneration[][];\n llmOutput?: Record<string, unknown>;\n}\n\n/** The subset of LangChain's `CallbackHandlerMethods` this adapter implements. */\nexport interface LangChainObserverHandler {\n handleChainStart(\n chain: Serialized,\n inputs: unknown,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runType?: string,\n runName?: string,\n ): void;\n handleChainEnd(outputs: unknown, runId: string): void;\n handleChainError(error: unknown, runId: string): void;\n handleLLMStart(\n llm: Serialized,\n prompts: string[],\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleChatModelStart(\n llm: Serialized,\n messages: unknown,\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleLLMEnd(output: LLMResult, runId: string): void;\n handleLLMError(error: unknown, runId: string): void;\n handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runName?: string,\n ): void;\n handleToolEnd(output: unknown, runId: string): void;\n handleToolError(error: unknown, runId: string): void;\n}\n\nexport interface LangChainObserverOptions {\n /**\n * Decide whether a chain run becomes an `invoke_agent` span. The default\n * skips LangChain/LangGraph plumbing (Runnable*, ChannelWrite, Branch,\n * prompts, `__start__`/`__end__`) so only the graph and its named nodes show;\n * children of a skipped chain reparent to the nearest kept ancestor.\n */\n skipChain?: (name: string) => boolean;\n}\n\n/** LangChain/LangGraph structural runnables that are noise, not real steps. */\nconst PLUMBING_CHAIN = /^(Runnable|Channel|Branch|Prompt|ChatPrompt|__)/i;\n\nexport function createLangChainObserver(\n observe: GenAiObserver,\n options: LangChainObserverOptions = {},\n): LangChainObserverHandler {\n const skipChain = options.skipChain ?? ((name) => PLUMBING_CHAIN.test(name));\n // Remember each LLM run's request model so `chat.end` can price it — the end\n // payload doesn't reliably carry the model. Cleared on end and error.\n const models = new Map<string, string>();\n // Skipped chains record the kept ancestor their children should parent to.\n const reparented = new Map<string, string | undefined>();\n\n /** The span id a child of `runId` should use as its parent. */\n function keptParent(runId: string | undefined): string | undefined {\n if (runId === undefined) return undefined;\n return reparented.has(runId) ? reparented.get(runId) : runId;\n }\n\n function startChat(\n llm: Serialized,\n runId: string,\n parentRunId: string | undefined,\n extraParams: Record<string, unknown> | undefined,\n ): void {\n const model = langChainModel(llm, extraParams);\n if (model) models.set(runId, model);\n observe({\n type: 'chat.start',\n id: runId,\n parentId: keptParent(parentRunId),\n request: { provider: langChainProvider(llm), model },\n });\n }\n\n return {\n handleChainStart(\n chain,\n _inputs,\n runId,\n parentRunId,\n _tags,\n _metadata,\n _runType,\n runName,\n ) {\n const name = runName ?? lastIdSegment(chain) ?? 'chain';\n const parentId = keptParent(parentRunId);\n if (skipChain(name)) {\n reparented.set(runId, parentId);\n return;\n }\n observe({ type: 'agent.start', id: runId, parentId, agent: { name } });\n },\n handleChainEnd(_outputs, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId });\n },\n handleChainError(error, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId, error });\n },\n\n handleLLMStart(llm, _prompts, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleChatModelStart(llm, _messages, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleLLMEnd(output, runId) {\n const costModel = models.get(runId);\n models.delete(runId);\n observe({\n type: 'chat.end',\n id: runId,\n response: { finishReasons: finishReasons(output) },\n usage: langChainUsage(output),\n costModel,\n });\n },\n handleLLMError(error, runId) {\n models.delete(runId);\n observe({ type: 'chat.end', id: runId, error });\n },\n\n handleToolStart(\n tool,\n input,\n runId,\n parentRunId,\n _tags,\n _metadata,\n runName,\n ) {\n observe({\n type: 'tool.start',\n id: runId,\n parentId: keptParent(parentRunId),\n tool: { name: runName ?? lastIdSegment(tool) },\n callArguments: input,\n });\n },\n handleToolEnd(output, runId) {\n observe({ type: 'tool.end', id: runId, callResult: output });\n },\n handleToolError(error, runId) {\n observe({ type: 'tool.end', id: runId, error });\n },\n };\n}\n\n/**\n * Provider from a `Serialized.id` path. LangChain serializes a chat model as\n * `[...namespace, provider, ClassName]` (e.g. `…, 'openai', 'ChatOpenAI'`), so\n * the provider is the lowercase module segment before the PascalCase class.\n */\nfunction langChainProvider(serialized: Serialized): string | undefined {\n const candidate = serialized.id?.at(-2);\n return candidate && candidate === candidate.toLowerCase()\n ? normalizeAiSdkProvider(candidate)\n : undefined;\n}\n\nfunction langChainModel(\n serialized: Serialized,\n extraParams: Record<string, unknown> | undefined,\n): string | undefined {\n const invocation = asRecord(extraParams?.invocation_params);\n return (\n str(invocation?.model) ??\n str(invocation?.model_name) ??\n str(serialized.kwargs?.model) ??\n str(serialized.kwargs?.model_name)\n );\n}\n\nfunction langChainUsage(output: LLMResult): TokenUsage | undefined {\n // `usage_metadata` is LangChain's canonical cross-provider shape (Ollama,\n // newer integrations); `llmOutput.tokenUsage` is OpenAI; `llmOutput.usage`\n // is Anthropic-style.\n const usageMetadata = asRecord(\n firstGeneration(output)?.message?.usage_metadata,\n );\n const tokenUsage = asRecord(output.llmOutput?.tokenUsage);\n const usage = asRecord(output.llmOutput?.usage);\n const inputTokens =\n num(usageMetadata?.input_tokens) ??\n num(tokenUsage?.promptTokens) ??\n num(usage?.input_tokens);\n const outputTokens =\n num(usageMetadata?.output_tokens) ??\n num(tokenUsage?.completionTokens) ??\n num(usage?.output_tokens);\n if (inputTokens === undefined && outputTokens === undefined) return undefined;\n return { inputTokens, outputTokens };\n}\n\nfunction finishReasons(output: LLMResult): string[] | undefined {\n const generation = firstGeneration(output);\n const reason =\n str(generation?.generationInfo?.finish_reason) ??\n str(asRecord(generation?.message?.response_metadata)?.done_reason);\n return reason ? [reason] : undefined;\n}\n\nfunction firstGeneration(output: LLMResult): LLMGeneration | undefined {\n return output.generations?.[0]?.[0];\n}\n\nfunction lastIdSegment(serialized: Serialized): string | undefined {\n return serialized.name ?? serialized.id?.at(-1);\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value)\n ? value\n : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiBA,IAAa,eAAb,MAA0B;CACxB,AAAiB,UAAU,IAAI,IAAmB;;CAGlD,IAAI,IAAY,MAAY,UAAoC;EAC9D,KAAK,QAAQ,IAAI,IAAI;GAAE;GAAM;EAAS,CAAC;CACzC;;CAGA,QAAQ,IAA8B;EACpC,OAAO,KAAK,QAAQ,IAAI,EAAE,CAAC,EAAE;CAC/B;;CAGA,KAAK,IAA8B;EACjC,MAAM,QAAQ,KAAK,QAAQ,IAAI,EAAE;EACjC,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,QAAQ,OAAO,EAAE;EACtB,OAAO,MAAM;CACf;;;;;;CAOA,gBACE,UACA,SACA,SACM;EAGN,KAAK,MAAM,CAAC,IAAI,UAAU,KAAK,SAAS;GACtC,IAAI,MAAM,aAAa,UAAU;GACjC,KAAK,QAAQ,OAAO,EAAE;GACtB,KAAK,gBAAgB,IAAI,SAAS,OAAO;GACzC,MAAM,KAAK,UAAU;IAAE,MAAMA,kCAAe;IAAO;GAAQ,CAAC;GAC5D,MAAM,KAAK,IAAI,OAAO;EACxB;CACF;;CAGA,IAAI,YAAoB;EACtB,OAAO,KAAK,QAAQ;CACtB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACQA,MAAM,iBACJ;AAKF,SAAgB,oBACd,UAAgC,CAAC,GAClB;CACf,MAAM,SACJ,QAAQ,UAAUC,yBAAU,UAAU,wBAAwB;CAChE,MAAM,WAAW,IAAI,aAAa;;CAGlC,SAAS,cAAc,OAAwC;EAC7D,MAAM,aAAa,MAAM,WACrB,SAAS,QAAQ,MAAM,QAAQ,IAC/B;EACJ,IAAI,YAAY,OAAOA,yBAAU,QAAQC,2BAAY,OAAO,GAAG,UAAU;EACzE,OAAO,QAAQ,uBAAuB,KAAK;CAC7C;;CAGA,SAAS,MACP,OACA,MACA,MACA,YACM;EACN,MAAM,SAAS,cAAc,KAAK;EAClC,MAAM,cAA2B;GAC/B;GACA;GACA,WAAW,MAAM;GACjB,OAAO,MAAM;GACb,MAAM,WAAW;EACnB;EACA,MAAM,OAAO,OAAO,UAAU,MAAM,aAAa,MAAM;EACvD,SAAS,IAAI,MAAM,IAAI,MAAM,MAAM,QAAQ;EAC3C,QAAQ,cAAc,MAAM,IAAI,IAAI;EACpC,OAAO;CACT;;;;;CAMA,SAAS,IAAI,OAAgB,UAAuC;EAClE,MAAM,OAAO,SAAS,KAAK,MAAM,EAAE;EACnC,IAAI,CAAC,MAAM;EACX,SAAS,gBAAgB,MAAM,IAAI,gBAAgB,MAAM,OAAO;EAChE,WAAW,IAAI;EACf,IAAI,MAAM,UAAU,WAAW;GAC7B,MAAM,UAAU,aAAa,MAAM,KAAK;GACxC,KAAK,UAAU;IAAE,MAAMC,kCAAe;IAAO;GAAQ,CAAC;GAGtD,KAAK,gBACH,MAAM,iBAAiB,QAAQ,MAAM,QAAS,WAAW,OAC3D;EACF;EACA,KAAK,IAAI,MAAM,OAAO;CACxB;;CAGA,SAAS,gBACP,OACgC;EAChC,IAAI,CAAC,QAAQ,eAAe,OAAO;EACnC,IAAI;GACF,OAAO,QAAQ,cAAc,EAAE,GAAG,MAAM,CAAC;EAC3C,SAAS,OAAO;GACd,QAAQ,MAAM,kDAAkD,KAAK;GACrE,OAAO;EACT;CACF;CAEA,SAAS,eAAe,MAAY,OAA6B;EAC/D,MAAM,UAAU,gBAAgB,KAAK;EACrC,IAAI,SAAS,SAAS,cAAc;EACpC,+BAAgB,SAAS,IAAI,GAAG;GAC9B,eAAe,QAAQ;GACvB,oBAAoB,QAAQ;EAC9B,CAAC;CACH;CAEA,SAAS,aAAa,MAAY,OAA2B;EAC3D,IAAI,MAAM,UAAU;GAClB,KAAK,cAAcC,2CAAwB,MAAM,QAAQ,CAAC;EAC5D;EACA,IAAI,MAAM,OAAO;GACf,MAAM,YAAY,MAAM,aAAa,MAAM,UAAU;GACrD,MAAM,UAAU,YACZC,6BAAgB,WAAW,MAAM,KAAK,IACtC;GACJ,KAAK,cAAcC,wCAAqB;IAAE,GAAG,MAAM;IAAO;GAAQ,CAAC,CAAC;EACtE;EACA,IAAI,MAAM,WAAW;GACnB,MAAM,EAAE,cAAc,uBAAuB,uBAC3C,MAAM;GACR,MAAM,iBAAoC,CAAC;GAC3C,IAAI,iBAAiB,WAAW;IAC9B,eAAeC,uBAAO,2BAA2B;GACnD;GACA,IAAI,0BAA0B,WAAW;IACvC,eAAeA,uBAAO,qCACpB;GACJ;GACA,IAAI,uBAAuB,WAAW;IACpC,eAAeA,uBAAO,kCACpB;GACJ;GACA,KAAK,cAAc,cAAc;EACnC;EACA,MAAM,UAAU,gBAAgB,KAAK;EACrC,IAAI,SAAS,SAAS,YAAY;GAChC,+BAAgB,SAAS,IAAI,GAAG,EAC9B,gBAAgB,QAAQ,eAC1B,CAAC;EACH;CACF;CAEA,SAAS,eAAe,MAAY,OAA6B;EAC/D,MAAM,UAAU,gBAAgB,KAAK;EACrC,IAAI,SAAS,SAAS,cAAc;EACpC,KAAK,cACHC,uCAAoB,EAAE,eAAe,QAAQ,cAAc,CAAC,CAC9D;CACF;CAEA,SAAS,aAAa,MAAY,OAA2B;EAC3D,MAAM,UAAU,gBAAgB,KAAK;EACrC,IAAI,SAAS,SAAS,YAAY;EAClC,KAAK,cAAcA,uCAAoB,EAAE,YAAY,QAAQ,WAAW,CAAC,CAAC;CAC5E;CAEA,SAAS,cACP,UACA,OACM;EACN,MAAM,SAAS,SAAS,QAAQ,QAAQ;EACxC,IAAI,CAAC,QAAQ;EACb,MAAMC,4CAAqB,MAAM,CAAC;CACpC;CAEA,QAAQ,UAAoC;EAC1C,QAAQ,MAAM,MAAd;GACE,KAAK,kBAAkB;IACrB,MACE,OACAC,8BACEC,iCAAiB,iBACjB,MAAM,SAAS,YACjB,GACAC,4BAAS,UACT;MACGL,uBAAO,iBAAiBI,iCAAiB;KAC1C,GAAGE,2CAAwB,MAAM,QAAQ;IAC3C,CACF;IACA;GACF;GACA,KAAK,eAAe;IAClB,MAAM,WAAW,CAAC,MAAM;IACxB,MACE,OACAH,8BAAcC,iCAAiB,cAAc,MAAM,MAAM,IAAI,GAC7D,WAAWC,4BAAS,WAAWA,4BAAS,QACxC;MACGL,uBAAO,iBAAiBI,iCAAiB;KAC1C,GAAI,MAAM,WACN,GAAGJ,uBAAO,gBAAgB,MAAM,SAAS,IACzC,CAAC;KACL,GAAGO,wCAAqB,MAAM,OAAO,EAAE,SAAS,CAAC;IACnD,CACF;IACA;GACF;GACA,KAAK,cAAc;IACjB,MAAM,YAAY,MAAM,QAAQ,aAAaH,iCAAiB;IAC9D,MAAM,OAAO,MACX,OACAD,8BAAc,WAAW,MAAM,QAAQ,KAAK,GAC5CE,4BAAS,QACTG,0CAAuB;KAAE,GAAG,MAAM;KAAS;IAAU,CAAC,CACxD;IACA,eAAe,MAAM,KAAK;IAC1B;GACF;GACA,KAAK,cAAc;IACjB,MAAM,OAAO,MACX,OACAL,8BAAcC,iCAAiB,cAAc,MAAM,KAAK,IAAI,GAC5DC,4BAAS,UACT;MACGL,uBAAO,iBAAiBI,iCAAiB;KAC1C,GAAGH,uCAAoB,MAAM,IAAI;IACnC,CACF;IACA,eAAe,MAAM,KAAK;IAC1B;GACF;GACA,KAAK;GACL,KAAK,aAAa;IAChB,IAAI,KAAK;IACT;GACF;GACA,KAAK,YAAY;IACf,IAAI,QAAQ,SAAS,aAAa,MAAM,KAAK,CAAC;IAC9C;GACF;GACA,KAAK,YAAY;IACf,IAAI,QAAQ,SAAS,aAAa,MAAM,KAAK,CAAC;IAC9C;GACF;GACA,KAAK,aAAa;IAChB,cAAc,MAAM,WAAW,QAC7BQ,sCAAe;KACb;KACA,WAAW,MAAM;KACjB,aAAa,MAAM;KACnB,WAAW,MAAM;KACjB,SAAS,MAAM;IACjB,CAAC,CACH;IACA;GACF;GACA,KAAK,oBAAoB;IACvB,cAAc,MAAM,WAAW,QAC7BC,6CAAsB;KAAE;KAAK,YAAY,MAAM;IAAW,CAAC,CAC7D;IACA;GACF;GACA,KAAK,iBAAiB;IACpB,cAAc,MAAM,WAAW,QAC7BC,0CAAmB;KACjB;KACA,WAAW,MAAM;KACjB,cAAc,MAAM;KACpB,aAAa,MAAM;IACrB,CAAC,CACH;IACA;GACF;GACA,KAAK,iBAAiB;IACpB,cAAc,MAAM,WAAW,QAC7BC,0CAAmB;KACjB;KACA,QAAQ,MAAM;KACd,aAAa,MAAM;KACnB,UAAU,MAAM;IAClB,CAAC,CACH;IACA;GACF;GACA,SAAS;IACP,MAAM,aAAoB;IAC1B,KAAK;GACP;EACF;CACF;AACF;;AAGA,SAAS,SAAS,MAA8B;CAC9C,OAAO;EACL,gBAAgB,UAAU;GACxB,KAAK,cAAc,KAAK;EAC1B;EACA,aAAa,CAAC;CAChB;AACF;AAEA,SAAS,aAAa,OAAoC;CACxD,IAAI,UAAU,WAAW,OAAO;CAChC,IAAI,OAAO,UAAU,UAAU,OAAO,SAAS;CAC/C,IAAI,iBAAiB,OAAO,OAAO,MAAM;CACzC,IAAI;EACF,OAAO,KAAK,UAAU,KAAK;CAC7B,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF;;;;;;;;;;;;;;;;AC9TA,SAAgB,aACd,OACwB;CACxB,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,aAAyB;EAC7B,aAAa,MAAM,eAAe,MAAM,gBAAgB,MAAM;EAC9D,cAAc,MAAM,gBAAgB,MAAM;EAC1C,uBACE,MAAM,mBAAmB,MAAM,oBAAoB;EACrD,sBACE,MAAM,qBAAqB,MAAM,mBAAmB;EACtD,0BAA0B,MAAM,mBAAmB;CACrD;CACA,OAAO,OAAO,OAAO,UAAU,CAAC,CAAC,MAAM,MAAM,MAAM,SAAS,IACxD,aACA;AACN;;AAGA,SAAgB,kBAAkB,UAA8B;CAC9D,OAAO,WAAWC,6CAAuB,QAAQ,IAAI;AACvD;;AAiBA,SAAgB,cAAc,OAA6C;CACzE,OAAO;EACL,WAAWC,iCAAiB;EAC5B,UAAU,kBAAkB,MAAM,QAAQ;EAC1C,OAAO,MAAM;EACb,aAAa,MAAM;EACnB,WAAW,MAAM;EACjB,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,kBAAkB,MAAM;EACxB,iBAAiB,MAAM;EACvB,eAAe,MAAM;EACrB,MAAM,MAAM;CACd;AACF;;;;ACRA,MAAMC,sBAAoB;AAE1B,SAAgB,mBACd,SACA,QACA,SACM;CACN,MAAM,WAA0C,QAAQ,WACpDC,6CAAuB,QAAQ,QAAQ,IACvC;CACJ,MAAM,QAAQ,OAAO,SAAS,CAAC,MAAM;CACrC,MAAM,YAAY,MAAM,QACrB,OAAO,SAAS,SAAS,KAAK,WAAW,UAAU,IACpD,CACF;CAEA,IAAI,MAAM,UAAU,KAAK,cAAc,GAAG;EACxC,SAAS,SAAS,MAAM,MAAM,QAAQ;GACpC,IAAI,QAAQ;GACZ,UAAU,QAAQ;GAClB;GACA,OAAO,QAAQ;EACjB,CAAC;EACD;CACF;CAEA,QAAQ;EACN,MAAM;EACN,IAAI,QAAQ;EACZ,UAAU,QAAQ;EAClB;EACA,OAAO,EAAE,MAAM,QAAQ,SAASD,oBAAkB;CACpD,CAAC;CACD,MAAM,SAAS,MAAM,UAAU;EAC7B,SAAS,SAAS,MAAM;GACtB,IAAI,GAAG,QAAQ,GAAG,QAAQ;GAC1B,UAAU,QAAQ;GAClB;GACA,OAAO,QAAQ;EACjB,CAAC;EACD,UAAU,SAAS,MAAM,QAAQ,IAAI,KAAK;CAC5C,CAAC;CACD,QAAQ;EAAE,MAAM;EAAa,IAAI,QAAQ;CAAG,CAAC;AAC/C;AASA,SAAS,SACP,SACA,MACA,KACM;CACN,QAAQ;EACN,MAAM;EACN,IAAI,IAAI;EACR,UAAU,IAAI;EACd,SAAS;GAAE,UAAU,IAAI;GAAU,OAAO,IAAI;EAAM;CACtD,CAAC;CACD,MAAM,gBAAgB,KAAK,UAAU;CACrC,QAAQ;EACN,MAAM;EACN,IAAI,IAAI;EACR,UAAU;GACR,OAAO;GACP,IAAI,KAAK,UAAU;GACnB,eAAe,KAAK,eAAe,CAAC,KAAK,YAAY,IAAI;EAC3D;EACA,OAAO,aAAa,KAAK,KAAK;EAC9B,WAAW,IAAI,SAAS;EACxB,SAAS,YAAY,KAAK,UAAU,SAAS;CAC/C,CAAC;AACH;AAEA,SAAS,UACP,SACA,MACA,UACA,WACM;CACN,MAAM,UAAU,KAAK,eAAe,CAAC;CACrC,CAAC,KAAK,aAAa,CAAC,EAAC,CAAE,SAAS,MAAM,cAAc;EAClD,MAAM,KAAK,GAAG,SAAS,QAAQ,KAAK,cAAc,GAAG,UAAU,GAAG;EAClE,QAAQ;GACN,MAAM;GACN;GACA;GACA,MAAM;IAAE,MAAM,KAAK;IAAU,QAAQ,KAAK;GAAW;GACrD,eAAe,KAAK,SAAS,KAAK;EACpC,CAAC;EACD,MAAM,SAAS,KAAK,aAChB,QAAQ,MAAM,MAAM,EAAE,eAAe,KAAK,UAAU,IACpD,QAAQ;EACZ,QAAQ;GACN,MAAM;GACN;GACA,YAAY,QAAQ,UAAU,QAAQ;EACxC,CAAC;CACH,CAAC;AACH;AAEA,SAAS,YAAY,OAAyD;CAC5E,OAAO,iBAAiB,QAAQ,OAAO,UAAU,WAAW,QAAQ;AACtE;;;;;;;;;ACjIA,SAAgB,sBACd,UACiB;CACjB,IAAI,CAAC,UAAU,QAAQ,OAAO,EAAE,UAAU,CAAC,EAAE;CAE7C,MAAM,MAAsB,CAAC;CAC7B,MAAM,SAA6B,CAAC;CAEpC,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,OAAO,QAAQ,QAAQ;EAC7B,MAAM,QAAQ,eAAe,QAAQ,OAAO;EAC5C,IAAI,SAAS,UAAU;GACrB,OAAO,KAAK,GAAG,KAAK;EACtB,OAAO;GACL,IAAI,KAAK;IAAE;IAAM;GAAM,CAAC;EAC1B;CACF;CAEA,OAAO,OAAO,SAAS,IACnB;EAAE,UAAU;EAAK,oBAAoB;CAAO,IAC5C,EAAE,UAAU,IAAI;AACtB;;;;;AAMA,SAAgB,sBACd,SACA,cAC0B;CAC1B,IAAI,CAAC,SAAS,QAAQ,OAAO;CAC7B,MAAM,UAAwB;EAC5B,MAAM;EACN,OAAO,QAAQ,SAAS,SAAS,YAAY,IAAI,CAAC;CACpD;CACA,IAAI,cAAc,QAAQ,gBAAgB;CAC1C,OAAO;AACT;AAEA,SAAS,eACP,SACoB;CACpB,IAAI,YAAY,WAAW,OAAO,CAAC;CACnC,IAAI,OAAO,YAAY,UAAU;EAC/B,OAAO,UAAU,CAAC;GAAE,MAAM;GAAQ;EAAQ,CAAC,IAAI,CAAC;CAClD;CACA,OAAO,QAAQ,SAAS,SAAS,YAAY,IAAI,CAAC;AACpD;;AAGA,SAAS,YAAY,MAA2C;CAC9D,QAAQ,KAAK,MAAb;EACE,KAAK,QAAQ;GACX,OAAO,KAAK,OAAO,CAAC;IAAE,MAAM;IAAQ,SAAS,KAAK;GAAK,CAAC,IAAI,CAAC;EAC/D;EACA,KAAK,aAAa;GAChB,OAAO,KAAK,OAAO,CAAC;IAAE,MAAM;IAAa,SAAS,KAAK;GAAK,CAAC,IAAI,CAAC;EACpE;EACA,KAAK,aAAa;GAChB,OAAO,CACL;IACE,MAAM;IACN,IAAI,KAAK;IACT,MAAM,KAAK;IACX,WAAW,KAAK;GAClB,CACF;EACF;EACA,KAAK,eAAe;GAClB,OAAO,CACL;IACE,MAAM;IACN,IAAI,KAAK;IACT,UAAU,KAAK;GACjB,CACF;EACF;EACA,KAAK;EACL,KAAK,QAAQ;GAEX,OAAO,CACL;IACE,MAAM;IACN,UAAU,KAAK,SAAS,UAAU,UAAU;IAC5C,WAAW,KAAK;GAClB,CACF;EACF;EACA,SAAS;GACP,OAAO,KAAK,OAAO,CAAC,EAAE,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC;EAC9C;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACwFA,MAAME,sBAAoB;;;;;;;;;;;;;AAc1B,SAAgB,iBACd,UAAmC,CAAC,GACP;CAC7B,MAAM,iBAAiB,QAAQ,kBAAkB;CAGjD,MAAM,gBACJ,QAAQ,kBACP,kBAAkB,UAA8B,QAAQ;CAI3D,MAAM,QAAQ,IAAI,IAAkB;CAEpC,MAAM,UAAyB,oBAAoB;EACjD,QAAQ,QAAQ;EAChB;EACA,sBAAsB,QAAQ;EAC9B,cAAc,IAAI,SAAS,MAAM,IAAI,IAAI,IAAI;CAC/C,CAAC;CACD,MAAM,QAAQ,IAAI,IAAuB;CAEzC,SAAS,gBACP,QACA,OACA,MACQ;EACR,IAAI,KAAK,YAAY,OAAO,GAAG,OAAO,QAAQ,KAAK;EACnD,MAAM,MAAM,OAAO,WAAW;EAC9B,IAAI,OAAO,MAAM,WAAW;EAC5B,MAAM,KAAK,GAAG,OAAO,aAAa;EAClC,OAAO,iBAAiB,KAAK,EAAE;EAC/B,OAAO;CACT;CAEA,SAAS,cACP,QACA,OACA,MACQ;EACR,IAAI,KAAK,YAAY,OAAO,GAAG,OAAO,QAAQ,KAAK;EACnD,OAAO,OAAO,iBAAiB,MAAM,KAAK,GAAG,OAAO;CACtD;;CAGA,SAAS,UACP,MACA,SACgB;EAChB,IAAI,CAAC,MAAM,OAAO,QAAQ;EAC1B,OAAOC,2BAAY,KACjBC,yBAAU,QAAQD,2BAAY,OAAO,GAAG,IAAI,GAC5C,OACF;CACF;CAEA,OAAO;EACL,QAAQ,OAAO;GAGb,IAAI,oBAAoB,MAAM,WAAW,GAAG;GAC5C,MAAM,QAAmB;IACvB,UAAU;IACV,QAAQ,MAAM,aAAa,SAAS,QAAQ;IAC5C,QAAQ,CAAC;IACT,OAAO;IACP,kBAAkB,CAAC;IACnB,SAAS;GACX;GACA,MAAM,IAAI,MAAM,QAAQ,KAAK;GAC7B,QAAQ;IACN,MAAM;IACN,IAAI,MAAM;IACV,UAAU,kBAAkB,MAAM,QAAQ;IAC1C,OAAO,EAAE,MAAM,MAAM,cAAc,MAAM,WAAWD,oBAAkB;GACxE,CAAC;EACH;EAEA,yBAAyB,OAAO;GAC9B,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM;GACpC,MAAM,KAAK,GAAG,MAAM,OAAO,MAAM,QAAQ,MAAM,UAAU;GACzD,IAAI,OAAO,MAAM,OAAO,KAAK,EAAE;GAC/B,MAAM,UACJ,kBAAkB,MAAM,iBAAiB,QACrC,sBAAsB,MAAM,QAAQ,IACpC;GACN,QAAQ;IACN,MAAM;IACN;IACA,UAAU,OAAO,WAAW,MAAM,SAAS;IAC3C,SAAS;KAAE,GAAG,cAAc,KAAK;KAAG,QAAQ,OAAO;IAAO;IAC1D,eAAe,SAAS;IACxB,oBAAoB,SAAS;GAC/B,CAAC;EACH;EAEA,uBAAuB,OAAO;GAC5B,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM;GACpC,MAAM,KAAK,OAAO,OAAO,IAAI,KAAK,GAAG,MAAM,OAAO;GAClD,MAAM,gBACJ,kBAAkB,MAAM,kBAAkB,QACtC,sBAAsB,MAAM,SAAS,MAAM,YAAY,IACvD;GACN,QAAQ;IACN,MAAM;IACN;IACA,UAAU;KACR,OAAO,MAAM;KACb,IAAI,MAAM;KACV,eAAe,MAAM,eAAe,CAAC,MAAM,YAAY,IAAI;KAC3D,kBAAkB,YAAY,MAAM,aAAa,mBAAmB;IACtE;IACA,OAAO,aAAa,MAAM,KAAK;IAC/B,WAAW,MAAM;IACjB,WAAW,aAAa,MAAM,WAAW;IACzC,gBAAgB,gBAAgB,CAAC,aAAa,IAAI;GACpD,CAAC;GACD,MAAM,OAAO,EAAE;EACjB;EAEA,qBAAqB,OAAO;GAC1B,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM;GACpC,MAAM,KAAK,gBAAgB,MAAM,QAAQ,OAAO,MAAM,QAAQ;GAC9D,QAAQ;IACN,MAAM;IACN;IACA,UAAU,OAAO,WAAW,MAAM,SAAS;IAC3C,MAAM;KACJ,MAAM,MAAM,SAAS;KACrB,QAAQ,MAAM,SAAS;KACvB,MAAMG,iCAAiB;IACzB;IACA,eACE,kBAAkB,MAAM,iBAAiB,QACrC,MAAM,SAAS,QACf;GACR,CAAC;EACH;EAEA,mBAAmB,OAAO;GACxB,MAAM,QAAQ,MAAM,IAAI,MAAM,MAAM;GACpC,MAAM,KAAK,cAAc,MAAM,QAAQ,OAAO,MAAM,QAAQ;GAC5D,MAAM,UAAU,MAAM,WAAW,SAAS;GAC1C,QAAQ;IACN,MAAM;IACN;IACA,OAAO,UAAU,MAAM,WAAW,QAAQ;IAC1C,YACE,CAAC,WAAW,kBAAkB,MAAM,kBAAkB,QAClD,MAAM,WAAW,SACjB;GACR,CAAC;GACD,MAAM,OAAO,EAAE;EACjB;EAEA,WAAW,OAAO;GAChB,MAAM,KAAK,GAAG,MAAM,OAAO,SAAS,MAAM,eAAe;GACzD,QAAQ;IACN,MAAM;IACN;IACA,SAAS;KACP,WAAWC,iCAAiB;KAC5B,UAAU,kBAAkB,MAAM,QAAQ;KAC1C,OAAO,MAAM;IACf;GACF,CAAC;GACD,QAAQ;IACN,MAAM;IACN;IACA,UAAU,EAAE,OAAO,MAAM,QAAQ;IACjC,OACE,MAAM,OAAO,WAAW,YACpB,YACA,EAAE,aAAa,MAAM,MAAM,OAAO;IACxC,WAAW,MAAM;GACnB,CAAC;GACD,MAAM,OAAO,EAAE;EACjB;EAEA,MAAM,OAAO;GACX,IAAI,CAAC,MAAM,OAAO,MAAM,MAAM,GAAG;GACjC,QAAQ;IAAE,MAAM;IAAa,IAAI,MAAM;GAAO,CAAC;GAC/C,MAAM,OAAO,MAAM,MAAM;EAC3B;EAEA,QAAQ,OAAO;GACb,IAAI,CAAC,MAAM,OAAO,MAAM,MAAM,GAAG;GACjC,QAAQ;IACN,MAAM;IACN,IAAI,MAAM;IACV,OAAO,MAAM,UAAU;GACzB,CAAC;GACD,MAAM,OAAO,MAAM,MAAM;EAC3B;EAEA,QAAQ,OAAO;GAIb,MAAM,SAAS,YAAY,KAAK;GAChC,IAAI,WAAW,aAAa,CAAC,MAAM,OAAO,MAAM,GAAG;GACnD,QAAQ;IACN,MAAM;IACN,IAAI;IACJ,OAAO,WAAW,KAAK,KAAK;GAC9B,CAAC;GACD,MAAM,OAAO,MAAM;EACrB;EAEA,yBAAyB,SAAS;GAIhC,MAAM,QAAQ,MAAM,IAAI,QAAQ,MAAM;GACtC,MAAM,KAAK,OAAO,OAAO,GAAG,CAAC,CAAC;GAC9B,OAAO,UAAU,KAAK,MAAM,IAAI,EAAE,IAAI,WAAW,QAAQ,OAAO;EAClE;EAEA,YAAY,SAAS;GAGnB,MAAM,KAAK,GAAG,QAAQ,OAAO,QAAQ,QAAQ;GAC7C,OAAO,UAAU,MAAM,IAAI,EAAE,GAAG,QAAQ,OAAO;EACjD;CACF;AACF;;AAGA,SAAS,oBAAoB,aAA0C;CACrE,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO,YAAY,SAAS,OAAO,KAAK,YAAY,SAAS,QAAQ;AACvE;AAEA,SAAS,aACP,aAC8B;CAC9B,IAAI,CAAC,aAAa,OAAO;CACzB,MAAM,SAA2B;EAC/B,cAAc,YAAY,YAAY,cAAc;EACpD,uBAAuB,YAAY;CACrC;CACA,OAAO,OAAO,iBAAiB,aAC7B,OAAO,0BAA0B,YAC/B,SACA;AACN;AAEA,SAAS,YAAY,IAA4C;CAC/D,OAAO,OAAO,YAAY,YAAY,KAAK;AAC7C;AAEA,SAAS,YAAY,OAAoC;CACvD,IACE,OAAO,UAAU,YACjB,UAAU,QACV,YAAY,SACZ,OAAQ,MAA8B,WAAW,UACjD;EACA,OAAQ,MAA6B;CACvC;CACA,OAAO;AACT;AAEA,SAAS,WAAW,OAAyB;CAC3C,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,WAAW,OAAO;EACnE,OAAQ,MAA6B;CACvC;CACA,OAAO;AACT;;;;AC1dA,MAAM,mCAAmC;AACzC,MAAM,oBAAoB;;;;;AA8C1B,SAAgB,qBACd,UAAuC,CAAC,GAC5B;CACZ,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,gBACJ,QAAQ,kBACP,kBAAkB,UAA8B,QAAQ;CAE3D,MAAM,UAAyB,oBAAoB;EACjD,QAAQ,QAAQ;EAChB;EACA,sBAAsB,QAAQ;CAChC,CAAC;CAID,MAAM,MAAM,IAAI,QAAgC;CAChD,IAAI,MAAM;CAEV,MAAM,WAAW;EACf,MAAM,SAAyB;GAC7B,MAAM,QAAQ,QAAQ,SAAS,CAAC;GAChC,MAAM,SAAS,MAAM,UAAU,MAAM;GACrC,QAAQ,QAAQ,MAAhB;IACE,KAAK;IACL,KAAK,cAAc;KACjB,IAAI,IAAI,SAAS,MAAM;KACvB,QAAQ;MACN,MAAM;MACN,IAAI;MACJ,UAAU,kBAAkB,MAAM,QAAQ;MAC1C,OAAO,EAAE,MAAM,MAAM,WAAW,kBAAkB;KACpD,CAAC;KACD;IACF;IACA,KAAK,qBAAqB;KACxB,MAAM,KAAK,GAAG,OAAO,MAAM;KAC3B,IAAI,IAAI,SAAS,EAAE;KACnB,MAAM,UACJ,kBAAkB,MAAM,iBAAiB,QACrC,sBAAsB,MAAM,QAAQ,IACpC;KACN,QAAQ;MACN,MAAM;MACN;MACA,UAAU;MACV,SAAS,cAAc,KAAK;MAC5B,eAAe,SAAS;MACxB,oBAAoB,SAAS;KAC/B,CAAC;KACD;IACF;IACA,KAAK,eAAe;KAClB,MAAM,KAAK,GAAG,OAAO,QAAQ,MAAM,UAAU,cAAc;KAC3D,IAAI,IAAI,SAAS,EAAE;KACnB,QAAQ;MACN,MAAM;MACN;MACA,UAAU;MACV,MAAM;OACJ,MAAM,MAAM,UAAU;OACtB,QAAQ,MAAM,UAAU;OACxB,MAAMC,iCAAiB;MACzB;MACA,eACE,kBAAkB,MAAM,iBAAiB,QACrC,MAAM,UAAU,QAChB;KACR,CAAC;KACD;IACF;IACA,KAAK,SAAS;KACZ,IAAI,IAAI,SAAS,MAAM;KACvB,QAAQ;MACN,MAAM;MACN,IAAI;MACJ,SAAS;OACP,WAAWC,iCAAiB;OAC5B,UAAU,kBAAkB,MAAM,QAAQ;OAC1C,OAAO,MAAM;MACf;KACF,CAAC;KACD;IACF;GAEF;EACF;EAEA,SAAS,SAAyB;GAChC,MAAM,KAAK,IAAI,IAAI,OAAO;GAC1B,IAAI,OAAO,WAAW;GACtB,UAAU,SAAS,SAAS,IAAI,cAAc;EAChD;EAEA,MAAM,SAAyB;GAC7B,MAAM,KAAK,IAAI,IAAI,OAAO;GAC1B,IAAI,OAAO,WAAW;GAEtB,UAAU,SAAS,SAAS,IAAI,cAAc;EAChD;CACF;CAEA,wDAA+B,kCAAkC,QAAQ;AAC3E;AAEA,SAAS,UACP,SACA,SACA,IACA,gBACM;CACN,MAAM,QAAQ,QAAQ;CACtB,MAAM,SAAS,QAAQ;CACvB,MAAM,QAAQ,QAAQ;CAEtB,QAAQ,QAAQ,MAAhB;EACE,KAAK;EACL,KAAK,cAAc;GACjB,QAAQ;IAAE,MAAM;IAAa;IAAI;GAAM,CAAC;GACxC;EACF;EACA,KAAK,SAAS;GACZ,QAAQ;IACN,MAAM;IACN;IACA,UAAU,EAAE,OAAO,OAAO,QAAQ;IAClC,OAAO,aAAa,QAAQ,KAAK;IACjC,WAAW,OAAO;IAClB;GACF,CAAC;GACD;EACF;EACA,KAAK,qBAAqB;GACxB,MAAM,gBACJ,kBAAkB,OAAO,kBAAkB,QACvC,sBAAsB,QAAQ,SAAS,QAAQ,YAAY,IAC3D;GACN,QAAQ;IACN,MAAM;IACN;IACA,UAAU;KACR,OAAO,QAAQ,UAAU,WAAW,OAAO;KAC3C,IAAI,QAAQ,UAAU;KACtB,eAAe,QAAQ,eAAe,CAAC,OAAO,YAAY,IAAI;IAChE;IACA,OAAO,aAAa,QAAQ,KAAK;IACjC,WAAW,OAAO;IAClB,gBAAgB,gBAAgB,CAAC,aAAa,IAAI;IAClD;GACF,CAAC;GACD;EACF;EACA,KAAK,eAAe;GAClB,QAAQ;IACN,MAAM;IACN;IACA,YACE,kBACA,OAAO,kBAAkB,SACzB,QAAQ,WAAW,YACf,OAAO,SACP;IACN;GACF,CAAC;GACD;EACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;AC/JA,MAAM,iBAAiB;AAEvB,SAAgB,wBACd,SACA,UAAoC,CAAC,GACX;CAC1B,MAAM,YAAY,QAAQ,eAAe,SAAS,eAAe,KAAK,IAAI;CAG1E,MAAM,SAAS,IAAI,IAAoB;CAEvC,MAAM,aAAa,IAAI,IAAgC;;CAGvD,SAAS,WAAW,OAA+C;EACjE,IAAI,UAAU,WAAW,OAAO;EAChC,OAAO,WAAW,IAAI,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI;CACzD;CAEA,SAAS,UACP,KACA,OACA,aACA,aACM;EACN,MAAM,QAAQ,eAAe,KAAK,WAAW;EAC7C,IAAI,OAAO,OAAO,IAAI,OAAO,KAAK;EAClC,QAAQ;GACN,MAAM;GACN,IAAI;GACJ,UAAU,WAAW,WAAW;GAChC,SAAS;IAAE,UAAU,kBAAkB,GAAG;IAAG;GAAM;EACrD,CAAC;CACH;CAEA,OAAO;EACL,iBACE,OACA,SACA,OACA,aACA,OACA,WACA,UACA,SACA;GACA,MAAM,OAAO,WAAW,cAAc,KAAK,KAAK;GAChD,MAAM,WAAW,WAAW,WAAW;GACvC,IAAI,UAAU,IAAI,GAAG;IACnB,WAAW,IAAI,OAAO,QAAQ;IAC9B;GACF;GACA,QAAQ;IAAE,MAAM;IAAe,IAAI;IAAO;IAAU,OAAO,EAAE,KAAK;GAAE,CAAC;EACvE;EACA,eAAe,UAAU,OAAO;GAC9B,IAAI,WAAW,OAAO,KAAK,GAAG;GAC9B,QAAQ;IAAE,MAAM;IAAa,IAAI;GAAM,CAAC;EAC1C;EACA,iBAAiB,OAAO,OAAO;GAC7B,IAAI,WAAW,OAAO,KAAK,GAAG;GAC9B,QAAQ;IAAE,MAAM;IAAa,IAAI;IAAO;GAAM,CAAC;EACjD;EAEA,eAAe,KAAK,UAAU,OAAO,aAAa,aAAa;GAC7D,UAAU,KAAK,OAAO,aAAa,WAAW;EAChD;EACA,qBAAqB,KAAK,WAAW,OAAO,aAAa,aAAa;GACpE,UAAU,KAAK,OAAO,aAAa,WAAW;EAChD;EACA,aAAa,QAAQ,OAAO;GAC1B,MAAM,YAAY,OAAO,IAAI,KAAK;GAClC,OAAO,OAAO,KAAK;GACnB,QAAQ;IACN,MAAM;IACN,IAAI;IACJ,UAAU,EAAE,eAAe,cAAc,MAAM,EAAE;IACjD,OAAO,eAAe,MAAM;IAC5B;GACF,CAAC;EACH;EACA,eAAe,OAAO,OAAO;GAC3B,OAAO,OAAO,KAAK;GACnB,QAAQ;IAAE,MAAM;IAAY,IAAI;IAAO;GAAM,CAAC;EAChD;EAEA,gBACE,MACA,OACA,OACA,aACA,OACA,WACA,SACA;GACA,QAAQ;IACN,MAAM;IACN,IAAI;IACJ,UAAU,WAAW,WAAW;IAChC,MAAM,EAAE,MAAM,WAAW,cAAc,IAAI,EAAE;IAC7C,eAAe;GACjB,CAAC;EACH;EACA,cAAc,QAAQ,OAAO;GAC3B,QAAQ;IAAE,MAAM;IAAY,IAAI;IAAO,YAAY;GAAO,CAAC;EAC7D;EACA,gBAAgB,OAAO,OAAO;GAC5B,QAAQ;IAAE,MAAM;IAAY,IAAI;IAAO;GAAM,CAAC;EAChD;CACF;AACF;;;;;;AAOA,SAAS,kBAAkB,YAA4C;CACrE,MAAM,YAAY,WAAW,IAAI,GAAG,CAAC,CAAC;CACtC,OAAO,aAAa,cAAc,UAAU,YAAY,IACpDC,6CAAuB,SAAS,IAChC;AACN;AAEA,SAAS,eACP,YACA,aACoB;CACpB,MAAM,aAAa,SAAS,aAAa,iBAAiB;CAC1D,OACE,IAAI,YAAY,KAAK,KACrB,IAAI,YAAY,UAAU,KAC1B,IAAI,WAAW,QAAQ,KAAK,KAC5B,IAAI,WAAW,QAAQ,UAAU;AAErC;AAEA,SAAS,eAAe,QAA2C;CAIjE,MAAM,gBAAgB,SACpB,gBAAgB,MAAM,CAAC,EAAE,SAAS,cACpC;CACA,MAAM,aAAa,SAAS,OAAO,WAAW,UAAU;CACxD,MAAM,QAAQ,SAAS,OAAO,WAAW,KAAK;CAC9C,MAAM,cACJ,IAAI,eAAe,YAAY,KAC/B,IAAI,YAAY,YAAY,KAC5B,IAAI,OAAO,YAAY;CACzB,MAAM,eACJ,IAAI,eAAe,aAAa,KAChC,IAAI,YAAY,gBAAgB,KAChC,IAAI,OAAO,aAAa;CAC1B,IAAI,gBAAgB,aAAa,iBAAiB,WAAW,OAAO;CACpE,OAAO;EAAE;EAAa;CAAa;AACrC;AAEA,SAAS,cAAc,QAAyC;CAC9D,MAAM,aAAa,gBAAgB,MAAM;CACzC,MAAM,SACJ,IAAI,YAAY,gBAAgB,aAAa,KAC7C,IAAI,SAAS,YAAY,SAAS,iBAAiB,CAAC,EAAE,WAAW;CACnE,OAAO,SAAS,CAAC,MAAM,IAAI;AAC7B;AAEA,SAAS,gBAAgB,QAA8C;CACrE,OAAO,OAAO,cAAc,EAAE,GAAG;AACnC;AAEA,SAAS,cAAc,YAA4C;CACjE,OAAO,WAAW,QAAQ,WAAW,IAAI,GAAG,CAAC,CAAC;AAChD;AAEA,SAAS,SAAS,OAAqD;CACrE,OAAO,OAAO,UAAU,YAAY,UAAU,OACzC,QACD;AACN;AAEA,SAAS,IAAI,OAAoC;CAC/C,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AACjE;AAEA,SAAS,IAAI,OAAoC;CAC/C,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IACrD,QACA;AACN"}
@@ -1 +1 @@
1
- {"version":3,"file":"observer-Ckha9xGv.js","names":["otelTrace","otelContext","AI_SDK_AGENT_NAME","AI_SDK_AGENT_NAME","otelContext","otelTrace"],"sources":["../src/observer/span-registry.ts","../src/observer/observer.ts","../src/observer/ai-sdk-shapes.ts","../src/observer/ai-sdk.ts","../src/observer/ai-sdk-messages.ts","../src/observer/ai-sdk-telemetry.ts","../src/observer/ai-sdk-channel.ts","../src/observer/langchain.ts"],"sourcesContent":["/**\n * Tracks the spans currently open in a {@link createGenAiObserver} stream and\n * the parent each was started under, so the observer can:\n *\n * - look up a parent span when a child starts, and\n * - force-close any descendant whose terminal event never arrived because its\n * parent ended first (an aborted tool call, a cancelled turn, a run that was\n * interrupted). Without this, abandoned spans leak open forever.\n */\n\nimport { SpanStatusCode, type Span, type TimeInput } from '@opentelemetry/api';\n\ninterface Entry {\n span: Span;\n parentId: string | undefined;\n}\n\nexport class SpanRegistry {\n private readonly entries = new Map<string, Entry>();\n\n /** Record an open span and the parent it was started under. */\n add(id: string, span: Span, parentId: string | undefined): void {\n this.entries.set(id, { span, parentId });\n }\n\n /** The open span for `id`, if any (used to parent a starting child). */\n spanFor(id: string): Span | undefined {\n return this.entries.get(id)?.span;\n }\n\n /** Remove and return the open span for `id`. */\n take(id: string): Span | undefined {\n const entry = this.entries.get(id);\n if (!entry) return undefined;\n this.entries.delete(id);\n return entry.span;\n }\n\n /**\n * Close every still-open descendant of `parentId` as ERROR — their terminal\n * event never arrived because the parent ended first. Depth-first, so the\n * deepest leaks close before their own parents.\n */\n reapDescendants(\n parentId: string,\n message: string,\n endTime?: TimeInput,\n ): void {\n // Deleting from a Map during its own for…of is well-defined; recursion only\n // removes deeper descendants (parentId === id), never an unvisited sibling.\n for (const [id, entry] of this.entries) {\n if (entry.parentId !== parentId) continue;\n this.entries.delete(id);\n this.reapDescendants(id, message, endTime);\n entry.span.setStatus({ code: SpanStatusCode.ERROR, message });\n entry.span.end(endTime);\n }\n }\n\n /** Count of spans still open — for tests and leak assertions. */\n get openCount(): number {\n return this.entries.size;\n }\n}\n","/**\n * `createGenAiObserver` — turn a stream of GenAI lifecycle events into a\n * canonical `gen_ai.*` span tree.\n *\n * A complement to {@link traceGenAI}: where the wrapper instruments code you\n * own, the observer instruments a framework that emits its own event stream.\n * Subscribe it once and feed every event through it; it reconstructs the span\n * hierarchy, estimates cost, force-closes abandoned children, and keeps\n * sensitive content off spans unless you opt in.\n *\n * Scope: this adapter records span attributes only. It deliberately does not\n * emit the `gen_ai.*` log events (`inference.operation.details`,\n * `operation.exception`) that `recordInferenceDetails` provides — the source\n * event stream already carries that detail.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * observe({ type: 'agent.start', id: 'a1', agent: { name: 'planner' } });\n * observe({ type: 'chat.start', id: 'c1', parentId: 'a1',\n * request: { provider: 'openai', model: 'gpt-4o' } });\n * observe({ type: 'chat.end', id: 'c1',\n * usage: { inputTokens: 412, outputTokens: 87 } });\n * observe({ type: 'agent.end', id: 'a1' });\n * ```\n */\n\nimport {\n context as otelContext,\n SpanKind,\n SpanStatusCode,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n} from '@opentelemetry/api';\nimport {\n genAiAgentAttributes,\n genAiRequestAttributes,\n genAiResponseAttributes,\n genAiToolAttributes,\n genAiUsageAttributes,\n genAiWorkflowAttributes,\n type GenAiAttributeMap,\n} from '../attributes.js';\nimport { estimateLLMCost } from '../cost.js';\nimport { setGenAiContent, type GenAiContentSink } from '../events.js';\nimport { GEN_AI, GEN_AI_OPERATION, genAiSpanName } from '../semconv.js';\nimport {\n recordInputProvenance,\n recordMemoryAccess,\n recordPlanStep,\n recordRenderOutput,\n} from '../agent/agent-security.js';\nimport { agentContextFromSpan } from '../agent/context.js';\nimport { SpanRegistry } from './span-registry.js';\nimport type {\n ChatEndEvent,\n ChatStartEvent,\n GenAiObserver,\n GenAiObserverEvent,\n GenAiObserverOptions,\n SpanEnd,\n InputProvenanceEvent,\n MemoryAccessEvent,\n PlanStepEvent,\n RenderOutputEvent,\n ToolEndEvent,\n ToolStartEvent,\n} from './types.js';\n\nconst ORPHAN_MESSAGE =\n 'Parent ended before this span received its terminal event.';\n\n/** The `*.start` half of the event union — the events that open a span. */\ntype StartEvent = Extract<GenAiObserverEvent, { type: `${string}.start` }>;\n\nexport function createGenAiObserver(\n options: GenAiObserverOptions = {},\n): GenAiObserver {\n const tracer =\n options.tracer ?? otelTrace.getTracer('autotel-genai/observer');\n const registry = new SpanRegistry();\n\n /** Parent context: a tracked parent span, else the resolver, else root. */\n function parentContext(event: StartEvent): Context | undefined {\n const parentSpan = event.parentId\n ? registry.spanFor(event.parentId)\n : undefined;\n if (parentSpan) return otelTrace.setSpan(otelContext.active(), parentSpan);\n return options.resolveParentContext?.(event);\n }\n\n /** Start a span, register it under its parent, and return it. */\n function start(\n event: StartEvent,\n name: string,\n kind: SpanKind,\n attributes: GenAiAttributeMap,\n ): Span {\n const parent = parentContext(event);\n const spanOptions: SpanOptions = {\n kind,\n attributes,\n startTime: event.startTime,\n links: event.links,\n root: parent === undefined,\n };\n const span = tracer.startSpan(name, spanOptions, parent);\n registry.add(event.id, span, event.parentId);\n options.onSpanStart?.(event.id, span);\n return span;\n }\n\n /**\n * Close the span for `event.id`: first force-close any descendant whose\n * terminal event never arrived, then decorate, set status, and end.\n */\n function end(event: SpanEnd, decorate?: (span: Span) => void): void {\n const span = registry.take(event.id);\n if (!span) return;\n registry.reapDescendants(event.id, ORPHAN_MESSAGE, event.endTime);\n decorate?.(span);\n if (event.error !== undefined) {\n const message = errorMessage(event.error);\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n // The SDK normalizes a string or Error; hand it the original Error when\n // we have one, otherwise the message we already derived.\n span.recordException(\n event.error instanceof Error ? event.error : (message ?? 'error'),\n );\n }\n span.end(event.endTime);\n }\n\n /** Run the privacy gate; without it, content is always omitted. */\n function approvedContent(\n event: GenAiObserverEvent,\n ): GenAiObserverEvent | undefined {\n if (!options.exportContent) return undefined;\n try {\n return options.exportContent({ ...event });\n } catch (error) {\n console.error('[autotel-genai:observer] exportContent failed:', error);\n return undefined;\n }\n }\n\n function applyChatStart(span: Span, event: ChatStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'chat.start') return;\n setGenAiContent(spanSink(span), {\n inputMessages: content.inputMessages,\n systemInstructions: content.systemInstructions,\n });\n }\n\n function applyChatEnd(span: Span, event: ChatEndEvent): void {\n if (event.response) {\n span.setAttributes(genAiResponseAttributes(event.response));\n }\n if (event.usage) {\n const costModel = event.costModel ?? event.response?.model;\n const costUsd = costModel\n ? estimateLLMCost(costModel, event.usage)\n : undefined;\n span.setAttributes(genAiUsageAttributes({ ...event.usage, costUsd }));\n }\n if (event.streaming) {\n const { timeToFinish, outputTokensPerSecond, timePerOutputChunk } =\n event.streaming;\n const streamingAttrs: GenAiAttributeMap = {};\n if (timeToFinish !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_TO_FINISH] = timeToFinish;\n }\n if (outputTokensPerSecond !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_OUTPUT_TOKENS_PER_SECOND] =\n outputTokensPerSecond;\n }\n if (timePerOutputChunk !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_PER_OUTPUT_CHUNK] =\n timePerOutputChunk;\n }\n span.setAttributes(streamingAttrs);\n }\n const content = approvedContent(event);\n if (content?.type === 'chat.end') {\n setGenAiContent(spanSink(span), {\n outputMessages: content.outputMessages,\n });\n }\n }\n\n function applyToolStart(span: Span, event: ToolStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.start') return;\n span.setAttributes(\n genAiToolAttributes({ callArguments: content.callArguments }),\n );\n }\n\n function applyToolEnd(span: Span, event: ToolEndEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.end') return;\n span.setAttributes(genAiToolAttributes({ callResult: content.callResult }));\n }\n\n function stampOnParent(\n parentId: string,\n apply: (ctx: ReturnType<typeof agentContextFromSpan>) => void,\n ): void {\n const parent = registry.spanFor(parentId);\n if (!parent) return;\n apply(agentContextFromSpan(parent));\n }\n\n return (event: GenAiObserverEvent): void => {\n switch (event.type) {\n case 'workflow.start': {\n start(\n event,\n genAiSpanName(\n GEN_AI_OPERATION.INVOKE_WORKFLOW,\n event.workflow.workflowName,\n ),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_WORKFLOW,\n ...genAiWorkflowAttributes(event.workflow),\n },\n );\n return;\n }\n case 'agent.start': {\n const internal = !event.remote;\n start(\n event,\n genAiSpanName(GEN_AI_OPERATION.INVOKE_AGENT, event.agent.name),\n internal ? SpanKind.INTERNAL : SpanKind.CLIENT,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_AGENT,\n ...(event.provider\n ? { [GEN_AI.PROVIDER_NAME]: event.provider }\n : {}),\n ...genAiAgentAttributes(event.agent, { internal }),\n },\n );\n return;\n }\n case 'chat.start': {\n const operation = event.request.operation ?? GEN_AI_OPERATION.CHAT;\n const span = start(\n event,\n genAiSpanName(operation, event.request.model),\n SpanKind.CLIENT,\n genAiRequestAttributes({ ...event.request, operation }),\n );\n applyChatStart(span, event);\n return;\n }\n case 'tool.start': {\n const span = start(\n event,\n genAiSpanName(GEN_AI_OPERATION.EXECUTE_TOOL, event.tool.name),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.EXECUTE_TOOL,\n ...genAiToolAttributes(event.tool),\n },\n );\n applyToolStart(span, event);\n return;\n }\n case 'workflow.end':\n case 'agent.end': {\n end(event);\n return;\n }\n case 'chat.end': {\n end(event, (span) => applyChatEnd(span, event));\n return;\n }\n case 'tool.end': {\n end(event, (span) => applyToolEnd(span, event));\n return;\n }\n case 'plan.step': {\n stampOnParent(event.parentId, (ctx) =>\n recordPlanStep({\n ctx,\n stepIndex: event.stepIndex,\n toolIntents: event.toolIntents,\n policyIds: event.policyIds,\n summary: event.summary,\n }),\n );\n return;\n }\n case 'input.provenance': {\n stampOnParent(event.parentId, (ctx) =>\n recordInputProvenance({ ctx, provenance: event.provenance }),\n );\n return;\n }\n case 'memory.access': {\n stampOnParent(event.parentId, (ctx) =>\n recordMemoryAccess({\n ctx,\n operation: event.operation,\n isolationKey: event.isolationKey,\n contentHash: event.contentHash,\n }),\n );\n return;\n }\n case 'render.output': {\n stampOnParent(event.parentId, (ctx) =>\n recordRenderOutput({\n ctx,\n format: event.format,\n containsUrl: event.containsUrl,\n urlCount: event.urlCount,\n }),\n );\n return;\n }\n default: {\n const unexpected: never = event;\n void unexpected;\n }\n }\n };\n}\n\n/** A {@link GenAiContentSink} backed by a raw span (only `setAttributes` is used). */\nfunction spanSink(span: Span): GenAiContentSink {\n return {\n setAttributes: (attrs) => {\n span.setAttributes(attrs);\n },\n track: () => {},\n };\n}\n\nfunction errorMessage(error: unknown): string | undefined {\n if (error === undefined) return undefined;\n if (typeof error === 'string') return error || undefined;\n if (error instanceof Error) return error.message;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n","/**\n * Shared structural converters from Vercel AI SDK shapes to autotel inputs.\n *\n * The AI SDK reports the same data three ways — the lifecycle `Telemetry`\n * events ({@link autotelTelemetry}), the `ai:telemetry` channel messages\n * ({@link subscribeAiTelemetry}), and a finished call result\n * ({@link observeAiSdkResult}) — and across v4/v5 with differing field names\n * (`promptTokens` vs `inputTokens`, flat vs nested token details). These helpers\n * are the single place that normalizes those shapes, so the three entry points\n * stay consistent and none of them re-derive the mapping.\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiRequestInput } from '../attributes.js';\nimport type { TokenUsage } from '../cost.js';\nimport { GEN_AI_OPERATION } from '../semconv.js';\n\n/** Every AI SDK token-usage field name we understand (v4 + v5, flat + nested). */\nexport interface AiSdkUsageShape {\n inputTokens?: number;\n outputTokens?: number;\n /** v4 aliases. */\n promptTokens?: number;\n completionTokens?: number;\n /** Embedding usage. */\n tokens?: number;\n /** Flat v5 detail fields. */\n reasoningTokens?: number;\n cachedInputTokens?: number;\n /** Nested v5 detail fields. */\n inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n}\n\n/** Map any AI SDK usage shape to {@link TokenUsage}; `undefined` if all empty. */\nexport function toTokenUsage(\n usage: AiSdkUsageShape | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined;\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens ?? usage.promptTokens ?? usage.tokens,\n outputTokens: usage.outputTokens ?? usage.completionTokens,\n reasoningOutputTokens:\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens,\n cacheReadInputTokens:\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens,\n cacheCreationInputTokens: usage.inputTokenDetails?.cacheWriteTokens,\n };\n return Object.values(tokenUsage).some((v) => v !== undefined)\n ? tokenUsage\n : undefined;\n}\n\n/** Normalize an optional AI SDK provider id to a canonical `gen_ai` provider. */\nexport function normalizeProvider(provider: string | undefined) {\n return provider ? normalizeAiSdkProvider(provider) : undefined;\n}\n\n/** Request-side fields shared by the lifecycle and channel model-call events. */\nexport interface AiSdkRequestShape {\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n}\n\n/** Build the `chat` request attributes from a model-call start event. */\nexport function toChatRequest(event: AiSdkRequestShape): GenAiRequestInput {\n return {\n operation: GEN_AI_OPERATION.CHAT,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n temperature: event.temperature,\n maxTokens: event.maxOutputTokens,\n topP: event.topP,\n topK: event.topK,\n frequencyPenalty: event.frequencyPenalty,\n presencePenalty: event.presencePenalty,\n stopSequences: event.stopSequences,\n seed: event.seed,\n };\n}\n","/**\n * Vercel AI SDK → {@link createGenAiObserver} glue.\n *\n * The AI SDK has no global event stream; its stable surface is the result of a\n * `generateText`/`streamText` call (`steps`, each with `usage`, `toolCalls`,\n * `toolResults`, `response`). {@link observeAiSdkResult} walks that result and\n * emits observer events:\n *\n * - a trivial single-step call with no tools → one `chat` span;\n * - a multi-step or tool-using call → an `invoke_agent` wrapper (mirroring the\n * AI SDK's own `invoke_agent › chat › execute_tool` hierarchy) with a `chat`\n * per step and an `execute_tool` per tool call.\n *\n * Typed structurally against the AI SDK result shape so it pulls in no\n * dependency and tolerates v4/v5 field differences (`promptTokens` vs\n * `inputTokens`). Cost is left to the observer, which prices `chat` usage.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * const result = await generateText({ model: openai('gpt-4o'), prompt });\n * observeAiSdkResult(observe, result, { id: 'gen-1', provider: 'openai', model: 'gpt-4o' });\n * ```\n */\n\nimport type { TimeInput } from '@opentelemetry/api';\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiProviderName } from '../semconv.js';\nimport { toTokenUsage } from './ai-sdk-shapes.js';\nimport type { GenAiObserver } from './types.js';\n\n/** AI SDK usage object — canonical (v5) or legacy (v4) field names. */\nexport interface AiSdkUsage {\n inputTokens?: number;\n outputTokens?: number;\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AiSdkToolCall {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n}\n\nexport interface AiSdkToolResult {\n toolCallId?: string;\n toolName?: string;\n output?: unknown;\n result?: unknown;\n}\n\n/** One AI SDK step (also the shape of a single-call result). */\nexport interface AiSdkStep {\n usage?: AiSdkUsage;\n finishReason?: string;\n response?: { id?: string; modelId?: string; timestamp?: Date | number };\n toolCalls?: AiSdkToolCall[];\n toolResults?: AiSdkToolResult[];\n}\n\n/** An AI SDK `generateText`/`streamText` result. */\nexport interface AiSdkResult extends AiSdkStep {\n steps?: AiSdkStep[];\n}\n\nexport interface ObserveAiSdkOptions {\n /** Base span id; per-step and per-tool ids derive from it. */\n id: string;\n parentId?: string;\n /** AI SDK provider id (e.g. `openai`, `amazon-bedrock`); normalized for you. */\n provider?: string;\n /** Request model — used for the span name and as the cost model. */\n model?: string;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\nexport function observeAiSdkResult(\n observe: GenAiObserver,\n result: AiSdkResult,\n options: ObserveAiSdkOptions,\n): void {\n const provider: GenAiProviderName | undefined = options.provider\n ? normalizeAiSdkProvider(options.provider)\n : undefined;\n const steps = result.steps ?? [result];\n const toolCount = steps.reduce(\n (total, step) => total + (step.toolCalls?.length ?? 0),\n 0,\n );\n\n if (steps.length <= 1 && toolCount === 0) {\n emitChat(observe, steps[0] ?? result, {\n id: options.id,\n parentId: options.parentId,\n provider,\n model: options.model,\n });\n return;\n }\n\n observe({\n type: 'agent.start',\n id: options.id,\n parentId: options.parentId,\n provider,\n agent: { name: options.model ?? AI_SDK_AGENT_NAME },\n });\n steps.forEach((step, index) => {\n emitChat(observe, step, {\n id: `${options.id}:step:${index}`,\n parentId: options.id,\n provider,\n model: options.model,\n });\n emitTools(observe, step, options.id, index);\n });\n observe({ type: 'agent.end', id: options.id });\n}\n\ninterface ChatContext {\n id: string;\n parentId?: string;\n provider?: GenAiProviderName;\n model?: string;\n}\n\nfunction emitChat(\n observe: GenAiObserver,\n step: AiSdkStep,\n ctx: ChatContext,\n): void {\n observe({\n type: 'chat.start',\n id: ctx.id,\n parentId: ctx.parentId,\n request: { provider: ctx.provider, model: ctx.model },\n });\n const responseModel = step.response?.modelId;\n observe({\n type: 'chat.end',\n id: ctx.id,\n response: {\n model: responseModel,\n id: step.response?.id,\n finishReasons: step.finishReason ? [step.finishReason] : undefined,\n },\n usage: toTokenUsage(step.usage),\n costModel: ctx.model ?? responseModel,\n endTime: asTimeInput(step.response?.timestamp),\n });\n}\n\nfunction emitTools(\n observe: GenAiObserver,\n step: AiSdkStep,\n parentId: string,\n stepIndex: number,\n): void {\n const results = step.toolResults ?? [];\n (step.toolCalls ?? []).forEach((call, callIndex) => {\n const id = `${parentId}:tool:${call.toolCallId ?? `${stepIndex}.${callIndex}`}`;\n observe({\n type: 'tool.start',\n id,\n parentId,\n tool: { name: call.toolName, callId: call.toolCallId },\n callArguments: call.input ?? call.args,\n });\n const result = call.toolCallId\n ? results.find((r) => r.toolCallId === call.toolCallId)\n : results[callIndex];\n observe({\n type: 'tool.end',\n id,\n callResult: result?.output ?? result?.result,\n });\n });\n}\n\nfunction asTimeInput(value: Date | number | undefined): TimeInput | undefined {\n return value instanceof Date || typeof value === 'number' ? value : undefined;\n}\n","/**\n * Vercel AI SDK messages → GenAI SemConv message format.\n *\n * The AI SDK carries prompts as `ModelMessage[]` (role + string-or-parts) and\n * model output as `ContentPart[]`. The GenAI semantic conventions instead model\n * `gen_ai.input.messages` / `gen_ai.output.messages` as `{ role, parts }` where\n * each part is `{ type, ...typeFields }`:\n *\n * - text: `{ type: 'text', content }`\n * - reasoning: `{ type: 'reasoning', content }`\n * - tool_call: `{ type: 'tool_call', id, name, arguments }`\n * - tool_call_response: `{ type: 'tool_call_response', id, response }`\n * - blob/uri: media, referenced by `mime_type` only (never inlined)\n *\n * Typed structurally against the AI SDK shapes (no `ai` / `@ai-sdk/*` import).\n * Media bytes are deliberately not copied onto spans — only the modality and\n * mime type — so content capture never balloons span size or leaks raw blobs.\n */\n\nimport type { GenAiMessage, GenAiMessagePart } from '../events.js';\n\n// --- Structural views of the AI SDK message shapes -------------------------\n// Exported so the telemetry/channel event views can type their `messages` /\n// `content` fields against these directly — no per-call-site casts, and (with no\n// index signature) the real SDK `ModelMessage[]` / `ContentPart[]` stay\n// assignable to them.\n\nexport interface ModelMessageView {\n role?: string;\n content?: string | readonly ContentPartView[];\n}\n\nexport interface ContentPartView {\n type?: string;\n /** text / reasoning */\n text?: string;\n /** tool-call */\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n /** tool-result */\n output?: unknown;\n /** image / file */\n mediaType?: string;\n}\n\n/** Result of converting an AI SDK prompt: messages plus split-out system parts. */\nexport interface ConvertedPrompt {\n messages: GenAiMessage[];\n systemInstructions?: GenAiMessagePart[];\n}\n\n/**\n * Convert an AI SDK `ModelMessage[]` prompt to GenAI input messages, splitting\n * `system` messages out into `systemInstructions` (recorded separately per the\n * conventions).\n */\nexport function promptToGenAiMessages(\n messages: readonly ModelMessageView[] | undefined,\n): ConvertedPrompt {\n if (!messages?.length) return { messages: [] };\n\n const out: GenAiMessage[] = [];\n const system: GenAiMessagePart[] = [];\n\n for (const message of messages) {\n const role = message.role ?? 'user';\n const parts = contentToParts(message.content);\n if (role === 'system') {\n system.push(...parts);\n } else {\n out.push({ role, parts });\n }\n }\n\n return system.length > 0\n ? { messages: out, systemInstructions: system }\n : { messages: out };\n}\n\n/**\n * Convert the `ContentPart[]` of a model response into a single GenAI output\n * message (always `assistant`), optionally tagged with a `finish_reason`.\n */\nexport function contentToGenAiMessage(\n content: readonly ContentPartView[] | undefined,\n finishReason?: string,\n): GenAiMessage | undefined {\n if (!content?.length) return undefined;\n const message: GenAiMessage = {\n role: 'assistant',\n parts: content.flatMap((part) => partToGenAi(part)),\n };\n if (finishReason) message.finish_reason = finishReason;\n return message;\n}\n\nfunction contentToParts(\n content: string | readonly ContentPartView[] | undefined,\n): GenAiMessagePart[] {\n if (content === undefined) return [];\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : [];\n }\n return content.flatMap((part) => partToGenAi(part));\n}\n\n/** Map one AI SDK content part to zero or more GenAI parts. */\nfunction partToGenAi(part: ContentPartView): GenAiMessagePart[] {\n switch (part.type) {\n case 'text': {\n return part.text ? [{ type: 'text', content: part.text }] : [];\n }\n case 'reasoning': {\n return part.text ? [{ type: 'reasoning', content: part.text }] : [];\n }\n case 'tool-call': {\n return [\n {\n type: 'tool_call',\n id: part.toolCallId,\n name: part.toolName,\n arguments: part.input,\n },\n ];\n }\n case 'tool-result': {\n return [\n {\n type: 'tool_call_response',\n id: part.toolCallId,\n response: part.output,\n },\n ];\n }\n case 'image':\n case 'file': {\n // Reference media by modality + mime type only — never inline bytes.\n return [\n {\n type: 'blob',\n modality: part.type === 'image' ? 'image' : 'file',\n mime_type: part.mediaType,\n },\n ];\n }\n default: {\n return part.type ? [{ type: part.type }] : [];\n }\n }\n}\n","/**\n * `autotelTelemetry` — a Vercel AI SDK telemetry integration.\n *\n * Implements the AI SDK's `Telemetry` lifecycle interface (stable since ai v7)\n * so you register it once and every `generateText` / `streamText` / `embed`\n * call streams a canonical `gen_ai.*` span tree — live, as the operation runs,\n * not reconstructed after the fact:\n *\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n *\n * Where the built-in `@ai-sdk/otel` `OpenTelemetry` integration emits the same\n * `gen_ai.*` spans, this one additionally:\n *\n * - prices every model call (`gen_ai.usage.cost.usd`) from {@link MODEL_PRICING};\n * - records streaming throughput (`time_to_first_chunk`, `time_to_finish`,\n * `output_tokens_per_second`) on each `chat` span;\n * - keeps token usage on leaf `chat` spans only, so the `invoke_agent` root\n * never double-counts.\n *\n * It is push-based and concurrency-safe: every lifecycle event carries the\n * AI SDK `callId`, so interleaved concurrent generations never cross wires. It\n * is the live counterpart to {@link observeAiSdkResult}, which walks a finished\n * result instead.\n *\n * Typed structurally against the AI SDK event shapes — it pulls in **no**\n * dependency on `ai` / `@ai-sdk/*`. The returned object satisfies the AI SDK\n * `Telemetry` interface by structural compatibility (every event the SDK passes\n * is a superset of the fields read here), so `registerTelemetry(autotelTelemetry())`\n * type-checks without importing the interface.\n *\n * Scope: `generateText` / `streamText` (incl. tool loops and `output`) and\n * `embed` / `embedMany`. A tool's nested `generateText` and the provider's own\n * HTTP spans parent correctly via the `executeTool` / `executeLanguageModelCall`\n * context runners. `rerank` has no canonical `gen_ai` operation in the v1.42.0\n * registry and is intentionally not mapped.\n *\n * Message content (prompts, responses, tool arguments/results) is **off by\n * default**; enable it with `captureContent`, gated by the SDK's `recordInputs`\n * / `recordOutputs` and the optional `exportContent` privacy callback.\n */\n\nimport {\n context as otelContext,\n trace as otelTrace,\n type Context,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type {\n ChatStreamTiming,\n GenAiObserverEvent,\n GenAiObserver,\n} from './types.js';\n\n// --- Structural views of the AI SDK telemetry event shapes -----------------\n// Only the fields this integration reads. The real events are supersets, so a\n// callback typed against these is assignable to the AI SDK `Telemetry` methods.\n\ninterface OperationStartEvent {\n callId: string;\n /** e.g. `'ai.generateText'`, `'ai.streamText'`, `'ai.embed'`. */\n operationId?: string;\n provider?: string;\n modelId?: string;\n /** From telemetry settings — used as the agent name. */\n functionId?: string;\n}\n\ninterface LanguageModelCallStartEventView {\n callId: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n /** Standardized prompt messages (when content capture is on). */\n messages?: readonly ModelMessageView[];\n /** Whether the SDK call permits recording inputs (default true). */\n recordInputs?: boolean;\n}\n\ninterface LanguageModelCallEndEventView {\n callId: string;\n modelId?: string;\n responseId?: string;\n finishReason?: string;\n usage?: AiSdkUsageShape;\n /** Output content parts (when content capture is on). */\n content?: readonly ContentPartView[];\n /** Whether the SDK call permits recording outputs (default true). */\n recordOutputs?: boolean;\n performance?: {\n responseTimeMs?: number;\n effectiveOutputTokensPerSecond?: number;\n timeToFirstOutputMs?: number;\n };\n}\n\ninterface ToolCallView {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\ninterface ToolExecutionStartEventView {\n callId: string;\n toolCall: ToolCallView;\n recordInputs?: boolean;\n}\n\ninterface ToolOutputView {\n type?: string;\n output?: unknown;\n error?: unknown;\n}\n\ninterface ToolExecutionEndEventView {\n callId: string;\n toolCall: ToolCallView;\n toolOutput: ToolOutputView;\n recordOutputs?: boolean;\n}\n\ninterface OperationEndEventView {\n callId: string;\n}\n\ninterface AbortEventView {\n callId: string;\n reason?: unknown;\n}\n\ninterface EmbeddingModelCallEndEventView {\n callId: string;\n embedCallId?: string;\n provider?: string;\n modelId?: string;\n usage?: { tokens?: number };\n}\n\n/**\n * The subset of the AI SDK `Telemetry` interface this integration implements.\n * The returned object is assignable to the SDK's `Telemetry` type.\n */\nexport interface AutotelTelemetryIntegration {\n onStart(event: OperationStartEvent): void;\n onLanguageModelCallStart(event: LanguageModelCallStartEventView): void;\n onLanguageModelCallEnd(event: LanguageModelCallEndEventView): void;\n onToolExecutionStart(event: ToolExecutionStartEventView): void;\n onToolExecutionEnd(event: ToolExecutionEndEventView): void;\n onEmbedEnd(event: EmbeddingModelCallEndEventView): void;\n onEnd(event: OperationEndEventView): void;\n onAbort(event: AbortEventView): void;\n onError(event: unknown): void;\n executeLanguageModelCall<T>(\n options: { callId: string; execute: () => PromiseLike<T> },\n ): PromiseLike<T>;\n executeTool<T>(\n options: {\n callId: string;\n toolCallId: string;\n execute: () => PromiseLike<T>;\n },\n ): PromiseLike<T>;\n}\n\nexport interface AutotelTelemetryOptions {\n /**\n * Tracer to emit spans on. Defaults to the global `autotel-genai/observer`\n * tracer. Pass an application-owned tracer to share one configuration.\n */\n tracer?: Tracer;\n /**\n * Privacy gate applied to all captured content (prompts, responses, tool\n * arguments/results) before it is written to a span. Receives each\n * content-bearing observer event; return it (optionally redacted) to keep its\n * content, or `undefined` to drop that event's content. Only consulted when\n * {@link AutotelTelemetryOptions.captureContent} is enabled.\n */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /**\n * Capture prompt/response message content (`gen_ai.input.messages`,\n * `gen_ai.output.messages`, `gen_ai.system_instructions`) and tool\n * arguments/results. **Off by default** for privacy. When `true`, content is\n * still filtered through {@link AutotelTelemetryOptions.exportContent} if you\n * provide one (redact there); with no `exportContent`, all approved content is\n * written. The AI SDK's per-call `recordInputs` / `recordOutputs` are still\n * honored — either being `false` suppresses that side.\n */\n captureContent?: boolean;\n /**\n * Resolve an OpenTelemetry parent context for an operation that has no\n * tracked parent — e.g. attach the `invoke_agent` root to the incoming\n * request span. Return `undefined` to keep it a root.\n */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/** Per-call correlation state, keyed by the AI SDK `callId`. */\ninterface CallState {\n /** Whether an `invoke_agent` root span was opened for this call. */\n hasAgent: boolean;\n /** Whether the root operation is streaming (`streamText`) or not. */\n stream?: boolean;\n /** Stack of open `chat` span ids (one per in-flight model call). */\n openLm: string[];\n /** Monotonic counter for unique model-call span ids within the call. */\n lmSeq: number;\n /** FIFO of anonymous tool span ids when the SDK omits `toolCallId`. */\n anonymousToolIds: string[];\n /** Monotonic counter for unique anonymous tool span ids within the call. */\n toolSeq: number;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/**\n * Create an AI SDK telemetry integration that streams canonical `gen_ai.*`\n * spans (with cost + streaming timing) for every AI SDK operation.\n *\n * @example\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n */\nexport function autotelTelemetry(\n options: AutotelTelemetryOptions = {},\n): AutotelTelemetryIntegration {\n const captureContent = options.captureContent ?? false;\n // When content capture is on with no explicit gate, allow all approved\n // content; an `exportContent` callback (redaction) always wins if provided.\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n // Live spans by observer id, so the context runners can re-enter a span's\n // OTel context to nest provider HTTP calls / a tool's inner `generateText`.\n const spans = new Map<string, Span>();\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n onSpanStart: (id, span) => spans.set(id, span),\n });\n const calls = new Map<string, CallState>();\n\n function startToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n const seq = state?.toolSeq ?? 0;\n if (state) state.toolSeq += 1;\n const id = `${callId}:tool:anon:${seq}`;\n state?.anonymousToolIds.push(id);\n return id;\n }\n\n function endToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n return state?.anonymousToolIds.shift() ?? `${callId}:tool:anon:0`;\n }\n\n /** Run `execute` within `span`'s OTel context so nested work parents to it. */\n function runInSpan<T>(\n span: Span | undefined,\n execute: () => PromiseLike<T>,\n ): PromiseLike<T> {\n if (!span) return execute();\n return otelContext.with(\n otelTrace.setSpan(otelContext.active(), span),\n execute,\n );\n }\n\n return {\n onStart(event) {\n // Embedding / rerank operations are not `invoke_agent` roots; they are\n // handled as leaf spans by `onEmbedEnd` (rerank is unsupported).\n if (isNonAgentOperation(event.operationId)) return;\n const state: CallState = {\n hasAgent: true,\n stream: event.operationId?.includes('stream'),\n openLm: [],\n lmSeq: 0,\n anonymousToolIds: [],\n toolSeq: 0,\n };\n calls.set(event.callId, state);\n observe({\n type: 'agent.start',\n id: event.callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.functionId ?? event.modelId ?? AI_SDK_AGENT_NAME },\n });\n },\n\n onLanguageModelCallStart(event) {\n const state = calls.get(event.callId);\n const id = `${event.callId}:lm:${state ? state.lmSeq++ : 0}`;\n if (state) state.openLm.push(id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n request: { ...toChatRequest(event), stream: state?.stream },\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n },\n\n onLanguageModelCallEnd(event) {\n const state = calls.get(event.callId);\n const id = state?.openLm.pop() ?? `${event.callId}:lm:0`;\n const outputMessage =\n captureContent && event.recordOutputs !== false\n ? contentToGenAiMessage(event.content, event.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: event.modelId,\n id: event.responseId,\n finishReasons: event.finishReason ? [event.finishReason] : undefined,\n timeToFirstChunk: msToSeconds(event.performance?.timeToFirstOutputMs),\n },\n usage: toTokenUsage(event.usage),\n costModel: event.modelId,\n streaming: mapStreaming(event.performance),\n outputMessages: outputMessage ? [outputMessage] : undefined,\n });\n spans.delete(id);\n },\n\n onToolExecutionStart(event) {\n const state = calls.get(event.callId);\n const id = startToolSpanId(event.callId, state, event.toolCall);\n observe({\n type: 'tool.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n tool: {\n name: event.toolCall.toolName,\n callId: event.toolCall.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall.input\n : undefined,\n });\n },\n\n onToolExecutionEnd(event) {\n const state = calls.get(event.callId);\n const id = endToolSpanId(event.callId, state, event.toolCall);\n const isError = event.toolOutput.type === 'tool-error';\n observe({\n type: 'tool.end',\n id,\n error: isError ? event.toolOutput.error : undefined,\n callResult:\n !isError && captureContent && event.recordOutputs !== false\n ? event.toolOutput.output\n : undefined,\n });\n spans.delete(id);\n },\n\n onEmbedEnd(event) {\n const id = `${event.callId}:embed:${event.embedCallId ?? '0'}`;\n observe({\n type: 'chat.start',\n id,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n observe({\n type: 'chat.end',\n id,\n response: { model: event.modelId },\n usage:\n event.usage?.tokens === undefined\n ? undefined\n : { inputTokens: event.usage.tokens },\n costModel: event.modelId,\n });\n spans.delete(id);\n },\n\n onEnd(event) {\n if (!calls.delete(event.callId)) return;\n observe({ type: 'agent.end', id: event.callId });\n spans.delete(event.callId);\n },\n\n onAbort(event) {\n if (!calls.delete(event.callId)) return;\n observe({\n type: 'agent.end',\n id: event.callId,\n error: event.reason ?? 'aborted',\n });\n spans.delete(event.callId);\n },\n\n onError(event) {\n // The dispatcher invokes `onError` with `{ callId, error }`. Close the\n // matching `invoke_agent` root when we can correlate it; otherwise leave\n // it for `onEnd`/`onAbort` (the observer reaps any open children on close).\n const callId = errorCallId(event);\n if (callId === undefined || !calls.delete(callId)) return;\n observe({\n type: 'agent.end',\n id: callId,\n error: errorValue(event) ?? event,\n });\n spans.delete(callId);\n },\n\n executeLanguageModelCall(options) {\n // `onLanguageModelCallStart` already opened this call's `chat` span (it is\n // the top of the stack). Run the provider call inside it so any\n // auto-instrumented HTTP spans become its children.\n const state = calls.get(options.callId);\n const id = state?.openLm.at(-1);\n return runInSpan(id ? spans.get(id) : undefined, options.execute);\n },\n\n executeTool(options) {\n // `onToolExecutionStart` already opened the tool span; run the tool's\n // `execute` inside it so a nested `generateText` nests under the tool.\n const id = `${options.callId}:tool:${options.toolCallId}`;\n return runInSpan(spans.get(id), options.execute);\n },\n };\n}\n\n/** Operations that are not `invoke_agent` roots (embeddings, rerank). */\nfunction isNonAgentOperation(operationId: string | undefined): boolean {\n if (!operationId) return false;\n return operationId.includes('embed') || operationId.includes('rerank');\n}\n\nfunction mapStreaming(\n performance: LanguageModelCallEndEventView['performance'],\n): ChatStreamTiming | undefined {\n if (!performance) return undefined;\n const timing: ChatStreamTiming = {\n timeToFinish: msToSeconds(performance.responseTimeMs),\n outputTokensPerSecond: performance.effectiveOutputTokensPerSecond,\n };\n return timing.timeToFinish !== undefined ||\n timing.outputTokensPerSecond !== undefined\n ? timing\n : undefined;\n}\n\nfunction msToSeconds(ms: number | undefined): number | undefined {\n return ms === undefined ? undefined : ms / 1000;\n}\n\nfunction errorCallId(event: unknown): string | undefined {\n if (\n typeof event === 'object' &&\n event !== null &&\n 'callId' in event &&\n typeof (event as { callId: unknown }).callId === 'string'\n ) {\n return (event as { callId: string }).callId;\n }\n return undefined;\n}\n\nfunction errorValue(event: unknown): unknown {\n if (typeof event === 'object' && event !== null && 'error' in event) {\n return (event as { error: unknown }).error;\n }\n return undefined;\n}\n","/**\n * `subscribeAiTelemetry` — the zero-config Vercel AI SDK path.\n *\n * Instead of `registerTelemetry(autotelTelemetry())`, subscribe to the AI SDK's\n * `ai:telemetry` Node [tracing channel](https://nodejs.org/api/diagnostics_channel.html).\n * The SDK publishes an operation span per `generateText` / `streamText` /\n * `languageModelCall` / `executeTool` / `embed` as long as the channel has a\n * subscriber — no registration call, and it binds async context across provider\n * calls and tool executions:\n *\n * ```ts\n * import { subscribeAiTelemetry } from 'autotel-genai/observer';\n *\n * const unsubscribe = subscribeAiTelemetry(); // once, at startup\n * ```\n *\n * Fidelity note: the channel exposes coarser data than the registered\n * {@link autotelTelemetry} integration. You get the canonical\n * `invoke_agent › chat › execute_tool` tree with token usage and cost (read\n * from each operation's result), but **not** the per-call streaming timing\n * (`time_to_first_chunk` etc.) that only the lifecycle `onLanguageModelCallEnd`\n * event carries. Prefer `registerTelemetry(autotelTelemetry())` when you can;\n * use this when you cannot add a registration call.\n *\n * No-ops (returning an unsubscribe that does nothing) on runtimes without Node\n * diagnostics-channel support, so it is safe to call unconditionally.\n */\n\nimport type { Context, Tracer } from '@opentelemetry/api';\nimport { subscribeTracingChannel } from 'autotel/diagnostics';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type { GenAiObserver, GenAiObserverEvent } from './types.js';\n\nconst AI_SDK_TELEMETRY_TRACING_CHANNEL = 'ai:telemetry';\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/** A published tracing-channel message: `{ type, event }` plus the settled result. */\ninterface ChannelMessage {\n type?: string;\n event?: ChannelEvent;\n result?: unknown;\n error?: unknown;\n}\n\ninterface ChannelEvent {\n callId?: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n toolCall?: { toolCallId?: string; toolName?: string; input?: unknown };\n messages?: readonly ModelMessageView[];\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\nexport interface SubscribeAiTelemetryOptions {\n /** Tracer to emit spans on. Defaults to the global observer tracer. */\n tracer?: Tracer;\n /**\n * Capture prompt/response content and tool arguments/results. Off by default.\n * @see {@link import('./ai-sdk-telemetry.js').AutotelTelemetryOptions.captureContent}\n */\n captureContent?: boolean;\n /** Privacy gate for captured content (redact or drop per event). */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /** Resolve an OTel parent context for otherwise-root operations. */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/**\n * Subscribe to the `ai:telemetry` tracing channel and emit canonical `gen_ai.*`\n * spans. Returns an unsubscribe function (a no-op on unsupported runtimes).\n */\nexport function subscribeAiTelemetry(\n options: SubscribeAiTelemetryOptions = {},\n): () => void {\n const captureContent = options.captureContent ?? false;\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n });\n\n // Pair each `start` with its `asyncEnd`/`error` by message-object identity,\n // recording the observer span id we assigned at start.\n const ids = new WeakMap<ChannelMessage, string>();\n let seq = 0;\n\n const handlers = {\n start(message: ChannelMessage) {\n const event = message.event ?? {};\n const callId = event.callId ?? `op:${seq}`;\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n ids.set(message, callId);\n observe({\n type: 'agent.start',\n id: callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.modelId ?? AI_SDK_AGENT_NAME },\n });\n return;\n }\n case 'languageModelCall': {\n const id = `${callId}:lm:${seq++}`;\n ids.set(message, id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: callId,\n request: toChatRequest(event),\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n return;\n }\n case 'executeTool': {\n const id = `${callId}:tool:${event.toolCall?.toolCallId ?? seq++}`;\n ids.set(message, id);\n observe({\n type: 'tool.start',\n id,\n parentId: callId,\n tool: {\n name: event.toolCall?.toolName,\n callId: event.toolCall?.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall?.input\n : undefined,\n });\n return;\n }\n case 'embed': {\n ids.set(message, callId);\n observe({\n type: 'chat.start',\n id: callId,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n return;\n }\n // 'step' and 'rerank' are intentionally not mapped.\n }\n },\n\n asyncEnd(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n closeSpan(observe, message, id, captureContent);\n },\n\n error(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n // `closeSpan` reads `message.error`; route by type to the right end event.\n closeSpan(observe, message, id, captureContent);\n },\n };\n\n return subscribeTracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL, handlers);\n}\n\nfunction closeSpan(\n observe: GenAiObserver,\n message: ChannelMessage,\n id: string,\n captureContent: boolean,\n): void {\n const error = message.error;\n const result = message.result as ResultView | undefined;\n const event = message.event;\n\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n observe({ type: 'agent.end', id, error });\n return;\n }\n case 'embed': {\n observe({\n type: 'chat.end',\n id,\n response: { model: event?.modelId },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n error,\n });\n return;\n }\n case 'languageModelCall': {\n const outputMessage =\n captureContent && event?.recordOutputs !== false\n ? contentToGenAiMessage(result?.content, result?.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: result?.response?.modelId ?? event?.modelId,\n id: result?.response?.id,\n finishReasons: result?.finishReason ? [result.finishReason] : undefined,\n },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n outputMessages: outputMessage ? [outputMessage] : undefined,\n error,\n });\n return;\n }\n case 'executeTool': {\n observe({\n type: 'tool.end',\n id,\n callResult:\n captureContent &&\n event?.recordOutputs !== false &&\n result?.output !== undefined\n ? result.output\n : undefined,\n error,\n });\n return;\n }\n }\n}\n\ninterface ResultView {\n content?: readonly ContentPartView[];\n finishReason?: string;\n output?: unknown;\n response?: { id?: string; modelId?: string };\n usage?: AiSdkUsageShape;\n}\n\n","/**\n * LangChain / LangGraph → {@link createGenAiObserver} glue.\n *\n * LangChain's callback system is a real event stream: register one handler and\n * it receives every run's start/end/error with a `runId` and `parentRunId` —\n * which map directly onto the observer's `id` / `parentId`. {@link\n * createLangChainObserver} returns a handler object you pass to `callbacks`:\n *\n * - chain runs (LangGraph nodes, agents) → `invoke_agent` spans;\n * - LLM / chat-model runs → `chat` spans (with usage and estimated cost);\n * - tool runs → `execute_tool` spans.\n *\n * Typed structurally against the callback payloads so it pulls in no LangChain\n * dependency. Pass it via `{ callbacks: [createLangChainObserver(observe)] }`.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * await graph.invoke(input, { callbacks: [createLangChainObserver(observe)] });\n * ```\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { TokenUsage } from '../cost.js';\nimport type { GenAiObserver } from './types.js';\n\n/** LangChain `Serialized` — the identity of a chain/LLM/tool. */\ninterface Serialized {\n id?: string[];\n name?: string;\n kwargs?: Record<string, unknown>;\n}\n\ninterface LLMGeneration {\n generationInfo?: Record<string, unknown>;\n message?: {\n usage_metadata?: Record<string, unknown>;\n response_metadata?: Record<string, unknown>;\n };\n}\n\n/** LangChain `LLMResult` — what an LLM/chat-model run returns. */\ninterface LLMResult {\n generations?: LLMGeneration[][];\n llmOutput?: Record<string, unknown>;\n}\n\n/** The subset of LangChain's `CallbackHandlerMethods` this adapter implements. */\nexport interface LangChainObserverHandler {\n handleChainStart(\n chain: Serialized,\n inputs: unknown,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runType?: string,\n runName?: string,\n ): void;\n handleChainEnd(outputs: unknown, runId: string): void;\n handleChainError(error: unknown, runId: string): void;\n handleLLMStart(\n llm: Serialized,\n prompts: string[],\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleChatModelStart(\n llm: Serialized,\n messages: unknown,\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleLLMEnd(output: LLMResult, runId: string): void;\n handleLLMError(error: unknown, runId: string): void;\n handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runName?: string,\n ): void;\n handleToolEnd(output: unknown, runId: string): void;\n handleToolError(error: unknown, runId: string): void;\n}\n\nexport interface LangChainObserverOptions {\n /**\n * Decide whether a chain run becomes an `invoke_agent` span. The default\n * skips LangChain/LangGraph plumbing (Runnable*, ChannelWrite, Branch,\n * prompts, `__start__`/`__end__`) so only the graph and its named nodes show;\n * children of a skipped chain reparent to the nearest kept ancestor.\n */\n skipChain?: (name: string) => boolean;\n}\n\n/** LangChain/LangGraph structural runnables that are noise, not real steps. */\nconst PLUMBING_CHAIN = /^(Runnable|Channel|Branch|Prompt|ChatPrompt|__)/i;\n\nexport function createLangChainObserver(\n observe: GenAiObserver,\n options: LangChainObserverOptions = {},\n): LangChainObserverHandler {\n const skipChain = options.skipChain ?? ((name) => PLUMBING_CHAIN.test(name));\n // Remember each LLM run's request model so `chat.end` can price it — the end\n // payload doesn't reliably carry the model. Cleared on end and error.\n const models = new Map<string, string>();\n // Skipped chains record the kept ancestor their children should parent to.\n const reparented = new Map<string, string | undefined>();\n\n /** The span id a child of `runId` should use as its parent. */\n function keptParent(runId: string | undefined): string | undefined {\n if (runId === undefined) return undefined;\n return reparented.has(runId) ? reparented.get(runId) : runId;\n }\n\n function startChat(\n llm: Serialized,\n runId: string,\n parentRunId: string | undefined,\n extraParams: Record<string, unknown> | undefined,\n ): void {\n const model = langChainModel(llm, extraParams);\n if (model) models.set(runId, model);\n observe({\n type: 'chat.start',\n id: runId,\n parentId: keptParent(parentRunId),\n request: { provider: langChainProvider(llm), model },\n });\n }\n\n return {\n handleChainStart(\n chain,\n _inputs,\n runId,\n parentRunId,\n _tags,\n _metadata,\n _runType,\n runName,\n ) {\n const name = runName ?? lastIdSegment(chain) ?? 'chain';\n const parentId = keptParent(parentRunId);\n if (skipChain(name)) {\n reparented.set(runId, parentId);\n return;\n }\n observe({ type: 'agent.start', id: runId, parentId, agent: { name } });\n },\n handleChainEnd(_outputs, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId });\n },\n handleChainError(error, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId, error });\n },\n\n handleLLMStart(llm, _prompts, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleChatModelStart(llm, _messages, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleLLMEnd(output, runId) {\n const costModel = models.get(runId);\n models.delete(runId);\n observe({\n type: 'chat.end',\n id: runId,\n response: { finishReasons: finishReasons(output) },\n usage: langChainUsage(output),\n costModel,\n });\n },\n handleLLMError(error, runId) {\n models.delete(runId);\n observe({ type: 'chat.end', id: runId, error });\n },\n\n handleToolStart(\n tool,\n input,\n runId,\n parentRunId,\n _tags,\n _metadata,\n runName,\n ) {\n observe({\n type: 'tool.start',\n id: runId,\n parentId: keptParent(parentRunId),\n tool: { name: runName ?? lastIdSegment(tool) },\n callArguments: input,\n });\n },\n handleToolEnd(output, runId) {\n observe({ type: 'tool.end', id: runId, callResult: output });\n },\n handleToolError(error, runId) {\n observe({ type: 'tool.end', id: runId, error });\n },\n };\n}\n\n/**\n * Provider from a `Serialized.id` path. LangChain serializes a chat model as\n * `[...namespace, provider, ClassName]` (e.g. `…, 'openai', 'ChatOpenAI'`), so\n * the provider is the lowercase module segment before the PascalCase class.\n */\nfunction langChainProvider(serialized: Serialized): string | undefined {\n const candidate = serialized.id?.at(-2);\n return candidate && candidate === candidate.toLowerCase()\n ? normalizeAiSdkProvider(candidate)\n : undefined;\n}\n\nfunction langChainModel(\n serialized: Serialized,\n extraParams: Record<string, unknown> | undefined,\n): string | undefined {\n const invocation = asRecord(extraParams?.invocation_params);\n return (\n str(invocation?.model) ??\n str(invocation?.model_name) ??\n str(serialized.kwargs?.model) ??\n str(serialized.kwargs?.model_name)\n );\n}\n\nfunction langChainUsage(output: LLMResult): TokenUsage | undefined {\n // `usage_metadata` is LangChain's canonical cross-provider shape (Ollama,\n // newer integrations); `llmOutput.tokenUsage` is OpenAI; `llmOutput.usage`\n // is Anthropic-style.\n const usageMetadata = asRecord(\n firstGeneration(output)?.message?.usage_metadata,\n );\n const tokenUsage = asRecord(output.llmOutput?.tokenUsage);\n const usage = asRecord(output.llmOutput?.usage);\n const inputTokens =\n num(usageMetadata?.input_tokens) ??\n num(tokenUsage?.promptTokens) ??\n num(usage?.input_tokens);\n const outputTokens =\n num(usageMetadata?.output_tokens) ??\n num(tokenUsage?.completionTokens) ??\n num(usage?.output_tokens);\n if (inputTokens === undefined && outputTokens === undefined) return undefined;\n return { inputTokens, outputTokens };\n}\n\nfunction finishReasons(output: LLMResult): string[] | undefined {\n const generation = firstGeneration(output);\n const reason =\n str(generation?.generationInfo?.finish_reason) ??\n str(asRecord(generation?.message?.response_metadata)?.done_reason);\n return reason ? [reason] : undefined;\n}\n\nfunction firstGeneration(output: LLMResult): LLMGeneration | undefined {\n return output.generations?.[0]?.[0];\n}\n\nfunction lastIdSegment(serialized: Serialized): string | undefined {\n return serialized.name ?? serialized.id?.at(-1);\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value)\n ? value\n : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"observer-Ckha9xGv.js","names":["otelTrace","otelContext","AI_SDK_AGENT_NAME","AI_SDK_AGENT_NAME","otelContext","otelTrace"],"sources":["../src/observer/span-registry.ts","../src/observer/observer.ts","../src/observer/ai-sdk-shapes.ts","../src/observer/ai-sdk.ts","../src/observer/ai-sdk-messages.ts","../src/observer/ai-sdk-telemetry.ts","../src/observer/ai-sdk-channel.ts","../src/observer/langchain.ts"],"sourcesContent":["/**\n * Tracks the spans currently open in a {@link createGenAiObserver} stream and\n * the parent each was started under, so the observer can:\n *\n * - look up a parent span when a child starts, and\n * - force-close any descendant whose terminal event never arrived because its\n * parent ended first (an aborted tool call, a cancelled turn, a run that was\n * interrupted). Without this, abandoned spans leak open forever.\n */\n\nimport { SpanStatusCode, type Span, type TimeInput } from '@opentelemetry/api';\n\ninterface Entry {\n span: Span;\n parentId: string | undefined;\n}\n\nexport class SpanRegistry {\n private readonly entries = new Map<string, Entry>();\n\n /** Record an open span and the parent it was started under. */\n add(id: string, span: Span, parentId: string | undefined): void {\n this.entries.set(id, { span, parentId });\n }\n\n /** The open span for `id`, if any (used to parent a starting child). */\n spanFor(id: string): Span | undefined {\n return this.entries.get(id)?.span;\n }\n\n /** Remove and return the open span for `id`. */\n take(id: string): Span | undefined {\n const entry = this.entries.get(id);\n if (!entry) return undefined;\n this.entries.delete(id);\n return entry.span;\n }\n\n /**\n * Close every still-open descendant of `parentId` as ERROR — their terminal\n * event never arrived because the parent ended first. Depth-first, so the\n * deepest leaks close before their own parents.\n */\n reapDescendants(\n parentId: string,\n message: string,\n endTime?: TimeInput,\n ): void {\n // Deleting from a Map during its own for…of is well-defined; recursion only\n // removes deeper descendants (parentId === id), never an unvisited sibling.\n for (const [id, entry] of this.entries) {\n if (entry.parentId !== parentId) continue;\n this.entries.delete(id);\n this.reapDescendants(id, message, endTime);\n entry.span.setStatus({ code: SpanStatusCode.ERROR, message });\n entry.span.end(endTime);\n }\n }\n\n /** Count of spans still open — for tests and leak assertions. */\n get openCount(): number {\n return this.entries.size;\n }\n}\n","/**\n * `createGenAiObserver` — turn a stream of GenAI lifecycle events into a\n * canonical `gen_ai.*` span tree.\n *\n * A complement to {@link traceGenAI}: where the wrapper instruments code you\n * own, the observer instruments a framework that emits its own event stream.\n * Subscribe it once and feed every event through it; it reconstructs the span\n * hierarchy, estimates cost, force-closes abandoned children, and keeps\n * sensitive content off spans unless you opt in.\n *\n * Scope: this adapter records span attributes only. It deliberately does not\n * emit the `gen_ai.*` log events (`inference.operation.details`,\n * `operation.exception`) that `recordInferenceDetails` provides — the source\n * event stream already carries that detail.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * observe({ type: 'agent.start', id: 'a1', agent: { name: 'planner' } });\n * observe({ type: 'chat.start', id: 'c1', parentId: 'a1',\n * request: { provider: 'openai', model: 'gpt-4o' } });\n * observe({ type: 'chat.end', id: 'c1',\n * usage: { inputTokens: 412, outputTokens: 87 } });\n * observe({ type: 'agent.end', id: 'a1' });\n * ```\n */\n\nimport {\n context as otelContext,\n SpanKind,\n SpanStatusCode,\n trace as otelTrace,\n type Context,\n type Span,\n type SpanOptions,\n} from '@opentelemetry/api';\nimport {\n genAiAgentAttributes,\n genAiRequestAttributes,\n genAiResponseAttributes,\n genAiToolAttributes,\n genAiUsageAttributes,\n genAiWorkflowAttributes,\n type GenAiAttributeMap,\n} from '../attributes.js';\nimport { estimateLLMCost } from '../cost.js';\nimport { setGenAiContent, type GenAiContentSink } from '../events.js';\nimport { GEN_AI, GEN_AI_OPERATION, genAiSpanName } from '../semconv.js';\nimport {\n recordInputProvenance,\n recordMemoryAccess,\n recordPlanStep,\n recordRenderOutput,\n} from '../agent/agent-security.js';\nimport { agentContextFromSpan } from '../agent/context.js';\nimport { SpanRegistry } from './span-registry.js';\nimport type {\n ChatEndEvent,\n ChatStartEvent,\n GenAiObserver,\n GenAiObserverEvent,\n GenAiObserverOptions,\n SpanEnd,\n InputProvenanceEvent,\n MemoryAccessEvent,\n PlanStepEvent,\n RenderOutputEvent,\n ToolEndEvent,\n ToolStartEvent,\n} from './types.js';\n\nconst ORPHAN_MESSAGE =\n 'Parent ended before this span received its terminal event.';\n\n/** The `*.start` half of the event union — the events that open a span. */\ntype StartEvent = Extract<GenAiObserverEvent, { type: `${string}.start` }>;\n\nexport function createGenAiObserver(\n options: GenAiObserverOptions = {},\n): GenAiObserver {\n const tracer =\n options.tracer ?? otelTrace.getTracer('autotel-genai/observer');\n const registry = new SpanRegistry();\n\n /** Parent context: a tracked parent span, else the resolver, else root. */\n function parentContext(event: StartEvent): Context | undefined {\n const parentSpan = event.parentId\n ? registry.spanFor(event.parentId)\n : undefined;\n if (parentSpan) return otelTrace.setSpan(otelContext.active(), parentSpan);\n return options.resolveParentContext?.(event);\n }\n\n /** Start a span, register it under its parent, and return it. */\n function start(\n event: StartEvent,\n name: string,\n kind: SpanKind,\n attributes: GenAiAttributeMap,\n ): Span {\n const parent = parentContext(event);\n const spanOptions: SpanOptions = {\n kind,\n attributes,\n startTime: event.startTime,\n links: event.links,\n root: parent === undefined,\n };\n const span = tracer.startSpan(name, spanOptions, parent);\n registry.add(event.id, span, event.parentId);\n options.onSpanStart?.(event.id, span);\n return span;\n }\n\n /**\n * Close the span for `event.id`: first force-close any descendant whose\n * terminal event never arrived, then decorate, set status, and end.\n */\n function end(event: SpanEnd, decorate?: (span: Span) => void): void {\n const span = registry.take(event.id);\n if (!span) return;\n registry.reapDescendants(event.id, ORPHAN_MESSAGE, event.endTime);\n decorate?.(span);\n if (event.error !== undefined) {\n const message = errorMessage(event.error);\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n // The SDK normalizes a string or Error; hand it the original Error when\n // we have one, otherwise the message we already derived.\n span.recordException(\n event.error instanceof Error ? event.error : (message ?? 'error'),\n );\n }\n span.end(event.endTime);\n }\n\n /** Run the privacy gate; without it, content is always omitted. */\n function approvedContent(\n event: GenAiObserverEvent,\n ): GenAiObserverEvent | undefined {\n if (!options.exportContent) return undefined;\n try {\n return options.exportContent({ ...event });\n } catch (error) {\n console.error('[autotel-genai:observer] exportContent failed:', error);\n return undefined;\n }\n }\n\n function applyChatStart(span: Span, event: ChatStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'chat.start') return;\n setGenAiContent(spanSink(span), {\n inputMessages: content.inputMessages,\n systemInstructions: content.systemInstructions,\n });\n }\n\n function applyChatEnd(span: Span, event: ChatEndEvent): void {\n if (event.response) {\n span.setAttributes(genAiResponseAttributes(event.response));\n }\n if (event.usage) {\n const costModel = event.costModel ?? event.response?.model;\n const costUsd = costModel\n ? estimateLLMCost(costModel, event.usage)\n : undefined;\n span.setAttributes(genAiUsageAttributes({ ...event.usage, costUsd }));\n }\n if (event.streaming) {\n const { timeToFinish, outputTokensPerSecond, timePerOutputChunk } =\n event.streaming;\n const streamingAttrs: GenAiAttributeMap = {};\n if (timeToFinish !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_TO_FINISH] = timeToFinish;\n }\n if (outputTokensPerSecond !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_OUTPUT_TOKENS_PER_SECOND] =\n outputTokensPerSecond;\n }\n if (timePerOutputChunk !== undefined) {\n streamingAttrs[GEN_AI.RESPONSE_TIME_PER_OUTPUT_CHUNK] =\n timePerOutputChunk;\n }\n span.setAttributes(streamingAttrs);\n }\n const content = approvedContent(event);\n if (content?.type === 'chat.end') {\n setGenAiContent(spanSink(span), {\n outputMessages: content.outputMessages,\n });\n }\n }\n\n function applyToolStart(span: Span, event: ToolStartEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.start') return;\n span.setAttributes(\n genAiToolAttributes({ callArguments: content.callArguments }),\n );\n }\n\n function applyToolEnd(span: Span, event: ToolEndEvent): void {\n const content = approvedContent(event);\n if (content?.type !== 'tool.end') return;\n span.setAttributes(genAiToolAttributes({ callResult: content.callResult }));\n }\n\n function stampOnParent(\n parentId: string,\n apply: (ctx: ReturnType<typeof agentContextFromSpan>) => void,\n ): void {\n const parent = registry.spanFor(parentId);\n if (!parent) return;\n apply(agentContextFromSpan(parent));\n }\n\n return (event: GenAiObserverEvent): void => {\n switch (event.type) {\n case 'workflow.start': {\n start(\n event,\n genAiSpanName(\n GEN_AI_OPERATION.INVOKE_WORKFLOW,\n event.workflow.workflowName,\n ),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_WORKFLOW,\n ...genAiWorkflowAttributes(event.workflow),\n },\n );\n return;\n }\n case 'agent.start': {\n const internal = !event.remote;\n start(\n event,\n genAiSpanName(GEN_AI_OPERATION.INVOKE_AGENT, event.agent.name),\n internal ? SpanKind.INTERNAL : SpanKind.CLIENT,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.INVOKE_AGENT,\n ...(event.provider\n ? { [GEN_AI.PROVIDER_NAME]: event.provider }\n : {}),\n ...genAiAgentAttributes(event.agent, { internal }),\n },\n );\n return;\n }\n case 'chat.start': {\n const operation = event.request.operation ?? GEN_AI_OPERATION.CHAT;\n const span = start(\n event,\n genAiSpanName(operation, event.request.model),\n SpanKind.CLIENT,\n genAiRequestAttributes({ ...event.request, operation }),\n );\n applyChatStart(span, event);\n return;\n }\n case 'tool.start': {\n const span = start(\n event,\n genAiSpanName(GEN_AI_OPERATION.EXECUTE_TOOL, event.tool.name),\n SpanKind.INTERNAL,\n {\n [GEN_AI.OPERATION_NAME]: GEN_AI_OPERATION.EXECUTE_TOOL,\n ...genAiToolAttributes(event.tool),\n },\n );\n applyToolStart(span, event);\n return;\n }\n case 'workflow.end':\n case 'agent.end': {\n end(event);\n return;\n }\n case 'chat.end': {\n end(event, (span) => applyChatEnd(span, event));\n return;\n }\n case 'tool.end': {\n end(event, (span) => applyToolEnd(span, event));\n return;\n }\n case 'plan.step': {\n stampOnParent(event.parentId, (ctx) =>\n recordPlanStep({\n ctx,\n stepIndex: event.stepIndex,\n toolIntents: event.toolIntents,\n policyIds: event.policyIds,\n summary: event.summary,\n }),\n );\n return;\n }\n case 'input.provenance': {\n stampOnParent(event.parentId, (ctx) =>\n recordInputProvenance({ ctx, provenance: event.provenance }),\n );\n return;\n }\n case 'memory.access': {\n stampOnParent(event.parentId, (ctx) =>\n recordMemoryAccess({\n ctx,\n operation: event.operation,\n isolationKey: event.isolationKey,\n contentHash: event.contentHash,\n }),\n );\n return;\n }\n case 'render.output': {\n stampOnParent(event.parentId, (ctx) =>\n recordRenderOutput({\n ctx,\n format: event.format,\n containsUrl: event.containsUrl,\n urlCount: event.urlCount,\n }),\n );\n return;\n }\n default: {\n const unexpected: never = event;\n void unexpected;\n }\n }\n };\n}\n\n/** A {@link GenAiContentSink} backed by a raw span (only `setAttributes` is used). */\nfunction spanSink(span: Span): GenAiContentSink {\n return {\n setAttributes: (attrs) => {\n span.setAttributes(attrs);\n },\n track: () => {},\n };\n}\n\nfunction errorMessage(error: unknown): string | undefined {\n if (error === undefined) return undefined;\n if (typeof error === 'string') return error || undefined;\n if (error instanceof Error) return error.message;\n try {\n return JSON.stringify(error);\n } catch {\n return String(error);\n }\n}\n","/**\n * Shared structural converters from Vercel AI SDK shapes to autotel inputs.\n *\n * The AI SDK reports the same data three ways — the lifecycle `Telemetry`\n * events ({@link autotelTelemetry}), the `ai:telemetry` channel messages\n * ({@link subscribeAiTelemetry}), and a finished call result\n * ({@link observeAiSdkResult}) — and across v4/v5 with differing field names\n * (`promptTokens` vs `inputTokens`, flat vs nested token details). These helpers\n * are the single place that normalizes those shapes, so the three entry points\n * stay consistent and none of them re-derive the mapping.\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiRequestInput } from '../attributes.js';\nimport type { TokenUsage } from '../cost.js';\nimport { GEN_AI_OPERATION } from '../semconv.js';\n\n/** Every AI SDK token-usage field name we understand (v4 + v5, flat + nested). */\nexport interface AiSdkUsageShape {\n inputTokens?: number;\n outputTokens?: number;\n /** v4 aliases. */\n promptTokens?: number;\n completionTokens?: number;\n /** Embedding usage. */\n tokens?: number;\n /** Flat v5 detail fields. */\n reasoningTokens?: number;\n cachedInputTokens?: number;\n /** Nested v5 detail fields. */\n inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n}\n\n/** Map any AI SDK usage shape to {@link TokenUsage}; `undefined` if all empty. */\nexport function toTokenUsage(\n usage: AiSdkUsageShape | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined;\n const tokenUsage: TokenUsage = {\n inputTokens: usage.inputTokens ?? usage.promptTokens ?? usage.tokens,\n outputTokens: usage.outputTokens ?? usage.completionTokens,\n reasoningOutputTokens:\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens,\n cacheReadInputTokens:\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens,\n cacheCreationInputTokens: usage.inputTokenDetails?.cacheWriteTokens,\n };\n return Object.values(tokenUsage).some((v) => v !== undefined)\n ? tokenUsage\n : undefined;\n}\n\n/** Normalize an optional AI SDK provider id to a canonical `gen_ai` provider. */\nexport function normalizeProvider(provider: string | undefined) {\n return provider ? normalizeAiSdkProvider(provider) : undefined;\n}\n\n/** Request-side fields shared by the lifecycle and channel model-call events. */\nexport interface AiSdkRequestShape {\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n}\n\n/** Build the `chat` request attributes from a model-call start event. */\nexport function toChatRequest(event: AiSdkRequestShape): GenAiRequestInput {\n return {\n operation: GEN_AI_OPERATION.CHAT,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n temperature: event.temperature,\n maxTokens: event.maxOutputTokens,\n topP: event.topP,\n topK: event.topK,\n frequencyPenalty: event.frequencyPenalty,\n presencePenalty: event.presencePenalty,\n stopSequences: event.stopSequences,\n seed: event.seed,\n };\n}\n","/**\n * Vercel AI SDK → {@link createGenAiObserver} glue.\n *\n * The AI SDK has no global event stream; its stable surface is the result of a\n * `generateText`/`streamText` call (`steps`, each with `usage`, `toolCalls`,\n * `toolResults`, `response`). {@link observeAiSdkResult} walks that result and\n * emits observer events:\n *\n * - a trivial single-step call with no tools → one `chat` span;\n * - a multi-step or tool-using call → an `invoke_agent` wrapper (mirroring the\n * AI SDK's own `invoke_agent › chat › execute_tool` hierarchy) with a `chat`\n * per step and an `execute_tool` per tool call.\n *\n * Typed structurally against the AI SDK result shape so it pulls in no\n * dependency and tolerates v4/v5 field differences (`promptTokens` vs\n * `inputTokens`). Cost is left to the observer, which prices `chat` usage.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * const result = await generateText({ model: openai('gpt-4o'), prompt });\n * observeAiSdkResult(observe, result, { id: 'gen-1', provider: 'openai', model: 'gpt-4o' });\n * ```\n */\n\nimport type { TimeInput } from '@opentelemetry/api';\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { GenAiProviderName } from '../semconv.js';\nimport { toTokenUsage } from './ai-sdk-shapes.js';\nimport type { GenAiObserver } from './types.js';\n\n/** AI SDK usage object — canonical (v5) or legacy (v4) field names. */\nexport interface AiSdkUsage {\n inputTokens?: number;\n outputTokens?: number;\n promptTokens?: number;\n completionTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n}\n\nexport interface AiSdkToolCall {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n args?: unknown;\n}\n\nexport interface AiSdkToolResult {\n toolCallId?: string;\n toolName?: string;\n output?: unknown;\n result?: unknown;\n}\n\n/** One AI SDK step (also the shape of a single-call result). */\nexport interface AiSdkStep {\n usage?: AiSdkUsage;\n finishReason?: string;\n response?: { id?: string; modelId?: string; timestamp?: Date | number };\n toolCalls?: AiSdkToolCall[];\n toolResults?: AiSdkToolResult[];\n}\n\n/** An AI SDK `generateText`/`streamText` result. */\nexport interface AiSdkResult extends AiSdkStep {\n steps?: AiSdkStep[];\n}\n\nexport interface ObserveAiSdkOptions {\n /** Base span id; per-step and per-tool ids derive from it. */\n id: string;\n parentId?: string;\n /** AI SDK provider id (e.g. `openai`, `amazon-bedrock`); normalized for you. */\n provider?: string;\n /** Request model — used for the span name and as the cost model. */\n model?: string;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\nexport function observeAiSdkResult(\n observe: GenAiObserver,\n result: AiSdkResult,\n options: ObserveAiSdkOptions,\n): void {\n const provider: GenAiProviderName | undefined = options.provider\n ? normalizeAiSdkProvider(options.provider)\n : undefined;\n const steps = result.steps ?? [result];\n const toolCount = steps.reduce(\n (total, step) => total + (step.toolCalls?.length ?? 0),\n 0,\n );\n\n if (steps.length <= 1 && toolCount === 0) {\n emitChat(observe, steps[0] ?? result, {\n id: options.id,\n parentId: options.parentId,\n provider,\n model: options.model,\n });\n return;\n }\n\n observe({\n type: 'agent.start',\n id: options.id,\n parentId: options.parentId,\n provider,\n agent: { name: options.model ?? AI_SDK_AGENT_NAME },\n });\n steps.forEach((step, index) => {\n emitChat(observe, step, {\n id: `${options.id}:step:${index}`,\n parentId: options.id,\n provider,\n model: options.model,\n });\n emitTools(observe, step, options.id, index);\n });\n observe({ type: 'agent.end', id: options.id });\n}\n\ninterface ChatContext {\n id: string;\n parentId?: string;\n provider?: GenAiProviderName;\n model?: string;\n}\n\nfunction emitChat(\n observe: GenAiObserver,\n step: AiSdkStep,\n ctx: ChatContext,\n): void {\n observe({\n type: 'chat.start',\n id: ctx.id,\n parentId: ctx.parentId,\n request: { provider: ctx.provider, model: ctx.model },\n });\n const responseModel = step.response?.modelId;\n observe({\n type: 'chat.end',\n id: ctx.id,\n response: {\n model: responseModel,\n id: step.response?.id,\n finishReasons: step.finishReason ? [step.finishReason] : undefined,\n },\n usage: toTokenUsage(step.usage),\n costModel: ctx.model ?? responseModel,\n endTime: asTimeInput(step.response?.timestamp),\n });\n}\n\nfunction emitTools(\n observe: GenAiObserver,\n step: AiSdkStep,\n parentId: string,\n stepIndex: number,\n): void {\n const results = step.toolResults ?? [];\n (step.toolCalls ?? []).forEach((call, callIndex) => {\n const id = `${parentId}:tool:${call.toolCallId ?? `${stepIndex}.${callIndex}`}`;\n observe({\n type: 'tool.start',\n id,\n parentId,\n tool: { name: call.toolName, callId: call.toolCallId },\n callArguments: call.input ?? call.args,\n });\n const result = call.toolCallId\n ? results.find((r) => r.toolCallId === call.toolCallId)\n : results[callIndex];\n observe({\n type: 'tool.end',\n id,\n callResult: result?.output ?? result?.result,\n });\n });\n}\n\nfunction asTimeInput(value: Date | number | undefined): TimeInput | undefined {\n return value instanceof Date || typeof value === 'number' ? value : undefined;\n}\n","/**\n * Vercel AI SDK messages → GenAI SemConv message format.\n *\n * The AI SDK carries prompts as `ModelMessage[]` (role + string-or-parts) and\n * model output as `ContentPart[]`. The GenAI semantic conventions instead model\n * `gen_ai.input.messages` / `gen_ai.output.messages` as `{ role, parts }` where\n * each part is `{ type, ...typeFields }`:\n *\n * - text: `{ type: 'text', content }`\n * - reasoning: `{ type: 'reasoning', content }`\n * - tool_call: `{ type: 'tool_call', id, name, arguments }`\n * - tool_call_response: `{ type: 'tool_call_response', id, response }`\n * - blob/uri: media, referenced by `mime_type` only (never inlined)\n *\n * Typed structurally against the AI SDK shapes (no `ai` / `@ai-sdk/*` import).\n * Media bytes are deliberately not copied onto spans — only the modality and\n * mime type — so content capture never balloons span size or leaks raw blobs.\n */\n\nimport type { GenAiMessage, GenAiMessagePart } from '../events.js';\n\n// --- Structural views of the AI SDK message shapes -------------------------\n// Exported so the telemetry/channel event views can type their `messages` /\n// `content` fields against these directly — no per-call-site casts, and (with no\n// index signature) the real SDK `ModelMessage[]` / `ContentPart[]` stay\n// assignable to them.\n\nexport interface ModelMessageView {\n role?: string;\n content?: string | readonly ContentPartView[];\n}\n\nexport interface ContentPartView {\n type?: string;\n /** text / reasoning */\n text?: string;\n /** tool-call */\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n /** tool-result */\n output?: unknown;\n /** image / file */\n mediaType?: string;\n}\n\n/** Result of converting an AI SDK prompt: messages plus split-out system parts. */\nexport interface ConvertedPrompt {\n messages: GenAiMessage[];\n systemInstructions?: GenAiMessagePart[];\n}\n\n/**\n * Convert an AI SDK `ModelMessage[]` prompt to GenAI input messages, splitting\n * `system` messages out into `systemInstructions` (recorded separately per the\n * conventions).\n */\nexport function promptToGenAiMessages(\n messages: readonly ModelMessageView[] | undefined,\n): ConvertedPrompt {\n if (!messages?.length) return { messages: [] };\n\n const out: GenAiMessage[] = [];\n const system: GenAiMessagePart[] = [];\n\n for (const message of messages) {\n const role = message.role ?? 'user';\n const parts = contentToParts(message.content);\n if (role === 'system') {\n system.push(...parts);\n } else {\n out.push({ role, parts });\n }\n }\n\n return system.length > 0\n ? { messages: out, systemInstructions: system }\n : { messages: out };\n}\n\n/**\n * Convert the `ContentPart[]` of a model response into a single GenAI output\n * message (always `assistant`), optionally tagged with a `finish_reason`.\n */\nexport function contentToGenAiMessage(\n content: readonly ContentPartView[] | undefined,\n finishReason?: string,\n): GenAiMessage | undefined {\n if (!content?.length) return undefined;\n const message: GenAiMessage = {\n role: 'assistant',\n parts: content.flatMap((part) => partToGenAi(part)),\n };\n if (finishReason) message.finish_reason = finishReason;\n return message;\n}\n\nfunction contentToParts(\n content: string | readonly ContentPartView[] | undefined,\n): GenAiMessagePart[] {\n if (content === undefined) return [];\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : [];\n }\n return content.flatMap((part) => partToGenAi(part));\n}\n\n/** Map one AI SDK content part to zero or more GenAI parts. */\nfunction partToGenAi(part: ContentPartView): GenAiMessagePart[] {\n switch (part.type) {\n case 'text': {\n return part.text ? [{ type: 'text', content: part.text }] : [];\n }\n case 'reasoning': {\n return part.text ? [{ type: 'reasoning', content: part.text }] : [];\n }\n case 'tool-call': {\n return [\n {\n type: 'tool_call',\n id: part.toolCallId,\n name: part.toolName,\n arguments: part.input,\n },\n ];\n }\n case 'tool-result': {\n return [\n {\n type: 'tool_call_response',\n id: part.toolCallId,\n response: part.output,\n },\n ];\n }\n case 'image':\n case 'file': {\n // Reference media by modality + mime type only — never inline bytes.\n return [\n {\n type: 'blob',\n modality: part.type === 'image' ? 'image' : 'file',\n mime_type: part.mediaType,\n },\n ];\n }\n default: {\n return part.type ? [{ type: part.type }] : [];\n }\n }\n}\n","/**\n * `autotelTelemetry` — a Vercel AI SDK telemetry integration.\n *\n * Implements the AI SDK's `Telemetry` lifecycle interface (stable since ai v7)\n * so you register it once and every `generateText` / `streamText` / `embed`\n * call streams a canonical `gen_ai.*` span tree — live, as the operation runs,\n * not reconstructed after the fact:\n *\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n *\n * Where the built-in `@ai-sdk/otel` `OpenTelemetry` integration emits the same\n * `gen_ai.*` spans, this one additionally:\n *\n * - prices every model call (`gen_ai.usage.cost.usd`) from {@link MODEL_PRICING};\n * - records streaming throughput (`time_to_first_chunk`, `time_to_finish`,\n * `output_tokens_per_second`) on each `chat` span;\n * - keeps token usage on leaf `chat` spans only, so the `invoke_agent` root\n * never double-counts.\n *\n * It is push-based and concurrency-safe: every lifecycle event carries the\n * AI SDK `callId`, so interleaved concurrent generations never cross wires. It\n * is the live counterpart to {@link observeAiSdkResult}, which walks a finished\n * result instead.\n *\n * Typed structurally against the AI SDK event shapes — it pulls in **no**\n * dependency on `ai` / `@ai-sdk/*`. The returned object satisfies the AI SDK\n * `Telemetry` interface by structural compatibility (every event the SDK passes\n * is a superset of the fields read here), so `registerTelemetry(autotelTelemetry())`\n * type-checks without importing the interface.\n *\n * Scope: `generateText` / `streamText` (incl. tool loops and `output`) and\n * `embed` / `embedMany`. A tool's nested `generateText` and the provider's own\n * HTTP spans parent correctly via the `executeTool` / `executeLanguageModelCall`\n * context runners. `rerank` has no canonical `gen_ai` operation in the v1.42.0\n * registry and is intentionally not mapped.\n *\n * Message content (prompts, responses, tool arguments/results) is **off by\n * default**; enable it with `captureContent`, gated by the SDK's `recordInputs`\n * / `recordOutputs` and the optional `exportContent` privacy callback.\n */\n\nimport {\n context as otelContext,\n trace as otelTrace,\n type Context,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type {\n ChatStreamTiming,\n GenAiObserverEvent,\n GenAiObserver,\n} from './types.js';\n\n// --- Structural views of the AI SDK telemetry event shapes -----------------\n// Only the fields this integration reads. The real events are supersets, so a\n// callback typed against these is assignable to the AI SDK `Telemetry` methods.\n\ninterface OperationStartEvent {\n callId: string;\n /** e.g. `'ai.generateText'`, `'ai.streamText'`, `'ai.embed'`. */\n operationId?: string;\n provider?: string;\n modelId?: string;\n /** From telemetry settings — used as the agent name. */\n functionId?: string;\n}\n\ninterface LanguageModelCallStartEventView {\n callId: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n /** Standardized prompt messages (when content capture is on). */\n messages?: readonly ModelMessageView[];\n /** Whether the SDK call permits recording inputs (default true). */\n recordInputs?: boolean;\n}\n\ninterface LanguageModelCallEndEventView {\n callId: string;\n modelId?: string;\n responseId?: string;\n finishReason?: string;\n usage?: AiSdkUsageShape;\n /** Output content parts (when content capture is on). */\n content?: readonly ContentPartView[];\n /** Whether the SDK call permits recording outputs (default true). */\n recordOutputs?: boolean;\n performance?: {\n responseTimeMs?: number;\n effectiveOutputTokensPerSecond?: number;\n timeToFirstOutputMs?: number;\n };\n}\n\ninterface ToolCallView {\n toolCallId?: string;\n toolName?: string;\n input?: unknown;\n}\n\ninterface ToolExecutionStartEventView {\n callId: string;\n toolCall: ToolCallView;\n recordInputs?: boolean;\n}\n\ninterface ToolOutputView {\n type?: string;\n output?: unknown;\n error?: unknown;\n}\n\ninterface ToolExecutionEndEventView {\n callId: string;\n toolCall: ToolCallView;\n toolOutput: ToolOutputView;\n recordOutputs?: boolean;\n}\n\ninterface OperationEndEventView {\n callId: string;\n}\n\ninterface AbortEventView {\n callId: string;\n reason?: unknown;\n}\n\ninterface EmbeddingModelCallEndEventView {\n callId: string;\n embedCallId?: string;\n provider?: string;\n modelId?: string;\n usage?: { tokens?: number };\n}\n\n/**\n * The subset of the AI SDK `Telemetry` interface this integration implements.\n * The returned object is assignable to the SDK's `Telemetry` type.\n */\nexport interface AutotelTelemetryIntegration {\n onStart(event: OperationStartEvent): void;\n onLanguageModelCallStart(event: LanguageModelCallStartEventView): void;\n onLanguageModelCallEnd(event: LanguageModelCallEndEventView): void;\n onToolExecutionStart(event: ToolExecutionStartEventView): void;\n onToolExecutionEnd(event: ToolExecutionEndEventView): void;\n onEmbedEnd(event: EmbeddingModelCallEndEventView): void;\n onEnd(event: OperationEndEventView): void;\n onAbort(event: AbortEventView): void;\n onError(event: unknown): void;\n executeLanguageModelCall<T>(\n options: { callId: string; execute: () => PromiseLike<T> },\n ): PromiseLike<T>;\n executeTool<T>(\n options: {\n callId: string;\n toolCallId: string;\n execute: () => PromiseLike<T>;\n },\n ): PromiseLike<T>;\n}\n\nexport interface AutotelTelemetryOptions {\n /**\n * Tracer to emit spans on. Defaults to the global `autotel-genai/observer`\n * tracer. Pass an application-owned tracer to share one configuration.\n */\n tracer?: Tracer;\n /**\n * Privacy gate applied to all captured content (prompts, responses, tool\n * arguments/results) before it is written to a span. Receives each\n * content-bearing observer event; return it (optionally redacted) to keep its\n * content, or `undefined` to drop that event's content. Only consulted when\n * {@link AutotelTelemetryOptions.captureContent} is enabled.\n */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /**\n * Capture prompt/response message content (`gen_ai.input.messages`,\n * `gen_ai.output.messages`, `gen_ai.system_instructions`) and tool\n * arguments/results. **Off by default** for privacy. When `true`, content is\n * still filtered through {@link AutotelTelemetryOptions.exportContent} if you\n * provide one (redact there); with no `exportContent`, all approved content is\n * written. The AI SDK's per-call `recordInputs` / `recordOutputs` are still\n * honored — either being `false` suppresses that side.\n */\n captureContent?: boolean;\n /**\n * Resolve an OpenTelemetry parent context for an operation that has no\n * tracked parent — e.g. attach the `invoke_agent` root to the incoming\n * request span. Return `undefined` to keep it a root.\n */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/** Per-call correlation state, keyed by the AI SDK `callId`. */\ninterface CallState {\n /** Whether an `invoke_agent` root span was opened for this call. */\n hasAgent: boolean;\n /** Whether the root operation is streaming (`streamText`) or not. */\n stream?: boolean;\n /** Stack of open `chat` span ids (one per in-flight model call). */\n openLm: string[];\n /** Monotonic counter for unique model-call span ids within the call. */\n lmSeq: number;\n /** FIFO of anonymous tool span ids when the SDK omits `toolCallId`. */\n anonymousToolIds: string[];\n /** Monotonic counter for unique anonymous tool span ids within the call. */\n toolSeq: number;\n}\n\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/**\n * Create an AI SDK telemetry integration that streams canonical `gen_ai.*`\n * spans (with cost + streaming timing) for every AI SDK operation.\n *\n * @example\n * ```ts\n * import { registerTelemetry } from 'ai';\n * import { autotelTelemetry } from 'autotel-genai/observer';\n *\n * registerTelemetry(autotelTelemetry());\n * ```\n */\nexport function autotelTelemetry(\n options: AutotelTelemetryOptions = {},\n): AutotelTelemetryIntegration {\n const captureContent = options.captureContent ?? false;\n // When content capture is on with no explicit gate, allow all approved\n // content; an `exportContent` callback (redaction) always wins if provided.\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n // Live spans by observer id, so the context runners can re-enter a span's\n // OTel context to nest provider HTTP calls / a tool's inner `generateText`.\n const spans = new Map<string, Span>();\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n onSpanStart: (id, span) => spans.set(id, span),\n });\n const calls = new Map<string, CallState>();\n\n function startToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n const seq = state?.toolSeq ?? 0;\n if (state) state.toolSeq += 1;\n const id = `${callId}:tool:anon:${seq}`;\n state?.anonymousToolIds.push(id);\n return id;\n }\n\n function endToolSpanId(\n callId: string,\n state: CallState | undefined,\n call: { toolCallId?: string },\n ): string {\n if (call.toolCallId) return `${callId}:tool:${call.toolCallId}`;\n return state?.anonymousToolIds.shift() ?? `${callId}:tool:anon:0`;\n }\n\n /** Run `execute` within `span`'s OTel context so nested work parents to it. */\n function runInSpan<T>(\n span: Span | undefined,\n execute: () => PromiseLike<T>,\n ): PromiseLike<T> {\n if (!span) return execute();\n return otelContext.with(\n otelTrace.setSpan(otelContext.active(), span),\n execute,\n );\n }\n\n return {\n onStart(event) {\n // Embedding / rerank operations are not `invoke_agent` roots; they are\n // handled as leaf spans by `onEmbedEnd` (rerank is unsupported).\n if (isNonAgentOperation(event.operationId)) return;\n const state: CallState = {\n hasAgent: true,\n stream: event.operationId?.includes('stream'),\n openLm: [],\n lmSeq: 0,\n anonymousToolIds: [],\n toolSeq: 0,\n };\n calls.set(event.callId, state);\n observe({\n type: 'agent.start',\n id: event.callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.functionId ?? event.modelId ?? AI_SDK_AGENT_NAME },\n });\n },\n\n onLanguageModelCallStart(event) {\n const state = calls.get(event.callId);\n const id = `${event.callId}:lm:${state ? state.lmSeq++ : 0}`;\n if (state) state.openLm.push(id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n request: { ...toChatRequest(event), stream: state?.stream },\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n },\n\n onLanguageModelCallEnd(event) {\n const state = calls.get(event.callId);\n const id = state?.openLm.pop() ?? `${event.callId}:lm:0`;\n const outputMessage =\n captureContent && event.recordOutputs !== false\n ? contentToGenAiMessage(event.content, event.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: event.modelId,\n id: event.responseId,\n finishReasons: event.finishReason ? [event.finishReason] : undefined,\n timeToFirstChunk: msToSeconds(event.performance?.timeToFirstOutputMs),\n },\n usage: toTokenUsage(event.usage),\n costModel: event.modelId,\n streaming: mapStreaming(event.performance),\n outputMessages: outputMessage ? [outputMessage] : undefined,\n });\n spans.delete(id);\n },\n\n onToolExecutionStart(event) {\n const state = calls.get(event.callId);\n const id = startToolSpanId(event.callId, state, event.toolCall);\n observe({\n type: 'tool.start',\n id,\n parentId: state?.hasAgent ? event.callId : undefined,\n tool: {\n name: event.toolCall.toolName,\n callId: event.toolCall.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall.input\n : undefined,\n });\n },\n\n onToolExecutionEnd(event) {\n const state = calls.get(event.callId);\n const id = endToolSpanId(event.callId, state, event.toolCall);\n const isError = event.toolOutput.type === 'tool-error';\n observe({\n type: 'tool.end',\n id,\n error: isError ? event.toolOutput.error : undefined,\n callResult:\n !isError && captureContent && event.recordOutputs !== false\n ? event.toolOutput.output\n : undefined,\n });\n spans.delete(id);\n },\n\n onEmbedEnd(event) {\n const id = `${event.callId}:embed:${event.embedCallId ?? '0'}`;\n observe({\n type: 'chat.start',\n id,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n observe({\n type: 'chat.end',\n id,\n response: { model: event.modelId },\n usage:\n event.usage?.tokens === undefined\n ? undefined\n : { inputTokens: event.usage.tokens },\n costModel: event.modelId,\n });\n spans.delete(id);\n },\n\n onEnd(event) {\n if (!calls.delete(event.callId)) return;\n observe({ type: 'agent.end', id: event.callId });\n spans.delete(event.callId);\n },\n\n onAbort(event) {\n if (!calls.delete(event.callId)) return;\n observe({\n type: 'agent.end',\n id: event.callId,\n error: event.reason ?? 'aborted',\n });\n spans.delete(event.callId);\n },\n\n onError(event) {\n // The dispatcher invokes `onError` with `{ callId, error }`. Close the\n // matching `invoke_agent` root when we can correlate it; otherwise leave\n // it for `onEnd`/`onAbort` (the observer reaps any open children on close).\n const callId = errorCallId(event);\n if (callId === undefined || !calls.delete(callId)) return;\n observe({\n type: 'agent.end',\n id: callId,\n error: errorValue(event) ?? event,\n });\n spans.delete(callId);\n },\n\n executeLanguageModelCall(options) {\n // `onLanguageModelCallStart` already opened this call's `chat` span (it is\n // the top of the stack). Run the provider call inside it so any\n // auto-instrumented HTTP spans become its children.\n const state = calls.get(options.callId);\n const id = state?.openLm.at(-1);\n return runInSpan(id ? spans.get(id) : undefined, options.execute);\n },\n\n executeTool(options) {\n // `onToolExecutionStart` already opened the tool span; run the tool's\n // `execute` inside it so a nested `generateText` nests under the tool.\n const id = `${options.callId}:tool:${options.toolCallId}`;\n return runInSpan(spans.get(id), options.execute);\n },\n };\n}\n\n/** Operations that are not `invoke_agent` roots (embeddings, rerank). */\nfunction isNonAgentOperation(operationId: string | undefined): boolean {\n if (!operationId) return false;\n return operationId.includes('embed') || operationId.includes('rerank');\n}\n\nfunction mapStreaming(\n performance: LanguageModelCallEndEventView['performance'],\n): ChatStreamTiming | undefined {\n if (!performance) return undefined;\n const timing: ChatStreamTiming = {\n timeToFinish: msToSeconds(performance.responseTimeMs),\n outputTokensPerSecond: performance.effectiveOutputTokensPerSecond,\n };\n return timing.timeToFinish !== undefined ||\n timing.outputTokensPerSecond !== undefined\n ? timing\n : undefined;\n}\n\nfunction msToSeconds(ms: number | undefined): number | undefined {\n return ms === undefined ? undefined : ms / 1000;\n}\n\nfunction errorCallId(event: unknown): string | undefined {\n if (\n typeof event === 'object' &&\n event !== null &&\n 'callId' in event &&\n typeof (event as { callId: unknown }).callId === 'string'\n ) {\n return (event as { callId: string }).callId;\n }\n return undefined;\n}\n\nfunction errorValue(event: unknown): unknown {\n if (typeof event === 'object' && event !== null && 'error' in event) {\n return (event as { error: unknown }).error;\n }\n return undefined;\n}\n","/**\n * `subscribeAiTelemetry` — the zero-config Vercel AI SDK path.\n *\n * Instead of `registerTelemetry(autotelTelemetry())`, subscribe to the AI SDK's\n * `ai:telemetry` Node [tracing channel](https://nodejs.org/api/diagnostics_channel.html).\n * The SDK publishes an operation span per `generateText` / `streamText` /\n * `languageModelCall` / `executeTool` / `embed` as long as the channel has a\n * subscriber — no registration call, and it binds async context across provider\n * calls and tool executions:\n *\n * ```ts\n * import { subscribeAiTelemetry } from 'autotel-genai/observer';\n *\n * const unsubscribe = subscribeAiTelemetry(); // once, at startup\n * ```\n *\n * Fidelity note: the channel exposes coarser data than the registered\n * {@link autotelTelemetry} integration. You get the canonical\n * `invoke_agent › chat › execute_tool` tree with token usage and cost (read\n * from each operation's result), but **not** the per-call streaming timing\n * (`time_to_first_chunk` etc.) that only the lifecycle `onLanguageModelCallEnd`\n * event carries. Prefer `registerTelemetry(autotelTelemetry())` when you can;\n * use this when you cannot add a registration call.\n *\n * No-ops (returning an unsubscribe that does nothing) on runtimes without Node\n * diagnostics-channel support, so it is safe to call unconditionally.\n */\n\nimport type { Context, Tracer } from '@opentelemetry/api';\nimport { subscribeTracingChannel } from 'autotel/diagnostics';\nimport { GEN_AI_OPERATION, GEN_AI_TOOL_TYPE } from '../semconv.js';\nimport {\n contentToGenAiMessage,\n promptToGenAiMessages,\n type ContentPartView,\n type ModelMessageView,\n} from './ai-sdk-messages.js';\nimport {\n normalizeProvider,\n toChatRequest,\n toTokenUsage,\n type AiSdkUsageShape,\n} from './ai-sdk-shapes.js';\nimport { createGenAiObserver } from './observer.js';\nimport type { GenAiObserver, GenAiObserverEvent } from './types.js';\n\nconst AI_SDK_TELEMETRY_TRACING_CHANNEL = 'ai:telemetry';\nconst AI_SDK_AGENT_NAME = 'ai-sdk';\n\n/** A published tracing-channel message: `{ type, event }` plus the settled result. */\ninterface ChannelMessage {\n type?: string;\n event?: ChannelEvent;\n result?: unknown;\n error?: unknown;\n}\n\ninterface ChannelEvent {\n callId?: string;\n provider?: string;\n modelId?: string;\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n frequencyPenalty?: number;\n presencePenalty?: number;\n stopSequences?: string[];\n seed?: number;\n toolCall?: { toolCallId?: string; toolName?: string; input?: unknown };\n messages?: readonly ModelMessageView[];\n recordInputs?: boolean;\n recordOutputs?: boolean;\n}\n\nexport interface SubscribeAiTelemetryOptions {\n /** Tracer to emit spans on. Defaults to the global observer tracer. */\n tracer?: Tracer;\n /**\n * Capture prompt/response content and tool arguments/results. Off by default.\n * @see {@link import('./ai-sdk-telemetry.js').AutotelTelemetryOptions.captureContent}\n */\n captureContent?: boolean;\n /** Privacy gate for captured content (redact or drop per event). */\n exportContent?: (event: GenAiObserverEvent) => GenAiObserverEvent | undefined;\n /** Resolve an OTel parent context for otherwise-root operations. */\n resolveParentContext?: (event: GenAiObserverEvent) => Context | undefined;\n}\n\n/**\n * Subscribe to the `ai:telemetry` tracing channel and emit canonical `gen_ai.*`\n * spans. Returns an unsubscribe function (a no-op on unsupported runtimes).\n */\nexport function subscribeAiTelemetry(\n options: SubscribeAiTelemetryOptions = {},\n): () => void {\n const captureContent = options.captureContent ?? false;\n const exportContent =\n options.exportContent ??\n (captureContent ? (event: GenAiObserverEvent) => event : undefined);\n\n const observe: GenAiObserver = createGenAiObserver({\n tracer: options.tracer,\n exportContent,\n resolveParentContext: options.resolveParentContext,\n });\n\n // Pair each `start` with its `asyncEnd`/`error` by message-object identity,\n // recording the observer span id we assigned at start.\n const ids = new WeakMap<ChannelMessage, string>();\n let seq = 0;\n\n const handlers = {\n start(message: ChannelMessage) {\n const event = message.event ?? {};\n const callId = event.callId ?? `op:${seq}`;\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n ids.set(message, callId);\n observe({\n type: 'agent.start',\n id: callId,\n provider: normalizeProvider(event.provider),\n agent: { name: event.modelId ?? AI_SDK_AGENT_NAME },\n });\n return;\n }\n case 'languageModelCall': {\n const id = `${callId}:lm:${seq++}`;\n ids.set(message, id);\n const content =\n captureContent && event.recordInputs !== false\n ? promptToGenAiMessages(event.messages)\n : undefined;\n observe({\n type: 'chat.start',\n id,\n parentId: callId,\n request: toChatRequest(event),\n inputMessages: content?.messages,\n systemInstructions: content?.systemInstructions,\n });\n return;\n }\n case 'executeTool': {\n const id = `${callId}:tool:${event.toolCall?.toolCallId ?? seq++}`;\n ids.set(message, id);\n observe({\n type: 'tool.start',\n id,\n parentId: callId,\n tool: {\n name: event.toolCall?.toolName,\n callId: event.toolCall?.toolCallId,\n type: GEN_AI_TOOL_TYPE.FUNCTION,\n },\n callArguments:\n captureContent && event.recordInputs !== false\n ? event.toolCall?.input\n : undefined,\n });\n return;\n }\n case 'embed': {\n ids.set(message, callId);\n observe({\n type: 'chat.start',\n id: callId,\n request: {\n operation: GEN_AI_OPERATION.EMBEDDINGS,\n provider: normalizeProvider(event.provider),\n model: event.modelId,\n },\n });\n return;\n }\n // 'step' and 'rerank' are intentionally not mapped.\n }\n },\n\n asyncEnd(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n closeSpan(observe, message, id, captureContent);\n },\n\n error(message: ChannelMessage) {\n const id = ids.get(message);\n if (id === undefined) return;\n // `closeSpan` reads `message.error`; route by type to the right end event.\n closeSpan(observe, message, id, captureContent);\n },\n };\n\n return subscribeTracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL, handlers);\n}\n\nfunction closeSpan(\n observe: GenAiObserver,\n message: ChannelMessage,\n id: string,\n captureContent: boolean,\n): void {\n const error = message.error;\n const result = message.result as ResultView | undefined;\n const event = message.event;\n\n switch (message.type) {\n case 'generateText':\n case 'streamText': {\n observe({ type: 'agent.end', id, error });\n return;\n }\n case 'embed': {\n observe({\n type: 'chat.end',\n id,\n response: { model: event?.modelId },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n error,\n });\n return;\n }\n case 'languageModelCall': {\n const outputMessage =\n captureContent && event?.recordOutputs !== false\n ? contentToGenAiMessage(result?.content, result?.finishReason)\n : undefined;\n observe({\n type: 'chat.end',\n id,\n response: {\n model: result?.response?.modelId ?? event?.modelId,\n id: result?.response?.id,\n finishReasons: result?.finishReason ? [result.finishReason] : undefined,\n },\n usage: toTokenUsage(result?.usage),\n costModel: event?.modelId,\n outputMessages: outputMessage ? [outputMessage] : undefined,\n error,\n });\n return;\n }\n case 'executeTool': {\n observe({\n type: 'tool.end',\n id,\n callResult:\n captureContent &&\n event?.recordOutputs !== false &&\n result?.output !== undefined\n ? result.output\n : undefined,\n error,\n });\n return;\n }\n }\n}\n\ninterface ResultView {\n content?: readonly ContentPartView[];\n finishReason?: string;\n output?: unknown;\n response?: { id?: string; modelId?: string };\n usage?: AiSdkUsageShape;\n}\n\n","/**\n * LangChain / LangGraph → {@link createGenAiObserver} glue.\n *\n * LangChain's callback system is a real event stream: register one handler and\n * it receives every run's start/end/error with a `runId` and `parentRunId` —\n * which map directly onto the observer's `id` / `parentId`. {@link\n * createLangChainObserver} returns a handler object you pass to `callbacks`:\n *\n * - chain runs (LangGraph nodes, agents) → `invoke_agent` spans;\n * - LLM / chat-model runs → `chat` spans (with usage and estimated cost);\n * - tool runs → `execute_tool` spans.\n *\n * Typed structurally against the callback payloads so it pulls in no LangChain\n * dependency. Pass it via `{ callbacks: [createLangChainObserver(observe)] }`.\n *\n * @example\n * ```ts\n * const observe = createGenAiObserver();\n * await graph.invoke(input, { callbacks: [createLangChainObserver(observe)] });\n * ```\n */\n\nimport { normalizeAiSdkProvider } from '../ai-sdk-bridge.js';\nimport type { TokenUsage } from '../cost.js';\nimport type { GenAiObserver } from './types.js';\n\n/** LangChain `Serialized` — the identity of a chain/LLM/tool. */\ninterface Serialized {\n id?: string[];\n name?: string;\n kwargs?: Record<string, unknown>;\n}\n\ninterface LLMGeneration {\n generationInfo?: Record<string, unknown>;\n message?: {\n usage_metadata?: Record<string, unknown>;\n response_metadata?: Record<string, unknown>;\n };\n}\n\n/** LangChain `LLMResult` — what an LLM/chat-model run returns. */\ninterface LLMResult {\n generations?: LLMGeneration[][];\n llmOutput?: Record<string, unknown>;\n}\n\n/** The subset of LangChain's `CallbackHandlerMethods` this adapter implements. */\nexport interface LangChainObserverHandler {\n handleChainStart(\n chain: Serialized,\n inputs: unknown,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runType?: string,\n runName?: string,\n ): void;\n handleChainEnd(outputs: unknown, runId: string): void;\n handleChainError(error: unknown, runId: string): void;\n handleLLMStart(\n llm: Serialized,\n prompts: string[],\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleChatModelStart(\n llm: Serialized,\n messages: unknown,\n runId: string,\n parentRunId?: string,\n extraParams?: Record<string, unknown>,\n ): void;\n handleLLMEnd(output: LLMResult, runId: string): void;\n handleLLMError(error: unknown, runId: string): void;\n handleToolStart(\n tool: Serialized,\n input: string,\n runId: string,\n parentRunId?: string,\n tags?: string[],\n metadata?: Record<string, unknown>,\n runName?: string,\n ): void;\n handleToolEnd(output: unknown, runId: string): void;\n handleToolError(error: unknown, runId: string): void;\n}\n\nexport interface LangChainObserverOptions {\n /**\n * Decide whether a chain run becomes an `invoke_agent` span. The default\n * skips LangChain/LangGraph plumbing (Runnable*, ChannelWrite, Branch,\n * prompts, `__start__`/`__end__`) so only the graph and its named nodes show;\n * children of a skipped chain reparent to the nearest kept ancestor.\n */\n skipChain?: (name: string) => boolean;\n}\n\n/** LangChain/LangGraph structural runnables that are noise, not real steps. */\nconst PLUMBING_CHAIN = /^(Runnable|Channel|Branch|Prompt|ChatPrompt|__)/i;\n\nexport function createLangChainObserver(\n observe: GenAiObserver,\n options: LangChainObserverOptions = {},\n): LangChainObserverHandler {\n const skipChain = options.skipChain ?? ((name) => PLUMBING_CHAIN.test(name));\n // Remember each LLM run's request model so `chat.end` can price it — the end\n // payload doesn't reliably carry the model. Cleared on end and error.\n const models = new Map<string, string>();\n // Skipped chains record the kept ancestor their children should parent to.\n const reparented = new Map<string, string | undefined>();\n\n /** The span id a child of `runId` should use as its parent. */\n function keptParent(runId: string | undefined): string | undefined {\n if (runId === undefined) return undefined;\n return reparented.has(runId) ? reparented.get(runId) : runId;\n }\n\n function startChat(\n llm: Serialized,\n runId: string,\n parentRunId: string | undefined,\n extraParams: Record<string, unknown> | undefined,\n ): void {\n const model = langChainModel(llm, extraParams);\n if (model) models.set(runId, model);\n observe({\n type: 'chat.start',\n id: runId,\n parentId: keptParent(parentRunId),\n request: { provider: langChainProvider(llm), model },\n });\n }\n\n return {\n handleChainStart(\n chain,\n _inputs,\n runId,\n parentRunId,\n _tags,\n _metadata,\n _runType,\n runName,\n ) {\n const name = runName ?? lastIdSegment(chain) ?? 'chain';\n const parentId = keptParent(parentRunId);\n if (skipChain(name)) {\n reparented.set(runId, parentId);\n return;\n }\n observe({ type: 'agent.start', id: runId, parentId, agent: { name } });\n },\n handleChainEnd(_outputs, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId });\n },\n handleChainError(error, runId) {\n if (reparented.delete(runId)) return;\n observe({ type: 'agent.end', id: runId, error });\n },\n\n handleLLMStart(llm, _prompts, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleChatModelStart(llm, _messages, runId, parentRunId, extraParams) {\n startChat(llm, runId, parentRunId, extraParams);\n },\n handleLLMEnd(output, runId) {\n const costModel = models.get(runId);\n models.delete(runId);\n observe({\n type: 'chat.end',\n id: runId,\n response: { finishReasons: finishReasons(output) },\n usage: langChainUsage(output),\n costModel,\n });\n },\n handleLLMError(error, runId) {\n models.delete(runId);\n observe({ type: 'chat.end', id: runId, error });\n },\n\n handleToolStart(\n tool,\n input,\n runId,\n parentRunId,\n _tags,\n _metadata,\n runName,\n ) {\n observe({\n type: 'tool.start',\n id: runId,\n parentId: keptParent(parentRunId),\n tool: { name: runName ?? lastIdSegment(tool) },\n callArguments: input,\n });\n },\n handleToolEnd(output, runId) {\n observe({ type: 'tool.end', id: runId, callResult: output });\n },\n handleToolError(error, runId) {\n observe({ type: 'tool.end', id: runId, error });\n },\n };\n}\n\n/**\n * Provider from a `Serialized.id` path. LangChain serializes a chat model as\n * `[...namespace, provider, ClassName]` (e.g. `…, 'openai', 'ChatOpenAI'`), so\n * the provider is the lowercase module segment before the PascalCase class.\n */\nfunction langChainProvider(serialized: Serialized): string | undefined {\n const candidate = serialized.id?.at(-2);\n return candidate && candidate === candidate.toLowerCase()\n ? normalizeAiSdkProvider(candidate)\n : undefined;\n}\n\nfunction langChainModel(\n serialized: Serialized,\n extraParams: Record<string, unknown> | undefined,\n): string | undefined {\n const invocation = asRecord(extraParams?.invocation_params);\n return (\n str(invocation?.model) ??\n str(invocation?.model_name) ??\n str(serialized.kwargs?.model) ??\n str(serialized.kwargs?.model_name)\n );\n}\n\nfunction langChainUsage(output: LLMResult): TokenUsage | undefined {\n // `usage_metadata` is LangChain's canonical cross-provider shape (Ollama,\n // newer integrations); `llmOutput.tokenUsage` is OpenAI; `llmOutput.usage`\n // is Anthropic-style.\n const usageMetadata = asRecord(\n firstGeneration(output)?.message?.usage_metadata,\n );\n const tokenUsage = asRecord(output.llmOutput?.tokenUsage);\n const usage = asRecord(output.llmOutput?.usage);\n const inputTokens =\n num(usageMetadata?.input_tokens) ??\n num(tokenUsage?.promptTokens) ??\n num(usage?.input_tokens);\n const outputTokens =\n num(usageMetadata?.output_tokens) ??\n num(tokenUsage?.completionTokens) ??\n num(usage?.output_tokens);\n if (inputTokens === undefined && outputTokens === undefined) return undefined;\n return { inputTokens, outputTokens };\n}\n\nfunction finishReasons(output: LLMResult): string[] | undefined {\n const generation = firstGeneration(output);\n const reason =\n str(generation?.generationInfo?.finish_reason) ??\n str(asRecord(generation?.message?.response_metadata)?.done_reason);\n return reason ? [reason] : undefined;\n}\n\nfunction firstGeneration(output: LLMResult): LLMGeneration | undefined {\n return output.generations?.[0]?.[0];\n}\n\nfunction lastIdSegment(serialized: Serialized): string | undefined {\n return serialized.name ?? serialized.id?.at(-1);\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\nfunction str(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction num(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value)\n ? value\n : undefined;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-genai",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Gold-standard OpenTelemetry GenAI semantic-convention instrumentation for LLM calls, tools, and agents — cost, tokens, metrics, events, and agent governance.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -84,8 +84,8 @@
84
84
  "author": "Jag Reehal <jag@jagreehal.com> (https://jagreehal.com)",
85
85
  "license": "MIT",
86
86
  "dependencies": {
87
- "autotel": "4.2.3",
88
- "autotel-audit": "0.4.3"
87
+ "autotel": "4.2.4",
88
+ "autotel-audit": "0.4.4"
89
89
  },
90
90
  "peerDependencies": {
91
91
  "@opentelemetry/api": "*",
@@ -100,15 +100,15 @@
100
100
  "@opentelemetry/api": "^1.9.1",
101
101
  "@opentelemetry/sdk-metrics": "^2.7.1",
102
102
  "@opentelemetry/sdk-trace-base": "^2.7.1",
103
- "@types/node": "^25.9.2",
104
- "@typescript-eslint/eslint-plugin": "^8.60.1",
105
- "@typescript-eslint/parser": "^8.60.1",
103
+ "@types/node": "^26.1.0",
104
+ "@typescript-eslint/eslint-plugin": "^8.63.0",
105
+ "@typescript-eslint/parser": "^8.63.0",
106
106
  "eslint-config-prettier": "^10.1.8",
107
107
  "eslint-plugin-unicorn": "^64.0.0",
108
- "tsdown": "^0.22.2",
108
+ "tsdown": "^0.22.3",
109
109
  "typescript": "^6.0.3",
110
- "typescript-eslint": "^8.60.1",
111
- "vitest": "^4.1.8"
110
+ "typescript-eslint": "^8.63.0",
111
+ "vitest": "^4.1.10"
112
112
  },
113
113
  "repository": {
114
114
  "type": "git",