@sentrial/sdk 0.4.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +357 -41
- package/dist/cli.cjs +6199 -107
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +4 -46
- package/dist/cli.d.ts +4 -46
- package/dist/cli.js +6260 -163
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +4955 -1060
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +949 -527
- package/dist/index.d.ts +949 -527
- package/dist/index.js +4939 -1060
- package/dist/index.js.map +1 -1
- package/dist/types-BEf8OATP.d.cts +612 -0
- package/dist/types-BEf8OATP.d.ts +612 -0
- package/package.json +23 -12
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/redact.ts","../src/async-context.ts","../src/cost.ts","../src/wrappers.ts","../src/batcher.ts","../src/types.ts","../src/client.ts","../src/vercel.ts","../src/claude-code.ts","../src/decorators.ts","../src/context.ts","../src/experiment.ts"],"sourcesContent":["/**\n * Sentrial SDK for TypeScript/Node.js\n *\n * AI agent observability and monitoring. Track sessions, tool calls, and metrics\n * to power signal detection, root cause analysis, and AI-suggested fixes.\n *\n * @packageDocumentation\n *\n * @example Basic Usage\n * ```ts\n * import { SentrialClient } from '@sentrial/sdk';\n *\n * const client = new SentrialClient({\n * apiKey: process.env.SENTRIAL_API_KEY!,\n * });\n *\n * // Create session\n * const sessionId = await client.createSession({\n * name: 'Support Request',\n * agentName: 'support-agent',\n * userId: 'user_123',\n * });\n *\n * // Track tool calls\n * await client.trackToolCall({\n * sessionId,\n * toolName: 'search_kb',\n * toolInput: { query: 'help' },\n * toolOutput: { results: ['...'] },\n * });\n *\n * // Complete session\n * await client.completeSession({\n * sessionId,\n * success: true,\n * customMetrics: { satisfaction: 4.5 },\n * });\n * ```\n *\n * @example Simple begin/finish Pattern\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const interaction = await sentrial.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * });\n *\n * // ... agent work ...\n *\n * await interaction.finish({ output: response, success: true });\n * ```\n */\n\n// Main client\nexport { SentrialClient, Interaction, configure, begin, getVariant, sentrial } from './client.js';\n\n// Types\nexport type {\n SentrialClientConfig,\n CreateSessionParams,\n CompleteSessionParams,\n TrackToolCallParams,\n TrackDecisionParams,\n TrackErrorParams,\n BeginParams,\n FinishParams,\n GetVariantParams,\n VariantAssignment,\n ProductionVariantConfig,\n Session,\n Event,\n SessionStatus,\n CostParams,\n ApiResponse,\n} from './types.js';\n\n// Enums\nexport { EventType } from './types.js';\n\n// Cost calculation utilities\nexport { calculateOpenAICost, calculateAnthropicCost, calculateGoogleCost } from './cost.js';\n\n// Errors\nexport { SentrialError, ApiError, NetworkError, ValidationError } from './errors.js';\n\n// Vercel AI SDK Integration\nexport { wrapAISDK, configureVercel } from './vercel.js';\n\nexport type { GenerateTextParams, GenerateTextResult, StreamTextResult } from './vercel.js';\n\n// Claude Agent SDK Integration\nexport { wrapClaudeAgent } from './claude-code.js';\nexport type { WrapClaudeAgentOptions } from './claude-code.js';\n\n// LLM Auto-Wrappers\nexport {\n wrapOpenAI,\n wrapAnthropic,\n wrapGoogle,\n wrapLLM,\n setSessionContext,\n clearSessionContext,\n getSessionContext,\n setDefaultClient,\n} from './wrappers.js';\n\n// Decorators & Higher-Order Functions\nexport {\n withTool,\n withSession,\n Tool,\n TrackSession,\n SessionContext,\n setClient,\n getCurrentSessionId,\n getCurrentInteraction,\n} from './decorators.js';\n\n// Experiment Context\nexport {\n getSystemPrompt,\n getExperimentContext,\n isExperimentMode,\n getVariantName,\n getExperimentId,\n setExperimentContext,\n clearExperimentContext,\n} from './context.js';\n\nexport type { ExperimentContext } from './context.js';\n\n// Experiments\nexport { Experiment, ExperimentRunTracker } from './experiment.js';\n\nexport type {\n ExperimentVariant,\n ExperimentTestCase,\n ExperimentRunResult,\n ExperimentResults,\n} from './experiment.js';\n\n// Event Batching\nexport { EventBatcher } from './batcher.js';\nexport type { BatcherConfig } from './batcher.js';\n\n// Async Context (for advanced concurrency-safe usage)\nexport { createContextVar } from './async-context.js';\nexport type { ContextVar, ContextToken } from './async-context.js';\n\n// PII Redaction\nexport { redactString, redactValue, redactPayload, hashValue, replaceMatch } from './redact.js';\n\nexport type {\n PiiMode,\n PiiField,\n PiiCustomPattern,\n PiiBuiltinPatterns,\n PiiConfig,\n} from './redact.js';\n","/**\n * Error classes for the Sentrial SDK\n */\n\n/**\n * Base error class for all Sentrial SDK errors\n */\nexport class SentrialError extends Error {\n /** HTTP status code (if applicable) */\n public readonly status?: number;\n /** Error code from the API */\n public readonly code?: string;\n /** Additional error details */\n public readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n options?: {\n status?: number;\n code?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n }\n ) {\n super(message, { cause: options?.cause });\n this.name = 'SentrialError';\n this.status = options?.status;\n this.code = options?.code;\n this.details = options?.details;\n\n // Maintain proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SentrialError);\n }\n }\n\n /**\n * Check if the error is a rate limit error\n */\n isRateLimitError(): boolean {\n return this.status === 429;\n }\n\n /**\n * Check if the error is an authentication error\n */\n isAuthError(): boolean {\n return this.status === 401 || this.status === 403;\n }\n\n /**\n * Check if the error is a network/connection error\n */\n isNetworkError(): boolean {\n return this.code === 'NETWORK_ERROR' || this.code === 'ECONNREFUSED';\n }\n\n /**\n * Convert to a plain object for logging\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n details: this.details,\n stack: this.stack,\n };\n }\n}\n\n/**\n * Error thrown when the API returns an error response\n */\nexport class ApiError extends SentrialError {\n constructor(\n message: string,\n status: number,\n code?: string,\n details?: Record<string, unknown>\n ) {\n super(message, { status, code, details });\n this.name = 'ApiError';\n }\n}\n\n/**\n * Error thrown when there's a network/connection issue\n */\nexport class NetworkError extends SentrialError {\n constructor(message: string, cause?: Error) {\n super(message, { code: 'NETWORK_ERROR', cause });\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Error thrown when validation fails\n */\nexport class ValidationError extends SentrialError {\n constructor(message: string, details?: Record<string, unknown>) {\n super(message, { code: 'VALIDATION_ERROR', details });\n this.name = 'ValidationError';\n }\n}\n","/**\n * PII Redaction Module\n *\n * Automatically detects and redacts personally identifiable information (PII)\n * from SDK payloads before they are sent to the Sentrial API.\n */\n\n// Lazy-load Node.js crypto to avoid crashing in browser/edge runtimes.\n// Uses Function to prevent bundlers from resolving the import at\n// build time (same pattern as async-context.ts for node:async_hooks).\nlet _createHash: typeof import('crypto').createHash | undefined;\n\ntry {\n // eslint-disable-next-line @typescript-eslint/no-implied-eval\n const mod = new Function('return require(\"crypto\")')();\n if (mod?.createHash) {\n _createHash = mod.createHash;\n }\n} catch {\n // Not in Node.js — hash mode will throw at call time.\n}\n\nfunction getCreateHash(): typeof import('crypto').createHash {\n if (!_createHash) {\n throw new Error(\n 'Sentrial PII hash mode requires Node.js crypto module. ' +\n 'Use mode \"label\" or \"remove\" in browser/edge environments.'\n );\n }\n return _createHash;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** How redacted values are replaced */\nexport type PiiMode = 'label' | 'hash' | 'remove';\n\n/** Fields that can be redacted in SDK payloads */\nexport type PiiField =\n | 'userInput'\n | 'assistantOutput'\n | 'toolInput'\n | 'toolOutput'\n | 'metadata'\n | 'reasoning'\n | 'failureReason';\n\n/** A custom regex pattern with a human-readable label */\nexport interface PiiCustomPattern {\n pattern: RegExp;\n label: string;\n}\n\n/** Toggle individual builtin pattern categories */\nexport interface PiiBuiltinPatterns {\n emails?: boolean;\n phones?: boolean;\n ssns?: boolean;\n creditCards?: boolean;\n ipAddresses?: boolean;\n}\n\n/** Full PII redaction configuration */\nexport interface PiiConfig {\n /** Enable PII redaction (default: false) */\n enabled: boolean;\n /** Which payload fields to redact (default: DEFAULT_FIELDS) */\n fields?: PiiField[];\n /** Replacement mode (default: 'label') */\n mode?: PiiMode;\n /** Enable enhanced detection heuristics (reserved for future use) */\n enhancedDetection?: boolean;\n /** Toggle builtin patterns (default: all enabled) */\n builtinPatterns?: PiiBuiltinPatterns;\n /** Additional custom patterns to apply */\n customPatterns?: PiiCustomPattern[];\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default fields that are redacted when PII redaction is enabled */\nexport const DEFAULT_FIELDS: PiiField[] = [\n 'userInput',\n 'assistantOutput',\n 'toolInput',\n 'toolOutput',\n];\n\n/** Default builtin pattern toggles (all enabled) */\nexport const DEFAULT_BUILTIN: Required<PiiBuiltinPatterns> = {\n emails: true,\n phones: true,\n ssns: true,\n creditCards: true,\n ipAddresses: true,\n};\n\n// ============================================================================\n// Builtin Regex Patterns\n// ============================================================================\n\n/** Email addresses (RFC 5322 simplified) */\nconst EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/** US phone numbers: (###) ###-####, ###-###-####, ### ### ####, +1########## */\nconst PHONE_PATTERN =\n /(?:\\+?1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}\\b/g;\n\n/** US Social Security Numbers: ###-##-#### */\nconst SSN_PATTERN = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\n\n/** Credit card numbers: 13-19 digits, optionally separated by spaces or dashes */\nconst CREDIT_CARD_PATTERN = /\\b(?:\\d[-\\s]?){13,19}\\b/g;\n\n/** IPv4 addresses */\nconst IP_ADDRESS_PATTERN =\n /\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\b/g;\n\n/** Map of builtin pattern keys to their regex and label */\nconst BUILTIN_PATTERNS: Record<keyof Required<PiiBuiltinPatterns>, { pattern: RegExp; label: string }> = {\n emails: { pattern: EMAIL_PATTERN, label: 'EMAIL' },\n phones: { pattern: PHONE_PATTERN, label: 'PHONE' },\n ssns: { pattern: SSN_PATTERN, label: 'SSN' },\n creditCards: { pattern: CREDIT_CARD_PATTERN, label: 'CREDIT_CARD' },\n ipAddresses: { pattern: IP_ADDRESS_PATTERN, label: 'IP_ADDRESS' },\n};\n\n// ============================================================================\n// Core Functions\n// ============================================================================\n\n/**\n * Hash a value using SHA-256 and return the first 6 hex characters.\n * Used in 'hash' mode to produce a short, deterministic pseudonym.\n */\nexport function hashValue(value: string): string {\n return getCreateHash()('sha256').update(value).digest('hex').slice(0, 6);\n}\n\n/**\n * Produce the replacement string for a matched PII value.\n *\n * - 'label' mode: [LABEL]\n * - 'hash' mode: [PII:abcdef]\n * - 'remove' mode: '' (empty string)\n */\nexport function replaceMatch(match: string, label: string, mode: PiiMode): string {\n switch (mode) {\n case 'label':\n return `[${label}]`;\n case 'hash':\n return `[PII:${hashValue(match)}]`;\n case 'remove':\n return '';\n }\n}\n\n/**\n * Run all enabled builtin and custom patterns against a single string,\n * replacing every match according to the specified mode.\n */\nexport function redactString(\n value: string,\n mode: PiiMode,\n builtinPatterns: Required<PiiBuiltinPatterns>,\n customPatterns: PiiCustomPattern[],\n): string {\n let result = value;\n\n // Apply enabled builtin patterns\n for (const [key, config] of Object.entries(BUILTIN_PATTERNS)) {\n if (builtinPatterns[key as keyof PiiBuiltinPatterns]) {\n // Create a fresh regex instance to reset lastIndex\n const regex = new RegExp(config.pattern.source, config.pattern.flags);\n result = result.replace(regex, (match) => replaceMatch(match, config.label, mode));\n }\n }\n\n // Apply custom patterns\n for (const custom of customPatterns) {\n const regex = new RegExp(custom.pattern.source, custom.pattern.flags);\n result = result.replace(regex, (match) => replaceMatch(match, custom.label, mode));\n }\n\n return result;\n}\n\n/**\n * Recursively redact PII from a value. Handles strings, arrays, plain objects,\n * and passes through primitives (numbers, booleans, null, undefined) unchanged.\n */\nexport function redactValue(\n value: unknown,\n mode: PiiMode,\n builtinPatterns: Required<PiiBuiltinPatterns>,\n customPatterns: PiiCustomPattern[],\n): unknown {\n if (typeof value === 'string') {\n return redactString(value, mode, builtinPatterns, customPatterns);\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => redactValue(item, mode, builtinPatterns, customPatterns));\n }\n\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n result[key] = redactValue(val, mode, builtinPatterns, customPatterns);\n }\n return result;\n }\n\n // Primitives (number, boolean, null, undefined) pass through unchanged\n return value;\n}\n\n/**\n * Main entry point: redact configured fields in a payload object.\n *\n * Only fields listed in the PiiConfig (or DEFAULT_FIELDS) are redacted.\n * Other fields are returned unchanged.\n *\n * @param payload - The data payload (e.g., session params, tool call params)\n * @param config - PII configuration from the client\n * @returns A new payload object with PII redacted from the configured fields\n */\nexport function redactPayload(\n payload: Record<string, unknown>,\n config: PiiConfig,\n): Record<string, unknown> {\n if (!config.enabled) {\n return payload;\n }\n\n const mode = config.mode ?? 'label';\n const fields = config.fields ?? DEFAULT_FIELDS;\n const builtinPatterns: Required<PiiBuiltinPatterns> = {\n ...DEFAULT_BUILTIN,\n ...config.builtinPatterns,\n };\n const customPatterns = config.customPatterns ?? [];\n\n const result: Record<string, unknown> = { ...payload };\n\n for (const field of fields) {\n if (field in result && result[field] !== undefined && result[field] !== null) {\n result[field] = redactValue(result[field], mode, builtinPatterns, customPatterns);\n }\n }\n\n return result;\n}\n","/**\n * Async-safe context variables for the Sentrial SDK.\n *\n * In Node.js (>= 18): uses AsyncLocalStorage for true per-request isolation.\n * In browsers: falls back to a simple variable (safe because browsers have no\n * concurrent request handling within a single page).\n *\n * This is the TypeScript equivalent of Python's `contextvars.ContextVar`.\n */\n\n// ============================================================================\n// Public Interface\n// ============================================================================\n\nexport interface ContextToken<T> {\n /** @internal */\n readonly _previous: T;\n}\n\nexport interface ContextVar<T> {\n /** Get the current value (returns defaultValue if unset in this context). */\n get(): T;\n /** Set a new value. Returns a token that can restore the previous value. */\n set(value: T): ContextToken<T>;\n /** Restore the value that was active before the given token's set() call. */\n reset(token: ContextToken<T>): void;\n}\n\n// ============================================================================\n// Detect AsyncLocalStorage availability\n// ============================================================================\n\nlet _AsyncLocalStorage: any = null;\n\ntry {\n // Use Function to prevent bundlers (webpack, esbuild, vite) from trying to\n // resolve node:async_hooks when building for browser targets.\n // eslint-disable-next-line @typescript-eslint/no-implied-eval\n const mod = new Function('return require(\"node:async_hooks\")')();\n if (mod?.AsyncLocalStorage) {\n _AsyncLocalStorage = mod.AsyncLocalStorage;\n }\n} catch {\n // Not in Node.js or async_hooks unavailable — fall back to simple vars.\n}\n\n// ============================================================================\n// Node.js Implementation (AsyncLocalStorage-backed)\n// ============================================================================\n\nclass NodeContextVar<T> implements ContextVar<T> {\n private readonly _storage: InstanceType<typeof _AsyncLocalStorage>;\n private readonly _defaultValue: T;\n\n constructor(defaultValue: T) {\n this._storage = new _AsyncLocalStorage();\n this._defaultValue = defaultValue;\n }\n\n get(): T {\n const store = this._storage.getStore();\n return store !== undefined ? (store as T) : this._defaultValue;\n }\n\n set(value: T): ContextToken<T> {\n const previous = this.get();\n this._storage.enterWith(value);\n return { _previous: previous };\n }\n\n reset(token: ContextToken<T>): void {\n this._storage.enterWith(token._previous);\n }\n}\n\n// ============================================================================\n// Browser Fallback (simple variable — no concurrency in single-tab context)\n// ============================================================================\n\nclass SimpleContextVar<T> implements ContextVar<T> {\n private _value: T;\n\n constructor(defaultValue: T) {\n this._value = defaultValue;\n }\n\n get(): T {\n return this._value;\n }\n\n set(value: T): ContextToken<T> {\n const previous = this._value;\n this._value = value;\n return { _previous: previous };\n }\n\n reset(token: ContextToken<T>): void {\n this._value = token._previous;\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create an async-safe context variable.\n *\n * Uses AsyncLocalStorage in Node.js for per-request isolation,\n * falls back to a simple variable in browsers.\n *\n * @param defaultValue - Value returned by get() when nothing has been set.\n */\nexport function createContextVar<T>(defaultValue: T): ContextVar<T> {\n if (_AsyncLocalStorage) {\n return new NodeContextVar(defaultValue);\n }\n return new SimpleContextVar(defaultValue);\n}\n","/**\n * Cost calculation utilities for various LLM providers\n */\n\nimport type { CostParams } from './types.js';\n\n// ============================================================================\n// Pricing Data (per 1M tokens) - Updated Jan 2026\n// ============================================================================\n\nconst OPENAI_PRICING: Record<string, { input: number; output: number }> = {\n 'gpt-5.2': { input: 5.0, output: 15.0 },\n 'gpt-5': { input: 4.0, output: 12.0 },\n 'gpt-4.1': { input: 2.0, output: 8.0 },\n 'gpt-4.1-mini': { input: 0.4, output: 1.6 },\n 'gpt-4.1-nano': { input: 0.1, output: 0.4 },\n 'gpt-4o': { input: 2.5, output: 10.0 },\n 'gpt-4o-mini': { input: 0.15, output: 0.6 },\n 'gpt-4-turbo': { input: 10.0, output: 30.0 },\n 'gpt-4': { input: 30.0, output: 60.0 },\n 'gpt-3.5-turbo': { input: 0.5, output: 1.5 },\n 'o4-mini': { input: 1.1, output: 4.4 },\n 'o3': { input: 10.0, output: 40.0 },\n 'o3-mini': { input: 1.1, output: 4.4 },\n 'o3-pro': { input: 20.0, output: 80.0 },\n 'o1': { input: 15.0, output: 60.0 },\n 'o1-preview': { input: 15.0, output: 60.0 },\n 'o1-mini': { input: 3.0, output: 12.0 },\n};\n\nconst ANTHROPIC_PRICING: Record<string, { input: number; output: number }> = {\n 'claude-opus-4': { input: 15.0, output: 75.0 },\n 'claude-sonnet-4': { input: 3.0, output: 15.0 },\n 'claude-4.5-opus': { input: 20.0, output: 100.0 },\n 'claude-4.5-sonnet': { input: 4.0, output: 20.0 },\n 'claude-4-opus': { input: 18.0, output: 90.0 },\n 'claude-4-sonnet': { input: 3.5, output: 17.5 },\n 'claude-3-7-sonnet': { input: 3.0, output: 15.0 },\n 'claude-3-5-sonnet': { input: 3.0, output: 15.0 },\n 'claude-3-5-haiku': { input: 0.8, output: 4.0 },\n 'claude-3-opus': { input: 15.0, output: 75.0 },\n 'claude-3-sonnet': { input: 3.0, output: 15.0 },\n 'claude-3-haiku': { input: 0.25, output: 1.25 },\n};\n\nconst GOOGLE_PRICING: Record<string, { input: number; output: number }> = {\n // Gemini 3 - Preview (Jan 2026)\n 'gemini-3-pro': { input: 2.0, output: 12.0 },\n 'gemini-3-flash': { input: 0.5, output: 3.0 },\n // Gemini 2.5\n 'gemini-2.5-pro': { input: 1.25, output: 5.0 },\n 'gemini-2.5-flash': { input: 0.15, output: 0.6 },\n // Gemini 2.0\n 'gemini-2.0-flash': { input: 0.1, output: 0.4 },\n 'gemini-2.0-flash-lite': { input: 0.075, output: 0.3 },\n // Gemini 1.5\n 'gemini-1.5-pro': { input: 1.25, output: 5.0 },\n 'gemini-1.5-flash': { input: 0.075, output: 0.3 },\n // Gemini 1.0\n 'gemini-1.0-pro': { input: 0.5, output: 1.5 },\n};\n\n// ============================================================================\n// Helper Function\n// ============================================================================\n\nfunction findModelKey(\n model: string,\n pricing: Record<string, { input: number; output: number }>\n): string | null {\n // Sort by length descending so longer (more specific) keys match first.\n // e.g. \"o3-mini\" must match before \"o3\", \"gpt-4-turbo\" before \"gpt-4\".\n const keys = Object.keys(pricing).sort((a, b) => b.length - a.length);\n for (const key of keys) {\n if (model.startsWith(key)) {\n return key;\n }\n }\n return null;\n}\n\nfunction calculateCost(\n inputTokens: number,\n outputTokens: number,\n rates: { input: number; output: number }\n): number {\n const inputCost = (inputTokens / 1_000_000) * rates.input;\n const outputCost = (outputTokens / 1_000_000) * rates.output;\n return inputCost + outputCost;\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Calculate cost for OpenAI API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateOpenAICost({\n * model: 'gpt-4o',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateOpenAICost(params: CostParams): number {\n const { model, inputTokens, outputTokens } = params;\n const modelKey = findModelKey(model, OPENAI_PRICING) ?? 'gpt-4';\n return calculateCost(inputTokens, outputTokens, OPENAI_PRICING[modelKey]!);\n}\n\n/**\n * Calculate cost for Anthropic API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateAnthropicCost({\n * model: 'claude-3-5-sonnet',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateAnthropicCost(params: CostParams): number {\n const { model, inputTokens, outputTokens } = params;\n const modelKey = findModelKey(model, ANTHROPIC_PRICING) ?? 'claude-3-sonnet';\n return calculateCost(inputTokens, outputTokens, ANTHROPIC_PRICING[modelKey]!);\n}\n\n/**\n * Calculate cost for Google/Gemini API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateGoogleCost({\n * model: 'gemini-2.0-flash',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateGoogleCost(params: CostParams): number {\n const { model, inputTokens, outputTokens } = params;\n const modelKey = findModelKey(model, GOOGLE_PRICING) ?? 'gemini-2.0-flash';\n return calculateCost(inputTokens, outputTokens, GOOGLE_PRICING[modelKey]!);\n}\n","/**\n * Sentrial LLM Wrappers - Auto-instrument LLM provider SDKs\n *\n * These wrappers automatically track all LLM calls with:\n * - Input messages\n * - Output responses\n * - Token counts\n * - Cost estimation\n * - Latency\n *\n * @example OpenAI\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapOpenAI, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const openai = wrapOpenAI(new OpenAI());\n *\n * // All calls are now automatically tracked!\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n *\n * @example Anthropic\n * ```ts\n * import Anthropic from '@anthropic-ai/sdk';\n * import { wrapAnthropic, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const anthropic = wrapAnthropic(new Anthropic());\n *\n * const response = await anthropic.messages.create({\n * model: 'claude-3-5-sonnet-20241022',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\n\nimport { SentrialClient } from './client.js';\nimport { calculateOpenAICost, calculateAnthropicCost, calculateGoogleCost } from './cost.js';\nimport { createContextVar, type ContextToken } from './async-context.js';\n\n// ============================================================================\n// Session Context (async-safe via AsyncLocalStorage in Node.js,\n// simple variable fallback in browsers)\n// ============================================================================\n\nconst _currentSessionId = createContextVar<string | null>(null);\nconst _currentClient = createContextVar<SentrialClient | null>(null);\nlet _defaultClient: SentrialClient | null = null;\n\n/** Tokens for restoring previous session context (used internally by decorators). */\nexport interface SessionContextTokens {\n _sessionToken: ContextToken<string | null>;\n _clientToken: ContextToken<SentrialClient | null>;\n}\n\n/**\n * Set the current session context for auto-tracking.\n *\n * Call this before making LLM calls to associate them with a session.\n * This is automatically called when using withSession() or decorators.\n *\n * @param sessionId - The session ID to track LLM calls under\n * @param client - Optional Sentrial client (uses default if not provided)\n */\nexport function setSessionContext(sessionId: string, client?: SentrialClient): void {\n _currentSessionId.set(sessionId);\n if (client) {\n _currentClient.set(client);\n }\n}\n\n/**\n * Clear the current session context.\n */\nexport function clearSessionContext(): void {\n _currentSessionId.set(null);\n _currentClient.set(null);\n}\n\n/**\n * Get the current session ID.\n */\nexport function getSessionContext(): string | null {\n return _currentSessionId.get();\n}\n\n/**\n * Set the default client for wrappers.\n */\nexport function setDefaultClient(client: SentrialClient): void {\n _defaultClient = client;\n}\n\n/**\n * Get the default client configured for wrappers.\n * @internal Used by decorators to share sentrial.configure().\n */\nexport function getDefaultClient(): SentrialClient | null {\n return _defaultClient;\n}\n\n/**\n * Set session context and return tokens for restoring the previous values.\n * @internal Used by decorators for proper nesting support.\n */\nexport function _setSessionContextWithTokens(\n sessionId: string,\n client?: SentrialClient,\n): SessionContextTokens {\n const _sessionToken = _currentSessionId.set(sessionId);\n const _clientToken = client\n ? _currentClient.set(client)\n : _currentClient.set(_currentClient.get()); // no-op token preserving current\n return { _sessionToken, _clientToken };\n}\n\n/**\n * Restore session context from tokens returned by _setSessionContextWithTokens.\n * @internal Used by decorators for proper nesting support.\n */\nexport function _restoreSessionContext(tokens: SessionContextTokens): void {\n _currentSessionId.reset(tokens._sessionToken);\n _currentClient.reset(tokens._clientToken);\n}\n\nfunction getTrackingClient(): SentrialClient | null {\n return _currentClient.get() ?? _defaultClient;\n}\n\n// ============================================================================\n// OpenAI Wrapper\n// ============================================================================\n\n/**\n * Wrap an OpenAI client to automatically track all LLM calls.\n *\n * @param client - OpenAI client instance\n * @param options - Wrapper options\n * @returns The same client, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapOpenAI, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n * const openai = wrapOpenAI(new OpenAI());\n *\n * // Now use client normally - all calls are tracked!\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello' }],\n * });\n * ```\n */\nexport function wrapOpenAI<T extends object>(\n client: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access chat.completions.create\n const chat = (client as any).chat;\n if (!chat?.completions?.create) {\n console.warn('Sentrial: OpenAI client does not have chat.completions.create');\n return client;\n }\n\n const originalCreate = chat.completions.create.bind(chat.completions);\n\n chat.completions.create = async function (...args: any[]) {\n const startTime = Date.now();\n const params = args[0] ?? {};\n const messages = params.messages ?? [];\n const model = params.model ?? 'unknown';\n const isStreaming = params.stream === true;\n\n // Auto-inject stream_options.include_usage so streaming calls report token counts\n if (isStreaming && !params.stream_options?.include_usage) {\n args[0] = { ...params, stream_options: { ...params.stream_options, include_usage: true } };\n }\n\n try {\n const response = await originalCreate(...args);\n\n if (isStreaming) {\n return wrapOpenAIStream(response, { startTime, messages, model, trackWithoutSession });\n }\n\n const durationMs = Date.now() - startTime;\n\n // Extract token usage\n const promptTokens = response.usage?.prompt_tokens ?? 0;\n const completionTokens = response.usage?.completion_tokens ?? 0;\n const totalTokens = response.usage?.total_tokens ?? 0;\n\n // Extract output text + tool calls\n let outputContent = '';\n const toolCalls: Array<{ name: string; arguments: string }> = [];\n const msg = response.choices?.[0]?.message;\n if (msg?.content) {\n outputContent = msg.content;\n }\n if (msg?.tool_calls) {\n for (const tc of msg.tool_calls) {\n toolCalls.push({\n name: tc.function?.name ?? 'unknown',\n arguments: tc.function?.arguments ?? '{}',\n });\n }\n }\n\n // Calculate cost\n const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });\n\n // Track the call\n trackLLMCall({\n provider: 'openai',\n model,\n messages,\n output: outputContent,\n toolCalls,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n });\n\n return response;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'openai',\n model,\n messages,\n error: error as Error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n }\n };\n\n return client;\n}\n\n// ============================================================================\n// Anthropic Wrapper\n// ============================================================================\n\n/**\n * Wrap an Anthropic client to automatically track all LLM calls.\n *\n * @param client - Anthropic client instance\n * @param options - Wrapper options\n * @returns The same client, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import Anthropic from '@anthropic-ai/sdk';\n * import { wrapAnthropic, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n * const anthropic = wrapAnthropic(new Anthropic());\n *\n * const response = await anthropic.messages.create({\n * model: 'claude-3-5-sonnet-20241022',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport function wrapAnthropic<T extends object>(\n client: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access messages.create\n const messages = (client as any).messages;\n if (!messages?.create) {\n console.warn('Sentrial: Anthropic client does not have messages.create');\n return client;\n }\n\n const originalCreate = messages.create.bind(messages);\n\n messages.create = async function (...args: any[]) {\n const startTime = Date.now();\n const params = args[0] ?? {};\n const inputMessages = params.messages ?? [];\n const model = params.model ?? 'unknown';\n const system = params.system ?? '';\n const isStreaming = params.stream === true;\n\n try {\n const response = await originalCreate(...args);\n\n if (isStreaming) {\n return wrapAnthropicStream(response, {\n startTime, messages: inputMessages, model, system, trackWithoutSession,\n });\n }\n\n const durationMs = Date.now() - startTime;\n\n // Anthropic uses input_tokens/output_tokens\n const promptTokens = response.usage?.input_tokens ?? 0;\n const completionTokens = response.usage?.output_tokens ?? 0;\n const totalTokens = promptTokens + completionTokens;\n\n // Extract output text + tool use blocks\n let outputContent = '';\n const toolCalls: Array<{ name: string; arguments: string }> = [];\n if (response.content) {\n for (const block of response.content) {\n if (block.type === 'text') {\n outputContent += block.text;\n } else if (block.type === 'tool_use') {\n toolCalls.push({\n name: block.name ?? 'unknown',\n arguments: JSON.stringify(block.input ?? {}),\n });\n }\n }\n }\n\n const cost = calculateAnthropicCost({ model, inputTokens: promptTokens, outputTokens: completionTokens });\n\n // Include system prompt in messages for tracking\n const fullMessages = system\n ? [{ role: 'system', content: system }, ...inputMessages]\n : inputMessages;\n\n trackLLMCall({\n provider: 'anthropic',\n model,\n messages: fullMessages,\n output: outputContent,\n toolCalls,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n });\n\n return response;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'anthropic',\n model,\n messages: inputMessages,\n error: error as Error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n }\n };\n\n return client;\n}\n\n// ============================================================================\n// Google/Gemini Wrapper\n// ============================================================================\n\n/**\n * Wrap a Google GenerativeModel to automatically track all LLM calls.\n *\n * @param model - Google GenerativeModel instance\n * @param options - Wrapper options\n * @returns The same model, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import { GoogleGenerativeAI } from '@google/generative-ai';\n * import { wrapGoogle, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);\n * const model = wrapGoogle(genAI.getGenerativeModel({ model: 'gemini-2.0-flash' }));\n *\n * const response = await model.generateContent('Hello!');\n * ```\n */\nexport function wrapGoogle<T extends object>(\n model: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access generateContent\n const originalGenerate = (model as any).generateContent;\n if (!originalGenerate) {\n console.warn('Sentrial: Google model does not have generateContent');\n return model;\n }\n\n (model as any).generateContent = async function (...args: any[]) {\n const startTime = Date.now();\n const contents = args[0];\n const modelName = (model as any).model ?? 'gemini-unknown';\n\n // Convert contents to messages format\n const messages = googleContentsToMessages(contents);\n\n try {\n const response = await originalGenerate.apply(model, args);\n const durationMs = Date.now() - startTime;\n\n // Extract usage — generateContent returns { response: GenerateContentResponse }\n // usageMetadata lives on the inner .response, or directly on the result in newer SDK versions\n let promptTokens = 0;\n let completionTokens = 0;\n const usageMeta = response.response?.usageMetadata ?? response.usageMetadata;\n if (usageMeta) {\n promptTokens = usageMeta.promptTokenCount ?? 0;\n completionTokens = usageMeta.candidatesTokenCount ?? 0;\n }\n const totalTokens = promptTokens + completionTokens;\n\n // Extract output — .text() can throw if response was safety-filtered\n let outputContent = '';\n try {\n outputContent = response.response?.text?.() ?? response.text?.() ?? '';\n } catch {\n // text() throws if no text content (e.g. safety filter)\n }\n\n const cost = calculateGoogleCost({ model: modelName, inputTokens: promptTokens, outputTokens: completionTokens });\n\n trackLLMCall({\n provider: 'google',\n model: modelName,\n messages,\n output: outputContent,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n });\n\n return response;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'google',\n model: modelName,\n messages,\n error: error as Error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n }\n };\n\n return model;\n}\n\nfunction googleContentsToMessages(contents: unknown): Array<{ role: string; content: string }> {\n if (typeof contents === 'string') {\n return [{ role: 'user', content: contents }];\n }\n if (Array.isArray(contents)) {\n return contents.map((item) => {\n if (typeof item === 'string') {\n return { role: 'user', content: item };\n }\n if (item && typeof item === 'object') {\n return { role: (item as any).role ?? 'user', content: String((item as any).content ?? item) };\n }\n return { role: 'user', content: String(item) };\n });\n }\n return [{ role: 'user', content: String(contents) }];\n}\n\n// ============================================================================\n// Auto-detect Wrapper\n// ============================================================================\n\n/**\n * Auto-detect and wrap any supported LLM client.\n *\n * @param client - Any supported LLM client (OpenAI, Anthropic, Google)\n * @param provider - Optional provider hint\n * @returns The wrapped client\n *\n * @example\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapLLM } from '@sentrial/sdk';\n *\n * const client = wrapLLM(new OpenAI()); // Auto-detected as OpenAI\n * ```\n */\nexport function wrapLLM<T extends object>(\n client: T,\n provider?: 'openai' | 'anthropic' | 'google'\n): T {\n // Try to auto-detect based on client shape\n if (provider === 'openai' || (client as any).chat?.completions?.create) {\n return wrapOpenAI(client);\n }\n if (provider === 'anthropic' || (client as any).messages?.create) {\n return wrapAnthropic(client);\n }\n if (provider === 'google' || (client as any).generateContent) {\n return wrapGoogle(client);\n }\n\n console.warn('Sentrial: Unknown LLM client type. No auto-tracking applied.');\n return client;\n}\n\n// ============================================================================\n// Stream Wrappers\n// ============================================================================\n\ninterface StreamContext {\n startTime: number;\n messages: unknown[];\n model: string;\n trackWithoutSession: boolean;\n}\n\ninterface AnthropicStreamContext extends StreamContext {\n system: string;\n}\n\n/**\n * Wrap an OpenAI streaming response to track after completion.\n * Preserves the original stream object shape while intercepting the async iterator.\n */\nfunction wrapOpenAIStream(stream: any, ctx: StreamContext): any {\n let fullContent = '';\n let usage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null = null;\n const toolCallMap = new Map<number, { name: string; arguments: string }>();\n let tracked = false;\n\n const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);\n if (!originalIterator) return stream; // Not an async iterable, return as-is\n\n const trackResult = () => {\n if (tracked) return;\n tracked = true;\n const durationMs = Date.now() - ctx.startTime;\n const promptTokens = usage?.prompt_tokens ?? 0;\n const completionTokens = usage?.completion_tokens ?? 0;\n const totalTokens = usage?.total_tokens ?? (promptTokens + completionTokens);\n const cost = calculateOpenAICost({ model: ctx.model, inputTokens: promptTokens, outputTokens: completionTokens });\n\n trackLLMCall({\n provider: 'openai',\n model: ctx.model,\n messages: ctx.messages,\n output: fullContent,\n toolCalls: Array.from(toolCallMap.values()),\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession: ctx.trackWithoutSession,\n });\n };\n\n // Use a Proxy to transparently delegate all property access to the original\n // stream while only overriding [Symbol.asyncIterator]. This avoids the\n // Object.create+Object.assign approach which can't copy JS private fields\n // (e.g. OpenAI's Stream.#client) — calling .tee() on a shallow copy crashes.\n return new Proxy(stream, {\n get(target, prop, receiver) {\n if (prop === Symbol.asyncIterator) {\n return function () {\n const iter = originalIterator();\n return {\n async next() {\n const result = await iter.next();\n if (!result.done) {\n const chunk = result.value;\n const delta = chunk.choices?.[0]?.delta;\n if (delta?.content) fullContent += delta.content;\n // Accumulate streaming tool calls across chunks\n if (delta?.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n const existing = toolCallMap.get(idx) ?? { name: '', arguments: '' };\n if (tc.function?.name) existing.name = tc.function.name;\n if (tc.function?.arguments) existing.arguments += tc.function.arguments;\n toolCallMap.set(idx, existing);\n }\n }\n if (chunk.usage) usage = chunk.usage;\n } else {\n trackResult();\n }\n return result;\n },\n async return(value?: any) {\n trackResult();\n return iter.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(error?: any) {\n return iter.throw?.(error) ?? { done: true, value: undefined };\n },\n };\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\n/**\n * Wrap an Anthropic streaming response to track after completion.\n * Anthropic streams yield events: content_block_delta, message_start, message_delta, etc.\n */\nfunction wrapAnthropicStream(stream: any, ctx: AnthropicStreamContext): any {\n let fullContent = '';\n let inputTokens = 0;\n let outputTokens = 0;\n const toolCallsById = new Map<number, { name: string; arguments: string }>();\n let currentBlockIdx = -1;\n let tracked = false;\n\n const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);\n if (!originalIterator) return stream;\n\n const trackResult = () => {\n if (tracked) return;\n tracked = true;\n const durationMs = Date.now() - ctx.startTime;\n const totalTokens = inputTokens + outputTokens;\n const cost = calculateAnthropicCost({ model: ctx.model, inputTokens, outputTokens });\n\n const fullMessages = ctx.system\n ? [{ role: 'system', content: ctx.system }, ...ctx.messages]\n : ctx.messages;\n\n trackLLMCall({\n provider: 'anthropic',\n model: ctx.model,\n messages: fullMessages,\n output: fullContent,\n toolCalls: Array.from(toolCallsById.values()),\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession: ctx.trackWithoutSession,\n });\n };\n\n return new Proxy(stream, {\n get(target, prop, receiver) {\n if (prop === Symbol.asyncIterator) {\n return function () {\n const iter = originalIterator();\n return {\n async next() {\n const result = await iter.next();\n if (!result.done) {\n const event = result.value;\n if (event.type === 'content_block_start') {\n currentBlockIdx = event.index ?? (currentBlockIdx + 1);\n if (event.content_block?.type === 'tool_use') {\n toolCallsById.set(currentBlockIdx, {\n name: event.content_block.name ?? 'unknown',\n arguments: '',\n });\n }\n }\n if (event.type === 'content_block_delta') {\n if (event.delta?.text) {\n fullContent += event.delta.text;\n }\n // Accumulate tool input JSON across delta chunks\n if (event.delta?.type === 'input_json_delta' && event.delta?.partial_json) {\n const idx = event.index ?? currentBlockIdx;\n const existing = toolCallsById.get(idx);\n if (existing) {\n existing.arguments += event.delta.partial_json;\n }\n }\n }\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = event.message.usage.input_tokens ?? 0;\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0;\n }\n } else {\n trackResult();\n }\n return result;\n },\n async return(value?: any) {\n trackResult();\n return iter.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(error?: any) {\n return iter.throw?.(error) ?? { done: true, value: undefined };\n },\n };\n };\n }\n return Reflect.get(target, prop, receiver);\n },\n });\n}\n\n// ============================================================================\n// Common Tracking Functions\n// ============================================================================\n\ninterface TrackLLMCallParams {\n provider: string;\n model: string;\n messages: unknown[];\n output: string;\n toolCalls?: Array<{ name: string; arguments: string }>;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n cost: number;\n durationMs: number;\n trackWithoutSession: boolean;\n}\n\nfunction trackLLMCall(params: TrackLLMCallParams): void {\n const client = getTrackingClient();\n if (!client) return;\n\n const sessionId = _currentSessionId.get();\n if (!sessionId && !params.trackWithoutSession) {\n return;\n }\n\n // Build tool output payload with tool calls if present\n const toolOutput: Record<string, unknown> = {\n content: params.output,\n tokens: {\n prompt: params.promptTokens,\n completion: params.completionTokens,\n total: params.totalTokens,\n },\n cost_usd: params.cost,\n };\n if (params.toolCalls && params.toolCalls.length > 0) {\n toolOutput.tool_calls = params.toolCalls;\n }\n\n if (sessionId) {\n // Track under the existing session\n client.trackToolCall({\n sessionId,\n toolName: `llm:${params.provider}:${params.model}`,\n toolInput: {\n messages: params.messages,\n model: params.model,\n provider: params.provider,\n },\n toolOutput,\n reasoning: `LLM call to ${params.provider} ${params.model}`,\n estimatedCost: params.cost,\n tokenCount: params.totalTokens,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n prompt_tokens: params.promptTokens,\n completion_tokens: params.completionTokens,\n },\n }).catch((err) => {\n console.warn('Sentrial: Failed to track LLM call:', err.message);\n });\n } else if (params.trackWithoutSession) {\n // Create an ad-hoc session, track the call, then auto-complete it\n client.createSession({\n name: `LLM: ${params.provider}/${params.model}`,\n agentName: `${params.provider}-wrapper`,\n userId: 'anonymous',\n }).then((sid) => {\n if (!sid) return;\n return client.trackToolCall({\n sessionId: sid,\n toolName: `llm:${params.provider}:${params.model}`,\n toolInput: {\n messages: params.messages,\n model: params.model,\n provider: params.provider,\n },\n toolOutput,\n estimatedCost: params.cost,\n tokenCount: params.totalTokens,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n },\n }).then(() => {\n // Auto-complete so the session doesn't dangle\n return client.completeSession({\n sessionId: sid,\n success: true,\n estimatedCost: params.cost,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n totalTokens: params.totalTokens,\n durationMs: params.durationMs,\n });\n });\n }).catch((err) => {\n console.warn('Sentrial: Failed to track standalone LLM call:', err.message);\n });\n }\n}\n\ninterface TrackLLMErrorParams {\n provider: string;\n model: string;\n messages: unknown[];\n error: Error;\n durationMs: number;\n trackWithoutSession: boolean;\n}\n\nfunction trackLLMError(params: TrackLLMErrorParams): void {\n const client = getTrackingClient();\n if (!client) return;\n\n const sessionId = _currentSessionId.get();\n if (!sessionId && !params.trackWithoutSession) {\n return;\n }\n\n if (!sessionId) return; // trackWithoutSession errors are not worth an ad-hoc session\n\n client.trackError({\n sessionId,\n errorMessage: params.error.message,\n errorType: params.error.name,\n toolName: `llm:${params.provider}:${params.model}`,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n },\n }).catch((err) => {\n console.warn('Sentrial: Failed to track LLM error:', err.message);\n });\n}\n","/**\n * EventBatcher — fire-and-forget event queue with periodic flushing.\n *\n * Buffers tracking events (tool calls, decisions, errors, generic events)\n * and flushes them in batches to reduce HTTP overhead. Session lifecycle\n * calls (createSession, completeSession) bypass the batcher entirely\n * because they need synchronous responses.\n *\n * Events are delivered in enqueue order so server-assigned sequence numbers\n * match the order an agent actually executed steps.\n *\n * Flush triggers:\n * 1. Timer — every `flushIntervalMs` (default 1 000 ms)\n * 2. Queue size — when queue reaches `flushThreshold` (default 10)\n * 3. Manual — `flush()` or `shutdown()`\n * 4. Process exit — `beforeExit` handler\n *\n * Back-pressure: queue is capped at `maxQueueSize` (default 1 000).\n * When full, oldest events are dropped and a warning is logged.\n */\n\nexport interface BatcherConfig {\n /** Whether batching is enabled (default: false — all calls are immediate) */\n enabled?: boolean;\n /** Milliseconds between automatic flushes (default: 1000) */\n flushIntervalMs?: number;\n /** Flush when the queue reaches this many items (default: 10) */\n flushThreshold?: number;\n /** Maximum queue size before dropping oldest events (default: 1000) */\n maxQueueSize?: number;\n}\n\n/** Signature for the function that actually sends an event to the API. */\nexport type SendFn = (\n method: string,\n url: string,\n body: unknown,\n) => Promise<unknown>;\n\nexport class EventBatcher {\n private readonly queue: Array<{ method: string; url: string; body: unknown }> = [];\n private readonly flushIntervalMs: number;\n private readonly flushThreshold: number;\n private readonly maxQueueSize: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n private sendFn: SendFn;\n private flushing = false;\n private shutdownCalled = false;\n private readonly exitHandler: () => void;\n\n constructor(sendFn: SendFn, config: BatcherConfig = {}) {\n this.sendFn = sendFn;\n this.flushIntervalMs = config.flushIntervalMs ?? 1_000;\n this.flushThreshold = config.flushThreshold ?? 10;\n this.maxQueueSize = config.maxQueueSize ?? 1_000;\n\n // Start periodic flush timer\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n\n // Don't hold the process open just for the flush timer\n if (this.timer && typeof this.timer === 'object' && 'unref' in this.timer) {\n (this.timer as NodeJS.Timeout).unref();\n }\n\n // Register process exit handler for best-effort final flush\n this.exitHandler = () => {\n void this.shutdown();\n };\n\n if (typeof process !== 'undefined' && process.on) {\n process.on('beforeExit', this.exitHandler);\n }\n }\n\n /**\n * Enqueue an event for batched delivery.\n *\n * If the queue hits `flushThreshold`, an automatic flush is triggered.\n * If the queue is full (`maxQueueSize`), the oldest event is dropped.\n */\n enqueue(method: string, url: string, body: unknown): void {\n if (this.shutdownCalled) return;\n\n // Back-pressure: drop oldest when at capacity\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n if (typeof console !== 'undefined') {\n console.warn(\n `Sentrial: Event queue full (${this.maxQueueSize}), dropping oldest event`,\n );\n }\n }\n\n this.queue.push({ method, url, body });\n\n // Flush if we hit the threshold\n if (this.queue.length >= this.flushThreshold) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued events to the API.\n *\n * Drains the queue and sends requests in enqueue order. Safe to call\n * concurrently — only one flush runs at a time.\n */\n async flush(): Promise<void> {\n if (this.flushing || this.queue.length === 0) return;\n\n this.flushing = true;\n\n // Drain the queue atomically\n const batch = this.queue.splice(0, this.queue.length);\n\n try {\n for (const event of batch) {\n try {\n await this.sendFn(event.method, event.url, event.body);\n } catch (err) {\n // Individual event failures shouldn't crash the batch\n if (typeof console !== 'undefined') {\n console.warn('Sentrial: Batched event failed:', err);\n }\n }\n }\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Stop the batcher: clear the timer, flush remaining events, remove exit handler.\n */\n async shutdown(): Promise<void> {\n if (this.shutdownCalled) return;\n this.shutdownCalled = true;\n\n // Clear periodic timer\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n // Remove exit handler\n if (typeof process !== 'undefined' && process.removeListener) {\n process.removeListener('beforeExit', this.exitHandler);\n }\n\n // Final flush\n this.flushing = false; // Reset so flush() will run\n await this.flush();\n }\n\n /** Number of events currently queued. */\n get size(): number {\n return this.queue.length;\n }\n}\n","/**\n * Type definitions for the Sentrial TypeScript SDK\n */\n\nimport type { PiiConfig } from './redact.js';\nimport type { BatcherConfig } from './batcher.js';\n\n// ============================================================================\n// Enums\n// ============================================================================\n\n/**\n * Event types that can be tracked in a session\n */\nexport enum EventType {\n TOOL_CALL = 'tool_call',\n LLM_DECISION = 'llm_decision',\n STATE_CHANGE = 'state_change',\n ERROR = 'error',\n}\n\n/**\n * Session status values\n */\nexport type SessionStatus = 'running' | 'completed' | 'failed' | 'cancelled';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration options for SentrialClient\n */\nexport interface SentrialClientConfig {\n /** API key for authentication (defaults to SENTRIAL_API_KEY env var) */\n apiKey?: string;\n /** URL of the Sentrial API server (defaults to SENTRIAL_API_URL env var or production) */\n apiUrl?: string;\n /**\n * If true (default), SDK errors are logged but won't crash your app.\n * Set to false during development to see full errors.\n */\n failSilently?: boolean;\n /**\n * PII redaction configuration.\n * - Pass a PiiConfig object for full control.\n * - Pass `true` to fetch the org's PII config from the server automatically.\n */\n pii?: PiiConfig | boolean;\n /**\n * Event batching configuration.\n * When enabled, fire-and-forget tracking calls (trackToolCall, trackDecision,\n * trackError, trackEvent) are queued and flushed periodically instead of\n * being sent immediately. Session lifecycle calls (createSession,\n * completeSession) always bypass the batcher.\n */\n batching?: BatcherConfig;\n}\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\n/**\n * Parameters for creating a new session\n */\nexport interface CreateSessionParams {\n /** Name of the session */\n name: string;\n /** Identifier for the agent type (used for grouping) */\n agentName: string;\n /** External user ID (for grouping sessions by end-user) */\n userId: string;\n /** Optional parent session ID for multi-agent hierarchies */\n parentSessionId?: string;\n /** Optional conversation ID to group related sessions into a thread */\n convoId?: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n /** Production experiment assignment ids to link to this session */\n experimentAssignmentIds?: string[];\n}\n\n/**\n * Parameters for completing a session\n */\nexport interface CompleteSessionParams {\n /** Session ID */\n sessionId: string;\n /** Whether the session successfully completed its goal (default: true) */\n success?: boolean;\n /** If success=false, why did it fail? */\n failureReason?: string;\n /** Total estimated cost in USD for this session */\n estimatedCost?: number;\n /** Custom metrics (e.g., { customer_satisfaction: 4.5, steps_taken: 3 }) */\n customMetrics?: Record<string, number>;\n /** Duration in milliseconds (auto-calculated if not provided) */\n durationMs?: number;\n /** Number of prompt/input tokens used */\n promptTokens?: number;\n /** Number of completion/output tokens used */\n completionTokens?: number;\n /** Total tokens used (prompt + completion) */\n totalTokens?: number;\n /** The user's original query/input for this session */\n userInput?: string;\n /** The final assistant response for this session */\n assistantOutput?: string;\n /** Alias for assistantOutput - the final output text */\n output?: string;\n}\n\n/**\n * Session data returned from the API\n */\nexport interface Session {\n id: string;\n agentId?: string;\n parentSessionId?: string;\n convoId?: string;\n name: string;\n agentName: string;\n userId: string;\n status: SessionStatus;\n totalEvents: number;\n\n // User interaction tracking\n userInput?: string;\n assistantOutput?: string;\n\n // Performance tracking\n success?: boolean;\n failureReason?: string;\n estimatedCost?: number;\n customMetrics?: Record<string, unknown>;\n score?: number;\n metricScores?: Record<string, unknown>;\n metricReasonings?: Record<string, string>;\n\n // Token tracking\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n\n // Timestamps\n durationMs?: number;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n completedAt?: Date;\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Parameters for tracking a tool call event\n */\nexport interface TrackToolCallParams {\n /** Session ID */\n sessionId: string;\n /** Name of the tool */\n toolName: string;\n /** Tool input data */\n toolInput: Record<string, unknown>;\n /** Tool output data */\n toolOutput: Record<string, unknown>;\n /** Optional reasoning for why this tool was called */\n reasoning?: string;\n /** Estimated cost in USD for this tool call */\n estimatedCost?: number;\n /** Error details if the tool failed */\n toolError?: Record<string, unknown>;\n /** Number of tokens used by this tool call (for LLM-based tools) */\n tokenCount?: number;\n /** OpenTelemetry trace ID for distributed tracing */\n traceId?: string;\n /** OpenTelemetry span ID for distributed tracing */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for tracking an LLM decision event\n */\nexport interface TrackDecisionParams {\n /** Session ID */\n sessionId: string;\n /** Decision reasoning */\n reasoning: string;\n /** Alternative options considered */\n alternatives?: string[];\n /** Confidence score (0.0 to 1.0) */\n confidence?: number;\n /** Estimated cost in USD for this decision */\n estimatedCost?: number;\n /** Number of tokens used */\n tokenCount?: number;\n /** OpenTelemetry trace ID for distributed tracing */\n traceId?: string;\n /** OpenTelemetry span ID for distributed tracing */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for tracking an error event\n */\nexport interface TrackErrorParams {\n /** Session ID */\n sessionId: string;\n /** Error message */\n errorMessage: string;\n /** Type of error (e.g., \"ValueError\", \"APIError\") */\n errorType?: string;\n /** Name of the tool that caused the error (if applicable) */\n toolName?: string;\n /** Stack trace for debugging */\n stackTrace?: string;\n /** OpenTelemetry trace ID for distributed tracing */\n traceId?: string;\n /** OpenTelemetry span ID for distributed tracing */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Event data returned from the API\n */\nexport interface Event {\n id: string;\n sessionId: string;\n eventType: EventType;\n timestamp: Date;\n sequenceNumber: number;\n\n // Tool call data\n toolName?: string;\n toolInput?: Record<string, unknown>;\n toolOutput?: Record<string, unknown>;\n toolError?: Record<string, unknown>;\n\n // LLM decision data\n reasoning?: string;\n alternativesConsidered?: string[];\n confidence?: number;\n\n // State tracking\n stateBefore?: Record<string, unknown>;\n stateAfter?: Record<string, unknown>;\n stateDiff?: Record<string, unknown>;\n\n // Performance tracking\n estimatedCost?: number;\n tokenCount?: number;\n\n // Metadata\n metadata?: Record<string, unknown>;\n traceId?: string;\n spanId?: string;\n}\n\n// ============================================================================\n// Interaction Types (Simple API)\n// ============================================================================\n\n/**\n * Parameters for beginning an interaction\n */\nexport interface BeginParams {\n /** External user ID for grouping sessions */\n userId: string;\n /** Event type/name (e.g., \"chat_message\", \"search_query\") */\n event: string;\n /** Optional input data for the interaction */\n input?: string;\n /** Optional custom event ID (auto-generated UUID if not provided) */\n eventId?: string;\n /** Optional conversation ID to group related interactions */\n convoId?: string;\n /** Optional additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for finishing an interaction\n */\nexport interface FinishParams {\n /** Output/response from the interaction */\n output?: string;\n /** Whether the interaction succeeded (default: true) */\n success?: boolean;\n /** Reason for failure if success=false */\n failureReason?: string;\n /** Total estimated cost in USD */\n estimatedCost?: number;\n /** Custom metrics */\n customMetrics?: Record<string, number>;\n /** Number of prompt/input tokens used */\n promptTokens?: number;\n /** Number of completion/output tokens used */\n completionTokens?: number;\n /** Total tokens used */\n totalTokens?: number;\n /** Duration in milliseconds (auto-calculated from begin/finish if not provided) */\n durationMs?: number;\n}\n\n// ============================================================================\n// Production Experiment Types\n// ============================================================================\n\nexport interface ProductionVariantConfig {\n name: string;\n weight?: number;\n prompt?: string;\n systemPrompt?: string;\n model?: string;\n promptVersion?: string;\n toolConfig?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface GetVariantParams {\n /** Production experiment name configured in Sentrial */\n experimentName: string;\n /** Agent name this variant is for */\n agentName?: string;\n /** External user id for sticky bucketing */\n userId?: string;\n /** Conversation id for sticky multi-turn bucketing */\n convoId?: string;\n /** Stable anonymous id if no user/conversation id exists */\n anonymousId?: string;\n /** Optional existing Sentrial session id */\n sessionId?: string;\n /** Variant to use when no active live experiment matches */\n defaultVariant?: string;\n /** Prompt version currently running in the customer app */\n promptVersion?: string;\n /** Model currently running in the customer app */\n model?: string;\n /** Extra assignment metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface VariantAssignment {\n active: boolean;\n reason: 'assigned' | 'holdout' | 'not_found';\n experimentId?: string;\n assignmentId?: string;\n variant: string;\n variantConfig?: ProductionVariantConfig;\n prompt?: string;\n systemPrompt?: string;\n model?: string;\n promptVersion?: string;\n}\n\n// ============================================================================\n// Cost Calculation Types\n// ============================================================================\n\n/**\n * Parameters for cost calculation\n */\nexport interface CostParams {\n /** Model name (e.g., \"gpt-4\", \"claude-3-sonnet\") */\n model: string;\n /** Number of input tokens */\n inputTokens: number;\n /** Number of output tokens */\n outputTokens: number;\n}\n\n// ============================================================================\n// API Response Types\n// ============================================================================\n\n/**\n * Standard API response wrapper\n */\nexport interface ApiResponse<T> {\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n };\n}\n","/**\n * Sentrial Client - Main SDK interface\n *\n * Captures agent sessions, tool calls, and metrics. This data powers:\n * - Signal detection (auto-detect patterns/anomalies)\n * - Root cause analysis (understand WHY agents fail)\n * - Code fixer (AI-suggested fixes with GitHub PRs)\n */\n\nimport { ApiError, NetworkError } from './errors.js';\nimport { redactPayload } from './redact.js';\nimport type { PiiConfig } from './redact.js';\nimport { createContextVar, type ContextVar } from './async-context.js';\nimport {\n _setSessionContextWithTokens,\n _restoreSessionContext,\n setDefaultClient,\n type SessionContextTokens,\n} from './wrappers.js';\nimport { EventBatcher } from './batcher.js';\nimport type {\n SentrialClientConfig,\n CreateSessionParams,\n CompleteSessionParams,\n TrackToolCallParams,\n TrackDecisionParams,\n TrackErrorParams,\n BeginParams,\n FinishParams,\n GetVariantParams,\n VariantAssignment,\n Session,\n Event,\n EventType,\n} from './types.js';\nimport { calculateOpenAICost, calculateAnthropicCost, calculateGoogleCost } from './cost.js';\n\n// Re-export EventType enum for convenience\nexport { EventType } from './types.js';\n\nconst DEFAULT_API_URL = 'https://api.sentrial.com';\n\n// Retry configuration (mirrors Python SDK)\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 8000;\nconst BACKOFF_MULTIPLIER = 2;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\nconst REQUEST_TIMEOUT_MS = 10_000;\n\n/**\n * Sentrial Client for agent observability.\n *\n * Captures the data needed for automated root cause analysis and fix suggestions.\n *\n * @example\n * ```ts\n * const client = new SentrialClient({\n * apiKey: 'sentrial_live_xxx',\n * apiUrl: 'https://api.sentrial.com',\n * });\n *\n * // Create a session for an agent run\n * const sessionId = await client.createSession({\n * name: 'Support Request #123',\n * agentName: 'support-agent',\n * userId: 'user_123',\n * });\n *\n * // Track events\n * await client.trackToolCall({\n * sessionId,\n * toolName: 'search_kb',\n * toolInput: { query: 'password reset' },\n * toolOutput: { articles: ['KB-001'] },\n * });\n *\n * // Complete session with metrics\n * await client.completeSession({\n * sessionId,\n * success: true,\n * customMetrics: { customer_satisfaction: 90 },\n * });\n * ```\n *\n * @remarks\n * By default, the SDK fails silently (failSilently=true) to ensure\n * monitoring never crashes your application. Set failSilently=false\n * during development to see errors.\n */\nexport class SentrialClient {\n private readonly apiUrl: string;\n private readonly apiKey?: string;\n private readonly failSilently: boolean;\n private piiConfig?: PiiConfig;\n private piiConfigNeedsHydration: boolean = false;\n private piiHydrationPromise?: Promise<void>;\n private readonly _stateVar: ContextVar<Record<string, unknown>> = createContextVar<\n Record<string, unknown>\n >({});\n private readonly batcher?: EventBatcher;\n private pendingExperimentAssignmentIds: string[] = [];\n\n /** Per-session cost/token accumulator — populated by trackToolCall/trackDecision */\n private sessionAccumulators = new Map<\n string,\n {\n cost: number;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n }\n >();\n\n private get currentState(): Record<string, unknown> {\n return this._stateVar.get();\n }\n\n private set currentState(value: Record<string, unknown>) {\n this._stateVar.set(value);\n }\n\n constructor(config: SentrialClientConfig = {}) {\n this.apiUrl = (\n config.apiUrl ??\n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_URL : undefined) ??\n DEFAULT_API_URL\n ).replace(/\\/$/, '');\n\n this.apiKey =\n config.apiKey ?? (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_KEY : undefined);\n\n this.failSilently = config.failSilently ?? true;\n\n if (config.pii === true) {\n // Minimal default until we fetch the real config from the server\n this.piiConfig = { enabled: true };\n this.piiConfigNeedsHydration = true;\n } else if (config.pii && typeof config.pii === 'object') {\n this.piiConfig = config.pii;\n this.piiConfigNeedsHydration = false;\n }\n\n // Initialize event batcher if enabled\n if (config.batching?.enabled) {\n this.batcher = new EventBatcher(\n (method, url, body) => this.safeRequest(method, url, body),\n config.batching\n );\n }\n }\n\n /**\n * Fetch the organization's PII config from the server.\n *\n * Called lazily on the first request when `pii: true` was passed to the constructor.\n * Uses a single shared promise so concurrent requests don't trigger duplicate fetches.\n */\n private async hydratePiiConfig(): Promise<void> {\n if (!this.piiConfigNeedsHydration) return;\n\n // Deduplicate: if a fetch is already in flight, reuse its promise\n if (this.piiHydrationPromise) {\n await this.piiHydrationPromise;\n return;\n }\n\n this.piiHydrationPromise = (async () => {\n try {\n const headers: Record<string, string> = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {\n method: 'GET',\n headers,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (response.ok) {\n const data = (await response.json()) as {\n config: {\n enabled: boolean;\n mode: string;\n fields: string[];\n builtinPatterns: Record<string, boolean>;\n customPatterns: Array<{ pattern: string; label: string }>;\n enhancedDetection: boolean;\n } | null;\n };\n if (data.config) {\n this.piiConfig = {\n enabled: data.config.enabled,\n mode: data.config.mode as PiiConfig['mode'],\n fields: data.config.fields as PiiConfig['fields'],\n builtinPatterns: data.config.builtinPatterns as PiiConfig['builtinPatterns'],\n customPatterns: (data.config.customPatterns || []).map(\n (cp: { pattern: string; label: string }) => ({\n pattern: new RegExp(cp.pattern, 'g'),\n label: cp.label,\n })\n ),\n enhancedDetection: data.config.enhancedDetection,\n };\n }\n }\n } catch {\n // Fail silently — keep the minimal default config (enabled: true)\n }\n this.piiConfigNeedsHydration = false;\n })();\n\n await this.piiHydrationPromise;\n }\n\n /**\n * Make an HTTP request with retry logic and exponential backoff.\n *\n * Retries on transient failures (network errors, timeouts, 429/5xx).\n * Up to MAX_RETRIES attempts with exponential backoff.\n */\n private async safeRequest<T>(method: string, url: string, body?: unknown): Promise<T | null> {\n // Hydrate server-driven PII config before first request\n if (this.piiConfigNeedsHydration) {\n await this.hydratePiiConfig();\n }\n\n let lastError: Error | undefined;\n let backoff = INITIAL_BACKOFF_MS;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n // Apply PII redaction before serialization\n const finalBody =\n this.piiConfig && body && typeof body === 'object'\n ? redactPayload(body as Record<string, unknown>, this.piiConfig)\n : body;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: finalBody ? JSON.stringify(finalBody) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n // Retryable HTTP status — back off and retry\n if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {\n await this.sleep(backoff);\n backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);\n continue;\n }\n\n if (!response.ok) {\n const errorBody = await response.text();\n let errorData: { error?: { message?: string; code?: string } } = {};\n try {\n errorData = JSON.parse(errorBody);\n } catch {\n // Ignore JSON parse errors\n }\n\n const error = new ApiError(\n errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n errorData.error?.code\n );\n\n if (this.failSilently) {\n console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);\n return null;\n }\n throw error;\n }\n\n return (await response.json()) as T;\n } catch (error) {\n // Non-retryable Sentrial errors — throw immediately\n if (error instanceof ApiError) {\n throw error;\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Network / timeout errors are retryable\n if (attempt < MAX_RETRIES) {\n await this.sleep(backoff);\n backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);\n continue;\n }\n }\n }\n\n // All retries exhausted\n const networkError = new NetworkError(lastError?.message ?? 'Unknown network error', lastError);\n\n if (this.failSilently) {\n console.warn(\n `Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`,\n networkError.message\n );\n return null;\n }\n throw networkError;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private accumulate(\n sessionId: string,\n cost?: number,\n tokenCount?: number,\n toolOutput?: Record<string, unknown>\n ): void {\n let acc = this.sessionAccumulators.get(sessionId);\n if (!acc) {\n acc = { cost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n this.sessionAccumulators.set(sessionId, acc);\n }\n if (cost != null) acc.cost += cost;\n if (tokenCount != null) acc.totalTokens += tokenCount;\n // LLM wrappers store prompt/completion breakdown in toolOutput.tokens\n const rawTokens = toolOutput?.tokens;\n if (rawTokens && typeof rawTokens === 'object' && !Array.isArray(rawTokens)) {\n const tokens = rawTokens as Record<string, unknown>;\n if (typeof tokens.prompt === 'number') acc.promptTokens += tokens.prompt;\n if (typeof tokens.completion === 'number') acc.completionTokens += tokens.completion;\n }\n }\n\n /**\n * Create a new session\n *\n * @param params - Session creation parameters\n * @returns Session ID, or null if the request failed and failSilently is true\n */\n async createSession(params: CreateSessionParams): Promise<string | null> {\n // Clear state from previous sessions to prevent memory leaks\n this.currentState = {};\n\n const payload: Record<string, unknown> = {\n name: params.name,\n agentName: params.agentName,\n userId: params.userId,\n metadata: params.metadata,\n };\n\n if (params.parentSessionId) {\n payload.parentSessionId = params.parentSessionId;\n }\n\n if (params.convoId) {\n payload.convoId = params.convoId;\n }\n\n const experimentAssignmentIds = [\n ...(params.experimentAssignmentIds || []),\n ...this.pendingExperimentAssignmentIds,\n ];\n\n if (experimentAssignmentIds.length > 0) {\n payload.experimentAssignmentIds = Array.from(new Set(experimentAssignmentIds));\n payload.metadata = {\n ...(params.metadata || {}),\n sentrial_experiment_assignment_ids: payload.experimentAssignmentIds,\n };\n }\n\n const response = await this.safeRequest<{ id: string }>(\n 'POST',\n `${this.apiUrl}/api/sdk/sessions`,\n payload\n );\n\n if (response?.id && experimentAssignmentIds.length > 0) {\n this.pendingExperimentAssignmentIds = [];\n }\n\n return response?.id ?? null;\n }\n\n /**\n * Get a sticky production experiment variant for this user/conversation.\n *\n * The returned assignment id is automatically linked to the next session\n * created by this client. You can also pass `sessionId` to link immediately.\n */\n async getVariant(params: GetVariantParams): Promise<VariantAssignment> {\n const fallback: VariantAssignment = {\n active: false,\n reason: 'not_found',\n variant: params.defaultVariant || 'control',\n };\n\n const response = await this.safeRequest<VariantAssignment>(\n 'POST',\n `${this.apiUrl}/api/sdk/experiments/variant`,\n {\n ...params,\n sdkName: '@sentrial/sdk',\n }\n );\n\n if (!response) return fallback;\n\n if (response.active && response.assignmentId && !params.sessionId) {\n this.pendingExperimentAssignmentIds.push(response.assignmentId);\n }\n\n return response;\n }\n\n /**\n * Track a tool call event\n *\n * @param params - Tool call parameters\n * @returns Event data\n */\n async trackToolCall(params: TrackToolCallParams): Promise<Event | null> {\n this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount, params.toolOutput);\n const stateBefore = { ...this.currentState };\n\n // Update current state\n this.currentState[`${params.toolName}_result`] = params.toolOutput;\n\n const payload: Record<string, unknown> = {\n sessionId: params.sessionId,\n eventType: 'tool_call' as EventType,\n toolName: params.toolName,\n toolInput: params.toolInput,\n toolOutput: params.toolOutput,\n reasoning: params.reasoning,\n stateBefore,\n stateAfter: { ...this.currentState },\n estimatedCost: params.estimatedCost ?? 0,\n };\n\n if (params.toolError !== undefined) payload.toolError = params.toolError;\n if (params.tokenCount !== undefined) payload.tokenCount = params.tokenCount;\n if (params.traceId !== undefined) payload.traceId = params.traceId;\n if (params.spanId !== undefined) payload.spanId = params.spanId;\n if (params.metadata !== undefined) payload.metadata = params.metadata;\n\n if (this.batcher) {\n this.batcher.enqueue('POST', `${this.apiUrl}/api/sdk/events`, payload);\n return null;\n }\n return this.safeRequest<Event>('POST', `${this.apiUrl}/api/sdk/events`, payload);\n }\n\n /**\n * Track an LLM decision event\n *\n * @param params - Decision parameters\n * @returns Event data\n */\n async trackDecision(params: TrackDecisionParams): Promise<Event | null> {\n this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount);\n const stateBefore = { ...this.currentState };\n\n const payload: Record<string, unknown> = {\n sessionId: params.sessionId,\n eventType: 'llm_decision' as EventType,\n reasoning: params.reasoning,\n alternativesConsidered: params.alternatives,\n confidence: params.confidence,\n stateBefore,\n stateAfter: { ...this.currentState },\n estimatedCost: params.estimatedCost ?? 0,\n };\n\n if (params.tokenCount !== undefined) payload.tokenCount = params.tokenCount;\n if (params.traceId !== undefined) payload.traceId = params.traceId;\n if (params.spanId !== undefined) payload.spanId = params.spanId;\n if (params.metadata !== undefined) payload.metadata = params.metadata;\n\n if (this.batcher) {\n this.batcher.enqueue('POST', `${this.apiUrl}/api/sdk/events`, payload);\n return null;\n }\n return this.safeRequest<Event>('POST', `${this.apiUrl}/api/sdk/events`, payload);\n }\n\n /**\n * Track an error event\n *\n * @param params - Error parameters\n * @returns Event data\n */\n async trackError(params: TrackErrorParams): Promise<Event | null> {\n const stateBefore = { ...this.currentState };\n\n const toolError: Record<string, unknown> = {\n message: params.errorMessage,\n };\n if (params.errorType) toolError.type = params.errorType;\n if (params.stackTrace) toolError.stack_trace = params.stackTrace;\n\n const payload: Record<string, unknown> = {\n sessionId: params.sessionId,\n eventType: 'error' as EventType,\n toolError,\n stateBefore,\n stateAfter: { ...this.currentState },\n };\n\n if (params.toolName !== undefined) payload.toolName = params.toolName;\n if (params.traceId !== undefined) payload.traceId = params.traceId;\n if (params.spanId !== undefined) payload.spanId = params.spanId;\n if (params.metadata !== undefined) payload.metadata = params.metadata;\n\n if (this.batcher) {\n this.batcher.enqueue('POST', `${this.apiUrl}/api/sdk/events`, payload);\n return null;\n }\n return this.safeRequest<Event>('POST', `${this.apiUrl}/api/sdk/events`, payload);\n }\n\n /**\n * Update the current state\n *\n * @param key - State key\n * @param value - State value\n */\n updateState(key: string, value: unknown): void {\n this.currentState[key] = value;\n }\n\n /**\n * Set the user input for a session\n *\n * @param sessionId - Session ID\n * @param input - User input text\n * @returns Updated session or null on error\n */\n async setInput(sessionId: string, input: string): Promise<Session | null> {\n return this.safeRequest<Session>('PATCH', `${this.apiUrl}/api/sdk/sessions/${sessionId}`, {\n userInput: input,\n });\n }\n\n /**\n * Track a generic event\n *\n * @param params - Event parameters\n * @returns Event data or null on error\n */\n async trackEvent(params: {\n sessionId: string;\n eventType: string;\n eventData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n }): Promise<Event | null> {\n const stateBefore = { ...this.currentState };\n\n const payload: Record<string, unknown> = {\n sessionId: params.sessionId,\n eventType: params.eventType,\n stateBefore,\n stateAfter: { ...this.currentState },\n };\n\n if (params.eventData) {\n Object.assign(payload, params.eventData);\n }\n if (params.metadata) {\n payload.metadata = params.metadata;\n }\n\n if (this.batcher) {\n this.batcher.enqueue('POST', `${this.apiUrl}/api/sdk/events`, payload);\n return null;\n }\n return this.safeRequest<Event>('POST', `${this.apiUrl}/api/sdk/events`, payload);\n }\n\n /**\n * Complete a session with performance metrics\n *\n * This is the recommended way to close sessions for performance monitoring.\n *\n * @param params - Session completion parameters\n * @returns Updated session data\n *\n * @example\n * ```ts\n * await client.completeSession({\n * sessionId,\n * success: true,\n * estimatedCost: 0.023,\n * promptTokens: 1500,\n * completionTokens: 500,\n * totalTokens: 2000,\n * userInput: \"What's the weather in San Francisco?\",\n * assistantOutput: \"The weather in San Francisco is 65°F and sunny.\",\n * customMetrics: {\n * customer_satisfaction: 4.5,\n * order_value: 129.99,\n * },\n * });\n * ```\n */\n async completeSession(params: CompleteSessionParams): Promise<Session | null> {\n // Flush queued events before completing the session so they arrive first\n if (this.batcher) {\n await this.batcher.flush();\n }\n\n // Auto-fill cost/tokens from accumulated event data if not explicitly provided\n const acc = this.sessionAccumulators.get(params.sessionId);\n if (acc) {\n if (params.estimatedCost === undefined && acc.cost > 0)\n params = { ...params, estimatedCost: acc.cost };\n if (params.promptTokens === undefined && acc.promptTokens > 0)\n params = { ...params, promptTokens: acc.promptTokens };\n if (params.completionTokens === undefined && acc.completionTokens > 0)\n params = { ...params, completionTokens: acc.completionTokens };\n if (params.totalTokens === undefined && acc.totalTokens > 0)\n params = { ...params, totalTokens: acc.totalTokens };\n this.sessionAccumulators.delete(params.sessionId);\n }\n\n const payload: Record<string, unknown> = {\n status: params.success !== false ? 'completed' : 'failed',\n success: params.success ?? true,\n };\n\n if (params.failureReason !== undefined) payload.failureReason = params.failureReason;\n if (params.estimatedCost !== undefined) payload.estimatedCost = params.estimatedCost;\n if (params.customMetrics !== undefined) payload.customMetrics = params.customMetrics;\n if (params.durationMs !== undefined) payload.durationMs = params.durationMs;\n if (params.promptTokens !== undefined) payload.promptTokens = params.promptTokens;\n if (params.completionTokens !== undefined) payload.completionTokens = params.completionTokens;\n if (params.totalTokens !== undefined) payload.totalTokens = params.totalTokens;\n if (params.userInput !== undefined) payload.userInput = params.userInput;\n // Support both 'assistantOutput' and 'output' (alias)\n const output = params.assistantOutput ?? params.output;\n if (output !== undefined) payload.assistantOutput = output;\n\n return this.safeRequest<Session>(\n 'PATCH',\n `${this.apiUrl}/api/sdk/sessions/${params.sessionId}`,\n payload\n );\n }\n\n /**\n * Flush any queued events immediately.\n *\n * No-op if batching is not enabled.\n */\n async flush(): Promise<void> {\n if (this.batcher) {\n await this.batcher.flush();\n }\n }\n\n /**\n * Shut down the event batcher, flushing remaining events.\n *\n * Call this before your process exits for a clean shutdown.\n * No-op if batching is not enabled.\n */\n async shutdown(): Promise<void> {\n if (this.batcher) {\n await this.batcher.shutdown();\n }\n }\n\n /**\n * Begin tracking an interaction (simplified API)\n *\n * @param params - Interaction parameters\n * @returns Interaction object with finish() method\n *\n * @example\n * ```ts\n * const interaction = await client.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * convoId: 'convo_456',\n * });\n *\n * // ... do your agent work ...\n *\n * await interaction.finish({ output: responseText });\n * ```\n */\n async begin(params: BeginParams): Promise<Interaction> {\n const eventId = params.eventId ?? crypto.randomUUID();\n\n // Build metadata with optional fields\n const fullMetadata: Record<string, unknown> = params.metadata ? { ...params.metadata } : {};\n if (params.input) fullMetadata.input = params.input;\n if (params.convoId) fullMetadata.convo_id = params.convoId;\n fullMetadata.event_id = eventId;\n\n // Create session first (clears old state)\n const sessionId = await this.createSession({\n name: `${params.event}:${eventId.slice(0, 8)}`,\n agentName: params.event,\n userId: params.userId,\n convoId: params.convoId,\n metadata: fullMetadata,\n });\n\n // Store input in current_state AFTER createSession clears old state\n if (params.input) {\n this.currentState.input = params.input;\n }\n\n // Auto-wire session context so wrapped LLM calls flow into this session\n let sessionTokens: SessionContextTokens | undefined;\n if (sessionId) {\n sessionTokens = _setSessionContextWithTokens(sessionId, this);\n }\n\n return new Interaction({\n client: this,\n sessionId,\n eventId,\n userId: params.userId,\n event: params.event,\n userInput: params.input,\n sessionTokens,\n });\n }\n\n // Cost calculation static methods for convenience\n static calculateOpenAICost = calculateOpenAICost;\n static calculateAnthropicCost = calculateAnthropicCost;\n static calculateGoogleCost = calculateGoogleCost;\n}\n\n// ============================================================================\n// Interaction Class\n// ============================================================================\n\ninterface InteractionConfig {\n client: SentrialClient;\n sessionId: string | null;\n eventId: string;\n userId: string;\n event: string;\n userInput?: string;\n sessionTokens?: SessionContextTokens;\n}\n\n/**\n * Represents an in-progress interaction that can be finished.\n *\n * Created by SentrialClient.begin() - provides a clean begin/finish API pattern.\n *\n * @remarks\n * If session creation failed (sessionId is null), all tracking methods\n * become no-ops to ensure your application continues running.\n */\nexport class Interaction {\n private readonly client: SentrialClient;\n private readonly sessionId: string | null;\n /** Event ID for this interaction */\n public readonly eventId: string;\n /** User ID for this interaction */\n public readonly userId: string;\n /** Event name for this interaction */\n public readonly event: string;\n private readonly startTime = Date.now();\n private finished = false;\n private success = true;\n private failureReason?: string;\n private output?: string;\n private readonly userInput?: string;\n private readonly degraded: boolean;\n\n /** Context tokens for restoring previous session context on finish() */\n private sessionTokens?: SessionContextTokens;\n\n constructor(config: InteractionConfig) {\n this.client = config.client;\n this.sessionId = config.sessionId;\n this.eventId = config.eventId;\n this.userId = config.userId;\n this.event = config.event;\n this.userInput = config.userInput;\n this.degraded = config.sessionId === null;\n this.sessionTokens = config.sessionTokens;\n }\n\n /**\n * Set the output for this interaction\n *\n * This will be used when finish() is called.\n * Useful when you want to set the output but call finish() later.\n *\n * @param output - The output/response text\n */\n setOutput(output: string): void {\n this.output = output;\n }\n\n /**\n * Finish the interaction and record metrics\n *\n * @param params - Finish parameters\n * @returns Updated session data, or null if degraded/already finished\n *\n * @example\n * ```ts\n * await interaction.finish({\n * output: \"Here's the answer to your question...\",\n * success: true,\n * customMetrics: { satisfaction: 4.5 },\n * });\n * ```\n */\n async finish(params: FinishParams = {}): Promise<Session | null> {\n if (this.finished) return null;\n if (this.degraded) {\n this.finished = true;\n return null;\n }\n\n this.finished = true;\n\n // Use stored output if not provided\n const finalOutput = params.output ?? this.output;\n\n const result = await this.client.completeSession({\n sessionId: this.sessionId!,\n success: params.success ?? this.success,\n failureReason: params.failureReason ?? this.failureReason,\n estimatedCost: params.estimatedCost,\n customMetrics: params.customMetrics,\n durationMs: params.durationMs ?? Date.now() - this.startTime,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n totalTokens: params.totalTokens,\n userInput: this.userInput,\n assistantOutput: finalOutput,\n });\n\n // Restore previous session context after completing (supports nesting)\n if (this.sessionTokens) {\n _restoreSessionContext(this.sessionTokens);\n this.sessionTokens = undefined;\n }\n\n return result;\n }\n\n /**\n * Track a tool call within this interaction\n */\n async trackToolCall(params: Omit<TrackToolCallParams, 'sessionId'>): Promise<Event | null> {\n if (this.degraded) return null;\n\n return this.client.trackToolCall({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /**\n * Track an LLM decision within this interaction\n */\n async trackDecision(params: Omit<TrackDecisionParams, 'sessionId'>): Promise<Event | null> {\n if (this.degraded) return null;\n\n return this.client.trackDecision({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /**\n * Track an error within this interaction\n */\n async trackError(params: Omit<TrackErrorParams, 'sessionId'>): Promise<Event | null> {\n // Store failure info\n this.success = false;\n this.failureReason = params.errorMessage;\n\n if (this.degraded) return null;\n\n return this.client.trackError({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /**\n * Get the session ID (null if session creation failed)\n */\n getSessionId(): string | null {\n return this.sessionId;\n }\n\n /**\n * Check if the interaction is in a degraded state (session creation failed)\n */\n isDegraded(): boolean {\n return this.degraded;\n }\n}\n\n// ============================================================================\n// Module-level Simple API\n// ============================================================================\n\nlet defaultClient: SentrialClient | null = null;\n\nfunction getClient(): SentrialClient {\n if (!defaultClient) {\n defaultClient = new SentrialClient();\n }\n return defaultClient;\n}\n\n/**\n * Configure the default Sentrial client\n *\n * @param config - Client configuration\n *\n * @example\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({\n * apiKey: 'sentrial_live_xxx',\n * apiUrl: 'https://api.sentrial.com',\n * });\n * ```\n */\nexport function configure(config: SentrialClientConfig): void {\n defaultClient = new SentrialClient(config);\n // Sync to wrappers so wrapOpenAI/wrapAnthropic/wrapGoogle use the same client\n setDefaultClient(defaultClient);\n}\n\n/**\n * Begin tracking an interaction (module-level convenience function)\n *\n * This is a shorthand for new SentrialClient().begin(...).\n * Configure the client first with sentrial.configure() or set SENTRIAL_API_KEY env var.\n *\n * @param params - Interaction parameters\n * @returns Interaction object with finish() method\n *\n * @example\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const interaction = await sentrial.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * });\n *\n * // ... do your agent work ...\n *\n * await interaction.finish({ output: responseText });\n * ```\n */\nexport function begin(params: BeginParams): Promise<Interaction> {\n return getClient().begin(params);\n}\n\nexport function getVariant(params: GetVariantParams): Promise<VariantAssignment> {\n return getClient().getVariant(params);\n}\n\n/**\n * Simple API namespace for module-level usage\n */\nasync function flush(): Promise<void> {\n if (defaultClient) await defaultClient.flush();\n}\n\nasync function shutdown(): Promise<void> {\n if (defaultClient) await defaultClient.shutdown();\n}\n\nexport const sentrial = {\n configure,\n begin,\n getVariant,\n flush,\n shutdown,\n};\n","/**\n * Vercel AI SDK Integration for Sentrial\n *\n * Automatically traces AI SDK calls with full input/output logging, metrics, and tool execution.\n * Supports Vercel AI SDK v3, v4, v5, and v6.\n *\n * @example Simple Usage\n * ```ts\n * import { configureVercel, wrapAISDK } from '@sentrial/sdk';\n * import * as ai from 'ai';\n * import { openai } from '@ai-sdk/openai';\n *\n * configureVercel({\n * apiKey: process.env.SENTRIAL_API_KEY,\n * defaultAgent: 'my-ai-agent',\n * });\n *\n * const { generateText } = wrapAISDK(ai);\n *\n * // This automatically logs to Sentrial\n * const { text } = await generateText({\n * model: openai('gpt-4'),\n * prompt: 'What is the capital of France?',\n * });\n * ```\n *\n * @example Multi-Turn Conversations\n * ```ts\n * const { generateText } = wrapAISDK(ai, { convoId: 'convo_123' });\n *\n * // All calls are linked to the same conversation thread\n * const { text } = await generateText({\n * model: openai('gpt-4o'),\n * messages: conversationHistory,\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { SentrialClient } from './client.js';\nimport { setDefaultClient } from './wrappers.js';\nimport { calculateOpenAICost, calculateAnthropicCost, calculateGoogleCost } from './cost.js';\n\n// ============================================================================\n// Types for AI SDK (we don't import to keep it as peer dep)\n// ============================================================================\n\ntype AIModule = {\n generateText?: Function;\n streamText?: Function;\n generateObject?: Function;\n streamObject?: Function;\n experimental_generateText?: Function;\n experimental_streamText?: Function;\n};\n\ntype GenerateTextParams = {\n model: { modelId?: string; provider?: string } & Record<string, unknown>;\n prompt?: string;\n messages?: Array<{ role: string; content: string | unknown }>;\n system?: string;\n tools?: Record<string, { execute?: Function } & Record<string, unknown>>;\n maxTokens?: number;\n maxSteps?: number;\n temperature?: number;\n [key: string]: unknown;\n};\n\n/**\n * Usage info — supports both AI SDK v3/v4 (promptTokens/completionTokens)\n * and v5/v6 (inputTokens/outputTokens) field names.\n */\ntype UsageInfo = {\n // AI SDK v5/v6 naming\n inputTokens?: number;\n outputTokens?: number;\n // AI SDK v3/v4 naming (OpenAI-style)\n promptTokens?: number;\n completionTokens?: number;\n // Common\n totalTokens?: number;\n};\n\ntype StepResult = {\n text?: string;\n toolCalls?: Array<{ toolName: string; args: unknown }>;\n toolResults?: Array<{ toolName: string; result: unknown }>;\n usage?: UsageInfo;\n finishReason?: string;\n};\n\ntype GenerateTextResult = {\n text: string;\n usage?: UsageInfo;\n finishReason?: string;\n toolCalls?: Array<{ toolName: string; args: unknown }>;\n toolResults?: Array<{ toolName: string; result: unknown }>;\n response?: { modelId?: string; id?: string };\n steps?: StepResult[];\n reasoning?: unknown;\n reasoningText?: string;\n sources?: unknown[];\n [key: string]: unknown;\n};\n\ntype StreamTextResult = {\n textStream: AsyncIterable<string>;\n fullStream?: AsyncIterable<unknown>;\n text?: string | Promise<string>;\n usage?: Promise<UsageInfo>;\n finishReason?: Promise<string>;\n toolCalls?: Promise<Array<{ toolName: string; args: unknown }>>;\n toolResults?: Promise<Array<{ toolName: string; result: unknown }>>;\n steps?: Promise<StepResult[]>;\n response?: Promise<{ modelId?: string; id?: string }>;\n [key: string]: unknown;\n};\n\n/** String or function that returns a string (for dynamic per-request values) */\ntype StringOrFn = string | (() => string | undefined);\n\nfunction resolveStringOrFn(value: StringOrFn | undefined): string | undefined;\nfunction resolveStringOrFn(value: StringOrFn | undefined, fallback: string): string;\nfunction resolveStringOrFn(value: StringOrFn | undefined, fallback?: string): string | undefined {\n if (typeof value === 'function') return value() ?? fallback;\n return value ?? fallback;\n}\n\n/** Per-instance config passed through closures (not global) */\ntype WrapperConfig = {\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n};\n\n// ============================================================================\n// Global Configuration (used only by configureVercel)\n// ============================================================================\n\nlet _defaultClient: SentrialClient | null = null;\nlet _globalConfig: WrapperConfig = {};\n\n/**\n * Configure the Sentrial SDK for Vercel AI SDK integration.\n *\n * @example\n * ```ts\n * import { configureVercel } from '@sentrial/sdk';\n *\n * configureVercel({\n * apiKey: process.env.SENTRIAL_API_KEY,\n * defaultAgent: 'my-chatbot',\n * userId: 'user_123',\n * });\n * ```\n */\nexport function configureVercel(config: {\n apiKey?: string;\n apiUrl?: string;\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n failSilently?: boolean;\n}): void {\n _defaultClient = new SentrialClient({\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n failSilently: config.failSilently ?? true,\n });\n // Sync to wrappers so all modules share the same client\n setDefaultClient(_defaultClient);\n _globalConfig = {\n defaultAgent: config.defaultAgent,\n userId: config.userId,\n convoId: config.convoId,\n };\n}\n\nfunction getClient(): SentrialClient {\n if (!_defaultClient) {\n _defaultClient = new SentrialClient();\n }\n return _defaultClient;\n}\n\n// ============================================================================\n// Model & Provider Utilities\n// ============================================================================\n\nfunction extractModelInfo(model: GenerateTextParams['model']): {\n modelId: string;\n provider: string;\n} {\n const modelId =\n model.modelId || (model as Record<string, unknown>).id as string || 'unknown';\n // AI SDK v5/v6 returns dotted providers like 'openai.chat', 'anthropic.messages'.\n // Extract the base name for cost calculation and consistent toolName naming.\n const rawProvider = model.provider || '';\n const provider = rawProvider.split('.')[0] || guessProvider(modelId);\n return { modelId, provider };\n}\n\nfunction guessProvider(modelId: string): string {\n const id = modelId.toLowerCase();\n if (id.includes('gpt') || id.startsWith('o1') || id.startsWith('o3') || id.startsWith('o4')\n || id.startsWith('chatgpt')) return 'openai';\n if (id.includes('claude')) return 'anthropic';\n if (id.includes('gemini')) return 'google';\n if (id.includes('mistral') || id.includes('mixtral') || id.includes('codestral')\n || id.includes('pixtral')) return 'mistral';\n if (id.includes('llama')) return 'meta';\n if (id.includes('deepseek')) return 'deepseek';\n if (id.includes('command')) return 'cohere';\n if (id.includes('qwen')) return 'alibaba';\n return 'unknown';\n}\n\nfunction calculateCostForCall(\n provider: string,\n modelId: string,\n promptTokens: number,\n completionTokens: number\n): number {\n const params = { model: modelId, inputTokens: promptTokens, outputTokens: completionTokens };\n switch (provider.toLowerCase()) {\n case 'openai':\n return calculateOpenAICost(params);\n case 'anthropic':\n return calculateAnthropicCost(params);\n case 'google':\n return calculateGoogleCost(params);\n case 'deepseek':\n return (promptTokens / 1_000_000) * 0.27 + (completionTokens / 1_000_000) * 1.10;\n case 'cohere':\n return (promptTokens / 1_000_000) * 0.50 + (completionTokens / 1_000_000) * 1.50;\n case 'mistral':\n return (promptTokens / 1_000_000) * 2.00 + (completionTokens / 1_000_000) * 6.00;\n default:\n return 0;\n }\n}\n\nfunction extractContentText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n // Vercel AI SDK content parts: [{type: \"text\", text: \"...\"}, ...]\n const textParts = content\n .filter((part: Record<string, unknown>) => part && part.type === 'text' && typeof part.text === 'string')\n .map((part: Record<string, unknown>) => part.text as string);\n if (textParts.length > 0) return textParts.join('\\n');\n }\n return JSON.stringify(content);\n}\n\nfunction extractInput(params: GenerateTextParams): string {\n if (params.prompt) return params.prompt;\n if (params.messages && params.messages.length > 0) {\n const lastUserMessage = [...params.messages].reverse().find(m => m.role === 'user');\n if (lastUserMessage) {\n return extractContentText(lastUserMessage.content);\n }\n // No user message found — fall back to last non-system message\n const lastNonSystem = [...params.messages].reverse().find(m => m.role !== 'system');\n if (lastNonSystem) {\n return extractContentText(lastNonSystem.content);\n }\n return '';\n }\n return '';\n}\n\n/**\n * Normalize usage from any AI SDK version into a consistent shape.\n * AI SDK v3/v4 uses promptTokens/completionTokens (OpenAI naming).\n * AI SDK v5/v6 uses inputTokens/outputTokens (generic naming).\n */\nfunction normalizeUsage(usage: UsageInfo | Record<string, unknown> | undefined): {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n} | undefined {\n if (!usage) return undefined;\n const u = usage as Record<string, unknown>;\n const promptTokens = ((u.inputTokens ?? u.promptTokens ?? 0) as number) || 0;\n const completionTokens = ((u.outputTokens ?? u.completionTokens ?? 0) as number) || 0;\n const totalTokens = ((u.totalTokens ?? (promptTokens + completionTokens)) as number) || 0;\n if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0) return undefined;\n return { promptTokens, completionTokens, totalTokens };\n}\n\n// ============================================================================\n// Tool Wrapping\n// ============================================================================\n\nfunction wrapTools(\n tools: Record<string, { execute?: Function } & Record<string, unknown>> | undefined,\n sessionId: string,\n client: SentrialClient\n): Record<string, { execute?: Function } & Record<string, unknown>> | undefined {\n if (!tools) return undefined;\n\n const wrappedTools: Record<string, { execute?: Function } & Record<string, unknown>> = {};\n\n for (const [toolName, tool] of Object.entries(tools)) {\n if (typeof tool.execute === 'function') {\n const originalExecute = tool.execute;\n wrappedTools[toolName] = {\n ...tool,\n execute: async (...args: unknown[]) => {\n const startTime = Date.now();\n try {\n const result = await originalExecute(...args);\n const durationMs = Date.now() - startTime;\n\n // Fire-and-forget: don't let tracking delay tool result\n client.trackToolCall({\n sessionId,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: result as Record<string, unknown>,\n reasoning: `Tool executed in ${durationMs}ms`,\n }).catch(() => {});\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n client.trackToolCall({\n sessionId,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: {},\n toolError: { message: error instanceof Error ? error.message : 'Unknown error' },\n reasoning: `Tool failed after ${durationMs}ms`,\n }).catch(() => {});\n\n throw error;\n }\n },\n };\n } else {\n wrappedTools[toolName] = tool;\n }\n }\n\n return wrappedTools;\n}\n\nfunction wrapToolsAsync(\n tools: Record<string, { execute?: Function } & Record<string, unknown>>,\n sessionPromise: Promise<string | null>,\n client: SentrialClient\n): Record<string, { execute?: Function } & Record<string, unknown>> {\n const wrappedTools: Record<string, { execute?: Function } & Record<string, unknown>> = {};\n\n for (const [toolName, tool] of Object.entries(tools)) {\n if (typeof tool.execute === 'function') {\n const originalExecute = tool.execute;\n wrappedTools[toolName] = {\n ...tool,\n execute: async (...args: unknown[]) => {\n const startTime = Date.now();\n\n // Execute the tool immediately — don't block on session creation.\n // Track asynchronously after the tool completes.\n try {\n const result = await originalExecute(...args);\n const durationMs = Date.now() - startTime;\n\n sessionPromise.then(sid => {\n if (sid) {\n client.trackToolCall({\n sessionId: sid,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: result as Record<string, unknown>,\n reasoning: `Tool executed in ${durationMs}ms`,\n }).catch(() => {});\n }\n });\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n sessionPromise.then(sid => {\n if (sid) {\n client.trackToolCall({\n sessionId: sid,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: {},\n toolError: { message: error instanceof Error ? error.message : 'Unknown error' },\n reasoning: `Tool failed after ${durationMs}ms`,\n }).catch(() => {});\n }\n });\n\n throw error;\n }\n },\n };\n } else {\n wrappedTools[toolName] = tool;\n }\n }\n\n return wrappedTools;\n}\n\n// ============================================================================\n// generateText Wrapper\n// ============================================================================\n\nfunction wrapGenerateText(\n originalFn: Function,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => Promise<GenerateTextResult> {\n return async (params: GenerateTextParams): Promise<GenerateTextResult> => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n const sessionId = await client.createSession({\n name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n model: modelId,\n provider,\n function: 'generateText',\n ...(params.maxSteps ? { maxSteps: params.maxSteps } : {}),\n },\n });\n\n if (!sessionId) {\n return originalFn(params);\n }\n\n await client.setInput(sessionId, input);\n\n const wrappedParams = {\n ...params,\n tools: wrapTools(params.tools, sessionId, client),\n };\n\n try {\n const result: GenerateTextResult = await originalFn(wrappedParams);\n const durationMs = Date.now() - startTime;\n\n const resolvedModelId = result.response?.modelId || modelId;\n\n const usage = normalizeUsage(result.usage);\n const promptTokens = usage?.promptTokens ?? 0;\n const completionTokens = usage?.completionTokens ?? 0;\n const totalTokens = usage?.totalTokens ?? 0;\n const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);\n\n // Track multi-step results (v4+ with maxSteps) — uses trackToolCall with\n // 'llm:provider:model' naming so data maps to proper API fields\n const steps = result.steps;\n if (steps && steps.length >= 1) {\n const stepPromises = steps.map((step) => {\n const su = normalizeUsage(step.usage);\n return client.trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { finishReason: step.finishReason },\n toolOutput: {\n text: step.text?.slice(0, 500),\n tokens: {\n prompt: su?.promptTokens ?? 0,\n completion: su?.completionTokens ?? 0,\n },\n },\n estimatedCost: calculateCostForCall(provider, resolvedModelId, su?.promptTokens ?? 0, su?.completionTokens ?? 0),\n tokenCount: su?.totalTokens,\n metadata: {\n tool_calls: step.toolCalls?.map(tc => tc.toolName),\n },\n }).catch(() => {});\n });\n await Promise.all(stepPromises);\n } else {\n await client.trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { finishReason: result.finishReason },\n toolOutput: {\n text: result.text?.slice(0, 500),\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n metadata: {\n tool_calls: result.toolCalls?.map(tc => tc.toolName),\n },\n }).catch(() => {});\n }\n\n await client.completeSession({\n sessionId,\n success: true,\n output: result.text,\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n });\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n await client.trackError({\n sessionId,\n errorType: error instanceof Error ? error.name : 'Error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n });\n\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: error instanceof Error ? error.message : 'Unknown error',\n durationMs,\n });\n\n throw error;\n }\n };\n}\n\n// ============================================================================\n// streamText Wrapper\n// ============================================================================\n\nfunction wrapStreamText(\n originalFn: Function,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => StreamTextResult {\n return (params: GenerateTextParams): StreamTextResult => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Session creation runs concurrently with the stream setup.\n // onStepFinish/onFinish callbacks await this before tracking.\n let sessionId: string | null = null;\n let sessionCompleted = false;\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n (async () => {\n try {\n const id = await client.createSession({\n name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n model: modelId,\n provider,\n function: 'streamText',\n },\n });\n sessionId = id;\n if (id) {\n client.setInput(id, input).catch(() => {});\n }\n } catch {\n sessionId = null;\n }\n resolveSessionReady!();\n })();\n\n // Preserve user callbacks\n const userOnStepFinish = params.onStepFinish as ((step: Record<string, unknown>) => void) | undefined;\n const userOnFinish = params.onFinish as ((event: Record<string, unknown>) => void) | undefined;\n const userOnError = params.onError as ((event: { error: Error }) => void) | undefined;\n\n // Inject our callbacks into the params — this is the v5/v6-compatible approach.\n // In v5/v6, StreamTextResult is a class with readonly getter properties;\n // mutating result.textStream silently fails. Callbacks are the only way.\n const wrappedParams = {\n ...params,\n tools: params.tools ? wrapToolsAsync(params.tools, sessionReady.then(() => sessionId), client) : undefined,\n\n onStepFinish: async (step: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId) {\n const su = normalizeUsage(step.usage as UsageInfo | undefined);\n const resolvedStepModel = (step.response as { modelId?: string })?.modelId ?? modelId;\n client.trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedStepModel}`,\n toolInput: { finishReason: step.finishReason },\n toolOutput: {\n text: (step.text as string)?.slice(0, 500),\n tokens: {\n prompt: su?.promptTokens ?? 0,\n completion: su?.completionTokens ?? 0,\n },\n },\n estimatedCost: calculateCostForCall(provider, resolvedStepModel, su?.promptTokens ?? 0, su?.completionTokens ?? 0),\n tokenCount: su?.totalTokens,\n metadata: {\n tool_calls: (step.toolCalls as { toolName: string }[] | undefined)?.map(tc => tc.toolName),\n },\n }).catch(() => {});\n }\n if (userOnStepFinish) {\n try { userOnStepFinish(step); } catch { /* don't crash */ }\n }\n },\n\n onFinish: async (event: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n // AI SDK v5/v6: totalUsage (aggregated), v3/v4: usage (single step)\n const usage = normalizeUsage((event.totalUsage ?? event.usage) as UsageInfo | undefined);\n const resolvedModelId = (event.response as { modelId?: string })?.modelId ?? modelId;\n const text = (event.text as string) ?? '';\n\n const promptTokens = usage?.promptTokens ?? 0;\n const completionTokens = usage?.completionTokens ?? 0;\n const totalTokens = usage?.totalTokens ?? (promptTokens + completionTokens);\n const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);\n\n await client.completeSession({\n sessionId,\n success: true,\n output: text,\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n }).catch(() => {});\n }\n if (userOnFinish) {\n try { userOnFinish(event); } catch { /* don't crash */ }\n }\n },\n\n onError: async (event: { error: Error }) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n const msg = event.error?.message ?? 'Unknown error';\n\n await client.trackError({\n sessionId,\n errorType: event.error?.name ?? 'Error',\n errorMessage: msg,\n }).catch(() => {});\n\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: msg,\n durationMs,\n }).catch(() => {});\n }\n if (userOnError) {\n try { userOnError(event); } catch { /* don't crash */ }\n }\n },\n };\n\n // streamText is synchronous in v5/v6 — returns the result immediately.\n // Wrap in try/catch: if it throws (invalid model, missing API key),\n // complete the orphaned session so it doesn't stay \"running\" forever.\n try {\n return originalFn(wrappedParams) as StreamTextResult;\n } catch (error) {\n sessionReady.then(() => {\n if (sessionId) {\n const durationMs = Date.now() - startTime;\n const msg = error instanceof Error ? error.message : String(error);\n client.trackError({ sessionId, errorType: error instanceof Error ? error.name : 'Error', errorMessage: msg }).catch(() => {});\n client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {});\n }\n });\n throw error;\n }\n };\n}\n\n// ============================================================================\n// generateObject Wrapper\n// ============================================================================\n\nfunction wrapGenerateObject(\n originalFn: Function,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => Promise<unknown> {\n return async (params: GenerateTextParams): Promise<unknown> => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n const sessionId = await client.createSession({\n name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n model: modelId,\n provider,\n function: 'generateObject',\n },\n });\n\n if (!sessionId) {\n return originalFn(params);\n }\n\n await client.setInput(sessionId, input);\n\n try {\n const result = await originalFn(params) as {\n object?: unknown;\n usage?: UsageInfo;\n response?: { modelId?: string };\n };\n const durationMs = Date.now() - startTime;\n\n const resolvedModelId = result.response?.modelId || modelId;\n const usage = normalizeUsage(result.usage);\n const promptTokens = usage?.promptTokens ?? 0;\n const completionTokens = usage?.completionTokens ?? 0;\n const totalTokens = usage?.totalTokens ?? 0;\n const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);\n\n await client.trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { function: 'generateObject' },\n toolOutput: {\n object: result.object,\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n }).catch(() => {});\n\n await client.completeSession({\n sessionId,\n success: true,\n output: JSON.stringify(result.object),\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n });\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n await client.trackError({\n sessionId,\n errorType: error instanceof Error ? error.name : 'Error',\n errorMessage: error instanceof Error ? error.message : 'Unknown error',\n });\n\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: error instanceof Error ? error.message : 'Unknown error',\n durationMs,\n });\n\n throw error;\n }\n };\n}\n\n// ============================================================================\n// streamObject Wrapper\n// ============================================================================\n\nfunction wrapStreamObject(\n originalFn: Function,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => unknown {\n return (params: GenerateTextParams): unknown => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Session creation runs concurrently\n let sessionId: string | null = null;\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n (async () => {\n try {\n const id = await client.createSession({\n name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n model: modelId,\n provider,\n function: 'streamObject',\n },\n });\n sessionId = id;\n if (id) {\n client.setInput(id, input).catch(() => {});\n }\n } catch {\n sessionId = null;\n }\n resolveSessionReady!();\n })();\n\n // Preserve user callbacks\n const userOnFinish = params.onFinish as ((event: Record<string, unknown>) => void) | undefined;\n const userOnError = params.onError as ((event: { error: Error }) => void) | undefined;\n\n // Inject onFinish/onError callbacks — v5/v6 result.object is readonly,\n // so we can't intercept the promise chain. Callbacks are the only way.\n const wrappedParams = {\n ...params,\n\n onFinish: async (event: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId) {\n const durationMs = Date.now() - startTime;\n const usage = normalizeUsage(event.usage as UsageInfo | undefined);\n const resolvedModelId = (event.response as { modelId?: string })?.modelId ?? modelId;\n\n const promptTokens = usage?.promptTokens ?? 0;\n const completionTokens = usage?.completionTokens ?? 0;\n const totalTokens = usage?.totalTokens ?? 0;\n const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);\n\n // If the event has an error (schema validation failure), track it\n if (event.error) {\n const errMsg = event.error instanceof Error\n ? event.error.message\n : String(event.error);\n await client.trackError({\n sessionId,\n errorType: 'SchemaValidationError',\n errorMessage: errMsg,\n }).catch(() => {});\n }\n\n await client.trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { function: 'streamObject' },\n toolOutput: {\n object: event.object,\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n }).catch(() => {});\n\n await client.completeSession({\n sessionId,\n success: !event.error,\n output: event.object != null ? JSON.stringify(event.object) : undefined,\n failureReason: event.error ? String(event.error) : undefined,\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n }).catch(() => {});\n }\n if (userOnFinish) {\n try { userOnFinish(event); } catch { /* don't crash */ }\n }\n },\n\n onError: async (event: { error: Error }) => {\n await sessionReady;\n if (sessionId) {\n const durationMs = Date.now() - startTime;\n const msg = event.error?.message ?? 'Unknown error';\n\n await client.trackError({\n sessionId,\n errorType: event.error?.name ?? 'Error',\n errorMessage: msg,\n }).catch(() => {});\n\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: msg,\n durationMs,\n }).catch(() => {});\n }\n if (userOnError) {\n try { userOnError(event); } catch { /* don't crash */ }\n }\n },\n };\n\n try {\n return originalFn(wrappedParams);\n } catch (error) {\n sessionReady.then(() => {\n if (sessionId) {\n const durationMs = Date.now() - startTime;\n const msg = error instanceof Error ? error.message : String(error);\n client.trackError({ sessionId, errorType: error instanceof Error ? error.name : 'Error', errorMessage: msg }).catch(() => {});\n client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {});\n }\n });\n throw error;\n }\n };\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Wrap the Vercel AI SDK to automatically trace all AI calls.\n *\n * @param ai - The imported `ai` module from the Vercel AI SDK\n * @param options - Optional configuration (client, defaultAgent, userId, convoId)\n * @returns Wrapped versions of the AI SDK functions\n *\n * @example Using with configureVercel\n * ```ts\n * configureVercel({ apiKey: process.env.SENTRIAL_API_KEY, defaultAgent: 'my-agent' });\n * const { generateText, streamText } = wrapAISDK(ai);\n * ```\n *\n * @example Multi-turn conversations\n * ```ts\n * const { generateText } = wrapAISDK(ai, { convoId: 'convo_123' });\n * ```\n *\n * @example Using with existing SentrialClient\n * ```ts\n * const client = new SentrialClient({ apiKey: process.env.SENTRIAL_API_KEY });\n * const { generateText } = wrapAISDK(ai, { client, defaultAgent: 'my-agent' });\n * ```\n */\nexport function wrapAISDK(\n ai: AIModule,\n options?: {\n client?: SentrialClient;\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n }\n): {\n generateText: ReturnType<typeof wrapGenerateText>;\n streamText: ReturnType<typeof wrapStreamText>;\n generateObject: ReturnType<typeof wrapGenerateObject>;\n streamObject: ReturnType<typeof wrapStreamObject>;\n} {\n const client = options?.client ?? getClient();\n\n // Create per-instance config — never mutate global state\n const config: WrapperConfig = {\n defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,\n userId: options?.userId ?? _globalConfig.userId,\n convoId: options?.convoId ?? _globalConfig.convoId,\n };\n\n return {\n generateText: ai.generateText\n ? wrapGenerateText(ai.generateText, client, config)\n : wrapGenerateText(\n () => Promise.reject(new Error('generateText not available')),\n client,\n config\n ),\n streamText: ai.streamText\n ? wrapStreamText(ai.streamText, client, config)\n : wrapStreamText(() => ({ textStream: (async function* () {})() }), client, config),\n generateObject: ai.generateObject\n ? wrapGenerateObject(ai.generateObject, client, config)\n : wrapGenerateObject(\n () => Promise.reject(new Error('generateObject not available')),\n client,\n config\n ),\n streamObject: ai.streamObject\n ? wrapStreamObject(ai.streamObject, client, config)\n : wrapStreamObject(() => ({}), client, config),\n };\n}\n\n// Export types for consumers\nexport type { GenerateTextParams, GenerateTextResult, StreamTextResult };\n","/**\n * Sentrial wrapper for the Claude Agent SDK (Claude Code).\n *\n * Wraps the `query()` async generator to automatically track sessions,\n * tool calls, tokens, and costs to Sentrial.\n *\n * @example\n * ```ts\n * import { query } from '@anthropic-ai/claude-agent-sdk';\n * import { SentrialClient, wrapClaudeAgent } from '@sentrial/sdk';\n *\n * const client = new SentrialClient({ apiKey: process.env.SENTRIAL_API_KEY });\n *\n * const trackedQuery = wrapClaudeAgent(query, {\n * client,\n * defaultAgent: 'my-agent',\n * userId: 'user-123',\n * });\n *\n * for await (const message of trackedQuery({ prompt: 'Fix the tests' })) {\n * // Messages pass through unchanged\n * console.log(message.type);\n * }\n * ```\n */\n\nimport type { SentrialClient } from './client.js';\n\n// ============================================================================\n// Types (local — Claude Agent SDK is a peer dep, not imported at runtime)\n// ============================================================================\n\ntype HookCallback = (\n input: any,\n toolUseID: string | undefined,\n options: { signal: AbortSignal },\n) => Promise<any>;\n\ninterface HookCallbackMatcher {\n matcher?: string;\n hooks: HookCallback[];\n}\n\ninterface QueryOptions {\n hooks?: Record<string, HookCallbackMatcher[]>;\n [key: string]: unknown;\n}\n\ninterface QueryParams {\n prompt: string | AsyncIterable<any>;\n options?: QueryOptions;\n}\n\ntype QueryFunction = (params: QueryParams) => AsyncGenerator<any, void>;\n\n// ============================================================================\n// Wrapper Options\n// ============================================================================\n\nexport interface WrapClaudeAgentOptions {\n /** Sentrial client instance for tracking */\n client: SentrialClient;\n /** Default agent name (used in session creation). Defaults to 'claude-agent'. */\n defaultAgent?: string;\n /** User ID to associate with sessions. Defaults to 'anonymous'. */\n userId?: string;\n /** Conversation ID to group related sessions into a thread. */\n convoId?: string;\n /** Extra metadata to include in every session. */\n extraMetadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Wrap the Claude Agent SDK's `query()` function to automatically track\n * sessions, tool calls, tokens, and costs to Sentrial.\n *\n * @param queryFn - The original `query()` function from `@anthropic-ai/claude-agent-sdk`\n * @param wrapOptions - Configuration for the wrapper\n * @returns A new function with the same signature as `query()`\n */\nexport function wrapClaudeAgent(\n queryFn: QueryFunction,\n wrapOptions: WrapClaudeAgentOptions,\n): QueryFunction {\n const {\n client,\n defaultAgent = 'claude-agent',\n userId = 'anonymous',\n convoId,\n extraMetadata,\n } = wrapOptions;\n\n return function wrappedQuery(params: QueryParams): AsyncGenerator<any, void> {\n const { prompt, options = {} } = params;\n const startTime = Date.now();\n let sessionId: string | null = null;\n\n // Session-ready gate: hooks await this before checking sessionId.\n // This prevents hooks that fire during the createSession HTTP request\n // from silently skipping because sessionId is still null.\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n // Build the session name from the prompt\n const sessionName =\n typeof prompt === 'string'\n ? `${defaultAgent}: ${prompt.slice(0, 100)}`\n : `${defaultAgent} session`;\n\n // Collect pending tool call promises so we can flush before completing\n const pendingToolCalls: Promise<unknown>[] = [];\n\n // --- PostToolUse hook for tracking successful tool calls ---\n const sentrialToolHook: HookCallbackMatcher = {\n hooks: [\n async (input: any, toolUseID: string | undefined, _opts: { signal: AbortSignal }) => {\n await sessionReady;\n if (!sessionId) return;\n const toolOutput =\n input?.tool_response && typeof input.tool_response === 'object'\n ? (input.tool_response as Record<string, unknown>)\n : { response: input?.tool_response ?? null };\n\n const p = client\n .trackToolCall({\n sessionId,\n toolName: input?.tool_name ?? 'unknown',\n toolInput: input?.tool_input ?? {},\n toolOutput,\n metadata: { tool_use_id: toolUseID },\n })\n .catch(() => {});\n pendingToolCalls.push(p);\n },\n ],\n };\n\n // --- PostToolUseFailure hook for tracking failed tool calls ---\n const sentrialToolFailureHook: HookCallbackMatcher = {\n hooks: [\n async (input: any, toolUseID: string | undefined, _opts: { signal: AbortSignal }) => {\n await sessionReady;\n if (!sessionId) return;\n const p = client\n .trackToolCall({\n sessionId,\n toolName: input?.tool_name ?? 'unknown',\n toolInput: input?.tool_input ?? {},\n toolOutput: {},\n toolError: { message: input?.error ?? 'unknown error' },\n metadata: { tool_use_id: toolUseID },\n })\n .catch(() => {});\n pendingToolCalls.push(p);\n },\n ],\n };\n\n // Merge hooks: preserve existing user hooks, append ours\n const mergedHooks: Record<string, HookCallbackMatcher[]> = {\n ...(options.hooks ?? {}),\n };\n\n const existingPostToolUse = mergedHooks.PostToolUse ?? [];\n mergedHooks.PostToolUse = [...existingPostToolUse, sentrialToolHook];\n\n const existingPostToolUseFailure = mergedHooks.PostToolUseFailure ?? [];\n mergedHooks.PostToolUseFailure = [...existingPostToolUseFailure, sentrialToolFailureHook];\n\n const mergedOptions: QueryOptions = {\n ...options,\n hooks: mergedHooks,\n };\n\n // Call the original query function with merged options\n const generator = queryFn({ prompt, options: mergedOptions });\n\n // Wrap the generator to observe messages\n return (async function* () {\n try {\n for await (const message of generator) {\n // --- Observe init message: create session ---\n if (message.type === 'system' && message.subtype === 'init') {\n const metadata: Record<string, unknown> = {\n model: message.model,\n tools: message.tools,\n cwd: message.cwd,\n mcp_servers: message.mcp_servers,\n sdk_session_id: message.session_id,\n ...(extraMetadata ?? {}),\n };\n\n // Await session creation so sessionId is available for tool call hooks\n try {\n sessionId = await client.createSession({\n name: sessionName,\n agentName: defaultAgent,\n userId,\n convoId,\n metadata,\n });\n } catch {\n // Degrade to passthrough if session creation fails\n sessionId = null;\n }\n // Unblock hooks waiting for session creation (success or failure)\n resolveSessionReady!();\n }\n\n // --- Observe result message: complete session ---\n // Await completeSession here because result is the terminal message —\n // no more yields follow, and short-lived processes may exit immediately.\n if (message.type === 'result' && sessionId) {\n const isError = !!message.is_error;\n const inputTokens = message.usage?.input_tokens ?? 0;\n const outputTokens = message.usage?.output_tokens ?? 0;\n\n let failureReason: string | undefined;\n if (isError) {\n if (message.errors && message.errors.length > 0) {\n failureReason = message.errors.join('; ');\n } else {\n failureReason = message.subtype;\n }\n }\n\n // Flush pending tool call tracking before completing the session\n await Promise.allSettled(pendingToolCalls);\n\n try {\n await client.completeSession({\n sessionId,\n success: !isError,\n failureReason,\n estimatedCost: message.total_cost_usd,\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens: inputTokens + outputTokens,\n durationMs: message.duration_ms ?? (Date.now() - startTime),\n userInput: typeof prompt === 'string' ? prompt : undefined,\n output: message.result,\n customMetrics: {\n num_turns: message.num_turns ?? 0,\n duration_api_ms: message.duration_api_ms ?? 0,\n },\n });\n } catch {\n // Swallow — never break the agent due to tracking failures\n }\n }\n\n yield message;\n }\n } catch (error) {\n // If the generator throws, complete the session as failed\n if (sessionId) {\n await Promise.allSettled(pendingToolCalls);\n try {\n await client.completeSession({\n sessionId,\n success: false,\n failureReason:\n error instanceof Error ? error.message : String(error),\n durationMs: Date.now() - startTime,\n });\n } catch {\n // Never break the agent due to tracking failures\n }\n }\n throw error;\n }\n })();\n };\n}\n","/**\n * Sentrial Decorators - Easy instrumentation for AI agents\n *\n * Provides decorators and higher-order functions for automatic tracking:\n * - withTool: Track tool/function calls\n * - withSession: Create session boundaries around agent runs\n *\n * @example Using higher-order functions (works everywhere)\n * ```ts\n * import { withTool, withSession, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * // Wrap a tool function\n * const searchWeb = withTool('search', async (query: string) => {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * });\n *\n * // Wrap an agent session\n * const handleRequest = withSession('support-agent', async (userId: string, message: string) => {\n * const results = await searchWeb(message);\n * return processResults(results);\n * });\n * ```\n *\n * @example Using TypeScript decorators (requires experimentalDecorators)\n * ```ts\n * import { Tool, Session } from '@sentrial/sdk';\n *\n * class MyAgent {\n * @Tool('search')\n * async searchWeb(query: string) {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * }\n *\n * @Session('support-agent')\n * async handleRequest(userId: string, message: string) {\n * const results = await this.searchWeb(message);\n * return processResults(results);\n * }\n * }\n * ```\n */\n\nimport { SentrialClient, Interaction } from './client.js';\nimport {\n setSessionContext,\n clearSessionContext,\n getSessionContext,\n setDefaultClient,\n getDefaultClient,\n _setSessionContextWithTokens,\n _restoreSessionContext,\n type SessionContextTokens,\n} from './wrappers.js';\nimport { createContextVar, type ContextToken } from './async-context.js';\n\n// Re-export context functions\nexport { setSessionContext, clearSessionContext, getSessionContext };\n\n// ============================================================================\n// Context Management\n// ============================================================================\n\nlet _defaultClient: SentrialClient | null = null;\nconst _currentInteraction = createContextVar<Interaction | null>(null);\n\n/**\n * Get the default client, creating one if needed\n */\nfunction getClient(): SentrialClient | null {\n const configuredClient = getDefaultClient();\n if (configuredClient) {\n _defaultClient = configuredClient;\n return configuredClient;\n }\n\n if (!_defaultClient) {\n try {\n _defaultClient = new SentrialClient();\n setDefaultClient(_defaultClient);\n } catch {\n return null;\n }\n }\n return _defaultClient;\n}\n\n/**\n * Set the default client for decorators\n */\nexport function setClient(client: SentrialClient): void {\n _defaultClient = client;\n setDefaultClient(client);\n}\n\n/**\n * Get the current session ID (if inside a session context)\n */\nexport function getCurrentSessionId(): string | null {\n return getSessionContext();\n}\n\n/**\n * Get the current interaction (if inside a session context)\n */\nexport function getCurrentInteraction(): Interaction | null {\n return _currentInteraction.get();\n}\n\n// ============================================================================\n// withTool - Higher-order function for tool tracking\n// ============================================================================\n\ntype AsyncFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => Promise<TReturn>;\ntype SyncFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn;\n\n/**\n * Wrap a function to automatically track it as a tool call.\n *\n * When called within a session context, automatically tracks:\n * - tool_input: Function arguments\n * - tool_output: Return value\n * - duration: Execution time\n * - errors: Any exceptions raised\n *\n * @param name - Name of the tool\n * @param fn - The function to wrap\n * @returns Wrapped function with automatic tracking\n *\n * @example\n * ```ts\n * const searchWeb = withTool('search', async (query: string) => {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * });\n *\n * // Use within a session - automatically tracked\n * const results = await searchWeb('AI agents');\n * ```\n */\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: AsyncFunction<TArgs, TReturn>\n): AsyncFunction<TArgs, TReturn>;\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: SyncFunction<TArgs, TReturn>\n): SyncFunction<TArgs, TReturn>;\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: (...args: TArgs) => TReturn | Promise<TReturn>\n): (...args: TArgs) => TReturn | Promise<TReturn> {\n // Check if function is async\n const isAsync = fn.constructor.name === 'AsyncFunction';\n\n if (isAsync) {\n return async function (...args: TArgs): Promise<TReturn> {\n return trackToolAsync(name, fn as AsyncFunction<TArgs, TReturn>, args);\n };\n } else {\n return function (...args: TArgs): TReturn {\n return trackToolSync(name, fn as SyncFunction<TArgs, TReturn>, args);\n };\n }\n}\n\nasync function trackToolAsync<TArgs extends unknown[], TReturn>(\n toolName: string,\n fn: AsyncFunction<TArgs, TReturn>,\n args: TArgs\n): Promise<TReturn> {\n const startTime = Date.now();\n const toolInput = buildToolInput(args);\n\n const client = getClient();\n const sessionId = getSessionContext();\n\n try {\n const result = await fn(...args);\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n const toolOutput = serializeOutput(result);\n client.trackToolCall({\n sessionId,\n toolName,\n toolInput,\n toolOutput,\n metadata: { duration_ms: durationMs },\n }).catch((err) => {\n console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);\n });\n }\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n client.trackError({\n sessionId,\n errorMessage: (error as Error).message,\n errorType: (error as Error).name,\n toolName,\n stackTrace: (error as Error).stack,\n metadata: { duration_ms: durationMs },\n }).catch(() => {});\n }\n\n throw error;\n }\n}\n\nfunction trackToolSync<TArgs extends unknown[], TReturn>(\n toolName: string,\n fn: SyncFunction<TArgs, TReturn>,\n args: TArgs\n): TReturn {\n const startTime = Date.now();\n const toolInput = buildToolInput(args);\n\n const client = getClient();\n const sessionId = getSessionContext();\n\n try {\n const result = fn(...args);\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n const toolOutput = serializeOutput(result);\n client.trackToolCall({\n sessionId,\n toolName,\n toolInput,\n toolOutput,\n metadata: { duration_ms: durationMs },\n }).catch((err) => {\n console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);\n });\n }\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n client.trackError({\n sessionId,\n errorMessage: (error as Error).message,\n errorType: (error as Error).name,\n toolName,\n stackTrace: (error as Error).stack,\n metadata: { duration_ms: durationMs },\n }).catch(() => {});\n }\n\n throw error;\n }\n}\n\n// ============================================================================\n// withSession - Higher-order function for session management\n// ============================================================================\n\ninterface SessionOptions {\n /** Parameter index or name for user ID (default: looks for 'userId' or first param) */\n userIdParam?: string | number;\n /** Parameter index or name for input (default: looks for 'input', 'message', 'query') */\n inputParam?: string | number;\n}\n\n/**\n * Wrap a function to automatically manage a session around it.\n *\n * Automatically:\n * - Creates a session when the function is called\n * - Sets up context for withTool and wrapped LLM calls\n * - Captures output from return value\n * - Completes the session when the function returns\n * - Marks as failed if an exception is raised\n *\n * @param agentName - Name of the agent\n * @param fn - The function to wrap\n * @param options - Session options\n * @returns Wrapped function with automatic session management\n *\n * @example\n * ```ts\n * const handleRequest = withSession('support-agent', async (userId: string, message: string) => {\n * // Session automatically created\n * // All withTool calls and wrapped LLM calls are tracked\n * const response = await processRequest(message);\n * return response; // Captured as output\n * });\n *\n * await handleRequest('user_123', 'Hello!');\n * ```\n */\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: AsyncFunction<TArgs, TReturn>,\n options?: SessionOptions\n): AsyncFunction<TArgs, TReturn>;\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: SyncFunction<TArgs, TReturn>,\n options?: SessionOptions\n): AsyncFunction<TArgs, TReturn>; // Session always returns a Promise\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: (...args: TArgs) => TReturn | Promise<TReturn>,\n options: SessionOptions = {}\n): (...args: TArgs) => Promise<TReturn> {\n return async function (...args: TArgs): Promise<TReturn> {\n const client = getClient();\n if (!client) {\n // No client, just run the function\n return fn(...args) as Promise<TReturn>;\n }\n\n // Extract userId and input from args\n const { userId, userInput } = extractParams(args, options);\n\n // Create interaction\n const interaction = await client.begin({\n userId,\n event: agentName,\n input: userInput,\n });\n\n // Save previous context and set new one (supports nesting)\n const sessionId = interaction.getSessionId();\n let sessionTokens: SessionContextTokens | undefined;\n if (sessionId) {\n sessionTokens = _setSessionContextWithTokens(sessionId, client);\n }\n const interactionToken = _currentInteraction.set(interaction);\n\n try {\n const result = await fn(...args);\n\n // Capture output\n let output: string | undefined;\n if (typeof result === 'string') {\n output = result;\n } else if (result && typeof result === 'object') {\n if ('response' in result) {\n output = String((result as any).response);\n } else if ('output' in result) {\n output = String((result as any).output);\n }\n }\n if (output === undefined && result !== null && result !== undefined) {\n output = String(result).slice(0, 1000);\n }\n\n await interaction.finish({ output, success: true });\n return result;\n } catch (error) {\n await interaction.finish({\n success: false,\n failureReason: `${(error as Error).name}: ${(error as Error).message}`,\n });\n throw error;\n } finally {\n // Restore previous context (supports nesting)\n if (sessionTokens) {\n _restoreSessionContext(sessionTokens);\n }\n _currentInteraction.reset(interactionToken);\n }\n };\n}\n\nfunction extractParams(\n args: unknown[],\n options: SessionOptions\n): { userId: string; userInput: string | undefined } {\n let userId = 'anonymous';\n let userInput: string | undefined;\n\n // Extract userId\n if (typeof options.userIdParam === 'number') {\n userId = String(args[options.userIdParam] ?? 'anonymous');\n } else if (typeof options.userIdParam === 'string') {\n // Can't access by name in JS, use index 0\n userId = String(args[0] ?? 'anonymous');\n } else {\n // Default: first arg is userId\n userId = String(args[0] ?? 'anonymous');\n }\n\n // Extract input\n if (typeof options.inputParam === 'number') {\n userInput = String(args[options.inputParam] ?? '');\n } else {\n // Default: second arg is input\n const input = args[1];\n if (typeof input === 'string') {\n userInput = input;\n } else if (Array.isArray(input)) {\n userInput = JSON.stringify(input).slice(0, 500);\n }\n }\n\n return { userId, userInput };\n}\n\n// ============================================================================\n// TypeScript Decorators (requires experimentalDecorators)\n// ============================================================================\n\n/**\n * Method decorator to track a method as a tool call.\n *\n * @param name - Name of the tool (optional, defaults to method name)\n *\n * @example\n * ```ts\n * class MyAgent {\n * @Tool('search')\n * async searchWeb(query: string) {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * }\n * }\n * ```\n */\nexport function Tool(name?: string): MethodDecorator {\n return function (\n _target: object,\n propertyKey: string | symbol,\n descriptor: PropertyDescriptor\n ): PropertyDescriptor {\n const originalMethod = descriptor.value;\n const toolName = name ?? String(propertyKey);\n\n descriptor.value = async function (...args: unknown[]) {\n const startTime = Date.now();\n const toolInput = buildToolInput(args);\n\n const client = getClient();\n const sessionId = getSessionContext();\n\n try {\n const result = await originalMethod.apply(this, args);\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n const toolOutput = serializeOutput(result);\n client.trackToolCall({\n sessionId,\n toolName,\n toolInput,\n toolOutput,\n metadata: { duration_ms: durationMs },\n }).catch((err) => {\n console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);\n });\n }\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n client.trackError({\n sessionId,\n errorMessage: (error as Error).message,\n errorType: (error as Error).name,\n toolName,\n stackTrace: (error as Error).stack,\n metadata: { duration_ms: durationMs },\n }).catch(() => {});\n }\n\n throw error;\n }\n };\n\n return descriptor;\n };\n}\n\n/**\n * Method decorator to wrap a method in a session.\n *\n * @param agentName - Name of the agent (optional, defaults to class name)\n * @param options - Session options\n *\n * @example\n * ```ts\n * class MyAgent {\n * @TrackSession('support-agent')\n * async handleRequest(userId: string, message: string) {\n * // Session automatically created\n * const results = await this.searchWeb(message);\n * return processResults(results);\n * }\n * }\n * ```\n */\nexport function TrackSession(agentName?: string, options?: SessionOptions): MethodDecorator {\n return function (\n target: object,\n _propertyKey: string | symbol,\n descriptor: PropertyDescriptor\n ): PropertyDescriptor {\n const originalMethod = descriptor.value;\n const agent = agentName ?? target.constructor.name;\n\n descriptor.value = async function (...args: unknown[]) {\n const client = getClient();\n if (!client) {\n return originalMethod.apply(this, args);\n }\n\n const { userId, userInput } = extractParams(args, options ?? {});\n\n const interaction = await client.begin({\n userId,\n event: agent,\n input: userInput,\n });\n\n // Save previous context and set new one (supports nesting)\n const sessionId = interaction.getSessionId();\n let sessionTokens: SessionContextTokens | undefined;\n if (sessionId) {\n sessionTokens = _setSessionContextWithTokens(sessionId, client);\n }\n const interactionToken = _currentInteraction.set(interaction);\n\n try {\n const result = await originalMethod.apply(this, args);\n\n let output: string | undefined;\n if (typeof result === 'string') {\n output = result;\n } else if (result && typeof result === 'object') {\n if ('response' in result) {\n output = String((result as any).response);\n } else if ('output' in result) {\n output = String((result as any).output);\n }\n }\n if (output === undefined && result !== null && result !== undefined) {\n output = String(result).slice(0, 1000);\n }\n\n await interaction.finish({ output, success: true });\n return result;\n } catch (error) {\n await interaction.finish({\n success: false,\n failureReason: `${(error as Error).name}: ${(error as Error).message}`,\n });\n throw error;\n } finally {\n // Restore previous context (supports nesting)\n if (sessionTokens) {\n _restoreSessionContext(sessionTokens);\n }\n _currentInteraction.reset(interactionToken);\n }\n };\n\n return descriptor;\n };\n}\n\n// ============================================================================\n// SessionContext - Manual session control\n// ============================================================================\n\n/**\n * Manual session context for when you need more control.\n *\n * @example\n * ```ts\n * const ctx = new SessionContext({\n * userId: 'user_123',\n * agent: 'my-agent',\n * input: 'Hello!',\n * });\n *\n * await ctx.start();\n * try {\n * // All tool calls are tracked\n * const result = await myTool(...);\n * ctx.setOutput(result);\n * await ctx.finish();\n * } catch (error) {\n * await ctx.finish({ success: false, error: error.message });\n * }\n * ```\n */\nexport class SessionContext {\n private readonly userId: string;\n private readonly agent: string;\n private readonly input?: string;\n private readonly client: SentrialClient | null;\n private interaction: Interaction | null = null;\n private output?: string;\n private sessionTokens?: SessionContextTokens;\n private interactionToken?: ContextToken<Interaction | null>;\n\n constructor(options: { userId: string; agent: string; input?: string; client?: SentrialClient }) {\n this.userId = options.userId;\n this.agent = options.agent;\n this.input = options.input;\n this.client = options.client ?? getClient();\n }\n\n /**\n * Start the session\n */\n async start(): Promise<this> {\n if (!this.client) return this;\n\n this.interaction = await this.client.begin({\n userId: this.userId,\n event: this.agent,\n input: this.input,\n });\n\n const sessionId = this.interaction.getSessionId();\n if (sessionId) {\n this.sessionTokens = _setSessionContextWithTokens(sessionId, this.client);\n }\n this.interactionToken = _currentInteraction.set(this.interaction);\n\n return this;\n }\n\n /**\n * Set the output for this session\n */\n setOutput(output: string): void {\n this.output = output;\n }\n\n /**\n * Finish the session\n */\n async finish(options?: { success?: boolean; error?: string }): Promise<void> {\n if (this.interaction) {\n await this.interaction.finish({\n output: this.output,\n success: options?.success ?? true,\n failureReason: options?.error,\n });\n }\n\n // Restore previous context (supports nesting)\n if (this.sessionTokens) {\n _restoreSessionContext(this.sessionTokens);\n }\n if (this.interactionToken) {\n _currentInteraction.reset(this.interactionToken);\n }\n }\n\n /**\n * Get the session ID\n */\n get sessionId(): string | null {\n return this.interaction?.getSessionId() ?? null;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction buildToolInput(args: unknown[]): Record<string, unknown> {\n if (args.length === 0) {\n return {};\n }\n if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) {\n return serializeValue(args[0]) as Record<string, unknown>;\n }\n return {\n args: args.map(serializeValue),\n };\n}\n\nfunction serializeValue(value: unknown): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map(serializeValue);\n }\n if (typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = serializeValue(v);\n }\n return result;\n }\n try {\n return String(value).slice(0, 1000);\n } catch {\n return `<${typeof value}>`;\n }\n}\n\nfunction serializeOutput(value: unknown): Record<string, unknown> {\n if (value === null || value === undefined) {\n return { result: null };\n }\n if (typeof value === 'object' && !Array.isArray(value)) {\n return serializeValue(value) as Record<string, unknown>;\n }\n return { result: serializeValue(value) };\n}\n","/**\n * Sentrial Experiment Context\n *\n * Provides context-aware configuration for experiments. When running experiments\n * via the CLI, the system prompt and other experiment config is automatically\n * available to your agent code via simple function calls.\n *\n * @example\n * ```ts\n * import { getSystemPrompt } from '@sentrial/sdk';\n *\n * async function myAgent(query: string): Promise<string> {\n * // When running normally: returns your default\n * // When running experiment: returns the variant's system prompt\n * const systemPrompt = getSystemPrompt('You are a helpful assistant.');\n *\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: systemPrompt },\n * { role: 'user', content: query },\n * ],\n * });\n *\n * return response.choices[0].message.content!;\n * }\n * ```\n *\n * That's it! No other changes needed. The experiment runner handles setting the context.\n */\n\n// ============================================================================\n// Experiment Context Types\n// ============================================================================\n\n/**\n * Context for the current experiment run.\n */\nexport interface ExperimentContext {\n /** The system prompt for this variant */\n systemPrompt: string;\n /** Name of the variant being tested */\n variantName: string;\n /** The experiment ID */\n experimentId: string;\n /** The test case session ID (if applicable) */\n testCaseId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Context Storage (async-safe via AsyncLocalStorage in Node.js)\n// ============================================================================\n\nimport { createContextVar } from './async-context.js';\n\nconst _experimentContext = createContextVar<ExperimentContext | null>(null);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Get the current system prompt.\n *\n * When running an experiment, this returns the variant's system prompt.\n * Otherwise, returns your default.\n *\n * @param defaultPrompt - The default system prompt to use when not in experiment mode.\n * This should be your normal production prompt.\n * @returns The system prompt to use.\n *\n * @example\n * ```ts\n * import { getSystemPrompt } from '@sentrial/sdk';\n *\n * async function myAgent(query: string) {\n * // Just wrap your existing prompt with getSystemPrompt()\n * const prompt = getSystemPrompt('You are a helpful weather assistant.');\n *\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: prompt },\n * { role: 'user', content: query },\n * ],\n * });\n *\n * return response.choices[0].message.content;\n * }\n * ```\n */\nexport function getSystemPrompt(defaultPrompt?: string): string {\n const ctx = _experimentContext.get();\n if (ctx?.systemPrompt) {\n return ctx.systemPrompt;\n }\n return defaultPrompt ?? '';\n}\n\n/**\n * Get the full experiment context if running in experiment mode.\n *\n * Returns null if not running an experiment.\n *\n * @returns ExperimentContext with variant info, or null.\n *\n * @example\n * ```ts\n * import { getExperimentContext } from '@sentrial/sdk';\n *\n * const ctx = getExperimentContext();\n * if (ctx) {\n * console.log(`Running variant: ${ctx.variantName}`);\n * }\n * ```\n */\nexport function getExperimentContext(): ExperimentContext | null {\n return _experimentContext.get();\n}\n\n/**\n * Check if currently running in experiment mode.\n *\n * @returns True if running via experiment runner, false otherwise.\n */\nexport function isExperimentMode(): boolean {\n return _experimentContext.get() !== null;\n}\n\n/**\n * Get the current variant name if in experiment mode.\n *\n * @returns The variant name (e.g., \"Control\", \"Variant A\"), or null.\n */\nexport function getVariantName(): string | null {\n return _experimentContext.get()?.variantName ?? null;\n}\n\n/**\n * Get the current experiment ID if in experiment mode.\n *\n * @returns The experiment ID, or null.\n */\nexport function getExperimentId(): string | null {\n return _experimentContext.get()?.experimentId ?? null;\n}\n\n// ============================================================================\n// Internal Functions (used by experiment runner)\n// ============================================================================\n\n/**\n * Set the experiment context. Called by the experiment runner before running agent.\n *\n * @internal This is an internal function - users don't need to call this directly.\n */\nexport function setExperimentContext(context: ExperimentContext): void {\n _experimentContext.set(context);\n}\n\n/**\n * Clear the experiment context. Called by the experiment runner after agent completes.\n *\n * @internal This is an internal function - users don't need to call this directly.\n */\nexport function clearExperimentContext(): void {\n _experimentContext.set(null);\n}\n","/**\n * Sentrial Experiments - Run prompt A/B tests with results synced to Sentrial\n *\n * @example\n * ```ts\n * import { Experiment } from '@sentrial/sdk';\n *\n * const experiment = new Experiment('exp_abc123');\n *\n * // Get config\n * await experiment.load();\n * console.log(`Testing ${experiment.variants.length} variants on ${experiment.testCases.length} inputs`);\n *\n * // Run with your agent function\n * await experiment.run(async (testCase, variant, tracker) => {\n * // Create a session for this run\n * const interaction = await client.begin({\n * userId: 'experiment',\n * event: 'my-agent',\n * input: testCase.userInput,\n * });\n * tracker.setResultSessionId(interaction.getSessionId()!);\n *\n * // Run your agent with the variant's system prompt\n * const response = await runAgent(testCase.userInput, variant.systemPrompt);\n *\n * await interaction.finish({ output: response, success: true });\n * });\n * ```\n *\n * @example Manual run tracking\n * ```ts\n * const experiment = new Experiment('exp_abc123');\n * await experiment.load();\n *\n * for (const variant of experiment.variants) {\n * for (const testCase of experiment.testCases) {\n * const tracker = await experiment.trackRun(variant.name, testCase.sessionId);\n * try {\n * const sessionId = await runAgent(testCase.userInput, variant.systemPrompt);\n * tracker.setResultSessionId(sessionId);\n * await tracker.complete();\n * } catch (error) {\n * await tracker.fail(error.message);\n * }\n * }\n * }\n * ```\n */\n\nimport { SentrialClient } from './client.js';\nimport { setExperimentContext, clearExperimentContext } from './context.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A prompt variant to test.\n */\nexport interface ExperimentVariant {\n /** Name of the variant */\n name: string;\n /** The system prompt for this variant */\n systemPrompt: string;\n /** Optional description */\n description?: string;\n}\n\n/**\n * A test case (input) to run against each variant.\n */\nexport interface ExperimentTestCase {\n /** Session ID of the original session */\n sessionId: string;\n /** The user input to test */\n userInput: string;\n}\n\n/**\n * Result of a single experiment run.\n */\nexport interface ExperimentRunResult {\n /** Name of the variant */\n variantName: string;\n /** Session ID of the original test case */\n testCaseSessionId: string;\n /** Session ID of the result session (if created) */\n resultSessionId?: string;\n /** Whether the run succeeded */\n success: boolean;\n /** Error message if failed */\n errorMessage?: string;\n}\n\n/**\n * Aggregated experiment results.\n */\nexport interface ExperimentResults {\n experimentId: string;\n status: string;\n variants: Array<{\n name: string;\n successRate: number;\n avgScore?: number;\n avgCost?: number;\n avgDuration?: number;\n runCount: number;\n }>;\n winner?: string;\n}\n\n// ============================================================================\n// Run Tracker\n// ============================================================================\n\n/**\n * Tracks a single experiment run.\n *\n * Created by Experiment.trackRun() - use this to manually track runs.\n */\nexport class ExperimentRunTracker {\n private readonly experiment: Experiment;\n private readonly variantName: string;\n private readonly baseSessionId: string;\n private runId?: string;\n private resultSessionId?: string;\n private _success = true;\n private _errorMessage?: string;\n\n /** @internal */\n constructor(experiment: Experiment, variantName: string, baseSessionId: string) {\n this.experiment = experiment;\n this.variantName = variantName;\n this.baseSessionId = baseSessionId;\n }\n\n /**\n * Start the run - creates a run record via API.\n */\n async start(): Promise<this> {\n try {\n const response = await this.experiment.request<{ run?: { id: string } }>(\n 'POST',\n `/api/experiments/${this.experiment.experimentId}/runs`,\n {\n variantName: this.variantName,\n baseSessionId: this.baseSessionId,\n }\n );\n this.runId = response?.run?.id;\n } catch {\n // Ignore errors starting the run\n }\n return this;\n }\n\n /**\n * Set the session ID of the result session.\n */\n setResultSessionId(sessionId: string): void {\n this.resultSessionId = sessionId;\n }\n\n /**\n * Mark the run as complete.\n */\n async complete(): Promise<void> {\n if (!this.runId) return;\n\n try {\n await this.experiment.request(\n 'PATCH',\n `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,\n {\n status: 'completed',\n resultSessionId: this.resultSessionId,\n }\n );\n } catch {\n // Ignore errors completing the run\n }\n }\n\n /**\n * Mark the run as failed.\n */\n async fail(errorMessage: string): Promise<void> {\n this._success = false;\n this._errorMessage = errorMessage;\n\n if (!this.runId) return;\n\n try {\n await this.experiment.request(\n 'PATCH',\n `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,\n {\n status: 'failed',\n resultSessionId: this.resultSessionId,\n errorMessage,\n }\n );\n } catch {\n // Ignore errors failing the run\n }\n }\n\n /**\n * Get the result of this run.\n */\n getResult(): ExperimentRunResult {\n return {\n variantName: this.variantName,\n testCaseSessionId: this.baseSessionId,\n resultSessionId: this.resultSessionId,\n success: this._success,\n errorMessage: this._errorMessage,\n };\n }\n}\n\n// ============================================================================\n// Experiment Class\n// ============================================================================\n\ntype AgentFunction = (\n testCase: ExperimentTestCase,\n variant: ExperimentVariant,\n tracker: ExperimentRunTracker\n) => Promise<void>;\n\n/**\n * Run experiments with results synced to Sentrial.\n *\n * @example\n * ```ts\n * const experiment = new Experiment('exp_abc123');\n *\n * await experiment.load();\n * console.log(`Testing ${experiment.variants.length} variants`);\n *\n * await experiment.run(async (testCase, variant, tracker) => {\n * const sessionId = await runMyAgent(testCase.userInput, variant.systemPrompt);\n * tracker.setResultSessionId(sessionId);\n * });\n * ```\n */\nexport class Experiment {\n /** The experiment ID */\n readonly experimentId: string;\n\n /** @internal */\n readonly client: SentrialClient;\n /** @internal */\n readonly apiUrl: string;\n /** @internal */\n readonly apiKey?: string;\n \n private config?: Record<string, unknown>;\n private _variants?: ExperimentVariant[];\n private _testCases?: ExperimentTestCase[];\n\n /**\n * Create an experiment instance.\n *\n * @param experimentId - The experiment ID from Sentrial dashboard\n * @param options - Configuration options\n */\n constructor(\n experimentId: string,\n options: {\n apiKey?: string;\n apiUrl?: string;\n } = {}\n ) {\n this.experimentId = experimentId;\n this.apiUrl = (\n options.apiUrl ??\n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_URL : undefined) ??\n 'https://api.sentrial.com'\n ).replace(/\\/$/, '');\n this.apiKey = options.apiKey ?? \n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_KEY : undefined);\n this.client = new SentrialClient({\n apiKey: this.apiKey,\n apiUrl: this.apiUrl,\n failSilently: false, // We want errors for experiments\n });\n }\n\n /**\n * Make an HTTP request to the API\n * @internal\n */\n async request<T>(method: string, path: string, body?: unknown): Promise<T | null> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const response = await fetch(`${this.apiUrl}${path}`, {\n method,\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n\n if (!response.ok) {\n return null;\n }\n\n return response.json() as Promise<T>;\n }\n\n /**\n * Load the experiment configuration from the API.\n */\n async load(): Promise<this> {\n interface ExperimentConfig {\n experiment?: {\n name?: string;\n status?: string;\n variants?: Array<{\n name: string;\n systemPrompt?: string;\n description?: string;\n }>;\n testCases?: Array<{\n sessionId: string;\n userInput?: string;\n }>;\n };\n }\n\n const response = await this.request<ExperimentConfig>(\n 'GET',\n `/api/experiments/${this.experimentId}/runs`\n );\n\n if (!response?.experiment) {\n throw new Error(`Failed to load experiment config for ${this.experimentId}`);\n }\n\n this.config = response.experiment;\n\n // Parse variants\n const variants = this.config.variants as Array<{ name: string; systemPrompt?: string; description?: string }> | undefined;\n this._variants = variants?.map((v) => ({\n name: v.name ?? '',\n systemPrompt: v.systemPrompt ?? '',\n description: v.description,\n })) ?? [];\n\n // Parse test cases (only include those with input)\n const testCases = this.config.testCases as Array<{ sessionId: string; userInput?: string }> | undefined;\n this._testCases = testCases\n ?.filter((tc) => tc.userInput)\n ?.map((tc) => ({\n sessionId: tc.sessionId ?? '',\n userInput: tc.userInput ?? '',\n })) ?? [];\n\n return this;\n }\n\n /**\n * Get experiment name.\n */\n get name(): string {\n return (this.config?.name as string) ?? '';\n }\n\n /**\n * Get experiment status.\n */\n get status(): string {\n return (this.config?.status as string) ?? '';\n }\n\n /**\n * Get list of variants to test.\n */\n get variants(): ExperimentVariant[] {\n if (!this._variants) {\n throw new Error('Experiment not loaded. Call load() first.');\n }\n return this._variants;\n }\n\n /**\n * Get list of test cases.\n */\n get testCases(): ExperimentTestCase[] {\n if (!this._testCases) {\n throw new Error('Experiment not loaded. Call load() first.');\n }\n return this._testCases;\n }\n\n /**\n * Get a specific variant by name.\n */\n getVariant(name: string): ExperimentVariant | undefined {\n return this.variants.find((v) => v.name === name);\n }\n\n /**\n * Create a run tracker for manual experiment runs.\n */\n async trackRun(variantName: string, baseSessionId: string): Promise<ExperimentRunTracker> {\n const tracker = new ExperimentRunTracker(this, variantName, baseSessionId);\n await tracker.start();\n return tracker;\n }\n\n /**\n * Run the experiment with all variants and test cases.\n *\n * @param agentFn - Function that runs your agent with a test case and variant\n * @param options - Run options\n * @returns List of run results\n */\n async run(\n agentFn: AgentFunction,\n options: {\n /** Run test cases in parallel */\n parallel?: boolean;\n /** Max parallel workers */\n maxWorkers?: number;\n } = {}\n ): Promise<ExperimentRunResult[]> {\n const { parallel = false, maxWorkers = 4 } = options;\n\n if (!this._variants || !this._testCases) {\n await this.load();\n }\n\n const results: ExperimentRunResult[] = [];\n const totalRuns = this.variants.length * this.testCases.length;\n let completed = 0;\n\n console.log(`Running experiment: ${this.name || this.experimentId}`);\n console.log(` Variants: ${this.variants.length}`);\n console.log(` Test cases: ${this.testCases.length}`);\n console.log(` Total runs: ${totalRuns}`);\n console.log();\n\n const runSingle = async (\n variant: ExperimentVariant,\n testCase: ExperimentTestCase\n ): Promise<ExperimentRunResult> => {\n const tracker = await this.trackRun(variant.name, testCase.sessionId);\n\n // Set experiment context so getSystemPrompt() returns the variant prompt\n setExperimentContext({\n systemPrompt: variant.systemPrompt,\n variantName: variant.name,\n experimentId: this.experimentId,\n testCaseId: testCase.sessionId,\n });\n\n try {\n await agentFn(testCase, variant, tracker);\n await tracker.complete();\n return tracker.getResult();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n await tracker.fail(errorMessage);\n return tracker.getResult();\n } finally {\n clearExperimentContext();\n }\n };\n\n if (parallel) {\n // Run in parallel with limited concurrency\n const queue: Array<() => Promise<void>> = [];\n\n for (const variant of this.variants) {\n for (const testCase of this.testCases) {\n queue.push(async () => {\n const result = await runSingle(variant, testCase);\n results.push(result);\n completed++;\n const status = result.success ? '✓' : '✗';\n console.log(\n ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`\n );\n });\n }\n }\n\n // Process queue with limited concurrency\n const executing: Promise<void>[] = [];\n for (const task of queue) {\n const promise = task().then(() => {\n executing.splice(executing.indexOf(promise), 1);\n });\n executing.push(promise);\n\n if (executing.length >= maxWorkers) {\n await Promise.race(executing);\n }\n }\n await Promise.all(executing);\n } else {\n // Run sequentially\n for (const variant of this.variants) {\n for (const testCase of this.testCases) {\n const result = await runSingle(variant, testCase);\n results.push(result);\n completed++;\n const status = result.success ? '✓' : '✗';\n console.log(\n ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`\n );\n }\n }\n }\n\n console.log();\n console.log('Experiment complete!');\n console.log(` Successful: ${results.filter((r) => r.success).length}/${totalRuns}`);\n console.log(` Failed: ${results.filter((r) => !r.success).length}/${totalRuns}`);\n\n return results;\n }\n\n /**\n * Reset all runs for this experiment (for re-running).\n */\n async reset(): Promise<boolean> {\n try {\n const response = await this.request(\n 'DELETE',\n `/api/experiments/${this.experimentId}/runs`\n );\n\n if (response !== null) {\n // Clear cached config\n this.config = undefined;\n this._variants = undefined;\n this._testCases = undefined;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n /**\n * Fetch aggregated results from the API.\n */\n async getResults(): Promise<ExperimentResults | null> {\n try {\n const response = await this.request<ExperimentResults>(\n 'GET',\n `/api/experiments/${this.experimentId}/results`\n );\n return response;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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","names":["EventType","_defaultClient","getClient","_defaultClient","getClient"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/constants.ts","../src/node-crypto.ts","../src/random.ts","../../otlp/pii-core.ts","../src/redact.ts","../src/async-context.ts","../src/pricing.json","../src/cost.ts","../src/wrappers.ts","../src/transport-diagnostics.ts","../src/batcher.ts","../src/large-tool-content.ts","../../otlp/content-reference.ts","../src/lineage.ts","../src/otlp-transport.ts","../../otlp/semconv-keys.json","../../otlp/otlp.ts","../package.json","../src/evict.ts","../src/contained.ts","../../otlp/agent-diagnostic.ts","../src/client.ts","../src/types.ts","../src/vercel.ts","../src/claude-code.ts","../src/openai-agents.ts","../src/google-adk.ts","../src/decorators.ts","../src/context.ts","../src/experiment.ts"],"sourcesContent":["/**\n * Sentrial SDK for TypeScript/Node.js\n *\n * AI agent observability and monitoring. Track sessions, tool calls, and metrics\n * to power signal detection, root cause analysis, and AI-suggested fixes.\n *\n * @packageDocumentation\n *\n * @example Basic Usage\n * ```ts\n * import { SentrialClient } from '@sentrial/sdk';\n *\n * const client = new SentrialClient({\n * apiKey: process.env.SENTRIAL_API_KEY!,\n * });\n *\n * // Create session\n * const sessionId = await client.createSession({\n * name: 'Support Request',\n * agentName: 'support-agent',\n * userId: 'user_123',\n * });\n *\n * // Track tool calls\n * await client.trackToolCall({\n * sessionId,\n * toolName: 'search_kb',\n * toolInput: { query: 'help' },\n * toolOutput: { results: ['...'] },\n * });\n *\n * // Complete session\n * await client.completeSession({\n * sessionId,\n * success: true,\n * customMetrics: { satisfaction: 4.5 },\n * });\n * ```\n *\n * @example Simple begin/finish Pattern\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const interaction = await sentrial.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * });\n *\n * // ... agent work ...\n *\n * await interaction.finish({ output: response, success: true });\n * ```\n */\n\n// Main client\nexport {\n SentrialClient,\n Interaction,\n configure,\n begin,\n getVariant,\n recordExperimentExposure,\n sentrial,\n} from './client.js';\n\n// Types\nexport type { LargeToolContentConfig } from './large-tool-content.js';\nexport type { AgentDiagnosticInput, AgentDiagnostic } from '@sentrial/otlp/agent-diagnostic';\nexport type {\n SentrialClientConfig,\n CreateSessionParams,\n CompleteSessionParams,\n TrackToolCallParams,\n TrackDecisionParams,\n TrackErrorParams,\n BeginParams,\n FinishParams,\n GetVariantParams,\n VariantAssignment,\n RecordExperimentExposureParams,\n ExperimentExposureReceipt,\n ProductionVariantConfig,\n Session,\n Event,\n SessionStatus,\n CostParams,\n ApiResponse,\n} from './types.js';\n\n// Enums\nexport { EventType } from './types.js';\n\n// Cost calculation utilities\nexport {\n calculateOpenAICost,\n calculateAnthropicCost,\n calculateGoogleCost,\n calculateCostByProvider,\n guessProvider,\n normalizeUsage,\n resolveTokensAndCost,\n foldProviderExtras,\n} from './cost.js';\nexport type { NormalizableUsage, NormalizedUsage } from './cost.js';\n\n// Errors\nexport { SentrialError, ApiError, NetworkError, ValidationError } from './errors.js';\n\n// Vercel AI SDK Integration\nexport { wrapAISDK, configureVercel, getVariantForAISDK } from './vercel.js';\n\nexport type {\n GenerateTextParams,\n GenerateTextResult,\n StreamTextResult,\n AISDKVariantOptions,\n} from './vercel.js';\n\n// Claude Agent SDK Integration\nexport { wrapClaudeAgent } from './claude-code.js';\nexport type { WrapClaudeAgentOptions } from './claude-code.js';\n\n// OpenAI Agents SDK Integration (tracing processor; distinct from wrapOpenAI,\n// which wraps a raw openai client)\nexport { SentrialOpenAIAgentsProcessor, instrumentOpenAIAgents } from './openai-agents.js';\nexport type {\n OpenAIAgentsProcessorOptions,\n AgentsModuleLike,\n AgentsTrace,\n AgentsSpan,\n AgentsSpanError,\n} from './openai-agents.js';\n\n// Google ADK Integration (Runner-level Plugin; distinct from wrapGoogle,\n// which wraps a raw @google/genai model)\nexport { SentrialAdkPlugin, instrumentAdk } from './google-adk.js';\nexport type {\n AdkPluginOptions,\n AdkRunnerLike,\n AdkInvocationContext,\n AdkCallbackContext,\n AdkLlmRequest,\n AdkLlmResponse,\n AdkContent,\n AdkTool,\n} from './google-adk.js';\n\n// LLM Auto-Wrappers\nexport {\n wrapOpenAI,\n wrapAnthropic,\n wrapGoogle,\n wrapLLM,\n setSessionContext,\n clearSessionContext,\n getSessionContext,\n setDefaultClient,\n} from './wrappers.js';\n\n// Decorators & Higher-Order Functions\nexport {\n withTool,\n withSession,\n Tool,\n TrackSession,\n SessionContext,\n setClient,\n getCurrentSessionId,\n getCurrentInteraction,\n} from './decorators.js';\n\n// Experiment Context\nexport {\n getSystemPrompt,\n getExperimentContext,\n isExperimentMode,\n getVariantName,\n getExperimentId,\n setExperimentContext,\n clearExperimentContext,\n} from './context.js';\n\nexport type { ExperimentContext } from './context.js';\n\n// Experiments\nexport { Experiment, ExperimentRunTracker } from './experiment.js';\n\nexport type {\n ExperimentVariant,\n ExperimentTestCase,\n ExperimentRunResult,\n ExperimentResults,\n} from './experiment.js';\n\n// Event Batching\nexport { EventBatcher } from './batcher.js';\nexport type { BatcherConfig, DeliveryDiagnostics } from './batcher.js';\n\n// Async Context (for advanced concurrency-safe usage)\nexport { createContextVar } from './async-context.js';\nexport type { ContextVar, ContextToken } from './async-context.js';\n\n// PII Redaction\nexport {\n redactString,\n redactValue,\n redactPayload,\n hashValue,\n replaceMatch,\n BUILTIN_PATTERNS,\n} from './redact.js';\n\nexport type {\n PiiMode,\n PiiField,\n PiiCustomPattern,\n PiiBuiltinPatterns,\n PiiConfig,\n} from './redact.js';\n\nexport {\n actionMetadata,\n createHandoffContext,\n resumeHandoffContext,\n createDispatchContext,\n} from './lineage.js';\nexport type { ActionIdentity, HandoffContext, DispatchContext, HandoffOutcome } from './lineage.js';\n\nexport type {\n TransportDiagnosticsSnapshot,\n TransportAttempt,\n DurationSummary,\n ServerDurations,\n} from './transport-diagnostics.js';\n","/**\n * Error classes for the Sentrial SDK\n */\n\n/**\n * Base error class for all Sentrial SDK errors\n */\nexport class SentrialError extends Error {\n /** HTTP status code (if applicable) */\n public readonly status?: number;\n /** Error code from the API */\n public readonly code?: string;\n /** Additional error details */\n public readonly details?: Record<string, unknown>;\n\n constructor(\n message: string,\n options?: {\n status?: number;\n code?: string;\n details?: Record<string, unknown>;\n cause?: Error;\n }\n ) {\n super(message, { cause: options?.cause });\n this.name = 'SentrialError';\n this.status = options?.status;\n this.code = options?.code;\n this.details = options?.details;\n\n // Maintain proper stack trace in V8 environments\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SentrialError);\n }\n }\n\n /**\n * Check if the error is a rate limit error\n */\n isRateLimitError(): boolean {\n return this.status === 429;\n }\n\n /**\n * Check if the error is an authentication error\n */\n isAuthError(): boolean {\n return this.status === 401 || this.status === 403;\n }\n\n /**\n * Check if the error is a network/connection error\n */\n isNetworkError(): boolean {\n return this.code === 'NETWORK_ERROR' || this.code === 'ECONNREFUSED';\n }\n\n /**\n * Convert to a plain object for logging\n */\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n code: this.code,\n details: this.details,\n stack: this.stack,\n };\n }\n}\n\n/**\n * Error thrown when the API returns an error response\n */\nexport class ApiError extends SentrialError {\n constructor(message: string, status: number, code?: string, details?: Record<string, unknown>) {\n super(message, { status, code, details });\n this.name = 'ApiError';\n }\n}\n\n/**\n * Error thrown when there's a network/connection issue\n */\nexport class NetworkError extends SentrialError {\n constructor(message: string, cause?: Error) {\n super(message, { code: 'NETWORK_ERROR', cause });\n this.name = 'NetworkError';\n }\n}\n\n/**\n * Error thrown when validation fails.\n *\n * @deprecated The SDK no longer throws this internally. Retained as a public export for backwards\n * compatibility with downstream consumers that import it; slated for removal in a future MINOR/MAJOR\n * release (not a patch), per the SDK compatibility policy.\n */\nexport class ValidationError extends SentrialError {\n constructor(message: string, details?: Record<string, unknown>) {\n super(message, { code: 'VALIDATION_ERROR', details });\n this.name = 'ValidationError';\n }\n}\n","/**\n * Shared SDK constants — single source so the client, OTLP transport, and CLI can't target\n * different endpoints if a path/URL changes.\n */\n\n/** Default Sentrial API base URL when neither a constructor arg nor SENTRIAL_API_URL is set. */\nexport const DEFAULT_API_URL = 'https://api.sentrial.com';\n\n/** OTLP traces ingest path, appended to the API base URL by the transport. */\nexport const OTEL_TRACES_PATH = '/api/otel/v1/traces';\n","// Single source of Node crypto access for the SDK.\n//\n// WHY a static `import` (not a lazy `require`/`new Function('return require(...)')`):\n// in pure-ESM Node there is no `require` in scope, so the lazy pattern throws a\n// ReferenceError and silently disables hash mode on a runtime that actually HAS\n// crypto. A static `import` is resolved by the bundler/runtime up-front and works\n// in both the ESM and CJS builds tsup emits, so `createHash` is available whenever\n// the platform provides it. `node:crypto`'s `webcrypto` is the Node 15-18 fallback\n// for `globalThis.crypto` (only unflagged on Node 19+); this SDK's floor is Node 18.\nimport { webcrypto, createHash as _createHash } from 'node:crypto';\n\n/** Minimal shape of the Web Crypto calls the SDK uses (avoids depending on the DOM `Crypto` type). */\nexport interface RandomSource {\n getRandomValues(array: Uint8Array): Uint8Array;\n randomUUID(): string;\n}\n\n/**\n * Web Crypto source: prefer the runtime global (browser + Node 19+), fall back to\n * `node:crypto`'s `webcrypto` (Node 15-18, where `globalThis.crypto` is `undefined`).\n */\nexport function getRandomSource(): RandomSource {\n return (\n (globalThis.crypto as unknown as RandomSource | undefined) ??\n (webcrypto as unknown as RandomSource)\n );\n}\n\n/**\n * `node:crypto`'s `createHash`, or `undefined` on a runtime whose bundle stubs it out\n * (browser/edge). Callers must guard before use.\n */\nexport const createHash: typeof _createHash | undefined =\n typeof _createHash === 'function' ? _createHash : undefined;\n","// Node-safe randomness for trace/span/event ids. The Web Crypto global `globalThis.crypto` is only\n// exposed unflagged on Node 19+, but this SDK's floor is Node 18 (engines: \">=18\", tsup target\n// node18), where it is `undefined` — so fall back to node:crypto's `webcrypto` (available since\n// Node 15). Works in the browser too (globalThis.crypto is present there). This is on the HOT PATH\n// under the default OTLP transport (every createSession / emitEvent / finalize), so it must never\n// throw on a stock Node 18 runtime. Crypto access is centralized in node-crypto.ts.\nimport { getRandomSource, type RandomSource } from './node-crypto.js';\n\nconst cryptoObj: RandomSource = getRandomSource();\n\n/** Random lowercase-hex string of `bytes` bytes (2 hex chars each). */\nexport function randomHex(bytes: number): string {\n const arr = new Uint8Array(bytes);\n cryptoObj.getRandomValues(arr);\n return Array.from(arr, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** RFC 4122 v4 UUID, Node-18-safe. */\nexport function randomId(): string {\n return cryptoObj.randomUUID();\n}\n","/**\n * Canonical PII pattern table + masking primitives — the SINGLE source shared by\n * the TS SDK's payload redaction (`packages/typescript-sdk/src/redact.ts`) and the\n * API's privacy-page redaction PREVIEW (`apps/api/src/lib/pii/preview-redact.ts`).\n *\n * WHY it lives in @sentrial/otlp: this package already solves the exact SDK↔API\n * \"one TypeScript implementation, zero copies\" problem — it is bundled into\n * `@sentrial/sdk` at build (tsup `noExternal`) and transpiled into `apps/api`\n * (Next `transpilePackages`). Colocating the PII core here reuses that wiring\n * verbatim instead of hand-maintaining two byte-for-byte copies (the old\n * `pii-test-route-parity` CI guard).\n *\n * Pattern ORDER is load-bearing (see BUILTIN_PATTERNS) and this logic is\n * security-sensitive, so it must have exactly ONE definition. The only\n * environment dependency — Node `crypto` for hash mode — is NOT baked in here:\n * the hash/replacement helpers take a `getCreateHash` PROVIDER so the SDK keeps\n * its browser/edge guard (crypto required only in hash mode) and the API passes\n * `node:crypto`. The Python SDK mirrors this same table/order in `redact.py`\n * (`_BUILTIN_PATTERNS` + `_luhn_valid`); keep them in lockstep.\n */\n\n/** How redacted values are replaced. */\nexport type PiiMode = 'label' | 'hash' | 'remove';\n\n/** The five builtin pattern categories, in their canonical (load-bearing) order. */\nexport type PiiPatternKey = 'emails' | 'creditCards' | 'ssns' | 'phones' | 'ipAddresses';\n\n/** Minimal shape of the Node `crypto.createHash` chain the hash helper needs. */\nexport interface PiiHasher {\n update(data: string): PiiHasher;\n digest(encoding: 'hex'): string;\n}\nexport type CreateHash = (algorithm: string) => PiiHasher;\n\n/**\n * Provider that resolves a `createHash` implementation ONLY when it is actually\n * needed (hash mode). Passed uncalled so `label`/`remove` mode never touches\n * crypto — that is what lets the SDK run redaction in a browser/edge runtime\n * that has no `node:crypto`.\n */\nexport type CreateHashProvider = () => CreateHash;\n\n/**\n * Luhn checksum over a matched digit run (separators stripped). Real card numbers pass;\n * arbitrary ids/timestamps fail ~90% of the time, which combined with the 13-19-digit shape\n * requirement makes false redactions rare while never missing a valid card number. Kept in\n * lockstep with the Python `_luhn_valid`.\n */\nfunction luhnValid(match: string): boolean {\n const digits = match.replace(/[-\\s]/g, '');\n let sum = 0;\n let double = false;\n for (let i = digits.length - 1; i >= 0; i--) {\n let d = digits.charCodeAt(i) - 48;\n if (double) {\n d *= 2;\n if (d > 9) d -= 9;\n }\n sum += d;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\n/** Email addresses (RFC 5322 simplified) */\nconst EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g;\n\n/**\n * US phone numbers: (###) ###-####, ###-###-####, ### ### ####, +1##########\n *\n * A bare 10-digit run (e.g. a 10-digit order id or unix-ms timestamp fragment) MUST NOT match — the\n * pattern requires phone-like structure via one of three alternatives: a country-code prefix (+1/1),\n * a parenthesized area code, or at least one separator between the area code and exchange. A\n * leading `(?<!\\d)` guard also prevents a match from starting mid-run inside a longer numeric id\n * (e.g. the '1'-prefixed tail of a 12+-digit order id / epoch-ms timestamp). Kept in\n * lockstep with the Python `_PHONE_PATTERN`.\n *\n * KNOWN BREADTH: because the first alternative makes the leading `1` and every separator optional,\n * an EXACTLY-11-digit run that begins with `1` (e.g. `17039991234`, the dialed `1XXXXXXXXXX` form) IS\n * treated as a phone number and redacted. This is deliberate (it catches the 11-digit country-code\n * dialed form), and it is strictly narrower than origin/main (which redacted ANY bare 10-digit run).\n * A separator-less 11-digit id that happens to start with `1` will therefore be redacted; 10-digit\n * and 12+-digit bare ids are unaffected (see the redact tests). If your ids collide with this shape,\n * exclude the field via `redactFields`.\n */\nconst PHONE_PATTERN =\n /(?<!\\d)(?:\\+?1[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}|\\(\\d{3}\\)[-.\\s]?\\d{3}[-.\\s]?\\d{4}|\\d{3}[-.\\s]\\d{3}[-.\\s]?\\d{4})\\b/g;\n\n/** US Social Security Numbers: ###-##-#### */\nconst SSN_PATTERN = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\n\n/**\n * Credit card numbers: 13-19 digits, optionally separated by spaces or dashes.\n *\n * A regex match alone is NOT sufficient — any bare 13-19-digit run (a 13-digit epoch-ms\n * timestamp string, an 18-19-digit snowflake/order/bigint id) would otherwise be mangled to\n * [CREDIT_CARD], and DEFAULT_FIELDS now includes metadata/name/failureReason where such ids\n * live. Matches are additionally validated with the Luhn checksum (see luhnValid) before\n * replacement, same principle as the PHONE pattern's bare-digit-run exclusion. Kept in\n * lockstep with the Python `_CREDIT_CARD_PATTERN` + `_luhn_valid`.\n */\nconst CREDIT_CARD_PATTERN = /\\b(?:\\d[-\\s]?){13,19}\\b/g;\n\n/** IPv4 addresses */\nconst IP_ADDRESS_PATTERN =\n /\\b(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\b/g;\n\n/**\n * Map of builtin pattern keys to their regex and label. ORDER MATTERS: consumers apply these in\n * insertion order, replacing each match before the next pattern runs. Longer/more-specific number\n * patterns MUST come before shorter ones — creditCards + ssns BEFORE phones — otherwise the PHONE\n * pattern's separator alternative can eat a phone-shaped tail of a separator-formatted card (e.g.\n * \"411-111-111-1111\" -> \"411-[PHONE]\"), leaking the leading digits; and for a bare 16-digit card\n * the PHONE pattern would eat the trailing 10 digits, leaking the leading 6 (e.g.\n * \"4111111111111111\" -> \"411111[PHONE]\"). Bare digit runs are already immune via PHONE_PATTERN's\n * (?<!\\d) guard. Kept in lockstep with the Python `_BUILTIN_PATTERNS`.\n */\nexport const BUILTIN_PATTERNS: Record<\n PiiPatternKey,\n { pattern: RegExp; label: string; validate?: (match: string) => boolean }\n> = {\n emails: { pattern: EMAIL_PATTERN, label: 'EMAIL' },\n // validate: only Luhn-valid runs are card numbers; timestamps/snowflake ids survive unchanged.\n creditCards: { pattern: CREDIT_CARD_PATTERN, label: 'CREDIT_CARD', validate: luhnValid },\n ssns: { pattern: SSN_PATTERN, label: 'SSN' },\n phones: { pattern: PHONE_PATTERN, label: 'PHONE' },\n ipAddresses: { pattern: IP_ADDRESS_PATTERN, label: 'IP_ADDRESS' },\n};\n\n/** Replace builtin matches without repeatedly scanning suffixes of a long email\n * candidate. Keep the public pattern table and its match semantics unchanged.\n * Each local/domain run is scanned a bounded number of times, including failed candidates.\n */\nexport function replaceBuiltinMatches(\n value: string,\n key: PiiPatternKey,\n replacement: (match: string, label: string) => string\n): string {\n const config = BUILTIN_PATTERNS[key];\n const replace = (match: string) =>\n config.validate && !config.validate(match) ? match : replacement(match, config.label);\n if (key !== 'emails')\n return value.replace(new RegExp(config.pattern.source, config.pattern.flags), replace);\n\n const locals = /[a-zA-Z0-9._%+-]+/g;\n const domains = /[a-zA-Z0-9.-]+/y;\n const isLetter = (code: number) => (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n const chunks: string[] = [];\n let copied = 0;\n for (let local = locals.exec(value); local; local = locals.exec(value)) {\n if (value[locals.lastIndex] !== '@') continue;\n const domainStart = locals.lastIndex + 1;\n domains.lastIndex = domainStart;\n if (!domains.exec(value)) continue;\n const domainEnd = domains.lastIndex;\n // Match the greedy domain + final dot + at least two ASCII letters.\n // The domain must contain a character before that final dot. A trailing\n // nonletter may remain for the next search, exactly as in String.replace.\n let dot = domainEnd - 3;\n while (\n dot > domainStart &&\n !(\n value[dot] === '.' &&\n isLetter(value.charCodeAt(dot + 1)) &&\n isLetter(value.charCodeAt(dot + 2))\n )\n )\n dot--;\n if (dot <= domainStart) continue;\n let end = dot + 3;\n while (end < domainEnd && isLetter(value.charCodeAt(end))) end++;\n chunks.push(value.slice(copied, local.index), replace(value.slice(local.index, end)));\n copied = end;\n // Resume at the match end, including adjacent email candidates. A simple\n // lookbehind boundary would leak the second address in a@b.co+c@d.com.\n locals.lastIndex = end;\n }\n return chunks.length ? chunks.join('') + value.slice(copied) : value;\n}\n\n/**\n * Hash a value using SHA-256 and return the first 6 hex characters.\n * Used in 'hash' mode to produce a short, deterministic pseudonym. `getCreateHash`\n * is resolved lazily so callers that never use hash mode never require crypto.\n */\nexport function hashValue(getCreateHash: CreateHashProvider, value: string): string {\n return getCreateHash()('sha256').update(value).digest('hex').slice(0, 6);\n}\n\n/**\n * Produce the replacement string for a matched PII value.\n *\n * - 'label' mode: [LABEL]\n * - 'hash' mode: [PII:abcdef]\n * - 'remove' mode: '' (empty string)\n *\n * Any unrecognised mode falls through to the 'remove' behaviour (empty string).\n */\nexport function getReplacement(\n getCreateHash: CreateHashProvider,\n match: string,\n label: string,\n mode: PiiMode\n): string {\n if (mode === 'label') return `[${label}]`;\n if (mode === 'hash') return `[PII:${hashValue(getCreateHash, match)}]`;\n return '';\n}\n","/**\n * PII Redaction Module\n *\n * Automatically detects and redacts personally identifiable information (PII)\n * from SDK payloads before they are sent to the Sentrial API.\n */\n\n// Node.js crypto access is centralized in node-crypto.ts (a static import, not a\n// lazy require — the lazy pattern throws a ReferenceError in pure-ESM Node and\n// silently disables hash mode on a runtime that actually has crypto).\nimport { createHash } from './node-crypto.js';\n// The builtin pattern table, Luhn check, and masking primitives are the SINGLE shared\n// source in @sentrial/otlp/pii-core (bundled into this SDK via tsup noExternal and transpiled\n// into apps/api via Next transpilePackages — one implementation, zero copies). This module keeps\n// only the SDK-specific payload traversal/field-selection around that core.\nimport {\n BUILTIN_PATTERNS,\n replaceBuiltinMatches,\n getReplacement as coreGetReplacement,\n hashValue as coreHashValue,\n} from '@sentrial/otlp/pii-core';\nimport type { CreateHash } from '@sentrial/otlp/pii-core';\n\n// Re-export the shared pattern table as part of the SDK's public API (unchanged shape:\n// `{ pattern, label, validate? }` per key, in the same load-bearing insertion order).\nexport { BUILTIN_PATTERNS };\n\n// Provider handed to the shared masking core: resolves Node crypto ONLY when hash mode\n// actually runs, so `label`/`remove` mode still works in a browser/edge runtime with no crypto.\nfunction getCreateHash(): CreateHash {\n if (!createHash) {\n throw new Error(\n 'Sentrial PII hash mode requires Node.js crypto module. ' +\n 'Use mode \"label\" or \"remove\" in browser/edge environments.'\n );\n }\n return createHash as unknown as CreateHash;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** How redacted values are replaced */\nexport type PiiMode = 'label' | 'hash' | 'remove';\n\n/** Fields that can be redacted in SDK payloads */\nexport type PiiField =\n | 'userInput'\n | 'assistantOutput'\n | 'toolInput'\n | 'toolOutput'\n | 'metadata'\n | 'reasoning'\n | 'name'\n | 'failureReason';\n\n/** A custom regex pattern with a human-readable label */\nexport interface PiiCustomPattern {\n pattern: RegExp;\n label: string;\n}\n\n/** Toggle individual builtin pattern categories */\nexport interface PiiBuiltinPatterns {\n emails?: boolean;\n phones?: boolean;\n ssns?: boolean;\n creditCards?: boolean;\n ipAddresses?: boolean;\n}\n\n/** Full PII redaction configuration */\nexport interface PiiConfig {\n /** Enable PII redaction (default: false) */\n enabled: boolean;\n /** Which payload fields to redact (default: DEFAULT_FIELDS) */\n fields?: PiiField[];\n /** Replacement mode (default: 'label') */\n mode?: PiiMode;\n /** Toggle builtin patterns (default: all enabled) */\n builtinPatterns?: PiiBuiltinPatterns;\n /** Additional custom patterns to apply */\n customPatterns?: PiiCustomPattern[];\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Default fields that are redacted when PII redaction is enabled */\nexport const DEFAULT_FIELDS: PiiField[] = [\n 'userInput',\n 'assistantOutput',\n 'toolInput',\n 'toolOutput',\n 'name',\n 'reasoning',\n 'failureReason',\n 'metadata',\n];\n\n/** Default builtin pattern toggles (all enabled) */\nexport const DEFAULT_BUILTIN: Required<PiiBuiltinPatterns> = {\n emails: true,\n phones: true,\n ssns: true,\n creditCards: true,\n ipAddresses: true,\n};\n\n// ============================================================================\n// Core Functions\n// ============================================================================\n//\n// The builtin regex table + Luhn validator live in @sentrial/otlp/pii-core (imported above).\n// `hashValue` and `replaceMatch` below are thin SDK-public wrappers that bind the shared masking\n// core to the SDK's lazy Node-crypto resolver (`getCreateHash`).\n\n/**\n * Hash a value using SHA-256 and return the first 6 hex characters.\n * Used in 'hash' mode to produce a short, deterministic pseudonym.\n */\nexport function hashValue(value: string): string {\n return coreHashValue(getCreateHash, value);\n}\n\n/**\n * Produce the replacement string for a matched PII value.\n *\n * - 'label' mode: [LABEL]\n * - 'hash' mode: [PII:abcdef]\n * - 'remove' mode: '' (empty string)\n */\nexport function replaceMatch(match: string, label: string, mode: PiiMode): string {\n return coreGetReplacement(getCreateHash, match, label, mode);\n}\n\n/**\n * Run all enabled builtin and custom patterns against a single string,\n * replacing every match according to the specified mode.\n */\nexport function redactString(\n value: string,\n mode: PiiMode,\n builtinPatterns: Required<PiiBuiltinPatterns>,\n customPatterns: PiiCustomPattern[]\n): string {\n let result = value;\n\n // Apply enabled builtin patterns\n for (const key of Object.keys(BUILTIN_PATTERNS)) {\n if (builtinPatterns[key as keyof PiiBuiltinPatterns]) {\n result = replaceBuiltinMatches(result, key as keyof typeof BUILTIN_PATTERNS, (match, label) =>\n replaceMatch(match, label, mode)\n );\n }\n }\n\n // Apply custom patterns\n for (const custom of customPatterns) {\n // Force the global flag so String.replace substitutes EVERY match — a custom pattern\n // supplied without /g would otherwise redact only the first occurrence and leak the rest.\n const flags = custom.pattern.flags.includes('g')\n ? custom.pattern.flags\n : custom.pattern.flags + 'g';\n const regex = new RegExp(custom.pattern.source, flags);\n result = result.replace(regex, (match) => replaceMatch(match, custom.label, mode));\n }\n\n return result;\n}\n\n/**\n * Maximum nesting depth for the redaction traversal. Far above any real payload (which the server\n * caps at 64KB serialized anyway) yet far below the engine's stack limit — a deep NON-cyclic\n * payload would otherwise blow the stack inside the redaction recursion BEFORE any guarded\n * stringify seam, throwing a RangeError out of the tracking call. Matches the Python\n * `_MAX_REDACT_DEPTH`.\n */\nconst MAX_REDACT_DEPTH = 256;\n\n/**\n * The single canonical deep traversal used for PII redaction. Applies `fn` to every string leaf,\n * maps over arrays, recurses into plain objects, and passes through primitives (numbers, booleans,\n * null, undefined) unchanged. Returns a redacted COPY — the input is never mutated. Both\n * `redactValue` (below) and the OTLP transport's metadata redaction delegate to this so the\n * traversal exists in exactly one place.\n *\n * Cycle- and depth-safe: a CYCLIC branch (a value containing itself) or a branch nested deeper\n * than MAX_REDACT_DEPTH is replaced with the cross-SDK `{ _unserializable: true }` marker instead\n * of recursing until the stack blows (a RangeError out of a tracking call would violate the\n * monitoring-never-crashes contract). Non-cyclic shared references are unaffected — only true\n * ancestors are guarded. Mirrors the Python `redact_value`.\n */\nexport function deepMapStrings(value: unknown, fn: (s: string) => string): unknown {\n return deepMapStringsGuarded(value, fn, new Set(), 0);\n}\n\nfunction deepMapStringsGuarded(\n value: unknown,\n fn: (s: string) => string,\n ancestors: Set<object>,\n depth: number\n): unknown {\n if (typeof value === 'string') {\n return fn(value);\n }\n\n if (Array.isArray(value)) {\n // Same marker convention as cycles: a deeper-than-cap branch degrades, never overflows.\n if (depth >= MAX_REDACT_DEPTH) return { _unserializable: true };\n if (ancestors.has(value)) return { _unserializable: true };\n ancestors.add(value);\n try {\n return value.map((item) => deepMapStringsGuarded(item, fn, ancestors, depth + 1));\n } finally {\n ancestors.delete(value);\n }\n }\n\n if (value !== null && typeof value === 'object') {\n if (depth >= MAX_REDACT_DEPTH) return { _unserializable: true };\n if (ancestors.has(value)) return { _unserializable: true };\n ancestors.add(value);\n try {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n result[key] = deepMapStringsGuarded(val, fn, ancestors, depth + 1);\n }\n return result;\n } finally {\n ancestors.delete(value);\n }\n }\n\n // Primitives (number, boolean, null, undefined) pass through unchanged\n return value;\n}\n\n/**\n * Recursively redact PII from a value. Handles strings, arrays, plain objects,\n * and passes through primitives (numbers, booleans, null, undefined) unchanged.\n */\nexport function redactValue(\n value: unknown,\n mode: PiiMode,\n builtinPatterns: Required<PiiBuiltinPatterns>,\n customPatterns: PiiCustomPattern[]\n): unknown {\n return deepMapStrings(value, (s) => redactString(s, mode, builtinPatterns, customPatterns));\n}\n\n/**\n * Apply a PiiConfig's redaction to a single content string.\n *\n * `redactPayload` matches PII fields by top-level name, which never covers the OTLP transport's\n * nested content values (e.g. `gen_ai.input.messages`/`gen_ai.output.messages`). The OTLP transport\n * calls this directly on the content so `pii` is honoured there too.\n */\nexport function redactText(text: string, config: PiiConfig): string {\n if (!config.enabled) return text;\n const builtinPatterns: Required<PiiBuiltinPatterns> = {\n ...DEFAULT_BUILTIN,\n ...config.builtinPatterns,\n };\n return redactString(text, config.mode ?? 'label', builtinPatterns, config.customPatterns ?? []);\n}\n\n/**\n * Main entry point: redact configured fields in a payload object.\n *\n * Only fields listed in the PiiConfig (or DEFAULT_FIELDS) are redacted.\n * Other fields are returned unchanged.\n *\n * @param payload - The data payload (e.g., session params, tool call params)\n * @param config - PII configuration from the client\n * @returns A new payload object with PII redacted from the configured fields\n */\nexport function redactPayload(\n payload: Record<string, unknown>,\n config: PiiConfig\n): Record<string, unknown> {\n if (!config.enabled) {\n return payload;\n }\n\n const mode = config.mode ?? 'label';\n const fields = config.fields ?? DEFAULT_FIELDS;\n const builtinPatterns: Required<PiiBuiltinPatterns> = {\n ...DEFAULT_BUILTIN,\n ...config.builtinPatterns,\n };\n const customPatterns = config.customPatterns ?? [];\n\n const result: Record<string, unknown> = { ...payload };\n\n for (const field of fields) {\n if (field in result && result[field] !== undefined && result[field] !== null) {\n result[field] = redactValue(result[field], mode, builtinPatterns, customPatterns);\n }\n }\n\n return result;\n}\n","/**\n * Async-safe context variables for the Sentrial SDK.\n *\n * In Node.js (>= 18): uses AsyncLocalStorage for true per-request isolation.\n * In browsers: falls back to a simple variable (safe because browsers have no\n * concurrent request handling within a single page).\n *\n * This is the TypeScript equivalent of Python's `contextvars.ContextVar`.\n */\n\n// ============================================================================\n// Public Interface\n// ============================================================================\n\nexport interface ContextToken<T> {\n /** @internal */\n readonly _previous: T;\n}\n\nexport interface ContextVar<T> {\n /** Get the current value (returns defaultValue if unset in this context). */\n get(): T;\n /** Set a new value. Returns a token that can restore the previous value. */\n set(value: T): ContextToken<T>;\n /** Restore the value that was active before the given token's set() call. */\n reset(token: ContextToken<T>): void;\n}\n\n// ============================================================================\n// Detect AsyncLocalStorage availability\n// ============================================================================\n\n// WHY a static `import` (not a lazy `require`/`new Function('return require(...)')`): in pure-ESM\n// Node there is no `require` in scope, so the lazy pattern threw a ReferenceError and silently fell\n// back to SimpleContextVar on a runtime that actually HAS async_hooks — meaning concurrent\n// withSession() flows leaked context across requests. A static `import` is resolved up-front and\n// works in both the ESM and CJS builds tsup emits (same reasoning as node-crypto.ts). This SDK's\n// floor is Node 18, where node:async_hooks is always present.\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nconst _AsyncLocalStorage: typeof AsyncLocalStorage | null =\n typeof AsyncLocalStorage === 'function' ? AsyncLocalStorage : null;\n\n// ============================================================================\n// Node.js Implementation (AsyncLocalStorage-backed)\n// ============================================================================\n\nclass NodeContextVar<T> implements ContextVar<T> {\n private readonly _storage: AsyncLocalStorage<T>;\n private readonly _defaultValue: T;\n\n constructor(defaultValue: T) {\n this._storage = new AsyncLocalStorage<T>();\n this._defaultValue = defaultValue;\n }\n\n get(): T {\n const store = this._storage.getStore();\n return store !== undefined ? (store as T) : this._defaultValue;\n }\n\n set(value: T): ContextToken<T> {\n const previous = this.get();\n this._storage.enterWith(value);\n return { _previous: previous };\n }\n\n reset(token: ContextToken<T>): void {\n this._storage.enterWith(token._previous);\n }\n}\n\n// ============================================================================\n// Browser Fallback (simple variable — no concurrency in single-tab context)\n// ============================================================================\n\nclass SimpleContextVar<T> implements ContextVar<T> {\n private _value: T;\n\n constructor(defaultValue: T) {\n this._value = defaultValue;\n }\n\n get(): T {\n return this._value;\n }\n\n set(value: T): ContextToken<T> {\n const previous = this._value;\n this._value = value;\n return { _previous: previous };\n }\n\n reset(token: ContextToken<T>): void {\n this._value = token._previous;\n }\n}\n\n// ============================================================================\n// Factory\n// ============================================================================\n\n/**\n * Create an async-safe context variable.\n *\n * Uses AsyncLocalStorage in Node.js for per-request isolation,\n * falls back to a simple variable in browsers.\n *\n * @param defaultValue - Value returned by get() when nothing has been set.\n */\nexport function createContextVar<T>(defaultValue: T): ContextVar<T> {\n if (_AsyncLocalStorage) {\n return new NodeContextVar(defaultValue);\n }\n return new SimpleContextVar(defaultValue);\n}\n","{\n \"_comment\": \"Canonical LLM pricing table, per 1,000,000 tokens (USD). Single source of truth for @sentrial/sdk (src/cost.ts imports this file directly) and the Python sentrial SDK (sentrial/pricing.py holds a per-1K mirror guarded by tests/test_pricing_sync.py). Update prices HERE; the Python drift test fails CI if the mirror falls out of sync. Base table Jan 2026; Claude 4.5 canonical model IDs and standard first-party rates verified Nov 1, 2026 at https://platform.claude.com/docs/en/about-claude/pricing. Estimates exclude batch and regional adjustments.\",\n \"openai\": {\n \"gpt-5.2\": {\n \"input\": 5.0,\n \"output\": 15.0\n },\n \"gpt-5\": {\n \"input\": 1.25,\n \"output\": 10.0\n },\n \"gpt-4.1\": {\n \"input\": 2.0,\n \"output\": 8.0\n },\n \"gpt-4.1-mini\": {\n \"input\": 0.4,\n \"output\": 1.6\n },\n \"gpt-4.1-nano\": {\n \"input\": 0.1,\n \"output\": 0.4\n },\n \"gpt-4o\": {\n \"input\": 2.5,\n \"output\": 10.0\n },\n \"gpt-4o-mini\": {\n \"input\": 0.15,\n \"output\": 0.6\n },\n \"gpt-4-turbo\": {\n \"input\": 10.0,\n \"output\": 30.0\n },\n \"gpt-4\": {\n \"input\": 30.0,\n \"output\": 60.0\n },\n \"gpt-3.5-turbo\": {\n \"input\": 0.5,\n \"output\": 1.5\n },\n \"o4-mini\": {\n \"input\": 1.1,\n \"output\": 4.4\n },\n \"o3\": {\n \"input\": 2.0,\n \"output\": 8.0\n },\n \"o3-mini\": {\n \"input\": 1.1,\n \"output\": 4.4\n },\n \"o3-pro\": {\n \"input\": 20.0,\n \"output\": 80.0\n },\n \"o1\": {\n \"input\": 15.0,\n \"output\": 60.0\n },\n \"o1-preview\": {\n \"input\": 15.0,\n \"output\": 60.0\n },\n \"o1-mini\": {\n \"input\": 1.1,\n \"output\": 4.4\n }\n },\n \"anthropic\": {\n \"claude-haiku-4-5\": {\n \"input\": 1.0,\n \"output\": 5.0\n },\n \"claude-sonnet-4-5\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-opus-4-5\": {\n \"input\": 5.0,\n \"output\": 25.0\n },\n \"claude-opus-4\": {\n \"input\": 15.0,\n \"output\": 75.0\n },\n \"claude-sonnet-4\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-4.5-opus\": {\n \"input\": 5.0,\n \"output\": 25.0\n },\n \"claude-4.5-sonnet\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-4-opus\": {\n \"input\": 15.0,\n \"output\": 75.0\n },\n \"claude-4-sonnet\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-3-7-sonnet\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-3-5-sonnet\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-3-5-haiku\": {\n \"input\": 0.8,\n \"output\": 4.0\n },\n \"claude-3-opus\": {\n \"input\": 15.0,\n \"output\": 75.0\n },\n \"claude-3-sonnet\": {\n \"input\": 3.0,\n \"output\": 15.0\n },\n \"claude-3-haiku\": {\n \"input\": 0.25,\n \"output\": 1.25\n }\n },\n \"google\": {\n \"gemini-3-pro\": {\n \"input\": 2.0,\n \"output\": 12.0\n },\n \"gemini-3-flash\": {\n \"input\": 0.5,\n \"output\": 3.0\n },\n \"gemini-2.5-pro\": {\n \"input\": 1.25,\n \"output\": 10.0\n },\n \"gemini-2.5-flash\": {\n \"input\": 0.3,\n \"output\": 2.5\n },\n \"gemini-2.0-flash\": {\n \"input\": 0.1,\n \"output\": 0.4\n },\n \"gemini-2.0-flash-lite\": {\n \"input\": 0.075,\n \"output\": 0.3\n },\n \"gemini-1.5-pro\": {\n \"input\": 1.25,\n \"output\": 5.0\n },\n \"gemini-1.5-flash\": {\n \"input\": 0.075,\n \"output\": 0.3\n },\n \"gemini-1.0-pro\": {\n \"input\": 0.5,\n \"output\": 1.5\n }\n },\n \"flatProviders\": {\n \"deepseek\": {\n \"input\": 0.27,\n \"output\": 1.1\n },\n \"cohere\": {\n \"input\": 0.5,\n \"output\": 1.5\n },\n \"mistral\": {\n \"input\": 2.0,\n \"output\": 6.0\n }\n },\n \"fallback\": {\n \"openai\": \"gpt-4o-mini\",\n \"anthropic\": \"claude-3-5-haiku\",\n \"google\": \"gemini-2.0-flash\",\n \"default\": {\n \"input\": 1.0,\n \"output\": 2.0\n }\n }\n}\n","/**\n * Cost calculation utilities for various LLM providers\n */\n\nimport type { CostParams } from './types.js';\nimport pricingData from './pricing.json';\n\n// ============================================================================\n// Pricing Data (per 1M tokens) - Updated Jan 2026\n// ============================================================================\n//\n// Sourced from the canonical pricing.json — the single source of truth shared with\n// the Python SDK's pricing.py mirror (guarded by tests/test_pricing_sync.py) so the\n// TS and Python tables can't silently drift (#490/#595). Update prices in pricing.json.\n\ntype Rate = { input: number; output: number };\n\nconst OPENAI_PRICING: Record<string, Rate> = pricingData.openai;\nconst ANTHROPIC_PRICING: Record<string, Rate> = pricingData.anthropic;\nconst GOOGLE_PRICING: Record<string, Rate> = pricingData.google;\n\n// Anthropic prompt-cache COST multipliers, relative to a model's base per-1M input rate.\n// Anthropic bills cache traffic at a fraction/multiple of the base input rate rather than at the\n// full rate: a cache READ is ~0.1x, a 5-minute cache WRITE is ~1.25x, and an extended-TTL\n// (1-hour) cache WRITE is ~2x. These weights are UNIFORM across Claude models (they are\n// Anthropic's standard cache pricing), so they live here as named constants instead of per-model\n// cache-rate keys in pricing.json. The reported TOKEN COUNT stays cache-inclusive; ONLY the cost\n// calc uses these. When the usage object exposes the `cache_creation` split\n// (ephemeral_5m_input_tokens / ephemeral_1h_input_tokens) we weight each tier exactly; otherwise\n// the aggregate cache_creation_input_tokens falls back to the 5-minute rate.\nexport const ANTHROPIC_CACHE_READ_MULTIPLIER = 0.1;\nexport const ANTHROPIC_CACHE_WRITE_MULTIPLIER = 1.25; // cache_creation (5-minute write)\nexport const ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2.0; // cache_creation (1-hour / extended-TTL write)\n\n// ============================================================================\n// Helper Function\n// ============================================================================\n\nfunction findModelKey(\n model: string,\n pricing: Record<string, { input: number; output: number }>\n): string | null {\n // Sort by length descending so longer (more specific) keys match first.\n // e.g. \"o3-mini\" must match before \"o3\", \"gpt-4-turbo\" before \"gpt-4\".\n // Case-insensitive: keys are lowercase, so a mixed/upper-case model id\n // (e.g. \"GPT-4o\") still matches instead of falling through to the fallback.\n // Null/undefined/non-string model → no match (caller applies its fallback key) — never throw\n // (mirrors Python's _match_sorted_prefix isinstance guard).\n if (typeof model !== 'string') return null;\n const modelLower = model.toLowerCase();\n const keys = Object.keys(pricing).sort((a, b) => b.length - a.length);\n for (const key of keys) {\n if (modelLower.startsWith(key.toLowerCase())) {\n return key;\n }\n }\n return null;\n}\n\nfunction calculateCost(\n inputTokens: number,\n outputTokens: number,\n rates: { input: number; output: number }\n): number {\n const inputCost = (inputTokens / 1_000_000) * rates.input;\n const outputCost = (outputTokens / 1_000_000) * rates.output;\n return inputCost + outputCost;\n}\n\n/**\n * Cost from a per-model pricing table, delegating gateway-style 'creator/model' ids to the provider\n * dispatcher. This is the ONE shared body behind calculateOpenAICost / calculateAnthropicCost /\n * calculateGoogleCost — they differ only in DATA (provider label, pricing table, fallback key), so\n * each public function maps its own data in here instead of repeating this logic. The `fallbackKey`\n * is passed PER CALLER so every provider keeps its own unknown-model default.\n */\nfunction perModelTableCost(\n provider: 'openai' | 'anthropic' | 'google',\n table: Record<string, Rate>,\n fallbackKey: string,\n params: CostParams\n): number {\n const { model, inputTokens, outputTokens } = params;\n // Gateway-style 'creator/model' ids (documented for OpenAI-compatible gateway endpoints) may name\n // ANOTHER creator — delegate so e.g. an Anthropic model routed through a wrapped OpenAI client\n // doesn't bill on the openai table. Delegate ONLY ids the splitter accepts — a leading/trailing\n // slash would round-trip un-stripped through the dispatcher and recurse forever.\n if (splitGatewayModelId(model) !== null) {\n return calculateCostByProvider(provider, model, inputTokens, outputTokens);\n }\n // cheap modern default for unknown models (#488, #490)\n const modelKey = findModelKey(model, table) ?? fallbackKey;\n return calculateCost(inputTokens, outputTokens, table[modelKey]!);\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Calculate cost for OpenAI API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateOpenAICost({\n * model: 'gpt-4o',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateOpenAICost(params: CostParams): number {\n return perModelTableCost('openai', OPENAI_PRICING, pricingData.fallback.openai, params);\n}\n\n/**\n * Calculate cost for Anthropic API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateAnthropicCost({\n * model: 'claude-3-5-sonnet',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateAnthropicCost(params: CostParams): number {\n return perModelTableCost('anthropic', ANTHROPIC_PRICING, pricingData.fallback.anthropic, params);\n}\n\n/**\n * Calculate cost for Google/Gemini API calls\n *\n * @param params - Cost calculation parameters\n * @returns Estimated cost in USD\n *\n * @example\n * ```ts\n * const cost = calculateGoogleCost({\n * model: 'gemini-2.0-flash',\n * inputTokens: 1000,\n * outputTokens: 500,\n * });\n * console.log(`Cost: $${cost.toFixed(4)}`);\n * ```\n */\nexport function calculateGoogleCost(params: CostParams): number {\n return perModelTableCost('google', GOOGLE_PRICING, pricingData.fallback.google, params);\n}\n\n// Flat per-provider rates ($/1M tokens) for providers that don't yet warrant a full\n// per-model table (deepseek/cohere/mistral). Sourced from the canonical pricing.json\n// `flatProviders` block — shared with the Python SDK's FLAT_PROVIDER_PRICING per-1K\n// mirror and guarded by tests/test_pricing_sync.py, so the two SDKs bill flat-rate\n// providers identically. calculateFlatProviderCost returns null for an unknown\n// provider — the caller picks the fallback (the shared `fallback.default` block in\n// calculateCostByProvider).\nconst FLAT_PROVIDER_RATES: Record<string, Rate> = pricingData.flatProviders;\n\nfunction calculateFlatProviderCost(\n provider: string,\n inputTokens: number,\n outputTokens: number\n): number | null {\n // Own-property guard: a JSON import inherits Object.prototype, so 'constructor' etc.\n // would otherwise pass the truthiness check and yield NaN costs.\n const key = provider.toLowerCase();\n const rate = Object.prototype.hasOwnProperty.call(FLAT_PROVIDER_RATES, key)\n ? FLAT_PROVIDER_RATES[key]\n : undefined;\n if (!rate) return null;\n return calculateCost(inputTokens, outputTokens, rate);\n}\n\n/**\n * Provider → cost dispatch. SINGLE source shared by the Vercel AI SDK wrapper (vercel.ts) and the\n * Mastra SDK so a provider is billed identically across integrations — previously each carried its\n * own switch and the Mastra copy dropped flat-rate providers to $0 (#488/#559).\n *\n * UNIFIED CROSS-SDK FALLBACK POLICY (lockstep with the Python SDK's `resolve_provider_cost` in\n * packages/python-sdk/sentrial/pricing.py, guarded by tests/test_pricing_sync.py). The chain,\n * identical in both SDKs:\n * 1. Resolve the provider HINT tolerantly, by SUBSTRING — OTel-semconv spellings like\n * 'az.ai.openai' or 'mistral_ai' resolve to the correct table (openai/anthropic/google first,\n * then the flat-rate providers). A hint that resolves nothing falls back to inferring the\n * provider from the model id (guessProvider), so an empty/'unknown'/'gateway' hint never\n * forces the default block when the model id names its family.\n * 2. A gateway 'creator/model' id whose creator is a FLAT-rate provider bills that flat rate\n * regardless of the hint (an Anthropic-compatible gateway may carry a keyset hint).\n * 3. openai/anthropic/google bill on their per-model tables; a gateway creator naming one of\n * them wins the table choice and the slash prefix is always stripped for the key match. An\n * UNKNOWN model within a known provider bills the provider's cheap-modern\n * `fallback.{provider}` key (never an expensive legacy flagship).\n * 4. Flat-rate providers (deepseek/cohere/mistral) bill their canonical `flatProviders` rate.\n * 5. A GENUINELY unknown provider (including recognized-but-unpriced families like meta/llama\n * and alibaba/qwen) bills the shared cheap `fallback.default` block — NEVER a silent $0,\n * which would understate real spend. (Unified Jul 2026; this dispatch previously returned $0\n * here while Python billed the default block.)\n * All rates come from the canonical pricing.json (fallback.default included).\n */\nexport function calculateCostByProvider(\n provider: string,\n model: string,\n inputTokens: number,\n outputTokens: number\n): number {\n if (!inputTokens && !outputTokens) return 0;\n\n // 1) Tolerant substring hint resolution, else infer from the model id (mirrors\n // resolve_provider_cost — same match order: keyset providers, flat providers, guess).\n const providerLower = (provider || '').toLowerCase();\n let resolved: string;\n if (providerLower.includes('openai')) resolved = 'openai';\n else if (providerLower.includes('anthropic')) resolved = 'anthropic';\n else if (providerLower.includes('google')) resolved = 'google';\n else {\n resolved =\n Object.keys(FLAT_PROVIDER_RATES).find((flat) => providerLower.includes(flat)) ??\n guessProvider(model);\n }\n\n // 2) A gateway id whose CREATOR is a flat-rate provider bills that flat rate regardless of the\n // resolved provider (mirrors Python's flat_gateway_cost short-circuit).\n const gw = splitGatewayModelId(model);\n if (gw && Object.prototype.hasOwnProperty.call(FLAT_PROVIDER_RATES, gw.provider)) {\n return calculateCost(inputTokens, outputTokens, FLAT_PROVIDER_RATES[gw.provider]!);\n }\n\n if (resolved === 'openai' || resolved === 'anthropic' || resolved === 'google') {\n // 3) Per-model tables. Gateway normalization mirrors Python's provider_model_key — it applies\n // ONLY on this keyset path: a keyset creator wins the table choice, and the slash prefix is\n // ALWAYS stripped so the longest-prefix key match sees the bare model id (the reserved\n // Google 'models/' resource prefix only pins the provider choice, never the bare id).\n if (gw) {\n if (gw.provider === 'openai' || gw.provider === 'anthropic' || gw.provider === 'google') {\n resolved = gw.provider;\n }\n model = gw.model;\n }\n const params = { model, inputTokens, outputTokens };\n if (resolved === 'openai') return calculateOpenAICost(params);\n if (resolved === 'anthropic') return calculateAnthropicCost(params);\n return calculateGoogleCost(params);\n }\n\n // 4) Flat-rate provider (deepseek/cohere/mistral): canonical per-provider rate.\n const flat = calculateFlatProviderCost(resolved, inputTokens, outputTokens);\n if (flat !== null) return flat;\n\n // 5) Genuinely unknown provider AND unknown model family: the shared cheap\n // `fallback.default` block (unified cross-SDK policy) — never a silent $0.\n return calculateCost(inputTokens, outputTokens, pricingData.fallback.default);\n}\n\n/**\n * Usage shape accepted by {@link normalizeUsage} — supports both AI SDK v3/v4\n * (promptTokens/completionTokens, OpenAI naming) and v5/v6 (inputTokens/outputTokens,\n * generic naming). All fields optional; a `totalTokens` may be present on either.\n */\nexport type NormalizableUsage = {\n inputTokens?: number;\n outputTokens?: number;\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n};\n\nexport type NormalizedUsage = {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n};\n\n/** True when `value` is a finite, non-negative number usable as a token count. */\nfunction isValidTokenCount(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n\n/**\n * Return the first VALID token count among the candidates, else 0. Unlike `??` (which only falls\n * through on null/undefined), this falls through a NaN/negative/non-numeric primary to a valid\n * secondary — e.g. `firstTokenCount(NaN, 100) === 100` — so a malformed primary field (inputTokens)\n * can never mask a valid secondary (promptTokens). This is the true `|| 0`-style semantics the\n * docstring promises.\n */\nfunction firstTokenCount(...candidates: unknown[]): number {\n for (const c of candidates) {\n if (isValidTokenCount(c)) return c;\n }\n return 0;\n}\n\n/**\n * Normalize LLM usage from any Vercel AI SDK version (and Mastra, which follows AI SDK naming)\n * into a consistent { promptTokens, completionTokens, totalTokens } shape; returns undefined when\n * there is no usage or every count is zero.\n *\n * AI SDK v3/v4 uses promptTokens/completionTokens (OpenAI naming); v5/v6 uses\n * inputTokens/outputTokens (generic naming). This is the SINGLE canonical implementation shared by\n * the Vercel AI SDK wrapper (vercel.ts) and the Mastra SDK, which previously each carried a\n * divergent copy: vercel.ts coerced with `|| 0` (NaN/non-numeric -> 0) while mastra used `?? 0`\n * (nullish-only, so a NaN or a non-numeric field could leak straight through). We adopt the SAFER\n * `|| 0`-style semantics here via `firstTokenCount` (an explicit Number.isFinite guard applied to\n * BOTH the field selection and the final value) so a malformed usage object can never surface\n * NaN/garbage token counts or costs, and a NaN primary field (e.g. inputTokens) falls through to a\n * valid secondary (promptTokens) rather than masking it — this does not change behavior for real\n * numeric inputs, and only tightens the previously looser mastra path.\n */\nexport function normalizeUsage(\n usage: NormalizableUsage | Record<string, unknown> | undefined\n): NormalizedUsage | undefined {\n if (!usage) return undefined;\n const u = usage as Record<string, unknown>;\n // Select the first VALID field (not merely the first non-nullish one): a malformed primary\n // (e.g. inputTokens: NaN) must not mask a valid secondary (promptTokens: 100). See firstTokenCount.\n const promptTokens = firstTokenCount(u.inputTokens, u.promptTokens);\n const completionTokens = firstTokenCount(u.outputTokens, u.completionTokens);\n // Use the provided totalTokens only when it is a valid finite non-negative number; otherwise\n // (missing, NaN, negative, non-numeric) fall back to the already-normalized prompt + completion\n // sum rather than silently coercing garbage to 0 — which would drop a real total on a malformed\n // usage object.\n const providedTotal = u.totalTokens;\n const totalTokens = isValidTokenCount(providedTotal)\n ? providedTotal\n : promptTokens + completionTokens;\n if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0) return undefined;\n return { promptTokens, completionTokens, totalTokens };\n}\n\n/**\n * Extract `{ promptTokens, completionTokens, totalTokens }` from a RAW OpenAI (or OpenAI-compatible)\n * chat/completion `usage` object (`prompt_tokens` / `completion_tokens` / `total_tokens`). Falls\n * back to `prompt + completion` when the provider omits `total_tokens` OR reports it as 0 while the\n * component counts are present, so total isn't 0 when the component counts are known (some proxies /\n * OpenAI-compatible gateways send `total_tokens: 0` alongside nonzero prompt/completion). Uses the\n * same finite-guard as `normalizeUsage` (`firstTokenCount`), so a NaN/negative/non-numeric provider\n * field coerces to 0 rather than leaking through. This is the single home for raw-provider-usage\n * shaping, kept in lockstep with the Python SDK's `_openai_usage` helper.\n */\nexport function extractOpenAIUsage(\n usage: Record<string, unknown> | null | undefined\n): NormalizedUsage {\n const promptTokens = firstTokenCount(usage?.prompt_tokens);\n const completionTokens = firstTokenCount(usage?.completion_tokens);\n // firstTokenCount clamps a NaN/negative/non-numeric total to 0; treat that 0 as \"no usable total\"\n // and fall back to prompt + completion (mirrors Python's `_openai_usage`).\n const providedTotal = firstTokenCount(usage?.total_tokens);\n const totalTokens = providedTotal > 0 ? providedTotal : promptTokens + completionTokens;\n return { promptTokens, completionTokens, totalTokens };\n}\n\n/**\n * Extract `{ promptTokens, completionTokens, totalTokens }` from a RAW Anthropic `usage` object\n * (`input_tokens` / `output_tokens`; no `total_tokens`, so total is always the sum). Uses the same\n * finite-guard as `normalizeUsage`. Mirrors the Python SDK's `_anthropic_usage` helper.\n */\n/**\n * Cache-inclusive Anthropic input-token total. Per the Messages API docs, \"Total input tokens in a\n * request is the summation of input_tokens, cache_creation_input_tokens, and cache_read_input_tokens\"\n * — input_tokens alone EXCLUDES cache traffic, so prompt-cached requests under-report without the\n * sum. Uses the same finite-guard (`firstTokenCount`) as `normalizeUsage`, so a malformed usage\n * object can never surface NaN/garbage counts. Single canonical home for this summation, shared by\n * `extractAnthropicUsage` and the streaming wrapper (wrappers.ts).\n *\n * COST NOTE: this cache-inclusive total is the TOKEN COUNT only — it is NOT the number fed into the\n * cost calc. COUNT and COST are deliberately separated: cost is computed from\n * `anthropicCostWeightedInputTokens`, which prices cache_read at 0.1x and cache_creation at 1.25x the\n * base per-model input rate (Anthropic's real 5-minute-cache billing) instead of charging every cache\n * token at the full input rate. So the reported COUNT stays cache-inclusive (matches Anthropic's\n * \"total input tokens\" definition) while the reported COST is now accurate rather than an over-estimate\n * on the cached portion of prompt-cached requests.\n */\nexport function anthropicCacheInclusiveInputTokens(\n usage: Record<string, unknown> | null | undefined\n): number {\n return (\n firstTokenCount(usage?.input_tokens) +\n firstTokenCount(usage?.cache_creation_input_tokens) +\n firstTokenCount(usage?.cache_read_input_tokens)\n );\n}\n\n/**\n * COST-weighted Anthropic input tokens. This is what the COST calc consumes (NOT the token COUNT):\n * it weights cache traffic by its real billing rate — cache_read at\n * {@link ANTHROPIC_CACHE_READ_MULTIPLIER} (0.1x), a 5-minute cache write at\n * {@link ANTHROPIC_CACHE_WRITE_MULTIPLIER} (1.25x), and a 1-hour (extended-TTL) cache write at\n * {@link ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER} (2x) the base per-model input rate — while pricing the\n * non-cache `input_tokens` at 1x. Feeding this into `calculateAnthropicCost` (whose per-token cost is\n * linear) yields the accurate Anthropic input cost. The reported token COUNT stays cache-inclusive and\n * unchanged (see `anthropicCacheInclusiveInputTokens`). Uses the same finite-guard (`firstTokenCount`)\n * so a malformed usage object can never surface NaN/garbage cost.\n */\nexport function anthropicCostWeightedInputTokens(\n usage: Record<string, unknown> | null | undefined\n): number {\n return (\n firstTokenCount(usage?.input_tokens) +\n anthropicWeightedCacheCreationTokens(usage) +\n ANTHROPIC_CACHE_READ_MULTIPLIER * firstTokenCount(usage?.cache_read_input_tokens)\n );\n}\n\n/**\n * Weighted cache-CREATION (write) token contribution to the cost-weighted input total. When the\n * usage object carries the per-TTL `cache_creation` breakdown, the 1-hour tier is billed at\n * {@link ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER} (2x) and the 5-minute tier at\n * {@link ANTHROPIC_CACHE_WRITE_MULTIPLIER} (1.25x); otherwise the aggregate\n * `cache_creation_input_tokens` falls back to the 5-minute rate (the API omits the split unless\n * 1-hour caching is used). Same finite-guard as the rest of the cost path.\n */\nfunction anthropicWeightedCacheCreationTokens(\n usage: Record<string, unknown> | null | undefined\n): number {\n const breakdown = usage?.cache_creation;\n if (breakdown && typeof breakdown === 'object') {\n const b = breakdown as Record<string, unknown>;\n const fiveMin = firstTokenCount(b.ephemeral_5m_input_tokens);\n const oneHour = firstTokenCount(b.ephemeral_1h_input_tokens);\n if (fiveMin > 0 || oneHour > 0) {\n return (\n ANTHROPIC_CACHE_WRITE_MULTIPLIER * fiveMin + ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER * oneHour\n );\n }\n }\n // Fallback: aggregate cache_creation_input_tokens, all at the 5-minute write rate.\n return ANTHROPIC_CACHE_WRITE_MULTIPLIER * firstTokenCount(usage?.cache_creation_input_tokens);\n}\n\nexport function extractAnthropicUsage(\n usage: Record<string, unknown> | null | undefined\n): NormalizedUsage {\n const promptTokens = anthropicCacheInclusiveInputTokens(usage);\n const completionTokens = firstTokenCount(usage?.output_tokens);\n return { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };\n}\n\n/**\n * Extract `{ promptTokens, completionTokens, totalTokens }` from a RAW Google generateContent\n * result. `usageMetadata` lives on the inner `.response` in older SDK versions and directly on the\n * result in newer ones (`promptTokenCount` / `candidatesTokenCount`; no total, so total is the sum).\n * Uses the same finite-guard as `normalizeUsage`. Mirrors the Python SDK's `_google_usage` helper.\n */\nexport function extractGoogleUsage(\n response: Record<string, unknown> | null | undefined\n): NormalizedUsage {\n const inner = response?.response as Record<string, unknown> | undefined;\n const usageMeta = (inner?.usageMetadata ?? response?.usageMetadata) as\n | Record<string, unknown>\n | undefined;\n const promptTokens = firstTokenCount(usageMeta?.promptTokenCount);\n // Docs: candidatesTokenCount EXCLUDES thinking tokens, which are billed as output — fold\n // thoughtsTokenCount into completion so output-token cost isn't undercounted.\n const completionTokens =\n firstTokenCount(usageMeta?.candidatesTokenCount) +\n firstTokenCount(usageMeta?.thoughtsTokenCount);\n // Docs define totalTokenCount = \"prompt + thoughts + response candidates\" — recomputing\n // prompt+candidates drops thinking tokens on thinking models, so prefer the API's own total.\n const docTotal = firstTokenCount(usageMeta?.totalTokenCount);\n return {\n promptTokens,\n completionTokens,\n totalTokens: docTotal > 0 ? docTotal : promptTokens + completionTokens,\n };\n}\n\n/**\n * Guess the LLM provider from a model id. Canonical shared implementation used by the Vercel AI\n * SDK wrapper (vercel.ts) and the Mastra SDK, which previously each carried a divergent copy —\n * Mastra's used includes('o1'/'o3'/'o4') (false-positive-prone) and covered only openai/anthropic/\n * google (#511/#560).\n */\n/**\n * Split a gateway-style `creator/model-name` id (the AI Gateway's documented format, e.g.\n * 'openai/gpt-5.4', 'xai/grok-4.5') into its creator segment and bare model id. Returns null\n * for non-gateway ids.\n */\nfunction splitGatewayModelId(modelId: string): { provider: string; model: string } | null {\n // A null/undefined/non-string model id is not a gateway id — return null so every caller\n // falls through to its unknown-model path instead of throwing on .indexOf (mirrors the\n // Python split_gateway_model_id's isinstance guard).\n if (typeof modelId !== 'string') return null;\n const idx = modelId.indexOf('/');\n if (idx <= 0 || idx === modelId.length - 1) return null;\n return { provider: modelId.slice(0, idx).toLowerCase(), model: modelId.slice(idx + 1) };\n}\n\nexport function guessProvider(modelId: string): string {\n // Null/undefined/non-string model ids resolve 'unknown' (never throw) — OTel/vendor payloads\n // can omit the model entirely (mirrors Python's guess_provider isinstance guard).\n if (typeof modelId !== 'string' || modelId === '') return 'unknown';\n // Gateway ids name their provider outright in the creator segment — trust it (falling back\n // to keyword-guessing the bare model id when the creator looks like a router alias).\n const gw = splitGatewayModelId(modelId);\n if (gw) {\n // 'models/' is Google's reserved resource prefix (e.g. 'models/gemini-2.0-flash'), NOT a\n // gateway creator — treat it like the router aliases so the keyword scan runs on the bare id.\n if (gw.provider !== 'gateway' && gw.provider !== 'unknown' && gw.provider !== 'models')\n return gw.provider;\n return guessProvider(gw.model);\n }\n const id = modelId.toLowerCase();\n if (\n id.includes('gpt') ||\n id.startsWith('o1') ||\n id.startsWith('o3') ||\n id.startsWith('o4') ||\n id.startsWith('chatgpt')\n )\n return 'openai';\n if (id.includes('claude')) return 'anthropic';\n if (id.includes('gemini')) return 'google';\n if (\n id.includes('mistral') ||\n id.includes('mixtral') ||\n id.includes('codestral') ||\n id.includes('pixtral')\n )\n return 'mistral';\n if (id.includes('llama')) return 'meta';\n if (id.includes('deepseek')) return 'deepseek';\n if (id.includes('command')) return 'cohere';\n if (id.includes('qwen')) return 'alibaba';\n return 'unknown';\n}\n\n/**\n * Canonical \"normalize raw AI-SDK usage → prompt/completion/total token counts → cost via\n * calculateCostByProvider(provider, model, ...)\" derivation, shared by BOTH AI-SDK wrappers\n * (Vercel `resolveTokensAndCost` call sites and the Mastra integration). Lives here — beside\n * normalizeUsage + calculateCostByProvider — so the two wrappers can never drift on how they\n * fold provider-extra token fields; previously only the Vercel copy folded Anthropic\n * cachedInputTokens / Google reasoningTokens, so @sentrial/mastra silently under-billed\n * cache-read-heavy Anthropic and thinking-heavy Google calls.\n *\n * totalTokens comes from normalizeUsage, which already falls back to promptTokens+completionTokens,\n * so this preserves the stricter sum-fallback everywhere (no caller regresses to `?? 0`). The\n * containment arithmetic gates each fold so subset-reporting providers (OpenAI) and AI-SDK v7\n * (fields moved into inputTokenDetails/outputTokenDetails) can never double-count.\n */\n/**\n * Fold AI-SDK provider-extra token fields into flat prompt/completion/total counts.\n *\n * AI SDK v5/v6 report provider extras as separate top-level fields that our flat reads drop:\n * - Anthropic: inputTokens EXCLUDES cache reads (cachedInputTokens is EXTRA; total = in+out)\n * - Google: outputTokens EXCLUDES thinking (reasoningTokens; total = in+out+reasoning)\n * OpenAI reports both as SUBSETS (already inside the flat counts), and v7 moved the fields into\n * inputTokenDetails/outputTokenDetails (absent here) — so each fold is gated on the containment\n * arithmetic so subset-reporting providers and v7 can never double-count. Shared by BOTH AI-SDK\n * wrappers (resolveTokensAndCost + the Mastra per-step aggregator) so they can never drift.\n */\nexport function foldProviderExtras(\n rawUsage: NormalizableUsage | Record<string, unknown> | undefined,\n provider: string,\n base: { promptTokens: number; completionTokens: number; totalTokens: number }\n): {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n /**\n * The Anthropic cache-READ tokens folded into `promptTokens` (0 when no fold applied). Reported\n * token COUNTS stay cache-inclusive, but cost must weight this slice at\n * {@link ANTHROPIC_CACHE_READ_MULTIPLIER} (0.1x), matching the direct wrapAnthropic path — so\n * callers computing cost subtract 0.9x of this back out. See resolveTokensAndCost.\n */\n foldedCachedInputTokens: number;\n} {\n let { promptTokens, completionTokens, totalTokens } = base;\n let foldedCachedInputTokens = 0;\n const extras = rawUsage as { cachedInputTokens?: number; reasoningTokens?: number } | undefined;\n const cached = extras?.cachedInputTokens;\n if (\n provider === 'anthropic' &&\n typeof cached === 'number' &&\n Number.isFinite(cached) &&\n cached > 0 &&\n promptTokens + completionTokens === totalTokens\n ) {\n promptTokens += cached;\n totalTokens += cached;\n foldedCachedInputTokens = cached;\n }\n const reasoning = extras?.reasoningTokens;\n if (\n typeof reasoning === 'number' &&\n Number.isFinite(reasoning) &&\n reasoning > 0 &&\n promptTokens + completionTokens + reasoning === totalTokens\n ) {\n // Reasoning demonstrably not contained in outputTokens (Google shape) — billed as output.\n completionTokens += reasoning;\n }\n return { promptTokens, completionTokens, totalTokens, foldedCachedInputTokens };\n}\n\nexport function resolveTokensAndCost(\n rawUsage: NormalizableUsage | Record<string, unknown> | undefined,\n provider: string,\n model: string,\n /**\n * Anthropic cache-READ tokens already folded into `rawUsage`'s prompt count by an UPSTREAM\n * per-step pass (the Mastra aggregator folds each step before summing, then discards the raw\n * `cachedInputTokens` field so foldProviderExtras here can't re-derive it). Pass the summed\n * folded count so the 0.1x cache-read discount is still applied to the cost basis. Omit (0)\n * for the normal single-usage path, where foldProviderExtras below computes the fold itself.\n */\n preFoldedCachedInputTokens = 0\n): { promptTokens: number; completionTokens: number; totalTokens: number; cost: number } {\n const usage = normalizeUsage(rawUsage);\n const { promptTokens, completionTokens, totalTokens, foldedCachedInputTokens } =\n foldProviderExtras(rawUsage, provider, {\n promptTokens: usage?.promptTokens ?? 0,\n completionTokens: usage?.completionTokens ?? 0,\n totalTokens:\n usage?.totalTokens ?? (usage?.promptTokens ?? 0) + (usage?.completionTokens ?? 0),\n });\n\n // Reported token COUNTS stay cache-inclusive (promptTokens), but the Anthropic cache-READ slice\n // must be priced at ANTHROPIC_CACHE_READ_MULTIPLIER (0.1x), not the full per-model input rate —\n // mirroring the direct wrapAnthropic path (anthropicCostWeightedInputTokens). Subtract 0.9x of the\n // folded cache-read tokens from the COST basis only (both this pass's fold and any upstream fold).\n const effectiveFoldedCached = foldedCachedInputTokens + preFoldedCachedInputTokens;\n const costPromptTokens =\n promptTokens - effectiveFoldedCached * (1 - ANTHROPIC_CACHE_READ_MULTIPLIER);\n const cost = calculateCostByProvider(provider, model, costPromptTokens, completionTokens);\n return { promptTokens, completionTokens, totalTokens, cost };\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any --\n * This module monkey-patches untyped third-party LLM provider SDK objects (OpenAI/Anthropic/Google\n * client instances and their streaming iterables), whose runtime shapes are not exported as types.\n * `any` is the boundary type at every `(client as any)` / `...args: any[]` / stream-chunk site here;\n * `unknown` + casts would add noise without added safety, so the rule is disabled file-wide. */\n/**\n * Sentrial LLM Wrappers - Auto-instrument LLM provider SDKs\n *\n * These wrappers automatically track all LLM calls with:\n * - Input messages\n * - Output responses\n * - Token counts\n * - Cost estimation\n * - Latency\n *\n * @example OpenAI\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapOpenAI, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const openai = wrapOpenAI(new OpenAI());\n *\n * // All calls are now automatically tracked!\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n *\n * @example Anthropic\n * ```ts\n * import Anthropic from '@anthropic-ai/sdk';\n * import { wrapAnthropic, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const anthropic = wrapAnthropic(new Anthropic());\n *\n * const response = await anthropic.messages.create({\n * model: 'claude-3-5-sonnet-20241022',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\n\nimport { SentrialClient } from './client.js';\nimport {\n calculateOpenAICost,\n calculateAnthropicCost,\n calculateGoogleCost,\n extractOpenAIUsage,\n extractAnthropicUsage,\n extractGoogleUsage,\n anthropicCacheInclusiveInputTokens,\n anthropicCostWeightedInputTokens,\n} from './cost.js';\nimport { createContextVar, type ContextToken } from './async-context.js';\n\n// ============================================================================\n// Session Context (async-safe via AsyncLocalStorage in Node.js,\n// simple variable fallback in browsers)\n// ============================================================================\n\nconst _currentSessionId = createContextVar<string | null>(null);\nconst _currentClient = createContextVar<SentrialClient | null>(null);\nlet _defaultClient: SentrialClient | null = null;\n\n/** Tokens for restoring previous session context (used internally by decorators). */\nexport interface SessionContextTokens {\n _sessionToken: ContextToken<string | null>;\n _clientToken: ContextToken<SentrialClient | null>;\n}\n\n/**\n * Set the current session context for auto-tracking.\n *\n * Call this before making LLM calls to associate them with a session.\n * This is automatically called when using withSession() or decorators.\n *\n * @param sessionId - The session ID to track LLM calls under\n * @param client - Optional Sentrial client (uses default if not provided)\n */\nexport function setSessionContext(sessionId: string, client?: SentrialClient): void {\n _currentSessionId.set(sessionId);\n if (client) {\n _currentClient.set(client);\n }\n}\n\n/**\n * Clear the current session context.\n */\nexport function clearSessionContext(): void {\n _currentSessionId.set(null);\n _currentClient.set(null);\n}\n\n/**\n * Get the current session ID.\n */\nexport function getSessionContext(): string | null {\n return _currentSessionId.get();\n}\n\n/**\n * Set the default client for wrappers.\n */\nexport function setDefaultClient(client: SentrialClient): void {\n _defaultClient = client;\n}\n\n/**\n * Get the default client configured for wrappers.\n * @internal Used by decorators to share sentrial.configure().\n */\nexport function getDefaultClient(): SentrialClient | null {\n return _defaultClient;\n}\n\n/**\n * Set session context and return tokens for restoring the previous values.\n * @internal Used by decorators for proper nesting support.\n */\nexport function _setSessionContextWithTokens(\n sessionId: string,\n client?: SentrialClient\n): SessionContextTokens {\n const _sessionToken = _currentSessionId.set(sessionId);\n const _clientToken = client\n ? _currentClient.set(client)\n : _currentClient.set(_currentClient.get()); // no-op token preserving current\n return { _sessionToken, _clientToken };\n}\n\n/**\n * Restore session context from tokens returned by _setSessionContextWithTokens.\n * @internal Used by decorators for proper nesting support.\n */\nexport function _restoreSessionContext(tokens: SessionContextTokens): void {\n _currentSessionId.reset(tokens._sessionToken);\n _currentClient.reset(tokens._clientToken);\n}\n\nfunction getTrackingClient(): SentrialClient | null {\n return _currentClient.get() ?? _defaultClient;\n}\n\n// ============================================================================\n// OpenAI Wrapper\n// ============================================================================\n\n/**\n * Wrap an OpenAI client to automatically track all LLM calls.\n *\n * @param client - OpenAI client instance\n * @param options - Wrapper options\n * @returns The same client, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapOpenAI, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n * const openai = wrapOpenAI(new OpenAI());\n *\n * // Now use client normally - all calls are tracked!\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Hello' }],\n * });\n * ```\n *\n * @remarks\n * Streaming via `chat.completions.create({ stream: true })` is auto-tracked. The higher-level\n * `chat.completions.stream()` helper and the Responses API are NOT yet auto-tracked — use\n * `create({ stream: true })` if you need streaming calls tracked.\n *\n * If you build on the OpenAI **Agents SDK**, use `instrumentOpenAIAgents` (see `openai-agents.ts`)\n * instead — don't combine it with `wrapOpenAI` on the same run, or LLM calls will be double-counted.\n */\n/**\n * Preserve the SDK's documented raw-response surface (\".asResponse() ... on the APIPromise\n * type that all methods return\") on our tracked promise by delegating to the underlying\n * APIPromise. Non-stream calls stay fully tracked (tracking rides a side channel on the same\n * APIPromise, which memoizes its parse). Stream calls made via withResponse()/asResponse()\n * receive the SDK's RAW stream and bypass wrap tracking — a disclosed limitation.\n */\nfunction attachRawResponseMethods(tracked: Promise<unknown>, apiPromise: unknown): void {\n for (const m of ['withResponse', 'asResponse'] as const) {\n const fn = (apiPromise as Record<string, unknown>)?.[m];\n if (typeof fn === 'function') {\n (tracked as unknown as Record<string, unknown>)[m] = (\n fn as (...args: unknown[]) => unknown\n ).bind(apiPromise);\n }\n }\n}\n\nexport function wrapOpenAI<T extends object>(\n client: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access chat.completions.create. Untyped third-party boundary: the SDK declares no `openai`\n // type dependency, so the client shape is `any` and is runtime-guarded below.\n const chat = (client as any).chat;\n if (!chat?.completions?.create) {\n console.warn('Sentrial: OpenAI client does not have chat.completions.create');\n return client;\n }\n\n const originalCreate = chat.completions.create.bind(chat.completions);\n\n chat.completions.create = function (...args: any[]) {\n const startTime = Date.now();\n const params = args[0] ?? {};\n const messages = params.messages ?? [];\n const model = params.model ?? 'unknown';\n const isStreaming = params.stream === true;\n\n // Auto-inject stream_options.include_usage so streaming calls report token counts\n if (isStreaming && !params.stream_options?.include_usage) {\n args[0] = { ...params, stream_options: { ...params.stream_options, include_usage: true } };\n }\n\n // Call WITHOUT awaiting so the SDK's APIPromise survives — its withResponse()/asResponse()\n // are delegated onto the tracked promise below.\n const apiPromise = originalCreate(...args);\n\n const tracked = (async () => {\n const response = await apiPromise;\n\n if (isStreaming) {\n return wrapOpenAIStream(response, { startTime, messages, model, trackWithoutSession });\n }\n\n const durationMs = Date.now() - startTime;\n\n // Extract token usage (shared per-provider extractor; falls back to prompt+completion when the\n // provider omits total_tokens, matching the stream path below, and finite-guards each field).\n const { promptTokens, completionTokens, totalTokens } = extractOpenAIUsage(response.usage);\n\n // Extract output text + tool calls\n let outputContent = '';\n const toolCalls: Array<{ name: string; arguments: string }> = [];\n const finishReason: string | null = response.choices?.[0]?.finish_reason ?? null;\n const msg = response.choices?.[0]?.message;\n if (msg?.content) {\n outputContent = msg.content;\n }\n if (msg?.tool_calls) {\n for (const tc of msg.tool_calls) {\n // Chat Completions tool calls carry either a `function` payload or\n // (type: \"custom\") a `custom` payload with name + free-form input.\n const custom = (tc as { custom?: { name?: string; input?: string } }).custom;\n toolCalls.push({\n name: tc.function?.name ?? custom?.name ?? 'unknown',\n arguments: tc.function?.arguments ?? custom?.input ?? '{}',\n });\n }\n }\n\n // Calculate cost\n const cost = calculateOpenAICost({\n model,\n inputTokens: promptTokens,\n outputTokens: completionTokens,\n });\n\n // Track the call\n trackLLMCall({\n provider: 'openai',\n model,\n messages,\n output: outputContent,\n toolCalls,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n finishReason,\n });\n\n return response;\n })().catch((error) => {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'openai',\n model,\n messages,\n error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n });\n\n attachRawResponseMethods(tracked, apiPromise);\n // Side-channel guard: a caller who only uses withResponse()/asResponse() never awaits\n // `tracked` — register a no-op handler so its (rethrown) rejection can't surface as an\n // unhandled-rejection warning. Awaiting callers still receive the rejection.\n tracked.catch(() => {});\n return tracked;\n };\n\n return client;\n}\n\n// ============================================================================\n// Anthropic Wrapper\n// ============================================================================\n\n/**\n * Wrap an Anthropic client to automatically track all LLM calls.\n *\n * @param client - Anthropic client instance\n * @param options - Wrapper options\n * @returns The same client, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import Anthropic from '@anthropic-ai/sdk';\n * import { wrapAnthropic, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n * const anthropic = wrapAnthropic(new Anthropic());\n *\n * const response = await anthropic.messages.create({\n * model: 'claude-3-5-sonnet-20241022',\n * max_tokens: 1024,\n * messages: [{ role: 'user', content: 'Hello!' }],\n * });\n * ```\n */\nexport function wrapAnthropic<T extends object>(\n client: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access messages.create. Untyped third-party boundary: the SDK declares no `@anthropic-ai/sdk`\n // type dependency, so the client shape is `any` and is runtime-guarded below.\n const messages = (client as any).messages;\n if (!messages?.create) {\n console.warn('Sentrial: Anthropic client does not have messages.create');\n return client;\n }\n\n const originalCreate = messages.create.bind(messages);\n\n messages.create = function (...args: any[]) {\n const startTime = Date.now();\n const params = args[0] ?? {};\n const inputMessages = params.messages ?? [];\n const model = params.model ?? 'unknown';\n const system = params.system ?? '';\n const isStreaming = params.stream === true;\n\n // Call WITHOUT awaiting so the SDK's APIPromise survives — its withResponse()/asResponse()\n // are delegated onto the tracked promise below.\n const apiPromise = originalCreate(...args);\n\n const tracked = (async () => {\n const response = await apiPromise;\n\n if (isStreaming) {\n return wrapAnthropicStream(response, {\n startTime,\n messages: inputMessages,\n model,\n system,\n trackWithoutSession,\n });\n }\n\n const durationMs = Date.now() - startTime;\n\n // Anthropic uses input_tokens/output_tokens (shared per-provider extractor).\n const { promptTokens, completionTokens, totalTokens } = extractAnthropicUsage(response.usage);\n\n // Extract output text + tool use blocks (+ why generation stopped)\n const finishReason: string | null = response.stop_reason ?? null;\n let outputContent = '';\n const toolCalls: Array<{ name: string; arguments: string }> = [];\n if (response.content) {\n for (const block of response.content) {\n if (block.type === 'text') {\n outputContent += block.text;\n } else if (block.type === 'tool_use') {\n toolCalls.push({\n name: block.name ?? 'unknown',\n arguments: JSON.stringify(block.input ?? {}),\n });\n }\n }\n }\n\n // COST is priced from the cache-WEIGHTED input (cache_read 0.1x, cache_creation 1.25x), NOT\n // the cache-inclusive COUNT above — otherwise cached prompt tokens overstate cost up to ~10x.\n const cost = calculateAnthropicCost({\n model,\n inputTokens: anthropicCostWeightedInputTokens(response.usage),\n outputTokens: completionTokens,\n });\n\n // Include system prompt in messages for tracking\n const fullMessages = system\n ? [{ role: 'system', content: system }, ...inputMessages]\n : inputMessages;\n\n trackLLMCall({\n provider: 'anthropic',\n model,\n messages: fullMessages,\n output: outputContent,\n toolCalls,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n finishReason,\n });\n\n return response;\n })().catch((error) => {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'anthropic',\n model,\n messages: inputMessages,\n error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n });\n\n attachRawResponseMethods(tracked, apiPromise);\n // Side-channel guard (see wrapOpenAI): keep withResponse-only callers free of\n // unhandled-rejection warnings.\n tracked.catch(() => {});\n return tracked;\n };\n\n // Also wrap messages.stream() — Anthropic's higher-level streaming API returns a MessageStream\n // (async-iterable + .finalMessage()/.on()). The Python SDK tracks it (TrackedMessageStream) but\n // the JS SDK didn't, so client.messages.stream(...) callers got zero tracking (#613). The\n // returned MessageStream yields the same events as create({ stream: true }), so we reuse\n // wrapAnthropicStream. Tracking fires on iteration — consumers that only use .on()/.finalMessage()\n // without iterating aren't tracked (same limitation as the Python SDK), but those methods no\n // longer crash (wrapStreamWithTracking binds them to the real instance).\n //\n // IMPORTANT: MessageStream.createMessage() re-enters `messages.create({ ...params, stream: true })\n // .withResponse()` (see @anthropic-ai/sdk lib/MessageStream). If it hit our wrapped create — a\n // plain async function returning a native Promise — .withResponse() would be undefined and every\n // stream() call would crash (and older SDKs would double-track). So we invoke the ORIGINAL stream\n // with a facade `this` whose `create` is the ORIGINAL (unwrapped) create, so the re-entry passes\n // through untouched and .withResponse() survives.\n const originalStreamFn = messages.stream;\n if (typeof originalStreamFn === 'function') {\n messages.stream = function (...args: any[]) {\n const startTime = Date.now();\n const params = args[0] ?? {};\n const inputMessages = params.messages ?? [];\n const model = params.model ?? 'unknown';\n const system = params.system ?? '';\n try {\n // Facade delegates every property to the real `messages` object except `create`, which is\n // the original so the internal re-entry never hits our wrapped create.\n const facade: any = Object.create(messages);\n facade.create = originalCreate;\n const messageStream = originalStreamFn.call(facade, ...args);\n return wrapAnthropicStream(messageStream, {\n startTime,\n messages: inputMessages,\n model,\n system,\n trackWithoutSession,\n });\n } catch (error) {\n // Parity with create(): record synchronous stream() failures (invalid params, etc.).\n trackLLMError({\n provider: 'anthropic',\n model,\n messages: system\n ? [{ role: 'system', content: system }, ...inputMessages]\n : inputMessages,\n error,\n durationMs: Date.now() - startTime,\n trackWithoutSession,\n });\n throw error;\n }\n };\n }\n\n return client;\n}\n\n// ============================================================================\n// Google/Gemini Wrapper\n// ============================================================================\n\n/**\n * Wrap a Google GenerativeModel to automatically track all LLM calls.\n *\n * @param model - Google GenerativeModel instance\n * @param options - Wrapper options\n * @returns The same model, now with auto-tracking enabled\n *\n * @example\n * ```ts\n * import { GoogleGenerativeAI } from '@google/generative-ai';\n * import { wrapGoogle, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY!);\n * const model = wrapGoogle(genAI.getGenerativeModel({ model: 'gemini-2.0-flash' }));\n *\n * const response = await model.generateContent('Hello!');\n * ```\n */\nexport function wrapGoogle<T extends object>(\n model: T,\n options: { trackWithoutSession?: boolean } = {}\n): T {\n const { trackWithoutSession = false } = options;\n\n // Access generateContent. Untyped third-party boundary: the SDK declares no\n // `@google/generative-ai` type dependency, so the model shape is `any` and is runtime-guarded.\n const originalGenerate = (model as any).generateContent;\n if (!originalGenerate) {\n console.warn('Sentrial: Google model does not have generateContent');\n return model;\n }\n\n (model as any).generateContent = async function (...args: any[]) {\n const startTime = Date.now();\n const contents = args[0];\n // Strip the \"models/\" prefix that some Google SDK versions report on\n // model ids (e.g. \"models/gemini-2.0-flash\") so cost lookup matches the\n // pricing table keys — mirrors the Python SDK's wrap_google behavior.\n const rawModelName = (model as any).model ?? 'gemini-unknown';\n const modelName =\n typeof rawModelName === 'string' && rawModelName.startsWith('models/')\n ? rawModelName.slice('models/'.length)\n : rawModelName;\n\n // Convert contents to messages format\n const messages = googleContentsToMessages(contents);\n\n try {\n const response = await originalGenerate.apply(model, args);\n const durationMs = Date.now() - startTime;\n\n // Extract usage — generateContent returns { response: GenerateContentResponse }; usageMetadata\n // lives on the inner .response, or directly on the result in newer SDK versions. The shared\n // per-provider extractor resolves both locations (and finite-guards each field).\n const { promptTokens, completionTokens, totalTokens } = extractGoogleUsage(response);\n\n // Extract output — .text() can throw if response was safety-filtered\n let outputContent = '';\n try {\n outputContent = response.response?.text?.() ?? response.text?.() ?? '';\n } catch {\n // text() throws if no text content (e.g. safety filter)\n }\n\n const cost = calculateGoogleCost({\n model: modelName,\n inputTokens: promptTokens,\n outputTokens: completionTokens,\n });\n\n const finishReason: string | null =\n response.response?.candidates?.[0]?.finishReason ??\n response.candidates?.[0]?.finishReason ??\n null;\n\n trackLLMCall({\n provider: 'google',\n model: modelName,\n messages,\n output: outputContent,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n finishReason,\n });\n\n return response;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n trackLLMError({\n provider: 'google',\n model: modelName,\n messages,\n error,\n durationMs,\n trackWithoutSession,\n });\n throw error;\n }\n };\n\n // generateContentStream is a SEPARATE method (\"Makes a single streaming call ... as well as\n // a promise that returns the final aggregated response\") — track via that aggregate promise\n // and return the SDK's object unchanged so the caller's stream is untouched (parity with the\n // Python wrapper's stream=True tracking).\n const originalGenerateStream = (model as any).generateContentStream;\n if (typeof originalGenerateStream === 'function') {\n (model as any).generateContentStream = async function (...args: any[]) {\n const startTime = Date.now();\n const contents = args[0];\n const rawModelName = (model as any).model ?? 'gemini-unknown';\n const modelName =\n typeof rawModelName === 'string' && rawModelName.startsWith('models/')\n ? rawModelName.slice('models/'.length)\n : rawModelName;\n const messages = googleContentsToMessages(contents);\n\n try {\n const result = await originalGenerateStream.apply(model, args);\n // result.response resolves once the stream is fully consumed — track then, without\n // consuming or wrapping the caller's stream.\n const aggregate = (result as { response?: Promise<unknown> })?.response;\n if (aggregate && typeof (aggregate as Promise<unknown>).then === 'function') {\n (aggregate as Promise<any>)\n .then((response: any) => {\n const durationMs = Date.now() - startTime;\n const { promptTokens, completionTokens, totalTokens } = extractGoogleUsage(response);\n let outputContent = '';\n try {\n outputContent = response?.text?.() ?? '';\n } catch {\n /* text() throws when safety-filtered */\n }\n const cost = calculateGoogleCost({\n model: modelName,\n inputTokens: promptTokens,\n outputTokens: completionTokens,\n });\n trackLLMCall({\n provider: 'google',\n model: modelName,\n messages,\n output: outputContent,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession,\n finishReason: response?.candidates?.[0]?.finishReason ?? null,\n });\n })\n .catch(() => {\n /* aggregate rejects when the stream errors/was abandoned — nothing to track */\n });\n }\n return result;\n } catch (error) {\n trackLLMError({\n provider: 'google',\n model: modelName,\n messages,\n error,\n durationMs: Date.now() - startTime,\n trackWithoutSession,\n });\n throw error;\n }\n };\n }\n\n return model;\n}\n\n/**\n * String() that never throws — user-supplied contents/errors can carry hostile\n * toString/Symbol.toPrimitive; ingress coercion on the tracking path must degrade to the\n * cross-SDK '[unserializable]' literal, never throw (the monitoring-never-crashes contract).\n */\nfunction safeString(value: unknown): string {\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/** JSON.stringify that never throws — cycles/hostile toJSON degrade via safeString. */\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? safeString(value);\n } catch {\n return safeString(value);\n }\n}\n\n/**\n * Read `error[key]` without ever throwing (users/iterator callers can inject arbitrary\n * values, including objects whose getters throw). Returns undefined when absent/hostile.\n */\nfunction safeErrorField(error: unknown, key: 'name' | 'message'): string | undefined {\n try {\n const raw = (error as Record<string, unknown> | null | undefined)?.[key];\n if (raw === null || raw === undefined) return undefined;\n return typeof raw === 'string' ? raw : safeString(raw);\n } catch {\n return undefined;\n }\n}\n\nfunction googleContentsToMessages(contents: unknown): Array<{ role: string; content: string }> {\n // Never-throw: this runs BEFORE the provider call (outside its try) on user-supplied\n // `contents` — a hostile part (throwing getter/toJSON/toString, cyclic object) must\n // degrade to a marker message, never prevent the user's generateContent from running.\n try {\n // generateContent accepts GenerateContentRequest | string | Array<string | Part> — unwrap\n // the request form so its Content[] goes through the array handling below.\n if (\n contents &&\n typeof contents === 'object' &&\n !Array.isArray(contents) &&\n Array.isArray((contents as { contents?: unknown[] }).contents)\n ) {\n contents = (contents as { contents: unknown[] }).contents;\n }\n if (typeof contents === 'string') {\n return [{ role: 'user', content: contents }];\n }\n if (Array.isArray(contents)) {\n return contents.map((item) => {\n if (typeof item === 'string') {\n return { role: 'user', content: item };\n }\n if (item && typeof item === 'object') {\n const obj = item as { role?: string; text?: string; parts?: unknown[] };\n // Content objects carry parts[] (each Part has .text); bare Parts carry .text.\n let text: string;\n if (Array.isArray(obj.parts)) {\n text = obj.parts\n .map((p) => (typeof p === 'string' ? p : ((p as { text?: string }).text ?? '')))\n .filter(Boolean)\n .join(' ');\n } else if (typeof obj.text === 'string') {\n text = obj.text;\n } else {\n text = safeStringify(item);\n }\n return { role: typeof obj.role === 'string' ? obj.role : 'user', content: text };\n }\n return { role: 'user', content: safeString(item) };\n });\n }\n return [{ role: 'user', content: safeString(contents) }];\n } catch {\n return [{ role: 'user', content: '[unserializable]' }];\n }\n}\n\n// ============================================================================\n// Auto-detect Wrapper\n// ============================================================================\n\n/**\n * Auto-detect and wrap any supported LLM client.\n *\n * @param client - Any supported LLM client (OpenAI, Anthropic, Google)\n * @param provider - Optional provider hint\n * @returns The wrapped client\n *\n * @example\n * ```ts\n * import OpenAI from 'openai';\n * import { wrapLLM } from '@sentrial/sdk';\n *\n * const client = wrapLLM(new OpenAI()); // Auto-detected as OpenAI\n * ```\n */\nexport function wrapLLM<T extends object>(\n client: T,\n provider?: 'openai' | 'anthropic' | 'google'\n): T {\n // Try to auto-detect based on client shape. Untyped third-party boundary: no openai/anthropic/\n // google type dependency is declared, so the client shape is `any` and sniffed by duck-typing.\n if (provider === 'openai' || (client as any).chat?.completions?.create) {\n return wrapOpenAI(client);\n }\n if (provider === 'anthropic' || (client as any).messages?.create) {\n return wrapAnthropic(client);\n }\n if (provider === 'google' || (client as any).generateContent) {\n return wrapGoogle(client);\n }\n\n console.warn('Sentrial: Unknown LLM client type. No auto-tracking applied.');\n return client;\n}\n\n// ============================================================================\n// Stream Wrappers\n// ============================================================================\n\ninterface StreamContext {\n startTime: number;\n messages: unknown[];\n model: string;\n trackWithoutSession: boolean;\n}\n\ninterface AnthropicStreamContext extends StreamContext {\n system: string;\n}\n\n/**\n * Shared stream-wrapping plumbing for wrapOpenAIStream/wrapAnthropicStream (#494). Owns the\n * Proxy + async-iterator interception (which must transparently delegate to the original stream\n * so private fields like OpenAI's Stream.#client survive .tee()); each provider supplies only its\n * per-chunk accumulator and its final trackResult callback. `trackResult` must be idempotent —\n * it can fire from both stream exhaustion and early .return(). `trackError` (optional) fires from\n * the iterator's throw()/rejection path so a mid-stream failure records an error event.\n *\n * Delegated function properties are bound to the underlying target so `this` is the real stream\n * instance — Anthropic's MessageStream exposes its API (.on()/.done()/.finalMessage()) via ES\n * class-private fields keyed to the original instance, which would throw a private-member\n * TypeError if invoked with the Proxy as `this` (#613).\n */\nfunction wrapStreamWithTracking(\n stream: any,\n processChunk: (chunk: any) => void,\n trackResult: () => void,\n trackError?: (error: unknown) => void\n): any {\n const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);\n if (!originalIterator) return stream; // Not an async iterable, return as-is\n\n return new Proxy(stream, {\n get(target, prop, receiver) {\n if (prop === Symbol.asyncIterator) {\n return function () {\n const iter = originalIterator();\n return {\n async next() {\n try {\n const result = await iter.next();\n if (!result.done) {\n processChunk(result.value);\n } else {\n trackResult();\n }\n return result;\n } catch (error) {\n trackError?.(error);\n throw error;\n }\n },\n async return(value?: any) {\n trackResult();\n return iter.return?.(value) ?? { done: true, value: undefined };\n },\n async throw(error?: any) {\n trackError?.(error);\n return iter.throw?.(error) ?? { done: true, value: undefined };\n },\n };\n };\n }\n const value = Reflect.get(target, prop, receiver);\n // Bind methods to the real target so ES-private-field access (MessageStream) works.\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n\n/**\n * Wrap an OpenAI streaming response to track after completion.\n * Preserves the original stream object shape while intercepting the async iterator.\n */\nfunction wrapOpenAIStream(stream: any, ctx: StreamContext): any {\n let fullContent = '';\n let usage: { prompt_tokens?: number; completion_tokens?: number; total_tokens?: number } | null =\n null;\n const toolCallMap = new Map<number, { name: string; arguments: string }>();\n let finishReason: string | null = null;\n let tracked = false;\n\n const trackResult = () => {\n if (tracked) return;\n tracked = true;\n const durationMs = Date.now() - ctx.startTime;\n // Shared per-provider extractor (identical to the OpenAI non-stream path above).\n const { promptTokens, completionTokens, totalTokens } = extractOpenAIUsage(usage);\n const cost = calculateOpenAICost({\n model: ctx.model,\n inputTokens: promptTokens,\n outputTokens: completionTokens,\n });\n\n trackLLMCall({\n provider: 'openai',\n model: ctx.model,\n messages: ctx.messages,\n output: fullContent,\n toolCalls: Array.from(toolCallMap.values()),\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession: ctx.trackWithoutSession,\n finishReason,\n });\n };\n\n return wrapStreamWithTracking(\n stream,\n (chunk) => {\n const delta = chunk.choices?.[0]?.delta;\n if (chunk.choices?.[0]?.finish_reason) finishReason = chunk.choices[0].finish_reason;\n if (delta?.content) fullContent += delta.content;\n // Accumulate streaming tool calls across chunks\n if (delta?.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n const existing = toolCallMap.get(idx) ?? { name: '', arguments: '' };\n if (tc.function?.name) existing.name = tc.function.name;\n if (tc.function?.arguments) existing.arguments += tc.function.arguments;\n toolCallMap.set(idx, existing);\n }\n }\n if (chunk.usage) usage = chunk.usage;\n },\n trackResult\n );\n}\n\n/**\n * Wrap an Anthropic streaming response to track after completion.\n * Anthropic streams yield events: content_block_delta, message_start, message_delta, etc.\n */\nfunction wrapAnthropicStream(stream: any, ctx: AnthropicStreamContext): any {\n let fullContent = '';\n let inputTokens = 0;\n // Cache-WEIGHTED input tokens for COST (cache_read 0.1x, cache_creation 1.25x). Tracked in\n // lockstep with inputTokens (the cache-inclusive COUNT) so cost isn't overstated on cached prompts.\n let costInputTokens = 0;\n let outputTokens = 0;\n const toolCallsById = new Map<number, { name: string; arguments: string }>();\n let currentBlockIdx = -1;\n let finishReason: string | null = null;\n let tracked = false;\n\n const trackResult = () => {\n if (tracked) return;\n tracked = true;\n const durationMs = Date.now() - ctx.startTime;\n const totalTokens = inputTokens + outputTokens;\n const cost = calculateAnthropicCost({\n model: ctx.model,\n inputTokens: costInputTokens,\n outputTokens,\n });\n\n const fullMessages = ctx.system\n ? [{ role: 'system', content: ctx.system }, ...ctx.messages]\n : ctx.messages;\n\n trackLLMCall({\n provider: 'anthropic',\n model: ctx.model,\n messages: fullMessages,\n output: fullContent,\n toolCalls: Array.from(toolCallsById.values()),\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens,\n cost,\n durationMs,\n trackWithoutSession: ctx.trackWithoutSession,\n finishReason,\n });\n };\n\n return wrapStreamWithTracking(\n stream,\n (event) => {\n if (event.type === 'message_delta' && event.delta?.stop_reason) {\n finishReason = event.delta.stop_reason;\n }\n if (event.type === 'content_block_start') {\n currentBlockIdx = event.index ?? currentBlockIdx + 1;\n if (event.content_block?.type === 'tool_use') {\n toolCallsById.set(currentBlockIdx, {\n name: event.content_block.name ?? 'unknown',\n arguments: '',\n });\n }\n }\n if (event.type === 'content_block_delta') {\n if (event.delta?.text) {\n fullContent += event.delta.text;\n }\n // Accumulate tool input JSON across delta chunks\n if (event.delta?.type === 'input_json_delta' && event.delta?.partial_json) {\n const idx = event.index ?? currentBlockIdx;\n const existing = toolCallsById.get(idx);\n if (existing) {\n existing.arguments += event.delta.partial_json;\n }\n }\n }\n if (event.type === 'message_start' && event.message?.usage) {\n inputTokens = anthropicCacheInclusiveInputTokens(event.message.usage);\n costInputTokens = anthropicCostWeightedInputTokens(event.message.usage);\n }\n if (event.type === 'message_delta' && event.usage) {\n outputTokens = event.usage.output_tokens ?? 0;\n // Cumulative usage: the final delta may carry an UPDATED input count (server tool use);\n // per the docs' own example input_tokens can grow from 2679 to 10682 mid-stream.\n if (event.usage.input_tokens != null) {\n inputTokens = anthropicCacheInclusiveInputTokens(event.usage);\n costInputTokens = anthropicCostWeightedInputTokens(event.usage);\n }\n }\n },\n trackResult,\n (error) => {\n if (tracked) return;\n tracked = true;\n trackLLMError({\n provider: 'anthropic',\n model: ctx.model,\n messages: ctx.system\n ? [{ role: 'system', content: ctx.system }, ...ctx.messages]\n : ctx.messages,\n // safeString: iterator .throw() callers can inject arbitrary hostile values.\n error: error instanceof Error ? error : new Error(safeString(error)),\n durationMs: Date.now() - ctx.startTime,\n trackWithoutSession: ctx.trackWithoutSession,\n });\n }\n );\n}\n\n// ============================================================================\n// Common Tracking Functions\n// ============================================================================\n\ninterface TrackLLMCallParams {\n provider: string;\n model: string;\n messages: unknown[];\n output: string;\n toolCalls?: Array<{ name: string; arguments: string }>;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n cost: number;\n durationMs: number;\n trackWithoutSession: boolean;\n /** Why generation stopped (OpenAI finish_reason / Anthropic stop_reason / Google finishReason). */\n finishReason?: string | null;\n}\n\nfunction trackLLMCall(params: TrackLLMCallParams): void {\n const client = getTrackingClient();\n if (!client) return;\n\n const sessionId = _currentSessionId.get();\n if (!sessionId && !params.trackWithoutSession) {\n return;\n }\n\n // Build tool output payload with tool calls if present\n const toolOutput: Record<string, unknown> = {\n content: params.output,\n tokens: {\n prompt: params.promptTokens,\n completion: params.completionTokens,\n total: params.totalTokens,\n },\n cost_usd: params.cost,\n };\n if (params.toolCalls && params.toolCalls.length > 0) {\n toolOutput.tool_calls = params.toolCalls;\n }\n\n if (sessionId) {\n // Track under the existing session\n client\n .trackToolCall({\n sessionId,\n toolName: `llm:${params.provider}:${params.model}`,\n toolInput: {\n messages: params.messages,\n model: params.model,\n provider: params.provider,\n },\n toolOutput,\n reasoning: `LLM call to ${params.provider} ${params.model}`,\n estimatedCost: params.cost,\n tokenCount: params.totalTokens,\n model: params.model,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n finishReason: params.finishReason ?? undefined,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n prompt_tokens: params.promptTokens,\n completion_tokens: params.completionTokens,\n },\n })\n .catch((err) => {\n console.warn('Sentrial: Failed to track LLM call:', err.message);\n });\n } else if (params.trackWithoutSession) {\n // Create an ad-hoc session, track the call, then auto-complete it\n client\n .createSession({\n name: `LLM: ${params.provider}/${params.model}`,\n agentName: `${params.provider}-wrapper`,\n userId: 'anonymous',\n })\n .then((sid) => {\n if (!sid) return;\n return client\n .trackToolCall({\n sessionId: sid,\n toolName: `llm:${params.provider}:${params.model}`,\n toolInput: {\n messages: params.messages,\n model: params.model,\n provider: params.provider,\n },\n toolOutput,\n estimatedCost: params.cost,\n tokenCount: params.totalTokens,\n model: params.model,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n finishReason: params.finishReason ?? undefined,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n },\n })\n .then(() => {\n // Auto-complete so the session doesn't dangle\n return client.completeSession({\n sessionId: sid,\n success: true,\n estimatedCost: params.cost,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n totalTokens: params.totalTokens,\n durationMs: params.durationMs,\n });\n });\n })\n .catch((err) => {\n console.warn('Sentrial: Failed to track standalone LLM call:', err.message);\n });\n }\n}\n\ninterface TrackLLMErrorParams {\n provider: string;\n model: string;\n messages: unknown[];\n // catch/`.catch` variables are `unknown` by construction (any throw shape can land here); the\n // only consumers, safeErrorField/safeString, already accept unknown, so keep the type honest.\n error: unknown;\n durationMs: number;\n trackWithoutSession: boolean;\n}\n\nfunction trackLLMError(params: TrackLLMErrorParams): void {\n const client = getTrackingClient();\n if (!client) return;\n\n const sessionId = _currentSessionId.get();\n if (!sessionId) return; // trackWithoutSession errors are not worth an ad-hoc session\n\n client\n .trackError({\n sessionId,\n // Guarded introspection: trackLLMError runs synchronously inside catch blocks before\n // the original error is rethrown — a hostile message/name getter must not replace it.\n errorMessage: safeErrorField(params.error, 'message') ?? safeString(params.error),\n errorType: safeErrorField(params.error, 'name') ?? 'Error',\n toolName: `llm:${params.provider}:${params.model}`,\n metadata: {\n provider: params.provider,\n model: params.model,\n duration_ms: params.durationMs,\n },\n })\n .catch((err) => {\n console.warn('Sentrial: Failed to track LLM error:', err.message);\n });\n}\n","/** Process-local OTLP transport observations. No URLs, bodies, IDs or credentials retained. */\nexport const SERVER_STAGES = [\n 'body',\n 'decode',\n 'ingest',\n 'resolve',\n 'transaction',\n 'postprocess',\n 'total',\n] as const;\ntype Stage = (typeof SERVER_STAGES)[number];\nexport type AttemptOutcome =\n | 'httpSuccess'\n | 'httpError'\n | 'transportError'\n | 'timeout'\n | 'cancelled'\n | 'bodyError';\nexport type RequestOutcome = 'returned' | 'failed' | 'cancelled';\nexport type DurationSummary = {\n measured: number;\n unavailable: number;\n totalMs: number;\n minMs: number | null;\n maxMs: number | null;\n};\nexport type ServerDurations = Record<Stage, number | null>;\nexport type TransportAttempt = {\n retry: boolean;\n outcome: AttemptOutcome;\n status: number | null;\n elapsedMs: number | null;\n serverTiming: ServerDurations;\n acknowledgment: { acceptedSpans: number | null; rejectedSpans: number | null } | null;\n};\nexport type TransportDiagnosticsSnapshot = {\n scope: 'otlp';\n recentLimit: 32;\n requests: {\n started: number;\n inFlight: number;\n returned: number;\n failed: number;\n cancelled: number;\n duration: DurationSummary;\n };\n attempts: {\n started: number;\n inFlight: number;\n retries: number;\n outcomes: Record<AttemptOutcome, number>;\n duration: DurationSummary;\n };\n acknowledgments: { complete: number; partial: number; unknown: number };\n serverTiming: Record<Stage, DurationSummary>;\n recent: TransportAttempt[];\n};\nconst duration = (): DurationSummary => ({\n measured: 0,\n unavailable: 0,\n totalMs: 0,\n minMs: null,\n maxMs: null,\n});\nconst emptyStages = (): ServerDurations =>\n Object.fromEntries(SERVER_STAGES.map((s) => [s, null])) as ServerDurations;\nconst valid = (value: number) =>\n Number.isFinite(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER;\nfunction observe(summary: DurationSummary, value: number | null) {\n if (value === null) {\n summary.unavailable++;\n return;\n }\n summary.measured++;\n summary.totalMs += value;\n summary.minMs = summary.minMs === null ? value : Math.min(summary.minMs, value);\n summary.maxMs = summary.maxMs === null ? value : Math.max(summary.maxMs, value);\n}\n// Bounded scanner preserves delimiters inside quoted descriptions. Ambiguous/malformed fields\n// become unavailable; we never retain arbitrary metric names or descriptions from a proxy.\nfunction splitHeader(value: string, delimiter: string): string[] | null {\n let quoted = false,\n escaped = false,\n start = 0;\n const parts: string[] = [];\n for (let i = 0; i < value.length; i++) {\n const c = value[i];\n if (escaped) {\n escaped = false;\n continue;\n }\n if (quoted && c === '\\\\') {\n escaped = true;\n continue;\n }\n if (c === '\"') quoted = !quoted;\n if (c === delimiter && !quoted) {\n parts.push(value.slice(start, i));\n start = i + 1;\n }\n }\n if (quoted || escaped) return null;\n parts.push(value.slice(start));\n return parts;\n}\nexport function parseServerTiming(header: unknown): ServerDurations {\n const result = emptyStages();\n if (typeof header !== 'string' || header.length > 4096) return result;\n const metrics = splitHeader(header, ',');\n if (!metrics || metrics.length > 64) return result;\n const seen = new Set<string>();\n for (const metric of metrics) {\n const parts = splitHeader(metric, ';');\n if (!parts) return emptyStages();\n const name = parts.shift()!.trim();\n const stage = SERVER_STAGES.find((s) => name === `sentrial_${s}`);\n if (!stage) continue;\n if (seen.has(stage)) {\n result[stage] = null;\n continue;\n }\n seen.add(stage);\n const durations = parts.filter((p) => /^dur(?:\\s|=|$)/i.test(p.trim()));\n const match =\n durations.length === 1\n ? /^dur\\s*=\\s*(?:([0-9]+(?:\\.[0-9]+)?)|\"([0-9]+(?:\\.[0-9]+)?)\")\\s*$/i.exec(\n durations[0].trim()\n )\n : null;\n const value = match ? Number(match[1] ?? match[2]) : NaN;\n result[stage] = valid(value) ? value : null;\n }\n return result;\n}\nfunction acknowledgment(body: unknown): TransportAttempt['acknowledgment'] {\n if (!body || typeof body !== 'object' || Array.isArray(body)) return null;\n const b = body as Record<string, unknown>;\n const count = (v: unknown): number | null =>\n typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 ? v : null;\n const acceptedSpans = count(b.acceptedSpans);\n const partial = b.partialSuccess;\n let rejectedSpans: number | null = null;\n if (partial === undefined) rejectedSpans = acceptedSpans === null ? null : 0;\n else if (partial && typeof partial === 'object' && !Array.isArray(partial)) {\n const n = (partial as Record<string, unknown>).rejectedSpans;\n rejectedSpans = n === undefined ? 0 : count(n);\n }\n return { acceptedSpans, rejectedSpans };\n}\nexport class TransportDiagnostics {\n private readonly state: TransportDiagnosticsSnapshot = {\n scope: 'otlp',\n recentLimit: 32,\n requests: {\n started: 0,\n inFlight: 0,\n returned: 0,\n failed: 0,\n cancelled: 0,\n duration: duration(),\n },\n attempts: {\n started: 0,\n inFlight: 0,\n retries: 0,\n outcomes: {\n httpSuccess: 0,\n httpError: 0,\n transportError: 0,\n timeout: 0,\n cancelled: 0,\n bodyError: 0,\n },\n duration: duration(),\n },\n acknowledgments: { complete: 0, partial: 0, unknown: 0 },\n serverTiming: Object.fromEntries(SERVER_STAGES.map((s) => [s, duration()])) as Record<\n Stage,\n DurationSummary\n >,\n recent: [],\n };\n constructor(private readonly clock: () => number = () => performance.now()) {}\n private now(): number {\n try {\n return this.clock();\n } catch {\n return NaN;\n }\n }\n private elapsed(start: number): number | null {\n const end = this.now(),\n value = end - start;\n return valid(start) && valid(end) && valid(value) ? value : null;\n }\n snapshot(): TransportDiagnosticsSnapshot {\n return JSON.parse(JSON.stringify(this.state));\n }\n startRequest() {\n const start = this.now();\n let done = false;\n this.state.requests.started++;\n this.state.requests.inFlight++;\n return (outcome: RequestOutcome) => {\n if (done) return;\n done = true;\n this.state.requests.inFlight--;\n this.state.requests[outcome]++;\n observe(this.state.requests.duration, this.elapsed(start));\n };\n }\n startAttempt(retry: boolean) {\n const start = this.now();\n let done = false;\n let status: number | null = null,\n serverTiming = emptyStages();\n this.state.attempts.started++;\n this.state.attempts.inFlight++;\n if (retry) this.state.attempts.retries++;\n return {\n response: (code: number, header: unknown) => {\n status = Number.isInteger(code) && code >= 100 && code <= 599 ? code : null;\n serverTiming = parseServerTiming(header);\n },\n finish: (outcome: AttemptOutcome, body?: unknown) => {\n if (done) return;\n done = true;\n const elapsedMs = this.elapsed(start);\n const ack = outcome === 'httpSuccess' ? acknowledgment(body) : null;\n this.state.attempts.inFlight--;\n this.state.attempts.outcomes[outcome]++;\n observe(this.state.attempts.duration, elapsedMs);\n for (const stage of SERVER_STAGES)\n observe(this.state.serverTiming[stage], serverTiming[stage]);\n if (outcome === 'httpSuccess') {\n const category =\n ack?.rejectedSpans !== null && ack?.rejectedSpans !== undefined && ack.rejectedSpans > 0\n ? 'partial'\n : ack?.acceptedSpans !== null &&\n ack?.acceptedSpans !== undefined &&\n ack.rejectedSpans === 0\n ? 'complete'\n : 'unknown';\n this.state.acknowledgments[category]++;\n }\n this.state.recent.push({\n retry,\n outcome,\n status,\n elapsedMs,\n serverTiming,\n acknowledgment: ack,\n });\n if (this.state.recent.length > 32) this.state.recent.shift();\n },\n };\n }\n}\n","/**\n * EventBatcher — fire-and-forget event queue with periodic flushing.\n *\n * Buffers tracking events (tool calls, decisions, errors, generic events)\n * and drains them off the caller's hot path. Each event is still delivered\n * as its own HTTP request today (the drain loop sends per-event), so this\n * trades caller latency for throughput, not request count. NOTE: the OTLP\n * endpoint (/api/otel/v1/traces) is a batch protocol and the server accepts\n * multi-span batches, so coalescing a flush's queued spans into ONE OTLP\n * request (one round-trip + one advisory-lock ingest txn per session) is a\n * viable follow-up — deferred, not blocked by a missing bulk endpoint.\n * Session lifecycle calls (createSession, completeSession) bypass the batcher\n * entirely because they need synchronous responses.\n *\n * Events are delivered in enqueue order so server-assigned sequence numbers\n * match the order an agent actually executed steps.\n *\n * Flush triggers:\n * 1. Timer — every `flushIntervalMs` (default 1 000 ms)\n * 2. Queue size — when queue reaches `flushThreshold` (default 10)\n * 3. Manual — `flush()` or `shutdown()`\n * 4. Process exit — `beforeExit` handler\n *\n * Back-pressure: queue is capped at `maxQueueSize` (default 1 000).\n * When full, oldest events are dropped and a warning is logged.\n */\n\nexport interface BatcherConfig {\n /** Whether batching is enabled (default: false — all calls are immediate) */\n enabled?: boolean;\n /** Milliseconds between automatic flushes (default: 1000) */\n flushIntervalMs?: number;\n /** Flush when the queue reaches this many items (default: 10) */\n flushThreshold?: number;\n /** Maximum queue size before dropping oldest events (default: 1000) */\n maxQueueSize?: number;\n}\n\n/** Signature for the function that actually sends an event to the API. */\nexport type SendFn = (method: string, url: string, body: unknown) => Promise<unknown>;\n\n/** Request counts for this batcher instance, never a trace-completeness claim. */\nexport interface DeliveryDiagnostics {\n enqueued: number;\n delivered: number;\n failed: number;\n dropped: number;\n rejectedAfterShutdown: number;\n queued: number;\n inFlight: number;\n}\n\nexport class EventBatcher {\n private readonly queue: Array<{ method: string; url: string; body: unknown }> = [];\n private readonly flushIntervalMs: number;\n private readonly flushThreshold: number;\n private readonly maxQueueSize: number;\n private timer: ReturnType<typeof setInterval> | null = null;\n private sendFn: SendFn;\n private flushPromise: Promise<void> | null = null;\n private shutdownCalled = false;\n private counters = {\n enqueued: 0,\n delivered: 0,\n failed: 0,\n dropped: 0,\n rejectedAfterShutdown: 0,\n inFlight: 0,\n };\n private readonly exitHandler: () => void;\n\n constructor(sendFn: SendFn, config: BatcherConfig = {}) {\n this.sendFn = sendFn;\n this.flushIntervalMs = config.flushIntervalMs ?? 1_000;\n this.flushThreshold = config.flushThreshold ?? 10;\n this.maxQueueSize = config.maxQueueSize ?? 1_000;\n for (const value of [this.flushIntervalMs, this.flushThreshold, this.maxQueueSize]) {\n if (!Number.isSafeInteger(value) || value <= 0)\n throw new RangeError('Batcher limits must be positive integers');\n }\n\n // Start periodic flush timer\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushIntervalMs);\n\n // Don't hold the process open just for the flush timer\n if (this.timer && typeof this.timer === 'object' && 'unref' in this.timer) {\n (this.timer as NodeJS.Timeout).unref();\n }\n\n // Register process exit handler for best-effort final flush\n this.exitHandler = () => {\n void this.shutdown();\n };\n\n if (typeof process !== 'undefined' && process.on) {\n process.on('beforeExit', this.exitHandler);\n }\n }\n\n /**\n * Enqueue an event for batched delivery.\n *\n * If the queue hits `flushThreshold`, an automatic flush is triggered.\n * If the queue is full (`maxQueueSize`), the oldest event is dropped.\n */\n enqueue(method: string, url: string, body: unknown): void {\n if (this.shutdownCalled) {\n this.counters.rejectedAfterShutdown++;\n return;\n }\n this.counters.enqueued++;\n\n // Back-pressure: drop oldest when at capacity\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n this.counters.dropped++;\n if (typeof console !== 'undefined') {\n console.warn(`Sentrial: Event queue full (${this.maxQueueSize}), dropping oldest event`);\n }\n }\n\n this.queue.push({ method, url, body });\n\n // Flush if we hit the threshold\n if (this.queue.length >= this.flushThreshold) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued events to the API.\n *\n * Drains the queue and sends requests in enqueue order. Safe to call\n * concurrently — only one flush runs at a time.\n */\n async flush(): Promise<void> {\n // Chain onto any in-flight flush instead of returning early, so a caller that\n // awaits flush() (e.g. completeSession, \"so queued events arrive first\") is\n // guaranteed its events were actually delivered, not resolved instantly mid-run\n // of the 1s timer flush (#486).\n const prior = this.flushPromise ?? Promise.resolve();\n const mine = prior.then(() => this.drainAll());\n this.flushPromise = mine;\n try {\n await mine;\n } finally {\n if (this.flushPromise === mine) this.flushPromise = null;\n }\n }\n\n /** Drain the queue completely, including events enqueued while sending. */\n private async drainAll(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, this.queue.length);\n this.counters.inFlight += batch.length;\n for (const event of batch) {\n try {\n const result = await this.sendFn(event.method, event.url, event.body);\n // The SDK's fail-silent transport returns null after exhausting retries.\n if (result == null) this.counters.failed++;\n else this.counters.delivered++;\n } catch (err) {\n this.counters.failed++;\n // Individual event failures shouldn't crash the batch\n if (typeof console !== 'undefined') {\n console.warn('Sentrial: Batched event failed:', err);\n }\n } finally {\n this.counters.inFlight--;\n }\n }\n }\n }\n\n /**\n * Stop the batcher: clear the timer, flush remaining events, remove exit handler.\n */\n async shutdown(): Promise<void> {\n if (this.shutdownCalled) return this.flush();\n this.shutdownCalled = true;\n\n // Clear periodic timer\n if (this.timer !== null) {\n clearInterval(this.timer);\n this.timer = null;\n }\n\n // Remove exit handler\n if (typeof process !== 'undefined' && process.removeListener) {\n process.removeListener('beforeExit', this.exitHandler);\n }\n\n // Final flush — chains onto any in-flight flush and drains everything remaining\n // (no force-reset of flush state, which could drop an in-progress batch).\n await this.flush();\n }\n\n /** Number of events currently queued. */\n get size(): number {\n return this.queue.length;\n }\n\n get diagnostics(): Readonly<DeliveryDiagnostics> {\n return { ...this.counters, queued: this.queue.length };\n }\n}\n","import { Buffer } from 'node:buffer';\nimport { createHash, getRandomSource } from './node-crypto.js';\nimport {\n MAX_CONTENT_REFERENCE_BYTES,\n parseContentReference,\n type ContentField,\n type ContentReference,\n} from '@sentrial/otlp/content-reference';\n\nexport interface LargeToolContentConfig {\n /** Explicit opt-in. The server and workspace must also enable uploads. */\n enabled: boolean;\n /** Identifies the caller's redaction policy, not proof that it was applied correctly. */\n redactionRevision: string;\n /** Per-field upload deadline, 1–60 seconds. Default: 20 seconds. */\n timeoutMs?: number;\n}\nexport const LARGE_TOOL_THRESHOLD_BYTES = 64 * 1024;\nexport const TOOL_CONTENT_PREFIX_BYTES = 4096;\nconst PART_BYTES = 5 * 1024 * 1024;\ntype MissingReason =\n | 'capacity'\n | 'too_large'\n | 'cancelled'\n | 'timeout'\n | 'unavailable'\n | 'invalid_response'\n | 'quota'\n | 'unauthorized'\n | 'crypto_unavailable';\nexport type ToolContentUploadResult =\n | { state: 'submitted'; reference: Readonly<ContentReference> }\n | { state: 'missing'; reason: MissingReason };\nclass UploadError extends Error {\n constructor(readonly reason: MissingReason) {\n super('Telemetry content upload failed');\n }\n}\nfunction invalid(): never {\n throw new UploadError('invalid_response');\n}\nconst record = (value: unknown): Record<string, unknown> =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : invalid();\nconst uuid = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;\n\n/** Byte accounting happens after serialization/redaction, before allocating a full upload buffer. */\nexport function largeToolField(text: string) {\n const byteLength = Buffer.byteLength(text, 'utf8');\n if (byteLength <= LARGE_TOOL_THRESHOLD_BYTES) return null;\n const bytes = Buffer.from(text.slice(0, TOOL_CONTENT_PREFIX_BYTES), 'utf8');\n let end = Math.min(bytes.length, TOOL_CONTENT_PREFIX_BYTES);\n const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });\n let prefix = '';\n for (let trim = 0; trim < 4; trim++) {\n try {\n prefix = decoder.decode(bytes.subarray(0, end));\n break;\n } catch {\n end--;\n }\n }\n // PostgreSQL text cannot contain NUL. The complete object still retains its exact\n // UTF-8 bytes; the small display excerpt represents NUL as a visible escape.\n return { byteLength, prefix: prefix.replaceAll('\\0', '\\\\u0000'), prefixBytes: end };\n}\n\ntype Part = { partNumber: number; offset: number; byteLength: number };\nfunction partPlan(byteLength: number): Part[] {\n return Array.from({ length: Math.ceil(byteLength / PART_BYTES) }, (_, i) => ({\n partNumber: i + 1,\n offset: i * PART_BYTES,\n byteLength: Math.min(PART_BYTES, byteLength - i * PART_BYTES),\n }));\n}\nfunction validatePlan(value: unknown, plan: Part[]) {\n if (!Array.isArray(value) || value.length !== plan.length) invalid();\n value.forEach((item, i) => {\n const p = record(item),\n expected = plan[i];\n if (\n p.partNumber !== expected.partNumber ||\n p.offset !== expected.offset ||\n p.byteLength !== expected.byteLength\n )\n invalid();\n });\n}\nfunction progress(value: unknown, id: string) {\n const row = record(value);\n if (row.id !== id || typeof row.phase !== 'string' || typeof row.contentState !== 'string')\n invalid();\n if (['deleting', 'deleted'].includes(row.contentState as string))\n throw new UploadError('unavailable');\n if (\n !['reserved', 'verifying', 'available'].includes(row.contentState as string) ||\n ![\n 'not_started',\n 'creating',\n 'create_unknown',\n 'uploading',\n 'completing',\n 'complete_unknown',\n 'completed',\n ].includes(row.phase as string)\n )\n invalid();\n return row;\n}\nfunction capability(value: unknown, part: Part, id: string) {\n const row = record(value),\n headers = record(row.headers);\n if (\n row.method !== 'PUT' ||\n row.partNumber !== part.partNumber ||\n row.offset !== part.offset ||\n row.byteLength !== part.byteLength ||\n headers['content-length'] !== String(part.byteLength) ||\n Object.keys(headers).length !== 1 ||\n typeof row.url !== 'string' ||\n row.url.length > 8192 ||\n typeof row.expiresAt !== 'string'\n )\n invalid();\n let url: URL;\n try {\n url = new URL(row.url as string);\n } catch {\n invalid();\n }\n const expiresAt = Date.parse(row.expiresAt as string);\n // An ingestion credential never follows a returned URL. PUT is allowed only to\n // the configured transport's Supabase S3 shape, with the exact object and part.\n const segments = url.pathname.split('/');\n const expires = Number(url.searchParams.get('X-Amz-Expires'));\n if (\n url.protocol !== 'https:' ||\n url.username ||\n url.password ||\n url.port ||\n url.hash ||\n !/^[a-z0-9]{20}\\.storage\\.supabase\\.co$/.test(url.hostname) ||\n segments.length !== 8 ||\n segments.slice(1, 4).join('/') !== 'storage/v1/s3' ||\n !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(segments[4]) ||\n segments[5] !== 'telemetry-content-v1' ||\n !uuid.test(segments[6]) ||\n segments[7] !== id ||\n url.searchParams.get('partNumber') !== String(part.partNumber) ||\n !url.searchParams.get('uploadId') ||\n url.searchParams.get('uploadId')!.length > 1024 ||\n new Set(url.searchParams.keys()).size !== [...url.searchParams].length ||\n url.searchParams.get('X-Amz-SignedHeaders') !== 'content-length;host' ||\n !Number.isInteger(expires) ||\n expires < 1 ||\n expires > 60 ||\n !Number.isFinite(expiresAt) ||\n expiresAt <= Date.now()\n )\n invalid();\n return { url: row.url as string, headers: { 'content-length': String(part.byteLength) } };\n}\n\n/** Two active objects at most, no unbounded payload queue. In-memory multipart\n * resumption only; process-restart recovery belongs to the durable server ledger. */\nexport class LargeToolContentUploader {\n private readonly apiUrl: string;\n private readonly redactionRevision: string;\n private readonly timeoutMs: number;\n private readonly controllers = new Set<AbortController>();\n private readonly captures = new Map<Promise<void>, string>();\n private closed = false;\n private attempted = 0;\n private submitted = 0;\n private missing = 0;\n constructor(\n config: LargeToolContentConfig,\n apiUrl: string,\n private readonly apiKey: string | undefined\n ) {\n let url: URL;\n try {\n url = new URL(apiUrl);\n } catch {\n throw new Error('Invalid large tool content configuration');\n }\n if (\n (url.protocol !== 'https:' &&\n !(\n url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)\n )) ||\n url.username ||\n url.password ||\n url.search ||\n url.hash ||\n !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(config.redactionRevision)\n )\n throw new Error('Invalid large tool content configuration');\n this.apiUrl = apiUrl.replace(/\\/$/, '');\n this.redactionRevision = config.redactionRevision;\n this.timeoutMs = config.timeoutMs ?? 20_000;\n if (!Number.isInteger(this.timeoutMs) || this.timeoutMs < 1000 || this.timeoutMs > 60_000)\n throw new Error('Invalid large tool content deadline');\n }\n track(sessionId: string, task: Promise<void>) {\n this.captures.set(task, sessionId);\n void task.then(\n () => this.captures.delete(task),\n () => this.captures.delete(task)\n );\n return task;\n }\n async drain(sessionId?: string) {\n // Include the span's delivery/enqueue continuation, not only storage completion.\n await Promise.allSettled(\n [...this.captures].filter(([, id]) => !sessionId || id === sessionId).map(([task]) => task)\n );\n }\n async shutdown() {\n this.closed = true;\n for (const controller of this.controllers) controller.abort();\n await this.drain();\n }\n diagnostics() {\n return {\n attempted: this.attempted,\n submitted: this.submitted,\n missing: this.missing,\n active: this.controllers.size,\n pendingEvents: this.captures.size,\n };\n }\n async upload(\n sessionId: string,\n traceId: string,\n spanId: string,\n field: ContentField,\n text: string\n ): Promise<ToolContentUploadResult> {\n this.attempted++;\n const failed = (reason: MissingReason): ToolContentUploadResult => {\n this.missing++;\n return { state: 'missing', reason };\n };\n if (this.closed) return failed('cancelled');\n if (this.controllers.size >= 2) return failed('capacity');\n const byteLength = Buffer.byteLength(text, 'utf8');\n if (byteLength > MAX_CONTENT_REFERENCE_BYTES) return failed('too_large');\n if (!createHash) return failed('crypto_unavailable');\n if (!this.apiKey || !uuid.test(sessionId)) return failed('unauthorized');\n const controller = new AbortController();\n this.controllers.add(controller);\n let timedOut = false;\n const deadline = performance.now() + this.timeoutMs;\n const timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, this.timeoutMs);\n let onAbort = () => {};\n const aborted = new Promise<never>((_, reject) => {\n onAbort = () => reject(new UploadError(timedOut ? 'timeout' : 'cancelled'));\n controller.signal.addEventListener('abort', onAbort, { once: true });\n });\n const check = () => {\n if (performance.now() >= deadline) {\n timedOut = true;\n controller.abort();\n }\n if (controller.signal.aborted) throw new UploadError(timedOut ? 'timeout' : 'cancelled');\n };\n const work = async () => {\n const bytes = Buffer.from(text, 'utf8');\n const reference = parseContentReference({\n revision: 'telemetry-content-v1',\n id: getRandomSource().randomUUID(),\n traceId,\n spanId,\n field,\n byteLength,\n sha256: createHash!('sha256').update(bytes).digest('hex'),\n contentType: 'text/plain',\n redactionRevision: this.redactionRevision,\n });\n if (!reference) invalid();\n const id = reference.id,\n plan = partPlan(byteLength);\n let requests = 0;\n const send = async (url: string, init: RequestInit, json: boolean) => {\n check();\n if (++requests > 24) throw new UploadError('unavailable');\n const response = await fetch(url, {\n ...init,\n signal: controller.signal,\n redirect: 'error',\n credentials: 'omit',\n });\n if (controller.signal.aborted) {\n void response.body?.cancel().catch(() => {});\n check();\n }\n if (!response.ok) {\n void response.body?.cancel().catch(() => {});\n throw new UploadError(\n response.status === 401 || response.status === 403\n ? 'unauthorized'\n : response.status === 429\n ? 'quota'\n : 'unavailable'\n );\n }\n if (!json) {\n void response.body?.cancel().catch(() => {});\n return null;\n }\n const reader = response.body?.getReader();\n if (!reader) invalid();\n let length = 0,\n chunks = 0,\n body = '';\n const decoder = new TextDecoder('utf-8', { fatal: true });\n const cancelReader = () => {\n void reader!.cancel().catch(() => {});\n };\n controller.signal.addEventListener('abort', cancelReader, { once: true });\n try {\n while (true) {\n check();\n const next = await reader!.read();\n check();\n if (next.done) break;\n length += next.value.byteLength;\n if (length > 40 * 1024 || ++chunks > 256) invalid();\n body += decoder.decode(next.value, { stream: true });\n }\n return JSON.parse(body + decoder.decode()) as unknown;\n } finally {\n controller.signal.removeEventListener('abort', cancelReader);\n cancelReader();\n }\n };\n const control = (suffix: string, body?: unknown) =>\n send(\n `${this.apiUrl}/api/otel/v1/content${suffix}`,\n {\n method: body === undefined ? 'GET' : 'POST',\n headers: { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/json' },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n },\n true\n );\n const action = (name: string, extra: Record<string, unknown> = {}) =>\n control(`/${id}/upload`, { action: name, ...extra });\n const retry = async (fn: () => Promise<unknown>) => {\n try {\n return await fn();\n } catch (error) {\n check();\n if (error instanceof UploadError && error.reason !== 'unavailable') throw error;\n return fn();\n }\n };\n const reserved = record(await retry(() => control('', { sessionId, reference })));\n if (\n reserved.id !== id ||\n reserved.contentState !== 'reserved' ||\n typeof reserved.uploadExpiresAt !== 'string' ||\n !Number.isFinite(Date.parse(reserved.uploadExpiresAt)) ||\n Date.parse(reserved.uploadExpiresAt) <= Date.now()\n )\n invalid();\n validatePlan(reserved.parts, plan);\n let state: Record<string, unknown>;\n try {\n state = progress(await action('begin'), id);\n } catch (error) {\n check();\n if (error instanceof UploadError && error.reason !== 'unavailable') throw error;\n state = progress(await retry(() => control(`/${id}`)), id);\n }\n // Server reconciliation owns ambiguous create/complete; never resend those\n // operations blindly, and never infer success from an absent upload.\n if (state.phase !== 'uploading') state = progress(await action('reconcile'), id);\n if (state.phase !== 'uploading' || state.contentState !== 'reserved')\n throw new UploadError('unavailable');\n const retainedParts = async () => {\n const result = record(await retry(() => action('parts')));\n if (result.id !== id || !Array.isArray(result.parts) || result.parts.length > plan.length)\n invalid();\n const present = new Set<number>();\n for (const entry of result.parts as unknown[]) {\n const p = record(entry),\n expected = plan.find((part) => part.partNumber === p.partNumber);\n if (\n !expected ||\n present.has(expected.partNumber) ||\n p.expectedByteLength !== expected.byteLength ||\n (p.reportedByteLength !== null && p.reportedByteLength !== expected.byteLength)\n )\n invalid();\n present.add(expected!.partNumber);\n }\n return present;\n };\n const present = await retainedParts();\n for (const part of plan) {\n if (present.has(part.partNumber)) continue;\n const permission = async () => {\n const result = record(\n await retry(() => action('capabilities', { partNumbers: [part.partNumber] }))\n );\n if (\n result.id !== id ||\n !Array.isArray(result.capabilities) ||\n result.capabilities.length !== 1\n )\n invalid();\n return capability((result.capabilities as unknown[])[0], part, id);\n };\n const put = async () => {\n const permitted = await permission();\n await send(\n permitted.url,\n {\n method: 'PUT',\n headers: permitted.headers,\n body: bytes.subarray(part.offset, part.offset + part.byteLength),\n },\n false\n );\n };\n try {\n await put();\n } catch (error) {\n check();\n if (error instanceof UploadError && error.reason !== 'unavailable') throw error;\n if (!(await retainedParts()).has(part.partNumber)) await put();\n }\n }\n try {\n state = progress(await action('complete'), id);\n } catch (error) {\n check();\n if (error instanceof UploadError && error.reason !== 'unavailable') throw error;\n state = progress(await retry(() => control(`/${id}`)), id);\n }\n if (state.phase !== 'completed') state = progress(await action('reconcile'), id);\n if (\n state.phase !== 'completed' ||\n !['verifying', 'available'].includes(state.contentState as string)\n )\n throw new UploadError('unavailable');\n check();\n return reference;\n };\n try {\n const reference = await Promise.race([work(), aborted]);\n this.submitted++;\n return { state: 'submitted', reference };\n } catch (error) {\n return failed(error instanceof UploadError ? error.reason : 'unavailable');\n } finally {\n clearTimeout(timer);\n controller.signal.removeEventListener('abort', onAbort);\n controller.abort();\n this.controllers.delete(controller);\n }\n }\n}\n","/** Versioned, dependency-free wire contract. A reference is not proof of storage\n * or inspection; the API must bind it to a trusted workspace reservation. */\nexport const MAX_CONTENT_REFERENCE_BYTES = 16 * 1024 * 1024;\nexport type ContentField = 'tool_input' | 'tool_output';\nexport const CONTENT_REFERENCE_ATTRIBUTES = {\n tool_input: 'sentrial.content.tool_input',\n tool_output: 'sentrial.content.tool_output',\n} as const;\nexport type ContentReferences = Partial<Record<ContentField, Readonly<ContentReference>>>;\nexport interface ContentReference {\n revision: 'telemetry-content-v1';\n id: string;\n traceId: string;\n spanId: string;\n field: ContentField;\n byteLength: number;\n sha256: string;\n contentType: 'application/json' | 'text/plain';\n redactionRevision: string;\n}\nconst keys = [\n 'revision',\n 'id',\n 'traceId',\n 'spanId',\n 'field',\n 'byteLength',\n 'sha256',\n 'contentType',\n 'redactionRevision',\n] as const;\nconst hex = (value: unknown, length: number) =>\n typeof value === 'string' &&\n value.length === length &&\n /^[0-9a-f]+$/.test(value) &&\n !/^0+$/.test(value);\n\n/** Strict and non-coercing; URLs, object paths, extra fields and accessors are rejected. */\nexport function parseContentReference(value: unknown): Readonly<ContentReference> | null {\n try {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return null;\n const descriptors = Object.getOwnPropertyDescriptors(value);\n if (\n Reflect.ownKeys(descriptors).length !== keys.length ||\n keys.some((key) => !descriptors[key] || !('value' in descriptors[key]))\n )\n return null;\n const row = Object.fromEntries(keys.map((key) => [key, descriptors[key].value]));\n if (\n row.revision !== 'telemetry-content-v1' ||\n typeof row.id !== 'string' ||\n !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(row.id) ||\n /^0{8}-0{4}-0{4}-0{4}-0{12}$/.test(row.id) ||\n !hex(row.traceId, 32) ||\n !hex(row.spanId, 16) ||\n !['tool_input', 'tool_output'].includes(row.field) ||\n !Number.isSafeInteger(row.byteLength) ||\n row.byteLength < 1 ||\n row.byteLength > MAX_CONTENT_REFERENCE_BYTES ||\n typeof row.sha256 !== 'string' ||\n !/^[0-9a-f]{64}$/.test(row.sha256) ||\n !['application/json', 'text/plain'].includes(row.contentType) ||\n typeof row.redactionRevision !== 'string' ||\n !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$/.test(row.redactionRevision)\n )\n return null;\n return Object.freeze(row as unknown as ContentReference);\n } catch {\n return null;\n }\n}\n\nexport function sameContentReference(\n left: Readonly<ContentReference>,\n right: Readonly<ContentReference>\n): boolean {\n return keys.every((key) => left[key] === right[key]);\n}\n\n/** `reservation` must come from authorized server storage, never request JSON.\n * Fail with one result for missing, foreign and mismatched references. */\nexport function bindContentReference(\n input: unknown,\n reservation: { organizationId: string; reference: unknown } | null,\n context: { organizationId: string; traceId: string; spanId: string; field: ContentField }\n): Readonly<ContentReference> | null {\n if (\n !context.organizationId ||\n !reservation ||\n reservation.organizationId !== context.organizationId\n )\n return null;\n const reference = parseContentReference(input),\n retained = parseContentReference(reservation.reference);\n if (!reference || !retained || !sameContentReference(reference, retained)) return null;\n return reference.traceId === context.traceId &&\n reference.spanId === context.spanId &&\n reference.field === context.field\n ? reference\n : null;\n}\n","/** Portable, payload-free context for a separately instrumented worker session.\n * This is correlation metadata, never an authorization token or proof of outcome. */\nexport interface ActionIdentity {\n actionId: string;\n attemptId?: string;\n retryOfAttemptId?: string;\n}\nexport interface HandoffContext extends ActionIdentity {\n version: 'sentrial-handoff-v1';\n parentSessionId: string;\n}\nexport interface DispatchContext extends ActionIdentity {\n version: 'sentrial-handoff-v2';\n parentSessionId: string;\n dispatchId: string;\n}\nexport type HandoffOutcome = 'completed' | 'failed' | 'cancelled';\nexport type HandoffPhase = 'dispatched' | 'accepted' | 'finished' | 'received';\nconst identifier = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;\nconst uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nfunction validateAction(identity: ActionIdentity) {\n if (!identifier.test(identity.actionId) || typeof identity.actionId !== 'string')\n throw new Error('Invalid actionId');\n for (const key of ['attemptId', 'retryOfAttemptId'] as const) {\n if (\n identity[key] !== undefined &&\n (typeof identity[key] !== 'string' || !identifier.test(identity[key]!))\n )\n throw new Error(`Invalid ${key}`);\n }\n if (\n identity.retryOfAttemptId &&\n (!identity.attemptId || identity.retryOfAttemptId === identity.attemptId)\n )\n throw new Error('A retry requires a different attemptId');\n}\nexport function actionMetadata(\n identity: ActionIdentity,\n metadata: Record<string, unknown> = {}\n): Record<string, unknown> {\n validateAction(identity);\n const fields = {\n signalActionId: identity.actionId,\n signalAttemptId: identity.attemptId,\n signalRetryOfAttemptId: identity.retryOfAttemptId,\n };\n for (const [key, value] of Object.entries(fields)) {\n if (metadata[key] !== undefined && metadata[key] !== value)\n throw new Error(`Conflicting ${key}`);\n }\n return {\n ...metadata,\n ...Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined)),\n };\n}\nexport function createHandoffContext(input: ActionIdentity & { parentSessionId: string }): string {\n validateAction(input);\n if (typeof input.parentSessionId !== 'string' || !uuid.test(input.parentSessionId))\n throw new Error('Invalid parentSessionId');\n return JSON.stringify({\n version: 'sentrial-handoff-v1',\n parentSessionId: input.parentSessionId,\n actionId: input.actionId,\n ...(input.attemptId ? { attemptId: input.attemptId } : {}),\n ...(input.retryOfAttemptId ? { retryOfAttemptId: input.retryOfAttemptId } : {}),\n });\n}\nexport function resumeHandoffContext(\n carrier: string,\n metadata: Record<string, unknown> = {}\n): { parentSessionId: string; metadata: Record<string, unknown> } {\n if (typeof carrier !== 'string' || carrier.length > 1024)\n throw new Error('Invalid handoff carrier');\n const parsed: unknown = JSON.parse(carrier);\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n throw new Error('Invalid handoff carrier');\n const value = parsed as HandoffContext | DispatchContext;\n if (\n Object.keys(value).some(\n (key) =>\n ![\n 'version',\n 'parentSessionId',\n 'actionId',\n 'attemptId',\n 'retryOfAttemptId',\n ...(value.version === 'sentrial-handoff-v2' ? ['dispatchId'] : []),\n ].includes(key)\n ) ||\n !['sentrial-handoff-v1', 'sentrial-handoff-v2'].includes(value.version)\n )\n throw new Error('Unsupported handoff carrier');\n createHandoffContext(value);\n if (value.version === 'sentrial-handoff-v2') {\n createDispatchContext(value);\n if (metadata.signalHandoffContext !== undefined)\n throw new Error('Reserved signalHandoffContext');\n return {\n parentSessionId: value.parentSessionId,\n metadata: { ...actionMetadata(value, metadata), signalHandoffContext: value },\n };\n }\n return { parentSessionId: value.parentSessionId, metadata: actionMetadata(value, metadata) };\n}\n\n/** Use a new dispatchId for each execution; keep it stable only for transport retries. */\nexport function createDispatchContext(\n input: ActionIdentity & { parentSessionId: string; dispatchId: string }\n): string {\n const base = JSON.parse(createHandoffContext(input));\n if (typeof input.dispatchId !== 'string' || !uuid.test(input.dispatchId))\n throw new Error('Invalid dispatchId');\n return JSON.stringify({ ...base, version: 'sentrial-handoff-v2', dispatchId: input.dispatchId });\n}\n\nexport function handoffRecord(\n carrier: string,\n phase: HandoffPhase,\n workerSessionId?: string,\n outcome?: HandoffOutcome\n) {\n const resumed = resumeHandoffContext(carrier);\n const context = resumed.metadata.signalHandoffContext as DispatchContext | undefined;\n if (!context) throw new Error('Lifecycle records require a v2 handoff carrier');\n if (!['dispatched', 'accepted', 'finished', 'received'].includes(phase))\n throw new Error('Invalid handoff phase');\n if (\n phase === 'dispatched'\n ? workerSessionId !== undefined\n : typeof workerSessionId !== 'string' ||\n !uuid.test(workerSessionId) ||\n workerSessionId === context.parentSessionId\n )\n throw new Error('Invalid workerSessionId');\n if (phase === 'finished' || phase === 'received') {\n if (!outcome || !['completed', 'failed', 'cancelled'].includes(outcome))\n throw new Error('A terminal handoff outcome is required');\n } else if (outcome !== undefined) throw new Error('Unexpected handoff outcome');\n return {\n ...context,\n phase,\n ...(workerSessionId ? { workerSessionId } : {}),\n ...(outcome ? { outcome } : {}),\n };\n}\n","/**\n * OTLP transport for SentrialClient — emit one OTLP/JSON trace per session (TypeScript port of\n * the Python `_otlp_transport.py`).\n *\n * Model: one trace per session.\n * - `createSession` posts the root SERVER span and reads the server-assigned sessionId back\n * synchronously (the route returns `{ sessions: [id] }`), so `begin()` stays byte-compatible.\n * - `emitEvent` posts a child span (same traceId, parentSpanId = root) which the route attaches\n * to the session by traceId (incremental ingest, PR #363).\n * - `finalize` re-posts the root span with the final output/tokens/status; the route updates the\n * session's outcome fields (finalize path, PR #363).\n *\n * When the caller already has an active OpenTelemetry span, we reuse its traceId so the Sentrial\n * session nests in their trace (context interop) without depending on the OTel SDK for emission.\n * Each client consumes a given ambient traceId AT MOST ONCE, EVER — see claimTraceId for why\n * every later session under the same ambient trace gets its own random trace id.\n */\nimport { context, trace } from '@opentelemetry/api';\nimport { randomHex } from './random.js';\nimport {\n buildTrace,\n attributes,\n eventAttributes,\n exceptionEvent,\n resourceAttributes,\n sessionAttributes,\n span,\n SPAN_KIND_INTERNAL,\n SPAN_KIND_SERVER,\n type EventAttrsInput,\n} from '@sentrial/otlp';\n// Named JSON import (no `with { type: 'json' }` attribute — that routes esbuild through the\n// spec-ESM path which exposes only a default export) so tsup/esbuild tree-shakes the manifest down\n// to just `version`. A default `import pkg from '../package.json'` inlines the ENTIRE manifest\n// (scripts, devDependencies, …) into every published bundle.\nimport { version as SDK_VERSION } from '../package.json';\n\n// This SDK's Resource identity: source platform 'sentrial' (NOT the customer's app name, NOT\n// OTEL_SERVICE_NAME — the ingest keys service.name to the source platform) + this SDK's own\n// telemetry.sdk.* identity. MUST match the Python `_otlp.resource_attributes` defaults.\nfunction sdkResourceAttributes(): Record<string, unknown> {\n return resourceAttributes({\n source: 'sentrial',\n sdkName: 'sentrial-ts',\n sdkVersion: SDK_VERSION,\n language: 'nodejs',\n });\n}\nimport { deepMapStrings } from './redact.js';\nimport type { PiiField } from './redact.js';\nimport { evictStaleEntries } from './evict.js';\nimport { OTEL_TRACES_PATH } from './constants.js';\nimport { CONTENT_REFERENCE_ATTRIBUTES, type ContentField } from '@sentrial/otlp/content-reference';\nimport { largeToolField, type LargeToolContentUploader } from './large-tool-content.js';\n\n/** Stringified int64 nanoseconds (BigInt avoids the 2^53 precision loss of Date.now()*1e6). */\nfunction nowNanos(): string {\n return (BigInt(Date.now()) * 1_000_000n).toString();\n}\n\n// Bound on the per-client consumed-ambient-traceId set (see claimAmbientTraceId). Grows one entry\n// per DISTINCT ambient trace this client has ever begun a session under; capped with FIFO eviction\n// mirroring the session-registry bounds. An EVICTED id could theoretically be claimed again (and\n// merge-resolve into the long-completed first session server-side), but that requires >10k distinct\n// ambient traces on one client AND a later begin() under the very first of them — accepted.\nconst MAX_CONSUMED_AMBIENT_TRACE_IDS = 10000;\n\n/**\n * A 16-byte (32 hex) trace id for a new session, claiming the active OpenTelemetry trace when\n * present — AT MOST ONCE PER CLIENT, EVER.\n *\n * Why once-ever, not first-wins-by-registry-occupancy: the server keys SDK-created sessions by\n * the fallback `otel-sess:<traceId>` external id, and that key is PERMANENT — the ingest's merge\n * lookup has no status filter and its finalize gate re-admits any fallback-keyed root, so ANY\n * later begin() reusing the same traceId resolves to the FIRST session server-side, even after\n * that session COMPLETED (a sequential reuse-after-finalize would silently overwrite the completed\n * session's outcome with the second run's). A registry-occupancy scan also races: two concurrent\n * begin()s both pass the scan before either registers (registration happens only after the awaited\n * create POST). So each client permanently remembers every ambient traceId it has ever consumed\n * (deps.consumedAmbientTraceIds) and claims it SYNCHRONOUSLY — the set is updated before any\n * await, so a concurrent begin() can never observe the id as unclaimed. Only a client's FIRST\n * session under a given ambient trace nests in it (interop preserved); every later session gets a\n * random trace id and is intentionally un-nested. The claim is released only if the create POST\n * fails without registering a session (so a caller's retry can nest after a transient error).\n */\nfunction claimTraceId(deps: OtlpDeps): { traceId: string; claimedAmbient: boolean } {\n try {\n const sc = trace.getSpan(context.active())?.spanContext();\n if (sc?.traceId && /[1-9a-f]/i.test(sc.traceId)) {\n const consumed = deps.consumedAmbientTraceIds;\n if (!consumed.has(sc.traceId)) {\n // Claim IMMEDIATELY (synchronously, before the caller's create POST awaits) so a\n // concurrent createSession on this client sees the id as consumed and goes random.\n if (consumed.size >= MAX_CONSUMED_AMBIENT_TRACE_IDS) {\n const oldest = consumed.values().next().value;\n if (oldest !== undefined) consumed.delete(oldest);\n }\n consumed.add(sc.traceId);\n return { traceId: sc.traceId, claimedAmbient: true };\n }\n }\n } catch {\n /* no active span / api unavailable — fall through */\n }\n return { traceId: randomHex(16), claimedAmbient: false };\n}\n\n/** An 8-byte (16 hex) span id. */\nfunction newSpanId(): string {\n return randomHex(8);\n}\n\nexport interface OtlpSession {\n /** Child spans constructed by this producer; excludes the repeated root marker. */\n emittedEvents?: number;\n traceId: string;\n rootSpanId: string;\n startNanos: string;\n agentName: string;\n /** The user-supplied session title (opts.name), REDACTED — emitted as `sentrial.session.name`. */\n sessionName?: string | null;\n userId?: string | null;\n convoId?: string | null;\n input?: string | null;\n model?: string | null;\n assignmentIds?: string[] | null;\n metadata?: Record<string, unknown> | null;\n parentSessionId?: string | null;\n /**\n * Monotonic input revision carried as `sentrial.session.input_seq` on every root (re-)post:\n * 0 at createSession, incremented whenever the carried input CHANGES (updateInput, finalize's\n * userInput override). Lets the server drop an out-of-order redelivery of an OLDER input-carrying\n * post instead of reverting sessions.userInput (last-write-wins otherwise).\n */\n inputSeq: number;\n /**\n * Last-activity timestamp (ms epoch) for TTL eviction of abandoned sessions (#561). Set at\n * registration and REFRESHED (with a Map re-insert, preserving the sweep's insertion-order\n * shortcut) on every emitEvent, so only genuinely idle sessions expire — an active\n * long-running session is never dropped mid-flight.\n */\n createdAt: number;\n}\n\n// The per-session OTLP registry (deps.sessions) is bounded the same way as the client's\n// sessionAccumulators: a session that never calls finalize() (crash, abandoned stream, event-only\n// path) would otherwise leak an OtlpSession forever in a long-lived process. Mirror the accumulator\n// bounds exactly — a 2h TTL sweep plus a hard 10000-entry backstop evicting the oldest\n// insertion-ordered entry — at registration time (#561).\nconst OTLP_SESSION_TTL_MS = 2 * 60 * 60 * 1000;\nconst MAX_OTLP_SESSIONS = 10000;\n\n/**\n * Evict OTLP session entries older than OTLP_SESSION_TTL_MS, then evict the oldest while over the\n * cap. Called only when a new session is registered — no timer, so it never keeps the event loop\n * alive. Shares the canonical bounded-Map policy with the client's accumulator via evictStaleEntries\n * (#561).\n */\nfunction evictStaleOtlpSessions(sessions: Map<string, OtlpSession>): void {\n evictStaleEntries(sessions, {\n ttlMs: OTLP_SESSION_TTL_MS,\n maxSize: MAX_OTLP_SESSIONS,\n // Loud, cap-only warning matching the Python SDK's _otlp_transport _warn_evicted: an over-cap\n // eviction means sessions are being created but never finalized (a missing finish()).\n onCapEvict: (id) =>\n console.warn(\n `Sentrial: OTLP session-registry cap (${MAX_OTLP_SESSIONS}) reached; evicting the oldest ` +\n `un-finalized session ${id} (its OTLP span state is dropped). Sessions are being created ` +\n `but never finalized — call completeSession()/finish().`\n ),\n });\n}\n\nexport interface OtlpDeps {\n apiUrl: string;\n request(method: string, url: string, body?: unknown): Promise<unknown>;\n enqueue?: (method: string, url: string, body: unknown) => void;\n sessions: Map<string, OtlpSession>;\n contentUploader?: LargeToolContentUploader;\n /**\n * Every ambient (caller-active OTel) traceId this client has EVER consumed for a session —\n * permanent (bounded by MAX_CONSUMED_AMBIENT_TRACE_IDS, FIFO), NOT popped at finalize, because\n * the server's `otel-sess:<traceId>` fallback key is permanent too (see claimTraceId). Must be\n * client-lifetime state (like `sessions`), rebuilt deps objects share the same Set.\n */\n consumedAmbientTraceIds: Set<string>;\n /** Redact PII from a content string (session messages / event prompt/completion) before it is\n * wrapped/emitted; set from the client's PiiConfig. */\n redact?: (text: string) => string;\n /**\n * The resolved set of payload fields to redact (`piiConfig.fields ?? DEFAULT_FIELDS`), set\n * alongside `redact` when redaction is enabled. The OTLP path redacts a field ONLY when it is in\n * this set — mirroring the legacy REST path's `redactPayload`, which redacts only keys in\n * `config.fields`. A field NOT in the set (or a field with no PiiField identity, e.g.\n * error/stackTrace/stateBefore/stateAfter/toolErrorDetails) is carried verbatim, exactly as main.\n */\n redactFields?: ReadonlySet<PiiField>;\n}\n\n/**\n * Apply the deps' PII redaction to a content string, if configured.\n *\n * The value is first coerced through toText — the canonical safe-string guard, the JS analog of\n * the Python SDK's `safe_str` coercion — so a type-illegal non-string reaching a string ingress\n * (e.g. a hostile object passed as `input` in plain JS, which would otherwise hit\n * inputMessagesJson's raw JSON.stringify, or a non-string handed to the redact regexes) degrades\n * to its JSON/string text instead of throwing out of the tracking call (pass-26).\n */\nfunction applyRedact(deps: OtlpDeps, field: PiiField, text: unknown): string | null {\n const t = toText(text);\n if (t == null) return null;\n // Redact ONLY when this field is in the resolved redact set (parity with the legacy REST path's\n // redactPayload, which redacts only config.fields). toText coercion still runs unconditionally —\n // it is the wire safe-string guard, independent of redaction.\n return deps.redact && deps.redactFields?.has(field) ? deps.redact(t) : t;\n}\n\nlet warnedRedactFailed = false;\n\n/** Redact a metadata object's string values (deep), returning a copy; null-safe passthrough. */\nfunction applyRedactMetadata(\n deps: OtlpDeps,\n field: PiiField,\n metadata: Record<string, unknown> | null | undefined\n): Record<string, unknown> | null {\n if (metadata == null) return null;\n // No-op when redaction is off OR this field isn't in the resolved redact set, so the OTLP path\n // redacts metadata on exactly the same condition as the legacy REST path's redactPayload (only\n // config.fields). deepMapStrings returns a COPY — the caller's original is never mutated.\n if (!deps.redact || !deps.redactFields?.has(field)) return metadata;\n try {\n return deepMapStrings(metadata, deps.redact) as Record<string, unknown>;\n } catch {\n // Belt-and-braces: deepMapStrings is cycle- and depth-guarded, but ANY future redaction throw\n // must be contained at this seam — an exception here would escape trackToolCall/emitEvent and\n // violate the monitoring-never-crashes contract. Degrade to the cross-SDK marker (never the\n // raw object: redaction failed, so passing it through could leak PII). Warns once per process.\n if (!warnedRedactFailed) {\n warnedRedactFailed = true;\n console.warn(\n 'Sentrial: PII redaction of a metadata/state snapshot threw and the snapshot was replaced ' +\n 'with an {_unserializable} marker. Keep metadata/updateState() payloads plain ' +\n 'JSON-serializable data.'\n );\n }\n return { _unserializable: true };\n }\n}\n\ninterface Outcome {\n endNanos?: string;\n output?: string | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n cost?: number | null;\n error?: string | null;\n customMetrics?: Record<string, number> | null;\n /** Resolved model known only at completion — overrides the create-time model. */\n model?: string | null;\n}\n\n/**\n * Fold the resolved session model into the session metadata under the reserved `model` key without\n * clobbering a caller's OWN `model` metadata key — that value is relocated to `model_user` (main\n * stored it verbatim). Mirrors the server's relocateReservedOtel handling of the reserved `otel` key.\n */\nfunction foldSessionModel(\n meta: Record<string, unknown> | null | undefined,\n model: string\n): Record<string, unknown> {\n const base = meta ?? {};\n if ('model' in base && base.model !== model) {\n const { model: userModel, ...rest } = base;\n // Pick a relocation key that does not clobber a user key already named model_user (or\n // model_user_N) — a caller who supplied BOTH `model` and `model_user` keeps both values (main\n // stored them verbatim). Mirrors the server's relocateReservedOtel collision guard.\n let key = 'model_user';\n let i = 1;\n while (key in rest) key = `model_user_${i++}`;\n return { ...rest, [key]: userModel, model };\n }\n return { ...base, model };\n}\n\nfunction rootTrace(s: OtlpSession, o: Outcome): Record<string, unknown> {\n // ALL field→attr conversion happens inside sessionAttributes (the single converter) — the\n // transport only assembles field values (experiments/hierarchy/metadata/custom metrics\n // included; see SessionAttrsInput for what each field feeds).\n const model = o.model ?? s.model;\n // The sessions table has no model column, so the session-level model is ALSO folded into the\n // session metadata under the stable `model` key — that is what the ingest persists onto\n // sessions.metadata (create + finalize merge); gen_ai.request.model stays the semconv carrier.\n // A caller's OWN `model` metadata key is PRESERVED under `model_user` rather than clobbered (main\n // stored it verbatim) — mirrors the server's relocateReservedOtel for the reserved `otel` key and\n // the Python `_root_trace`.\n const metadata = model != null ? foldSessionModel(s.metadata, model) : s.metadata;\n const attrs = sessionAttributes({\n agentName: s.agentName,\n sessionName: s.sessionName,\n userId: s.userId,\n convoId: s.convoId,\n input: s.input,\n output: o.output,\n model,\n promptTokens: o.promptTokens,\n completionTokens: o.completionTokens,\n totalTokens: o.totalTokens,\n cost: o.cost,\n parentSessionId: s.parentSessionId,\n assignmentIds: s.assignmentIds,\n customMetrics: o.customMetrics,\n metadata,\n inputSeq: s.inputSeq,\n });\n const root = span({\n // Spec: span name SHOULD be `invoke_agent {gen_ai.agent.name}` when the name is available.\n name: `invoke_agent ${s.agentName}`,\n traceId: s.traceId,\n spanId: s.rootSpanId,\n attrs: {\n ...attrs,\n 'sentrial.capture.revision': 'sdk-sequence-v1',\n ...(o.endNanos ? { 'sentrial.capture.expected_events': s.emittedEvents ?? 0 } : {}),\n },\n kind: SPAN_KIND_SERVER,\n startTimeUnixNano: s.startNanos,\n endTimeUnixNano: o.endNanos ?? null,\n error: o.error,\n });\n return buildTrace([root], sdkResourceAttributes());\n}\n\n// Derived from EventAttrsInput (otlp.ts) — the single source for the event field list — so a new\n// attribute added there flows here automatically instead of silently being dropped on the default\n// transport. Only input/output are widened to `unknown` (narrowed via toText() before\n// eventAttributes), plus the transport-only `error` field and the structured error details\n// (errorType/stackTrace), which are no longer span ATTRIBUTES: per the OTel spec they ride the\n// `exception` SPAN EVENT (see exceptionEvent), built in eventTrace below.\nexport interface EventInput extends Omit<EventAttrsInput, 'input' | 'output'> {\n input?: unknown;\n output?: unknown;\n error?: string | null;\n /** events.toolError.type — carried on the `exception` span event (`exception.type`). */\n errorType?: string | null;\n /** events.toolError.stack_trace — carried on the `exception` span event (`exception.stacktrace`). */\n stackTrace?: string | null;\n /**\n * How long the tracked call took (ms). Transport-only: sets the event span's start/end times\n * (start = startedAt ?? now − durationMs, end = start + durationMs) so the server-derived\n * per-event duration (metadata.otel.durationMs) reflects the real measurement instead of the\n * zero-duration start=end=now default. Absent → prior behavior (start=end=now).\n */\n durationMs?: number | null;\n /** When the tracked call started (Date or ms epoch). See durationMs. */\n startedAt?: Date | number | null;\n}\n\nfunction toText(v: unknown): string | null {\n if (v == null) return null;\n if (typeof v === 'string') return v;\n try {\n // JSON.stringify returns undefined for values with no JSON representation (function/symbol) —\n // fold that into the same String(v) fallback used on throw.\n return JSON.stringify(v) ?? String(v);\n } catch {\n // Circular (or otherwise unserializable) tool I/O must degrade, never throw out of a tracking\n // call like trackToolCall — same str(v) fallback as the Python `_io_text`.\n try {\n return String(v);\n } catch {\n // A hostile object can throw from toString/Symbol.toPrimitive too — the last-resort\n // fallback must be a literal, never another throw.\n return '[unserializable]';\n }\n }\n}\n\nfunction eventTrace(s: OtlpSession, e: EventInput) {\n const attrs = eventAttributes({\n eventType: e.eventType,\n toolName: e.toolName,\n input: toText(e.input),\n output: toText(e.output),\n model: e.model,\n promptTokens: e.promptTokens,\n completionTokens: e.completionTokens,\n totalTokens: e.totalTokens,\n cost: e.cost,\n reasoning: e.reasoning,\n alternativesConsidered: e.alternativesConsidered,\n confidence: e.confidence,\n metadata: e.metadata,\n stateBefore: e.stateBefore,\n stateAfter: e.stateAfter,\n stateRole: e.stateRole,\n contentKind: e.contentKind,\n toolErrorDetails: e.toolErrorDetails,\n finishReasons: e.finishReasons,\n });\n // Real per-event timing when the caller measured it (durationMs / startedAt): the wrappers time\n // their tool calls locally, and the server derives the per-event duration from the span's\n // start/end (metadata.otel.durationMs) — without this every event span was emitted zero-duration\n // (start=end=now). start = startedAt ?? now − durationMs; end = start + durationMs; when only\n // startedAt is known, end stays \"now\" (duration = elapsed since start); when neither is present\n // the prior start=end=now behavior is preserved. Invalid values (non-finite or non-positive\n // startedAt epoch, negative duration) are ignored rather than emitting a nonsensical time range.\n const nowMs = Date.now();\n const rawStartedAt =\n e.startedAt == null\n ? null\n : typeof e.startedAt === 'number'\n ? e.startedAt\n : e.startedAt.getTime();\n // Reject non-finite AND non-positive epochs (0 / negative would emit a 1970-or-earlier span\n // start) — aligned with the Python transport's `started_at > 0` guard.\n const startedAtMs =\n rawStartedAt != null && Number.isFinite(rawStartedAt) && rawStartedAt > 0 ? rawStartedAt : null;\n const durMs =\n typeof e.durationMs === 'number' && Number.isFinite(e.durationMs) && e.durationMs >= 0\n ? e.durationMs\n : null;\n const startMs = startedAtMs ?? (durMs != null ? nowMs - durMs : nowMs);\n const endMs = durMs != null ? startMs + durMs : nowMs;\n const toNanos = (ms: number) => (BigInt(Math.round(ms)) * 1_000_000n).toString();\n const startNanos = toNanos(startMs);\n // Never emit end < start (a future startedAt with no duration): clamp to a zero-duration span.\n const endNanos = toNanos(Math.max(startMs, endMs));\n // Structured error details ride the spec-shaped `exception` SPAN EVENT (the OTel exception\n // conventions define exception.* on that event, not as span attributes). Emitted only when a\n // structured detail (type / stack trace) is present — a plain error message is already carried\n // by the span's error STATUS, exactly as before; exception.message accompanies the details.\n // The exception is stamped at the span END (when the failure was observed).\n const exception =\n e.errorType != null || e.stackTrace != null\n ? exceptionEvent({\n type: e.errorType,\n message: e.error,\n stacktrace: e.stackTrace,\n timeUnixNano: endNanos,\n })\n : null;\n // An error EVENT must produce an ERROR span status even when its message is EMPTY (a bare\n // `new Error()` has message ''; Python str(ValueError())==''), so the server keeps the 'error'\n // classification and reconstructs events.toolError (type/stack included) instead of dropping it and\n // relabelling the event 'state_change'. Coerce a falsy message to the error type or a default when\n // ANY error signal is present; a non-error event keeps its (absent) error → OK status.\n const isErrorEvent =\n e.eventType === 'error' || e.error != null || e.errorType != null || e.stackTrace != null;\n const spanError = isErrorEvent ? e.error || e.errorType || 'error' : e.error;\n s.emittedEvents = (s.emittedEvents ?? 0) + 1;\n Object.assign(attrs, {\n 'sentrial.capture.revision': 'sdk-sequence-v1',\n 'sentrial.capture.sequence': s.emittedEvents,\n 'sentrial.capture.root_span_id': s.rootSpanId,\n });\n const child = span({\n name: e.toolName || e.eventType,\n traceId: s.traceId,\n spanId: newSpanId(),\n attrs,\n parentSpanId: s.rootSpanId,\n kind: SPAN_KIND_INTERNAL,\n startTimeUnixNano: startNanos,\n endTimeUnixNano: endNanos,\n error: spanError,\n events: exception ? [exception] : null,\n });\n // The ingest route resolves the owning session from the POST's root span (no parent / SERVER\n // kind), matching that span's (traceId, spanId) against the session's existing root event. A\n // child-only body has no such span, so the route would create a new session for every event.\n // Include a lightweight root marker (same root span id) so events attach to the one session\n // created by `createSession` — the route skips it as an already-ingested event.\n const rootMarker = span({\n // Spec: span name SHOULD be `invoke_agent {gen_ai.agent.name}` when the name is available.\n name: `invoke_agent ${s.agentName}`,\n traceId: s.traceId,\n spanId: s.rootSpanId,\n attrs: {},\n kind: SPAN_KIND_SERVER,\n startTimeUnixNano: s.startNanos,\n });\n return { body: buildTrace([rootMarker, child], sdkResourceAttributes()), child, attrs };\n}\n\nexport interface CreateOpts {\n name: string;\n agentName: string;\n userId?: string | null;\n convoId?: string | null;\n input?: string | null;\n model?: string | null;\n assignmentIds?: string[];\n metadata?: Record<string, unknown> | null;\n parentSessionId?: string | null;\n}\n\n/** Post the root span, register session state, return the server-assigned sessionId. */\nexport async function createSession(deps: OtlpDeps, opts: CreateOpts): Promise<string | null> {\n // Once-ever ambient nesting: the ambient traceId (if any) is claimed SYNCHRONOUSLY here — added\n // to deps.consumedAmbientTraceIds before the create POST awaits — so a concurrent or later\n // begin() under the same ambient OTel trace gets its own trace (and thus its own server\n // session) instead of merge-resolving into this one. See claimTraceId.\n const { traceId, claimedAmbient } = opts.parentSessionId\n ? { traceId: randomHex(16), claimedAmbient: false }\n : claimTraceId(deps);\n // Release the claim ONLY when no server session was registered under it (request threw or the\n // response carried no session id) so a caller's retry can still nest in the ambient trace.\n const releaseClaim = () => {\n if (claimedAmbient) deps.consumedAmbientTraceIds.delete(traceId);\n };\n const s: OtlpSession = {\n traceId,\n rootSpanId: newSpanId(),\n startNanos: nowNanos(),\n agentName: opts.agentName || opts.name || 'otel-agent',\n // Carry the user-supplied session title REDACTED (same applyRedact seam as input/metadata) so\n // the title survives the default OTLP transport without leaking PII. agentName stays raw (agent\n // identity → gen_ai.agent.name, not PII) — only when it falls back to opts.name does it echo the\n // title, but the DB session name is taken from this redacted attribute, not from agentName.\n sessionName: applyRedact(deps, 'name', opts.name),\n userId: opts.userId,\n convoId: opts.convoId,\n input: applyRedact(deps, 'userInput', opts.input),\n model: opts.model,\n assignmentIds: opts.assignmentIds ?? null,\n // Cap/sanitize the root metadata at ingress exactly like the EVENT path (emitEvent routes\n // metadata/state snapshots through capStateSnapshot): a hostile or unserializable metadata\n // object (throwing getters, Proxy ownKeys traps, cycles) would otherwise flow raw into\n // sessionAttributes' Object.keys and the rootTrace spread and throw out of begin() (pass-26).\n // After this, s.metadata is always a safe, serializable plain object (or a marker) — the\n // downstream spreads/re-posts (rootTrace, updateMetadata's merge, finalize) need no per-site\n // hostile-shape guard. Mirrors the Python transport's safe_dict_copy ingress invariant.\n metadata: capStateSnapshot(applyRedactMetadata(deps, 'metadata', opts.metadata)),\n parentSessionId: opts.parentSessionId ?? null,\n inputSeq: 0,\n createdAt: Date.now(),\n };\n let resp: { sessions?: string[] } | null;\n try {\n resp = (await deps.request('POST', `${deps.apiUrl}${OTEL_TRACES_PATH}`, rootTrace(s, {}))) as {\n sessions?: string[];\n } | null;\n } catch (err) {\n // Release the ambient claim, then RETHROW unchanged: the transport stays a thin layer — the\n // outcome-level containment seam on SentrialClient.createSession (contained.ts) is what\n // honors failSilently for this throw. Swallowing here would hide a failSilently:false error\n // from the caller and skip the seam's warn-once logging.\n releaseClaim();\n throw err;\n }\n const sessionId = resp?.sessions?.[0] ?? null;\n if (!sessionId) {\n releaseClaim();\n return null;\n }\n // Bound the registry before inserting so abandoned sessions can't leak the Map forever (#561).\n evictStaleOtlpSessions(deps.sessions);\n deps.sessions.set(sessionId, s);\n return sessionId;\n}\n\n// One-time warning (like the 403-scope warning) when a track/finalize call targets a session this\n// client instance never created under the OTLP transport — otherwise the skip below is invisible\n// and the caller silently loses data (see the cross-instance note in the README Transport section).\nlet warnedUnknownOtlpSession = false;\nfunction warnUnknownOtlpSession(sessionId: string): void {\n if (warnedUnknownOtlpSession) return;\n warnedUnknownOtlpSession = true;\n console.warn(\n `Sentrial: skipping OTLP event/finalize for session ${sessionId} — it was not created via ` +\n `createSession()/begin() on this client instance, so the OTLP transport has no span to attach ` +\n `to. Track on the same client instance that created the session.`\n );\n}\n\n// Client-side cap on a serialized per-event state snapshot (stateBefore/stateAfter). Matches the\n// server's per-attribute JSON cap (MAX_SESSION_ATTR_CHARS = 64 KB in\n// apps/api/src/lib/otel/parse-session-attrs.ts) — an oversized snapshot would be dropped/ignored\n// server-side anyway, so emitting it only wastes wire + memory. Every tracked event emits the\n// FULL {...currentState} twice (before/after), so without this cap a large accumulated state is\n// shipped 2x per event, unbounded.\nconst MAX_STATE_SNAPSHOT_CHARS = 64 * 1024;\n\nlet warnedStateTruncated = false;\nlet warnedStateUnserializable = false;\n\n/**\n * Replace an over-cap state snapshot with a small marker object ({_truncated, _bytes}) so the\n * event still records THAT state existed (and how large it was) instead of silently shipping a\n * payload the server would reject. Warns once per process.\n *\n * An UNSERIALIZABLE snapshot (circular reference, BigInt, throwing toJSON) is likewise replaced\n * with an `{_unserializable: true}` marker — it must never reach the converter as the raw object\n * (a later stringify would throw out of the tracking call, violating the monitoring-never-crashes\n * contract). Same marker convention as the Python `_cap_state_snapshot`.\n */\nfunction capStateSnapshot(\n state: Record<string, unknown> | null | undefined\n): Record<string, unknown> | null {\n if (state == null) return null;\n let serialized: string;\n try {\n serialized = JSON.stringify(state);\n } catch {\n if (!warnedStateUnserializable) {\n warnedStateUnserializable = true;\n console.warn(\n 'Sentrial: a per-event state/metadata snapshot could not be JSON-serialized (circular ' +\n 'reference?) and was replaced with an {_unserializable} marker. Keep updateState()/' +\n 'metadata payloads JSON-serializable.'\n );\n }\n return { _unserializable: true };\n }\n if (serialized == null || serialized.length <= MAX_STATE_SNAPSHOT_CHARS) return state;\n if (!warnedStateTruncated) {\n warnedStateTruncated = true;\n console.warn(\n `Sentrial: a per-event state snapshot exceeded ${MAX_STATE_SNAPSHOT_CHARS} serialized ` +\n `characters (got ${serialized.length}) and was replaced with a {_truncated} marker. ` +\n `Keep updateState() payloads small — snapshots are shipped with every tracked event.`\n );\n }\n return { _truncated: true, _bytes: serialized.length };\n}\n\n/** Post a child span for an event; batched when an enqueue fn is provided. */\nexport async function emitEvent(deps: OtlpDeps, sessionId: string, e: EventInput): Promise<void> {\n const s = deps.sessions.get(sessionId);\n if (!s) {\n // session not created via this transport (or already finalized) — skip\n warnUnknownOtlpSession(sessionId);\n return;\n }\n // Refresh the TTL on activity: re-insert so the Map's insertion order stays monotonic with the\n // timestamp (the sweep's early-break shortcut depends on that). Without this, a session emitting\n // events for >2h would be evicted mid-flight and every later emit/finalize silently skipped.\n s.createdAt = Date.now();\n deps.sessions.delete(sessionId);\n deps.sessions.set(sessionId, s);\n const input = applyRedact(deps, 'toolInput', e.input),\n output = applyRedact(deps, 'toolOutput', e.output);\n const fields: Array<{\n field: ContentField;\n text: string;\n info: NonNullable<ReturnType<typeof largeToolField>>;\n }> = [];\n if (deps.contentUploader && e.eventType === 'tool_call' && e.toolName) {\n for (const [field, text] of [\n ['tool_input', input],\n ['tool_output', output],\n ] as const) {\n const info = text === null ? null : largeToolField(text);\n if (info && text !== null) fields.push({ field, text, info });\n }\n }\n const prepared = {\n ...e,\n // Event input/output map to the toolInput/toolOutput payload fields (trackToolCall + the LLM\n // wrappers), redacted only when those fields are selected — parity with main's redactPayload.\n input: fields.find((f) => f.field === 'tool_input')?.info.prefix ?? input,\n output: fields.find((f) => f.field === 'tool_output')?.info.prefix ?? output,\n reasoning: applyRedact(deps, 'reasoning', e.reasoning),\n // error, stackTrace, stateBefore, stateAfter and toolErrorDetails are NOT PiiFields (main's\n // redactPayload never touched them — they carry no PiiField key), so they are size-capped but\n // carried VERBATIM. `error`/`stackTrace` ride the `...e` spread unchanged. The state/detail\n // objects are still size-capped (the server nulls any per-event JSON attr over its 64KB\n // pre-parse guard, so an uncapped oversize object would be silently dropped with no marker).\n metadata: capStateSnapshot(applyRedactMetadata(deps, 'metadata', e.metadata)),\n stateBefore: capStateSnapshot(e.stateBefore),\n stateAfter: capStateSnapshot(e.stateAfter),\n toolErrorDetails: capStateSnapshot(e.toolErrorDetails),\n };\n // Allocate sequence, span identity and source timing BEFORE the first upload\n // await, so concurrent finish/reordering cannot hide an emitted event or inflate\n // tool duration with storage latency.\n const { body, child, attrs } = eventTrace(s, prepared);\n const url = `${deps.apiUrl}${OTEL_TRACES_PATH}`;\n const deliver = async () => {\n if (deps.enqueue) deps.enqueue('POST', url, body);\n else await deps.request('POST', url, body);\n };\n if (!fields.length || !deps.contentUploader) return deliver();\n const uploader = deps.contentUploader;\n const capture = async () => {\n const outcomes: Record<string, unknown> = {};\n let missing = false;\n await Promise.all(\n fields.map(async ({ field, text, info }) => {\n const result = await uploader.upload(\n sessionId,\n s.traceId,\n child.spanId as string,\n field,\n text\n );\n outcomes[field] = {\n state: result.state,\n byteLength: info.byteLength,\n prefixBytes: info.prefixBytes,\n ...(result.state === 'missing' ? { reason: result.reason } : {}),\n };\n if (result.state === 'submitted')\n attrs[CONTENT_REFERENCE_ATTRIBUTES[field]] = JSON.stringify(result.reference);\n else missing = true;\n })\n );\n const contentMetadata = {\n ...prepared.metadata,\n _sentrialToolContent: { revision: 'telemetry-content-v1', ...outcomes },\n // Reuse the existing retained-loss marker understood by telemetry and V2\n // evidence. A successful reference instead carries an inspection gap.\n ...(missing ? { _truncated: true } : {}),\n };\n const serialized = JSON.stringify(contentMetadata);\n // Adding upload diagnostics must not push metadata past the collector's\n // parse limit and hide the loss marker along with the user's metadata.\n attrs['sentrial.event.metadata'] =\n serialized.length <= MAX_STATE_SNAPSHOT_CHARS\n ? serialized\n : JSON.stringify({\n _truncated: true,\n _bytes: serialized.length,\n _sentrialToolContent: contentMetadata._sentrialToolContent,\n });\n child.attributes = attributes(attrs);\n await deliver();\n };\n return uploader.track(sessionId, capture());\n}\n\nexport interface FinalizeOpts {\n /**\n * Explicit semantic outcome. `false` marks the re-posted root span with OTel ERROR status\n * (status.message = failureReason), which is the OTLP carrier for the legacy PATCH's\n * `success:false`: the server derives session success from error spans (traceSucceeded →\n * buildFinalizeFields), so an explicit failure WITHOUT any exception/error event still lands as\n * status='failed' + failureReason. No separate success attribute is needed — every failure input\n * (finish({success:false}) / completeSession({success:false})) funnels through this flag.\n */\n success?: boolean;\n output?: string | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n cost?: number | null;\n failureReason?: string | null;\n customMetrics?: Record<string, number> | null;\n /** Resolved model known only at completion (→ gen_ai.request.model on the final root span). */\n model?: string | null;\n /**\n * User input supplied at completion time (completeSession({ userInput })) — overrides the input\n * captured at createSession so the common main-era pattern of passing userInput at finish isn't\n * silently dropped under the OTLP transport (it becomes the structured gen_ai.input.messages on\n * the final root span).\n */\n userInput?: string | null;\n /**\n * Explicit session duration override (ms). When given, the final root span's end time is derived\n * as start + durationMs instead of \"now\", matching the legacy PATCH's durationMs field.\n */\n durationMs?: number | null;\n}\n\n/** Re-post the root span with final outcome fields; the route finalizes the session. */\nexport async function finalize(\n deps: OtlpDeps,\n sessionId: string,\n opts: FinalizeOpts\n): Promise<void> {\n const s = deps.sessions.get(sessionId);\n if (!s) {\n warnUnknownOtlpSession(sessionId);\n return;\n }\n const error =\n opts.success === false\n ? applyRedact(deps, 'failureReason', opts.failureReason) ||\n 'OpenTelemetry trace contained error spans'\n : undefined;\n // A userInput passed at completion time overrides the input captured at createSession\n // (same precedence as the legacy PATCH, where a completion-time userInput wins). The input\n // revision is bumped so the server applies this override even after an earlier setInput\n // established a stored seq (and so a redelivered older post can never revert it).\n if (opts.userInput != null) {\n s.input = applyRedact(deps, 'userInput', opts.userInput);\n s.inputSeq += 1;\n }\n // Honor an explicit durationMs by deriving the end time from the recorded start, so the\n // server-computed duration matches the caller's override instead of wall-clock \"now\".\n // Finite-guarded like the event path: a pathological inf/NaN/negative duration falls back\n // to wall-clock now — BigInt(Math.round(Infinity)) would throw out of finish().\n const endNanos =\n opts.durationMs != null && Number.isFinite(opts.durationMs) && opts.durationMs >= 0\n ? (BigInt(s.startNanos) + BigInt(Math.round(opts.durationMs)) * 1_000_000n).toString()\n : nowNanos();\n const body = rootTrace(s, {\n endNanos,\n output: applyRedact(deps, 'assistantOutput', opts.output),\n promptTokens: opts.promptTokens,\n completionTokens: opts.completionTokens,\n totalTokens: opts.totalTokens,\n cost: opts.cost,\n error,\n customMetrics: opts.customMetrics,\n model: opts.model,\n });\n // Deregister ONLY after the finalize POST settles. If request() throws (failSilently:false\n // exhausts retries and raises NetworkError), the entry is left in place so the caller's retry\n // of finish() resolves the session again instead of hitting the unknown-session no-op — the\n // server's finalize re-post is idempotent, so a re-send is safe.\n await deps.request('POST', `${deps.apiUrl}${OTEL_TRACES_PATH}`, body);\n deps.sessions.delete(sessionId);\n}\n\n/**\n * Merge post-begin session metadata into the registered session state (no network call).\n *\n * The merged metadata is carried on the NEXT root re-post — in particular the finalize re-post,\n * where the ingest route merges it into sessions.metadata (user keys top-level, the reserved\n * `otel` diagnostics key preserved). String values are redacted like createSession's metadata.\n */\nexport function updateMetadata(\n deps: OtlpDeps,\n sessionId: string,\n metadata: Record<string, unknown>\n): void {\n const s = deps.sessions.get(sessionId);\n if (!s) {\n warnUnknownOtlpSession(sessionId);\n return;\n }\n // Same capStateSnapshot ingress as createSession's root metadata: preserves the invariant that\n // s.metadata only ever holds safe, serializable objects — a hostile merged object would\n // otherwise throw later out of finalize's rootTrace spread, poisoning the session's finalize.\n const redacted = capStateSnapshot(applyRedactMetadata(deps, 'metadata', metadata));\n if (redacted && Object.keys(redacted).length > 0) {\n s.metadata = { ...(s.metadata ?? {}), ...redacted };\n }\n}\n\n/**\n * Set/override the session-level model post-begin (no network call). Carried on the finalize\n * re-post as gen_ai.request.model AND under the stable `model` key in the session metadata\n * (see rootTrace) — the sessions table has no model column, so the metadata key is what\n * persists. An explicit completeSession({ model }) still wins at finalize time.\n * The model id is deliberately NOT redacted: model identifiers are configuration, not user content.\n */\nexport function updateModel(deps: OtlpDeps, sessionId: string, model: string): void {\n const s = deps.sessions.get(sessionId);\n if (!s) {\n warnUnknownOtlpSession(sessionId);\n return;\n }\n s.model = model;\n}\n\n/**\n * Update a running session's user input WITHOUT finalizing it.\n *\n * Re-posts the true root span start-only (NO endTimeUnixNano, like createSession) with the new\n * `gen_ai.input.messages`, so the ingest route persists the updated `sessions.userInput` while the\n * session stays running (a re-post of a single-trace session's true root carrying the input updates\n * userInput, end-time-agnostic). The session is left registered so later events/finalize still\n * resolve it.\n */\nexport async function updateInput(deps: OtlpDeps, sessionId: string, input: string): Promise<void> {\n const s = deps.sessions.get(sessionId);\n if (!s) {\n warnUnknownOtlpSession(sessionId);\n return;\n }\n // Refresh the stored input (redacted) so a later finalize without an explicit userInput keeps it,\n // and re-post the start-only root so only the input messages are refreshed (no end time → not\n // finalized). The input revision is bumped BEFORE the re-post so an out-of-order redelivery of\n // the original begin() post (older seq) can no longer revert this update server-side.\n s.input = applyRedact(deps, 'userInput', input);\n s.inputSeq += 1;\n await deps.request('POST', `${deps.apiUrl}${OTEL_TRACES_PATH}`, rootTrace(s, {}));\n}\n","{\n \"_comment\": \"OTel GenAI semconv attribute key chains. DESIGN (decoupled emit vs accept — see _emit and _acceptOrderComment): each field's array is the SERVER ACCEPT ORDER (first match wins) and follows MAIN's ingest ordering so a payload carrying two CONFLICTING keys of one field resolves EXACTLY as main's route.ts did (git show origin/main:apps/api/src/app/api/otel/v1/traces/route.ts). The keys the SDKs/importers EMIT are NOT chain[0] — they are declared separately in the `_emit` block below (the canonical CURRENT OTel attributes), and packages/otlp/otlp.ts derives every emit constant from `_emit` (never from chain[0]). Every `_emit` value is ALSO a member of its accept chain (so what the SDK emits is always accepted). Branch-added keys main lacked (the current OTel tool-call attrs, Traceloop/Vercel/OpenInference/legacy vendor keys) are KEPT ('more comprehensive') and appended AFTER main's keys, so they never preempt a main key for a conflicting payload. Behavior for single-key (normal) payloads — every real SDK/exporter emits ONE key per field — is unchanged; only synthetic conflicting-multi-key payloads shift to main's choice. #597 originally narrowed these chains to current-only keys, which silently dropped content/cost/tool/convo/agent/user data from older Sentrial SDKs and vendor exporters (Traceloop/OpenLLMetry llm.*, OpenInference/Phoenix input.value/output.value + tool.name, Vercel AI SDK ai.usage.*, LangSmith flat gen_ai.prompt/completion + thread.id); the narrowing is REVERTED and the accept side now honors main's precedence while the emit side stays canonical. Single source of truth for the ONE TypeScript field->OTel converter (packages/otlp/otlp.ts, which derives every emit constant from this file's `_emit` and serves both @sentrial/sdk and apps/api), the server ingest mapper (apps/api/src/lib/otel/map-attributes.ts, imports the accept chains directly), and the Python SDK (packages/python-sdk/sentrial/_otlp.py emit + otel.py accept, mirrors kept in sync by tests/test_semconv_keys_sync.py).\",\n \"_emitComment\": \"The canonical CURRENT OTel attribute the SDKs/importers EMIT for each converter field — DECOUPLED from the accept ORDER below. otlp.ts (and the Python _otlp.py mirror) build every emitted attribute from THESE keys, not chain[0]. input/output carry two emit keys each because chat content and tool I/O use distinct current attrs: gen_ai.input.messages/output.messages for chat content and gen_ai.tool.call.arguments/result for tool_call spans. totalTokens and cost have no OTel GenAI attr so their emit key is Sentrial-namespaced. INVARIANT (asserted by the parity/sync tests): every value here is a member of the field's accept chain below, so the SDK never emits a key the server won't accept.\",\n \"_emit\": {\n \"agentName\": \"gen_ai.agent.name\",\n \"externalSessionId\": \"session.id\",\n \"convoId\": \"gen_ai.conversation.id\",\n \"userId\": \"enduser.id\",\n \"inputMessages\": \"gen_ai.input.messages\",\n \"inputToolCall\": \"gen_ai.tool.call.arguments\",\n \"outputMessages\": \"gen_ai.output.messages\",\n \"outputToolCall\": \"gen_ai.tool.call.result\",\n \"model\": \"gen_ai.request.model\",\n \"inputTokens\": \"gen_ai.usage.input_tokens\",\n \"outputTokens\": \"gen_ai.usage.output_tokens\",\n \"totalTokens\": \"sentrial.usage.total_tokens\",\n \"cost\": \"sentrial.usage.cost\",\n \"toolName\": \"gen_ai.tool.name\",\n \"finishReasons\": \"gen_ai.response.finish_reasons\"\n },\n \"_contentChainsComment\": \"input/output ACCEPT order = main's eventPayloadFor order (gen_ai.prompt/completion, gen_ai.input/output.messages, input/output.value, llm.prompt/completion, sentrial.input/output) followed by the branch's ACCEPT-ONLY extras: the current OTel tool-call attrs (gen_ai.tool.call.arguments/result — the EMIT keys for tool_call spans, appended so a conflicting payload still defers to main's chat keys; a real tool span carries ONLY the tool-call key, no chat key, so it resolves regardless of position) and Vercel AI SDK raw-span keys (ai.prompt.messages — the JSON messages array, richer, so it precedes the ai.prompt {prompt:...} wrapper; ai.response.text). NOT covered by any chain: Traceloop/OpenLLMetry's N-INDEXED message attrs (gen_ai.prompt.N.role/.content, gen_ai.completion.N.*) — those are a key FAMILY, not fixed keys, reconstructed by the server's indexedMessagesText fallback in map-attributes.ts when no chain key resolves.\",\n \"_acceptOrderComment\": \"RESOLVED (was _acceptOrderDivergencesComment). Every accept chain now follows main's ordering — the branch's earlier canonical-first reordering, which made a conflicting-key payload resolve a DIFFERENT key than main for toolName (main read tool.name first), input/output (main read gen_ai.prompt/completion first, sentrial.input/output LAST), cost/totalTokens/convoId (main read its vendor/legacy key before the branch's Sentrial-namespaced/canonical emit key), is undone on the ACCEPT side. The EMIT side is unchanged (see _emit): the SDKs still emit gen_ai.input.messages / gen_ai.tool.name / gen_ai.conversation.id / sentrial.usage.cost / sentrial.usage.total_tokens etc. TWO ACCEPTED CARVE-OUTS keep their emit key first (they cannot follow main without breaking the current SDK's own ingest): (1) userId — enduser.id stays chain[0] because it is the SDK emit key AND packages/typescript-sdk otlp.test.ts locks userId[0]===the SDK's USER_ID_ATTR; among the ACCEPT fallbacks sentrial.user_id still outranks user.id (matching main's SDK-override-wins). (2) agentName — gen_ai.agent.name stays chain[0] because the SDK ALWAYS co-emits service.name='sentrial' as a RESOURCE attr (merged into the accept map); demoting gen_ai.agent.name below service.name (main's mid-chain fallback) would name every Sentrial OTLP session 'sentrial'. Both carve-outs are safe for main-parity in practice: no real emitter ships a conflicting enduser.id+sentrial.user_id or gen_ai.agent.name+service.name(with a different agent) pair. SESSION vs EVENT content ordering (both match main): main's route had TWO distinct input/output orderings — the per-EVENT eventPayloadFor read gen_ai.prompt/completion FIRST and sentrial.input/output LAST (the `input`/`output` chains), while the SESSION-CREATE POST handler read sentrial.input/output FIRST ('SDK override wins' at the session level; the `sessionInput`/`sessionOutput` chains). Both are now honored on the accept side: the per-event insert loop resolves with `input`/`output` and the session userInput/assistantOutput rollup resolves with `sessionInput`/`sessionOutput` (see map-attributes.ts extractEventFields vs extractSessionContentFields), so a conflicting-key payload resolves the SAME key main did at BOTH levels.\",\n \"input\": [\n \"gen_ai.prompt\",\n \"gen_ai.input.messages\",\n \"input.value\",\n \"llm.prompt\",\n \"sentrial.input\",\n \"gen_ai.tool.call.arguments\",\n \"ai.prompt.messages\",\n \"ai.prompt\"\n ],\n \"output\": [\n \"gen_ai.completion\",\n \"gen_ai.output.messages\",\n \"output.value\",\n \"llm.completion\",\n \"sentrial.output\",\n \"gen_ai.tool.call.result\",\n \"ai.response.text\"\n ],\n \"_sessionContentChainsComment\": \"SESSION-LEVEL input/output ACCEPT order — DISTINCT from the EVENT chains above. main's OTLP route had TWO different content orderings: the per-EVENT eventPayloadFor read gen_ai.prompt/completion FIRST and sentrial.input/output LAST (the `input`/`output` chains above), while the SESSION-CREATE POST handler read sentrial.input/output FIRST ('SDK override wins' at the session level) then gen_ai.prompt/completion, gen_ai.input/output.messages, input/output.value, llm.prompt/completion. The branch's session ingest path (ingest-trace-spans.ts) must resolve the session userInput/assistantOutput with THIS order (sentrial FIRST), per 'defer to main for ordering', so a session-root payload carrying BOTH sentrial.input AND a conflicting gen_ai.prompt resolves sentrial.input exactly as main's POST handler did — while the SAME attrs on an EVENT resolve gen_ai.prompt via the `input` chain. main's SESSION keys occupy the LEADING positions here, in main's exact order; the branch's ACCEPT-ONLY extras (the tool-call attrs gen_ai.tool.call.arguments/result and the Vercel raw-span keys ai.prompt.messages/ai.prompt/ai.response.text) are appended AFTER, identical to the `input`/`output` chains, so they never preempt a main session key. The EMIT keys are unchanged (see _emit: gen_ai.input.messages / gen_ai.output.messages) — session emit is not affected, only the server-side ACCEPT resolution differs. A single-key session (e.g. only gen_ai.input.messages) resolves identically under both chains.\",\n \"sessionInput\": [\n \"sentrial.input\",\n \"gen_ai.prompt\",\n \"gen_ai.input.messages\",\n \"input.value\",\n \"llm.prompt\",\n \"gen_ai.tool.call.arguments\",\n \"ai.prompt.messages\",\n \"ai.prompt\"\n ],\n \"sessionOutput\": [\n \"sentrial.output\",\n \"gen_ai.completion\",\n \"gen_ai.output.messages\",\n \"output.value\",\n \"llm.completion\",\n \"gen_ai.tool.call.result\",\n \"ai.response.text\"\n ],\n \"_tokenChainsComment\": \"TOKEN CHAINS ARE NOT REORDERED TO MAIN (deliberate). chain[0] is the current OTel key (gen_ai.usage.input/output_tokens) — that IS both the EMIT key and main's first key, so main-precedence already holds for the outcome-bearing case. EVERY OTHER member (Traceloop's gen_ai.usage.prompt/completion_tokens, OpenInference's llm.token_count.prompt/completion, Vercel's ai.usage.promptTokens/completionTokens, OpenLLMetry's llm.usage.prompt/completion_tokens, legacy sentrial.* overrides) carries the SAME token count, so their relative order is OUTCOME-NEUTRAL — a conflicting payload resolves the same VALUE whichever wins. The branch's dialect-prominence order is kept: (a) it changes no real result, and (b) apps/web/src/lib/waterfall-events.ts hand-mirrors these two chains key-for-key under a lockstep test, so a reorder here would require editing that web file. The bridge reads the same dialects' model/provider keys, so their token keys must be read too or dialect spans price at zero.\",\n \"inputTokens\": [\n \"gen_ai.usage.input_tokens\",\n \"gen_ai.usage.prompt_tokens\",\n \"llm.token_count.prompt\",\n \"ai.usage.promptTokens\",\n \"llm.usage.prompt_tokens\",\n \"sentrial.prompt_tokens\"\n ],\n \"outputTokens\": [\n \"gen_ai.usage.output_tokens\",\n \"gen_ai.usage.completion_tokens\",\n \"llm.token_count.completion\",\n \"ai.usage.completionTokens\",\n \"llm.usage.completion_tokens\",\n \"sentrial.completion_tokens\"\n ],\n \"_totalTokensComment\": \"The OTel GenAI registry defines NO total-tokens attribute (input/output/cache/reasoning only), so the canonical EMIT key lives under the Sentrial namespace (sentrial.usage.total_tokens — see _emit). ACCEPT order follows main (gen_ai.usage.total_tokens which Traceloop also documents as GEN_AI_USAGE_TOTAL_TOKENS, then llm.usage.total_tokens for OpenLLMetry, then legacy sentrial.total_tokens), with the branch's emit key sentrial.usage.total_tokens and OpenInference's llm.token_count.total appended AFTER — accept-only extras that never preempt a main key. A pure current-SDK payload carries ONLY sentrial.usage.total_tokens, so it resolves regardless of position.\",\n \"totalTokens\": [\n \"gen_ai.usage.total_tokens\",\n \"llm.usage.total_tokens\",\n \"sentrial.total_tokens\",\n \"sentrial.usage.total_tokens\",\n \"llm.token_count.total\"\n ],\n \"_costComment\": \"Monetary cost has NO OTel GenAI semconv attribute; the SDKs EMIT sentrial.usage.cost (see _emit). ACCEPT order follows main (sentrial.cost from pre-flip Sentrial SDKs, then the gen_ai.usage.cost squat some exporters emit, then llm.cost from Traceloop/OpenLLMetry), with the branch emit key sentrial.usage.cost appended AFTER as an accept-only extra. A pure current-SDK payload carries ONLY sentrial.usage.cost, so it resolves regardless of position; a conflicting synthetic payload defers to main's sentrial.cost.\",\n \"cost\": [\"sentrial.cost\", \"gen_ai.usage.cost\", \"llm.cost\", \"sentrial.usage.cost\"],\n \"_toolNameComment\": \"ACCEPT order follows main (tool.name first — main's eventPayloadFor read it before gen_ai.tool.name), then the canonical gen_ai.tool.name (the EMIT key — see _emit), then the legacy sentrial.tool.name. A current SDK tool_call span emits ONLY gen_ai.tool.name, so it resolves even though tool.name precedes it; a conflicting synthetic payload (tool.name + gen_ai.tool.name) now resolves tool.name as main did.\",\n \"toolName\": [\"tool.name\", \"gen_ai.tool.name\", \"sentrial.tool.name\"],\n \"_modelComment\": \"gen_ai.request.model is both the EMIT key (see _emit) and main's model signal, so it stays chain[0]. ACCEPT-ONLY fallbacks (appended after, as on main which read only gen_ai.request.model): gen_ai.response.model (the actually-served model — OTel semconv on response; LangSmith/Traceloop emit it, the better signal when only it is present), then the documented vendor model keys llm.model_name (OpenInference/Phoenix) and ai.model.id (Vercel AI SDK). The server reads this chain only as an is-this-an-LLM-span signal (eventTypeFor), so precedence is not outcome-bearing there; the Python otel.py bridge reads the same chain to resolve the model that feeds pricing, so dropping the vendor keys would price those dialects' spans at zero.\",\n \"model\": [\"gen_ai.request.model\", \"gen_ai.response.model\", \"llm.model_name\", \"ai.model.id\"],\n \"_convoIdComment\": \"ACCEPT order follows main (conversation.id first, then sentrial.convo_id / convo.id / thread.id / chat.id / session.conversation_id), with the canonical EMIT key gen_ai.conversation.id (see _emit) appended AFTER — accept-only, so a conflicting payload defers to main's conversation.id/thread.id as main did. A pure current-SDK/importer payload carries ONLY gen_ai.conversation.id, so it resolves regardless of position.\",\n \"convoId\": [\n \"conversation.id\",\n \"sentrial.convo_id\",\n \"convo.id\",\n \"thread.id\",\n \"chat.id\",\n \"session.conversation_id\",\n \"gen_ai.conversation.id\"\n ],\n \"_identityComment\": \"Session-identity chains. userId (CARVE-OUT, see _acceptOrderComment): enduser.id stays chain[0] — it is the SDK EMIT key (see _emit) and packages/typescript-sdk otlp.test.ts locks userId[0]===USER_ID_ATTR; among the ACCEPT fallbacks the explicit SDK override sentrial.user_id outranks the generic user.id (matching main's SDK-override-wins), with session.user_id last. externalSessionId: session.id (OTel session convention) is both emit and main's first key; sentrial.session_id / sentrial.session.id were accepted by main. agentName (CARVE-OUT): gen_ai.agent.name stays chain[0] — it is the EMIT key and MUST outrank the service.name RESOURCE attr the SDK always co-emits (see _acceptOrderComment); the sentrial.* overrides, generic agent.name, service.name (a RESOURCE attr — third-party resource attrs merge into the accept map so collector traffic without gen_ai.agent.name still names its agent), and gen_ai.system (provider name, main's last resort) follow in main's relative order. finishReasons: the canonical array attr (emit) first, then the singular gen_ai.response.finish_reason main's completeness path read.\",\n \"userId\": [\"enduser.id\", \"sentrial.user_id\", \"user.id\", \"session.user_id\"],\n \"externalSessionId\": [\"session.id\", \"sentrial.session_id\", \"sentrial.session.id\"],\n \"agentName\": [\n \"gen_ai.agent.name\",\n \"sentrial.agent.name\",\n \"sentrial.agent\",\n \"agent.name\",\n \"service.name\",\n \"gen_ai.system\"\n ],\n \"finishReasons\": [\"gen_ai.response.finish_reasons\", \"gen_ai.response.finish_reason\"],\n \"_exceptionComment\": \"OTel exception convention. The spec defines exception.* attributes on a SPAN EVENT whose name MUST be 'exception' — the SDKs emit that event (name = the `event` entry below, carrying type/message/stacktrace attributes) on errored event spans. The server reads the exception span EVENT first and falls back to the same keys as FLAT SPAN ATTRIBUTES for payloads from older Sentrial SDKs, which (spec-violatingly) emitted them flat.\",\n \"exception\": {\n \"event\": \"exception\",\n \"type\": \"exception.type\",\n \"message\": \"exception.message\",\n \"stacktrace\": \"exception.stacktrace\"\n },\n \"_sentrialEventComment\": \"Sentrial-proprietary per-EVENT attribute keys (no GenAI semconv equivalent exists for these event columns). Vendor-namespaced (sentrial.*) attributes are legal custom attributes per the OTel spec, but they are NOT semconv-defined — only Sentrial's own ingest understands them. Emitted by both SDK OTLP transports and read back by the server event mapper (extractSentrialEventFields / eventTypeFor); single-keyed because only Sentrial SDKs emit them. stateBefore/stateAfter carry the client's per-event state snapshots (main's REST-path events.state_before/state_after JSONB columns) as JSON strings, emitted like metadata (only when non-empty). Guarded by the same lockstep tests as the blocks above.\",\n \"_providerNameComment\": \"ACCEPTED CAVEAT: gen_ai.provider.name is Required on invoke_agent CLIENT spans per the agent-spans spec (Sentrial roots are SERVER kind, so the accepted gap is narrower than a blanket violation), but Sentrial session spans omit it — the SDK cannot know a single session-level provider (a session may span many providers) and guessing would be a semantically-inequivalent fallback. Sentrial-ingest-only carrier, same rationale as the evaluation caveat below.\",\n \"_evaluationComment\": \"OTel GenAI evaluation-result attribute keys (gen_ai.evaluation.name/.score.value/.explanation). The spec defines these on the gen_ai.evaluation.result LOG EVENT; Sentrial does not emit OTLP logs, so the emitters carry them on a SPAN EVENT of the same name (the `event` entry below) — the closest spec-shaped carrier available on the traces signal. The server reads BOTH that span event AND the same keys as flat span attributes (the legacy carrier: dedicated evaluation spans from older importers/SDKs).\",\n \"evaluation\": {\n \"event\": \"gen_ai.evaluation.result\",\n \"name\": \"gen_ai.evaluation.name\",\n \"score\": \"gen_ai.evaluation.score.value\",\n \"explanation\": \"gen_ai.evaluation.explanation\"\n },\n \"sentrialEvent\": {\n \"type\": \"sentrial.event.type\",\n \"reasoning\": \"sentrial.event.reasoning\",\n \"alternatives\": \"sentrial.event.alternatives\",\n \"confidence\": \"sentrial.event.confidence\",\n \"metadata\": \"sentrial.event.metadata\",\n \"stateBefore\": \"sentrial.event.state_before\",\n \"stateAfter\": \"sentrial.event.state_after\",\n \"stateRole\": \"sentrial.event.state_role\",\n \"contentKind\": \"sentrial.event.content_kind\",\n \"toolErrorDetails\": \"sentrial.event.tool_error_details\",\n \"toolName\": \"sentrial.event.tool_name\"\n },\n \"_streamingComment\": \"READ-SIDE ONLY: streamed-output reconstruction for the server ingest (span-events.ts). The GenAI semconv for streaming chunk events is still evolving, so this block records the ACCEPTED span-event names and chunk-content attribute keys (incl. vendor variants and bare keys) rather than one canonical attr; no Sentrial emitter produces these.\",\n \"streaming\": {\n \"eventNames\": [\n \"llm.token\",\n \"llm.chunk\",\n \"chunk\",\n \"delta\",\n \"gen_ai.event.chunk\",\n \"gen_ai.content.delta\",\n \"gen_ai.choice.delta\"\n ],\n \"contentKeys\": [\"gen_ai.event.content\", \"gen_ai.content\", \"content\", \"delta\", \"token\", \"text\"]\n },\n \"_experimentAnnotationComment\": \"ANNOTATION-ONLY keys the TS SDK writes onto the CUSTOMER's own Vercel AI SDK experimental_telemetry.metadata (a foreign OTel channel) so the customer's traces show which Sentrial experiment/variant served the call. Sentrial ingest never reads them; the assignment itself is linked field-shaped via assignmentIds.\",\n \"experimentAnnotation\": {\n \"experiment\": \"sentrial.experiment\",\n \"variant\": \"sentrial.variant\",\n \"assignmentId\": \"sentrial.assignment_id\",\n \"promptVersion\": \"sentrial.prompt_version\"\n },\n \"_sentrialComment\": \"Sentrial-proprietary session-LINKING attribute keys (legal vendor-namespaced custom attributes, not GenAI semconv). These are emitted verbatim by BOTH SDK OTLP transports (packages/typescript-sdk/src/otlp-transport.ts, packages/python-sdk/sentrial/_otlp_transport.py) and consumed by the server (apps/api/src/lib/otel/parse-session-attrs.ts + trace-mapping.ts). A one-sided rename would silently unlink experiments/hierarchy/titles end-to-end, so both SDK emit-key constants and the server parser assert against this block via the same lockstep tests that guard the GenAI keys above. assignmentIdSingular is an ACCEPT chain (first match wins): the namespaced sentrial.experiment.assignment_id first, then the bare experiment.assignment_id main's ingest also linked — accept-only, kept so old payloads carrying only the bare key keep resolving their experiment assignment (frozen by the main-contract parity tests). inputSeq is a monotonic per-session revision the transports stamp on every root (re-)post — 0 at begin, incremented whenever the carried input CHANGES (set_input/update_input, completion-time userInput override) — so the server's userInput refresh can drop an out-of-order redelivery of an older post instead of reverting the input (absent on old-SDK payloads, which keep last-write-wins).\",\n \"sentrial\": {\n \"assignmentIds\": \"sentrial.experiment.assignment_ids\",\n \"assignmentIdSingular\": [\"sentrial.experiment.assignment_id\", \"experiment.assignment_id\"],\n \"parentSessionId\": \"sentrial.session.parent_session_id\",\n \"sessionMetadata\": \"sentrial.session.metadata\",\n \"customMetrics\": \"sentrial.session.custom_metrics\",\n \"sessionName\": \"sentrial.session.name\",\n \"inputSeq\": \"sentrial.session.input_seq\"\n }\n}\n","/**\n * The single TypeScript field→OTel converter.\n *\n * Maps Sentrial FIELD-shaped session/event/evaluation data to standard OpenTelemetry GenAI\n * semantic-convention attributes and the exact OTLP/JSON wire shape the ingest route\n * (POST /api/otel/v1/traces) parses. Pure helpers, no dependencies.\n *\n * ONE implementation, consumed everywhere TypeScript runs:\n * - `@sentrial/sdk` re-exports it (src/otlp.ts) and bundles it into the published dist\n * (tsup `noExternal`), so the SDK OTLP transport uses exactly this code.\n * - `apps/api` transpiles it (Next `transpilePackages`) for server-side importers (the Langfuse\n * import) AND sources the ingest mapper's accept-chains from the same semconv-keys.json.\n *\n * Every emit constant below is DERIVED from ./semconv-keys.json (the canonical first key of each\n * accept chain) — the key map and the converter cannot drift because they are the same file pair.\n * The Python SDK (`_otlp.py`) is the one unavoidable mirror (separate runtime, published\n * separately); tests/test_semconv_keys_sync.py locks it to this same JSON.\n */\n\nimport semconvKeys from './semconv-keys.json';\n\nexport { semconvKeys };\n\n// OTel span kinds the ingest route understands. The route treats a span with no parent OR\n// kind === SERVER(2) as the session root; child spans (INTERNAL) become events.\nexport const SPAN_KIND_INTERNAL = 1;\nexport const SPAN_KIND_SERVER = 2;\n\n// ── Canonical emit keys (derived — never literal) ────────────────────────────\n// Sourced from semconvKeys._emit, NOT chain[0]: the ACCEPT chains now follow main's ordering (so a\n// conflicting-key payload resolves as main did), which decouples them from the EMIT key. Each\n// _emit value is guaranteed a member of its accept chain (asserted by the parity/sync tests), so\n// what the SDK emits is always accepted. input/output carry two emit keys each because chat content\n// and tool I/O use distinct current attrs: the structured messages key + the tool-call key.\nconst AGENT_NAME_ATTR = semconvKeys._emit.agentName;\nconst EXTERNAL_SESSION_ID_ATTR = semconvKeys._emit.externalSessionId;\nconst CONVO_ID_ATTR = semconvKeys._emit.convoId;\nconst MESSAGES_INPUT_ATTR = semconvKeys._emit.inputMessages;\nconst TOOL_ARGS_ATTR = semconvKeys._emit.inputToolCall;\nconst MESSAGES_OUTPUT_ATTR = semconvKeys._emit.outputMessages;\nconst TOOL_RESULT_ATTR = semconvKeys._emit.outputToolCall;\nconst MODEL_ATTR = semconvKeys._emit.model;\nconst INPUT_TOKENS_ATTR = semconvKeys._emit.inputTokens;\nconst OUTPUT_TOKENS_ATTR = semconvKeys._emit.outputTokens;\nconst TOTAL_TOKENS_ATTR = semconvKeys._emit.totalTokens;\nconst TOOL_NAME_ATTR = semconvKeys._emit.toolName;\nconst FINISH_REASONS_ATTR = semconvKeys._emit.finishReasons;\n\n// Canonical current OTel attr for the application END USER — the SDK emit key (semconvKeys._emit).\n// It is ALSO chain[0] of the \"userId\" accept chain (a documented carve-out: enduser.id stays first\n// because otlp.test.ts locks userId[0]===USER_ID_ATTR). The server accepts the legacy `user.id`\n// further down the chain, so pre-flip SDKs keep resolving. Mirrors the Python `_otlp.USER_ID_ATTR`.\nexport const USER_ID_ATTR = semconvKeys._emit.userId;\n\n// The root span of an agent run is an agent invocation per the GenAI semconv operation registry.\n// Structural (not a mapped DB field), so it is not part of the accept-chain map.\nexport const OPERATION_NAME_ATTR = 'gen_ai.operation.name';\nconst INVOKE_AGENT_OPERATION = 'invoke_agent';\n\n// Sentrial-proprietary session-LINKING attribute keys emitted on the OTLP root span, plus the\n// cost CONTENT key (monetary cost has NO OTel GenAI attribute, so it lives under the Sentrial\n// namespace — the `_emit.cost` key; the \"cost\" ACCEPT chain now leads with main's sentrial.cost,\n// so usageCost is derived from _emit, not chain[0]). A one-sided rename would silently unlink\n// experiments/hierarchy/titles end-to-end (#597); deriving from the JSON makes that structurally\n// impossible on the TS side. Mirrors the Python `_otlp.SENTRIAL_ATTR_KEYS`.\nexport const SENTRIAL_ATTR_KEYS = {\n assignmentIds: semconvKeys.sentrial.assignmentIds,\n parentSessionId: semconvKeys.sentrial.parentSessionId,\n sessionMetadata: semconvKeys.sentrial.sessionMetadata,\n customMetrics: semconvKeys.sentrial.customMetrics,\n sessionName: semconvKeys.sentrial.sessionName,\n inputSeq: semconvKeys.sentrial.inputSeq,\n usageCost: semconvKeys._emit.cost,\n} as const;\n\n// Standard OTel exception convention: the spec defines exception.type/message/stacktrace as\n// attributes of a SPAN EVENT whose name MUST be 'exception' — see exceptionEvent() below, which\n// builds that event (the flat-span-attr form older SDKs emitted stays server-ACCEPTED only).\n// Mirrors the Python `_otlp.EXCEPTION_ATTR_KEYS`.\nexport const EXCEPTION_ATTR_KEYS = semconvKeys.exception;\n\n// Sentrial-proprietary per-EVENT attribute keys (no GenAI semconv equivalent), read back by the\n// server's extractSentrialEventFields / eventTypeFor. Mirrors `_otlp.SENTRIAL_EVENT_ATTR_KEYS`.\nexport const SENTRIAL_EVENT_ATTR_KEYS = semconvKeys.sentrialEvent;\n\n// Standard OTel GenAI evaluation-result keys (read back by parse-session-attrs'\n// extractEvaluations and folded into the session's customMetrics/metricReasonings).\nconst EVALUATION_ATTR_KEYS = semconvKeys.evaluation;\n\n/**\n * Structured GenAI message content (the spec's `gen_ai.input.messages`/`gen_ai.output.messages`\n * convention) emitted AS A JSON STRING — the spec permits the JSON-string form on spans. Used for\n * SESSION-level LLM input/output on the root span (gen_ai.prompt/completion are deprecated) and\n * for non-tool event content stated by importers. The text passed here is already redacted by the\n * caller. MUST match the Python `_input_messages_json`.\n */\nexport function inputMessagesJson(text: string): string {\n return JSON.stringify([{ role: 'user', parts: [{ type: 'text', content: text }] }]);\n}\nexport function outputMessagesJson(text: string, finishReason?: string | null): string {\n // The gen-ai-output-messages JSON schema REQUIRES role+parts+finish_reason on every output\n // message; default to 'stop' (a schema enum value) when the caller has no better signal —\n // an output that was produced normally is a natural stop.\n return JSON.stringify([\n {\n role: 'assistant',\n parts: [{ type: 'text', content: text }],\n finish_reason: finishReason || 'stop',\n },\n ]);\n}\n\n/**\n * JSON.stringify that NEVER throws — the converter-level guard of the cross-SDK \"monitoring never\n * crashes the host app\" contract (mirrored by the Python `_otlp.json_dumps_safe`). User-provided\n * objects (metadata, state snapshots, tool payloads) reach this converter as attribute values, and\n * a pathological value (circular reference, BigInt, throwing toJSON) must degrade instead of\n * raising out of a tracking call: any stringify failure — including the undefined result for\n * values with no JSON representation — returns the JSON string '\"[unserializable]\"'. The SDK\n * transports additionally replace unserializable state/metadata snapshots with an\n * `{_unserializable: true}` marker at ingress (capStateSnapshot / _cap_state_snapshot); this is\n * the belt-and-braces layer beneath them.\n */\nexport function safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? '\"[unserializable]\"';\n } catch {\n return '\"[unserializable]\"';\n }\n}\n\ntype OtlpAnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { doubleValue: number | string }\n | { intValue: string }\n | { arrayValue: { values: OtlpAnyValue[] } };\n\ninterface OtlpKeyValue {\n key: string;\n value: OtlpAnyValue;\n}\n\n/** Map a JS value to an OTLP AnyValue object. */\nexport function anyValue(value: unknown): OtlpAnyValue {\n if (typeof value === 'boolean') return { boolValue: value };\n if (typeof value === 'number') {\n // OTLP/JSON encodes int64 fields as decimal strings (\"64-bit integer numbers in\n // JSON-encoded payloads are encoded as decimal strings\"); decoders accept both.\n if (Number.isInteger(value)) return { intValue: String(value) };\n // Proto3 JSON: non-finite doubles are the special string values \"NaN\"/\"Infinity\"/\n // \"-Infinity\" — a raw NaN would JSON.stringify to null (invalid for a double field).\n if (!Number.isFinite(value)) {\n return { doubleValue: value > 0 ? 'Infinity' : value < 0 ? '-Infinity' : 'NaN' };\n }\n return { doubleValue: value };\n }\n if (typeof value === 'string') return { stringValue: value };\n // Primitive arrays ride a REAL OTLP arrayValue — string[] attrs like\n // gen_ai.response.finish_reasons have no JSON-string allowance in the spec.\n if (\n Array.isArray(value) &&\n value.every((v) => ['string', 'number', 'boolean'].includes(typeof v))\n ) {\n return { arrayValue: { values: value.map((v) => anyValue(v)) } };\n }\n // Objects/mixed arrays: the ingest route's firstAttr() stringifies non-strings anyway, so emit\n // a JSON string for a stable, lossless representation (throw-proof — see safeJsonStringify).\n return { stringValue: safeJsonStringify(value) };\n}\n\n/** Map a flat object to an OTLP KeyValue list, dropping null/undefined values. */\nexport function attributes(attrs: Record<string, unknown>): OtlpKeyValue[] {\n return Object.entries(attrs)\n .filter(([, v]) => v !== null && v !== undefined)\n .map(([key, v]) => ({\n key,\n // gen_ai.evaluation.score.value is registry-typed double — an integer score must not\n // degrade to intValue.\n value:\n key === EVALUATION_ATTR_KEYS.score && typeof v === 'number' && Number.isFinite(v)\n ? { doubleValue: v }\n : anyValue(v),\n }));\n}\n\nexport interface SessionAttrsInput {\n agentName: string;\n /**\n * The user-supplied session title (opts.name), carried REDACTED as `sentrial.session.name` so the\n * ingest route can persist it as the DB session name. Kept distinct from `agentName`\n * (agent identity → `gen_ai.agent.name`, not PII) so a user-supplied title with PII is never\n * emitted as the agent name.\n */\n sessionName?: string | null;\n /**\n * Application-session grouping id (→ sessions.external_session_id, the cross-trace merge key).\n * IMPORTER-ONLY: server-side importers (e.g. Langfuse) state an explicit id; the SDK transports\n * never pass it — the ingest derives the deterministic `otel-sess:<traceId>` fallback instead.\n */\n externalSessionId?: string | null;\n userId?: string | null;\n convoId?: string | null;\n input?: string | null;\n output?: string | null;\n model?: string | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n cost?: number | null;\n /** Multi-agent hierarchy link (→ sessions.parent_session_id). */\n parentSessionId?: string | null;\n /** EVERY live-experiment assignment for the session (the ingest links them all). */\n assignmentIds?: string[] | null;\n /** User-supplied numeric metrics (no semconv field) — feeds session scoring. */\n customMetrics?: Record<string, number> | null;\n /** User-supplied session metadata (+ begin()'s auto-attached event_id) → sessions.metadata. */\n metadata?: Record<string, unknown> | null;\n /**\n * Monotonic input revision (0 at begin, incremented by the transport whenever the carried input\n * CHANGES: setInput/update_input, completion-time userInput override). The ingest stores the\n * last-applied value in metadata.otel.inputSeq and skips its userInput refresh for an\n * out-of-order redelivery carrying an older revision. SDK-transport-only; importers omit it.\n */\n inputSeq?: number | null;\n}\n\n/**\n * The GenAI semconv USAGE attributes (model, token usage, cost) shared by the session root span and\n * the event child span — each emitted only when present. Single source so the two builders can't\n * drift on the key names. MUST match the Python `_apply_gen_ai_usage`.\n */\nfunction applyGenAiUsage(\n attrs: Record<string, unknown>,\n opts: {\n model?: string | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n cost?: number | null;\n }\n): void {\n if (opts.model != null) attrs[MODEL_ATTR] = opts.model;\n if (opts.promptTokens != null) attrs[INPUT_TOKENS_ATTR] = opts.promptTokens;\n if (opts.completionTokens != null) attrs[OUTPUT_TOKENS_ATTR] = opts.completionTokens;\n if (opts.totalTokens != null) attrs[TOTAL_TOKENS_ATTR] = opts.totalTokens;\n // Monetary cost has no standard GenAI attribute → Sentrial namespace (see SENTRIAL_ATTR_KEYS).\n if (opts.cost != null) attrs[SENTRIAL_ATTR_KEYS.usageCost] = opts.cost;\n}\n\n/** Standard GenAI semconv attributes for a session root span. */\nexport function sessionAttributes(opts: SessionAttrsInput): Record<string, unknown> {\n const attrs: Record<string, unknown> = {\n // Agent identity per the GenAI semconv. This is the ONLY agent-name carrier on the span —\n // `service.name` is a RESOURCE attribute for the telemetry source platform, NOT the agent,\n // so it is never emitted here. The ingest reads the agent from gen_ai.agent.name.\n [AGENT_NAME_ATTR]: opts.agentName,\n // ACCEPTED CAVEAT: the agent-spans spec marks gen_ai.provider.name Required on\n // invoke_agent CLIENT spans (Sentrial roots are SERVER kind, so the gap is narrower\n // than a blanket violation), but the SDK cannot know a session-level provider (a session may\n // span many providers) and guessing would be a semantically-inequivalent fallback.\n // These spans target only the Sentrial ingest, which does not read a session provider.\n [OPERATION_NAME_ATTR]: INVOKE_AGENT_OPERATION,\n };\n if (opts.externalSessionId != null && opts.externalSessionId !== '') {\n attrs[EXTERNAL_SESSION_ID_ATTR] = opts.externalSessionId;\n }\n if (opts.userId != null) attrs[USER_ID_ATTR] = opts.userId;\n if (opts.convoId != null) attrs[CONVO_ID_ATTR] = opts.convoId; // canonical convoId key\n // The user-supplied session title, carried as a dedicated Sentrial attribute so the ingest route\n // persists it as the DB session name (otherwise otel mode falls back to the constant span name\n // 'invoke_agent'). The caller redacts it before it reaches here (mirroring the input). MUST\n // match the Python session_attributes.\n if (opts.sessionName != null) attrs[SENTRIAL_ATTR_KEYS.sessionName] = opts.sessionName;\n // SESSION-level LLM content uses the structured messages convention emitted as a JSON string;\n // gen_ai.prompt/gen_ai.completion are genuinely deprecated. Emitted only when present.\n if (opts.input != null) attrs[MESSAGES_INPUT_ATTR] = inputMessagesJson(opts.input);\n if (opts.output != null) attrs[MESSAGES_OUTPUT_ATTR] = outputMessagesJson(opts.output);\n applyGenAiUsage(attrs, opts);\n // Session-LINKING extras (experiments/hierarchy/metadata/custom metrics) — emitted HERE, not in\n // the transport, so this converter is the single field→attr point for every session field.\n if (opts.assignmentIds && opts.assignmentIds.length) {\n attrs[SENTRIAL_ATTR_KEYS.assignmentIds] = safeJsonStringify(opts.assignmentIds);\n }\n if (opts.customMetrics && Object.keys(opts.customMetrics).length) {\n attrs[SENTRIAL_ATTR_KEYS.customMetrics] = safeJsonStringify(opts.customMetrics);\n }\n if (opts.metadata && Object.keys(opts.metadata).length) {\n attrs[SENTRIAL_ATTR_KEYS.sessionMetadata] = safeJsonStringify(opts.metadata);\n }\n if (opts.parentSessionId) {\n attrs[SENTRIAL_ATTR_KEYS.parentSessionId] = opts.parentSessionId;\n }\n // Input revision — emitted on EVERY root (re-)post when the transport supplies it (including 0\n // at begin) so the server can order input-carrying re-posts; see SessionAttrsInput.inputSeq.\n if (opts.inputSeq != null) attrs[SENTRIAL_ATTR_KEYS.inputSeq] = opts.inputSeq;\n return attrs;\n}\n\nexport interface EventAttrsInput {\n eventType: string;\n toolName?: string | null;\n input?: string | null;\n output?: string | null;\n model?: string | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n cost?: number | null;\n // Sentrial-specific event fields with no standard GenAI semconv equivalent. Carried as\n // `sentrial.event.*` attributes (mirroring the session-level `sentrial.session.*` extras) so\n // the OTLP path is field-complete with the event columns the ingest persists (reasoning,\n // alternatives_considered, confidence, metadata).\n reasoning?: string | null;\n alternativesConsidered?: string[] | null;\n confidence?: number | null;\n metadata?: Record<string, unknown> | null;\n /** Client-state snapshot BEFORE the event (→ events.state_before, main's REST-path column). */\n stateBefore?: Record<string, unknown> | null;\n /** Client-state snapshot AFTER the event (→ events.state_after). */\n stateAfter?: Record<string, unknown> | null;\n /** Explicitly auxiliary client state; never an externally verified outcome. */\n stateRole?: 'context' | null;\n /** Native trackDecision reasoning note, which does not promise an assistant response. */\n contentKind?: 'decision_context' | null;\n /**\n * Extra caller-supplied toolError keys BEYOND the spec exception fields (message/type/stack_trace)\n * — e.g. {code, statusCode, retryAfter, provider}. The spec exception event only carries the three\n * standard fields, so these ride as a JSON blob the server merges back onto events.toolError\n * (main stored the whole toolError object verbatim).\n */\n toolErrorDetails?: Record<string, unknown> | null;\n /** Why generation stopped for this call (→ gen_ai.response.finish_reasons; capture-only server-side). */\n finishReasons?: string[] | null;\n}\n\n// The event types whose classification the ingest route derives from standard semconv signals\n// (tool_call → gen_ai.tool.name; decision → llm_decision; error → error span status). For these we\n// must NOT emit an explicit `sentrial.event.type`, so the semconv-driven classification stays the\n// single source of truth. Any OTHER value is a custom type from the generic trackEvent() path that\n// the substring heuristics can't recover, so we round-trip it (Finding A). MUST match Python.\nconst STANDARD_EVENT_TYPES = new Set(['tool_call', 'decision', 'error']);\n\n/** Standard GenAI semconv attributes for an event (child) span. */\nexport function eventAttributes(opts: EventAttrsInput): Record<string, unknown> {\n const attrs: Record<string, unknown> = {};\n // A tool_call event carrying a tool name is the ONLY event the ingest classifies via\n // gen_ai.tool.name — its input/output are tool-call args/result (below), not chat content.\n const isToolCall = opts.eventType === 'tool_call' && Boolean(opts.toolName);\n if (isToolCall) {\n attrs[TOOL_NAME_ATTR] = opts.toolName;\n // execute_tool is the Required gen_ai.operation.name on tool-execution spans.\n attrs[OPERATION_NAME_ATTR] = 'execute_tool';\n } else if (opts.toolName) {\n // A toolName on a NON-tool_call event (e.g. an 'error' event from a failed tool call, via\n // trackError). gen_ai.tool.name would reclassify the event as a tool_call, so carry it on the\n // Sentrial-namespaced attr instead — the server persists events.toolName without flipping the\n // event type. Main stored tool_name on every event that supplied one.\n attrs[SENTRIAL_EVENT_ATTR_KEYS.toolName] = opts.toolName;\n }\n // Round-trip a custom event type so the ingest doesn't collapse it to 'state_change' (Finding A).\n // Only for non-standard types — standard ones keep their semconv-driven classification untouched.\n if (opts.eventType && !STANDARD_EVENT_TYPES.has(opts.eventType)) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.type] = opts.eventType;\n }\n // tool_call events carry input/output on the CURRENT OTel tool-call attributes\n // (gen_ai.tool.call.arguments / gen_ai.tool.call.result). Non-tool event content, when a caller\n // states it (importers like Langfuse do for GENERATION observations; the SDK's own non-tool\n // paths never pass input/output), rides the structured messages convention — the deprecated\n // flat gen_ai.prompt/completion keys are gone.\n if (isToolCall) {\n if (opts.input != null) attrs[TOOL_ARGS_ATTR] = opts.input;\n if (opts.output != null) attrs[TOOL_RESULT_ATTR] = opts.output;\n } else {\n if (opts.input != null) attrs[MESSAGES_INPUT_ATTR] = inputMessagesJson(opts.input);\n if (opts.output != null) {\n attrs[MESSAGES_OUTPUT_ATTR] = outputMessagesJson(opts.output, opts.finishReasons?.[0]);\n }\n }\n applyGenAiUsage(attrs, opts);\n // Sentrial event extras — emit only when present; JSON-encode arrays/objects so they survive the\n // OTLP string-attribute wire (the ingest route JSON.parses them back). Mirrors sentrial.session.*.\n if (opts.reasoning != null) attrs[SENTRIAL_EVENT_ATTR_KEYS.reasoning] = opts.reasoning;\n if (opts.alternativesConsidered != null && opts.alternativesConsidered.length) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.alternatives] = safeJsonStringify(opts.alternativesConsidered);\n }\n if (opts.confidence != null) attrs[SENTRIAL_EVENT_ATTR_KEYS.confidence] = opts.confidence;\n if (opts.metadata != null && Object.keys(opts.metadata).length) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.metadata] = safeJsonStringify(opts.metadata);\n }\n if (opts.stateRole === 'context') attrs[SENTRIAL_EVENT_ATTR_KEYS.stateRole] = opts.stateRole;\n if (opts.contentKind === 'decision_context')\n attrs[SENTRIAL_EVENT_ATTR_KEYS.contentKind] = opts.contentKind;\n // Per-event state snapshots (main's REST-path events.state_before/state_after), carried like\n // metadata: JSON-stringified, only when non-empty (an empty snapshot carries no information).\n if (opts.stateBefore != null && Object.keys(opts.stateBefore).length) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.stateBefore] = safeJsonStringify(opts.stateBefore);\n }\n if (opts.stateAfter != null && Object.keys(opts.stateAfter).length) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.stateAfter] = safeJsonStringify(opts.stateAfter);\n }\n // Extra toolError keys beyond the spec exception fields (message/type/stack_trace), carried as a\n // JSON blob the server merges back onto events.toolError (main stored the whole object verbatim).\n if (opts.toolErrorDetails != null && Object.keys(opts.toolErrorDetails).length) {\n attrs[SENTRIAL_EVENT_ATTR_KEYS.toolErrorDetails] = safeJsonStringify(opts.toolErrorDetails);\n }\n // NOTE: structured error details (toolError.type / toolError.stack_trace) are NOT flat span\n // attributes — the OTel spec puts exception.* on the `exception` SPAN EVENT; the transports\n // attach exceptionEvent() to the errored span instead. Caller-supplied traceId/spanId are\n // deliberately NOT carried on the OTLP path (span identity comes from OTLP itself); the\n // stateBefore/stateAfter snapshots ARE carried above as sentrial.event.state_before/_after.\n // Finish reasons ride the canonical string[] attr as a REAL OTLP array (anyValue emits\n // arrayValue for primitive arrays; the ingest's spanFinishReasons handles the array form).\n if (opts.finishReasons && opts.finishReasons.length) {\n attrs[FINISH_REASONS_ATTR] = opts.finishReasons;\n }\n return attrs;\n}\n\nexport interface ExceptionEventInput {\n /** exception.type — the error class/type name (toolError.type / errorType). */\n type?: string | null;\n /** exception.message — the human-readable error message. */\n message?: string | null;\n /** exception.stacktrace — the stack trace as a string (toolError.stack_trace). */\n stacktrace?: string | null;\n /** int64 nanoseconds; stringified onto the wire. Omitted → the consumer uses the span times. */\n timeUnixNano?: string | null;\n}\n\n/**\n * The spec-shaped `exception` SPAN EVENT (the event name MUST be 'exception' per the OTel\n * exception conventions) carrying exception.type/message/stacktrace. Attached to an errored event\n * span by the SDK transports via span({ events }); the server reads this event first and falls\n * back to the legacy flat exception.* span attrs older SDKs emitted. Returns null when no field\n * is present so callers can skip the event entirely. MUST match the Python `_otlp.exception_event`.\n */\nexport function exceptionEvent(opts: ExceptionEventInput): Record<string, unknown> | null {\n const attrs: Record<string, unknown> = {};\n if (opts.type != null) attrs[EXCEPTION_ATTR_KEYS.type] = opts.type;\n if (opts.message != null) attrs[EXCEPTION_ATTR_KEYS.message] = opts.message;\n if (opts.stacktrace != null) attrs[EXCEPTION_ATTR_KEYS.stacktrace] = opts.stacktrace;\n if (Object.keys(attrs).length === 0) return null;\n const event: Record<string, unknown> = {\n name: EXCEPTION_ATTR_KEYS.event,\n attributes: attributes(attrs),\n };\n if (opts.timeUnixNano != null) event.timeUnixNano = String(opts.timeUnixNano);\n return event;\n}\n\nexport interface EvaluationAttrsInput {\n /** Evaluation/metric name — folded into the session's customMetrics by the ingest. */\n name: string;\n /** Numeric score; omit for text-only evaluations (the ingest then folds only the reasoning). */\n value?: number | null;\n explanation?: string | null;\n}\n\n/** Standard OTel GenAI evaluation-result attributes (gen_ai.evaluation.name/.score.value/\n * .explanation). Used by evaluationEvent() below; kept exported for the legacy flat-span-attr\n * carrier the server still accepts. */\nexport function evaluationAttributes(opts: EvaluationAttrsInput): Record<string, unknown> {\n const attrs: Record<string, unknown> = {\n [EVALUATION_ATTR_KEYS.name]: opts.name,\n };\n if (opts.value != null && Number.isFinite(opts.value)) {\n attrs[EVALUATION_ATTR_KEYS.score] = opts.value;\n }\n if (opts.explanation != null && opts.explanation !== '') {\n attrs[EVALUATION_ATTR_KEYS.explanation] = opts.explanation;\n }\n return attrs;\n}\n\n/**\n * A `gen_ai.evaluation.result` SPAN EVENT carrying the evaluation attributes. The spec defines\n * these keys on a LOG event of that name; Sentrial does not emit OTLP logs, so a span event of\n * the same name is the closest spec-shaped carrier available on the traces signal. Attached via\n * span({ events }) to the span the evaluation targets (root or observation); the server's\n * extractEvaluations reads this event AND the legacy flat-attr evaluation spans.\n */\nexport function evaluationEvent(\n opts: EvaluationAttrsInput & { timeUnixNano?: string | null }\n): Record<string, unknown> {\n const event: Record<string, unknown> = {\n name: EVALUATION_ATTR_KEYS.event,\n attributes: attributes(evaluationAttributes(opts)),\n };\n if (opts.timeUnixNano != null) event.timeUnixNano = String(opts.timeUnixNano);\n return event;\n}\n\nexport interface ResourceAttrsInput {\n /**\n * The telemetry SOURCE platform (`service.name` on the Resource): 'sentrial' for the SDKs,\n * 'langfuse' for the Langfuse importer. NOT the customer's application name and deliberately\n * NOT OTEL_SERVICE_NAME — the ingest keys off service.name to attribute data to its source.\n * Agent identity is gen_ai.agent.name on the span, never here.\n */\n source: string;\n /** The emitting SDK/importer's own identity (`telemetry.sdk.name` per the OTel Resource semconv). */\n sdkName: string;\n /** The emitting SDK's version (telemetry.sdk.version — Required group member). */\n sdkVersion?: string | null;\n /** Implementation language (`telemetry.sdk.language` — Required group member). */\n language?: string | null;\n}\n\n/**\n * OpenTelemetry Resource attributes for a trace. Field→attr conversion for the resource level,\n * owned by the converter — mirroring the Python `_otlp.resource_attributes`.\n */\nexport function resourceAttributes(opts: ResourceAttrsInput): Record<string, unknown> {\n const attrs: Record<string, unknown> = {\n 'service.name': opts.source,\n 'telemetry.sdk.name': opts.sdkName,\n };\n // telemetry.sdk.name/version/language are an all-Required resource group.\n if (opts.sdkVersion != null) attrs['telemetry.sdk.version'] = opts.sdkVersion;\n if (opts.language != null) attrs['telemetry.sdk.language'] = opts.language;\n return attrs;\n}\n\ninterface SpanInput {\n name: string;\n traceId: string;\n spanId: string;\n attrs: Record<string, unknown>;\n parentSpanId?: string | null;\n kind?: number;\n /** int64 nanoseconds; pass as a string to avoid JS number precision loss. */\n startTimeUnixNano?: string | null;\n endTimeUnixNano?: string | null;\n error?: string | null;\n /** OTLP span events (e.g. exceptionEvent() / evaluationEvent()); omitted when empty. */\n events?: Array<Record<string, unknown>> | null;\n}\n\n/** Build a single OTLP span object. */\nexport function span(opts: SpanInput): Record<string, unknown> {\n const s: Record<string, unknown> = {\n traceId: opts.traceId,\n spanId: opts.spanId,\n name: opts.name,\n kind: opts.kind ?? SPAN_KIND_INTERNAL,\n attributes: attributes(opts.attrs),\n // OTLP/JSON: \"Values of enum fields MUST be encoded as integer values\" — 1=OK, 2=ERROR.\n status: opts.error ? { code: 2, message: opts.error } : { code: 1 },\n };\n if (opts.parentSpanId) s.parentSpanId = opts.parentSpanId;\n if (opts.startTimeUnixNano != null) s.startTimeUnixNano = String(opts.startTimeUnixNano);\n if (opts.endTimeUnixNano != null) s.endTimeUnixNano = String(opts.endTimeUnixNano);\n if (opts.events && opts.events.length) s.events = opts.events;\n return s;\n}\n\n/** Build the OTLP/JSON ExportTraceServiceRequest body the ingest route accepts. */\nexport function buildTrace(\n spans: Array<Record<string, unknown>>,\n resourceAttrs?: Record<string, unknown>\n): Record<string, unknown> {\n return {\n resourceSpans: [\n {\n resource: { attributes: attributes(resourceAttrs ?? {}) },\n scopeSpans: [{ spans }],\n },\n ],\n };\n}\n","{\n \"name\": \"@sentrial/sdk\",\n \"version\": \"0.6.0\",\n \"description\": \"Sentrial SDK for TypeScript/Node.js - AI agent observability and monitoring\",\n \"type\": \"module\",\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"bin\": {\n \"sentrial\": \"./dist/cli.js\"\n },\n \"exports\": {\n \".\": {\n \"import\": {\n \"types\": \"./dist/index.d.ts\",\n \"default\": \"./dist/index.js\"\n },\n \"require\": {\n \"types\": \"./dist/index.d.cts\",\n \"default\": \"./dist/index.cjs\"\n }\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint .\",\n \"test\": \"vitest run\",\n \"clean\": \"rm -rf dist\",\n \"prepublishOnly\": \"pnpm run build\"\n },\n \"keywords\": [\n \"sentrial\",\n \"ai\",\n \"agent\",\n \"observability\",\n \"monitoring\",\n \"llm\",\n \"tracing\",\n \"openai\",\n \"openai-agents\",\n \"anthropic\",\n \"vercel-ai-sdk\",\n \"google-adk\"\n ],\n \"dependencies\": {\n \"@opentelemetry/api\": \"^1.7.0\"\n },\n \"peerDependencies\": {\n \"@anthropic-ai/claude-agent-sdk\": \">=0.2.0 <1.0.0\",\n \"@google/adk\": \">=0.1.0 <2.0.0\",\n \"@openai/agents\": \">=0.1.0 <1.0.0\",\n \"ai\": \">=3.0.0 <8.0.0\"\n },\n \"peerDependenciesMeta\": {\n \"ai\": {\n \"optional\": true\n },\n \"@anthropic-ai/claude-agent-sdk\": {\n \"optional\": true\n },\n \"@google/adk\": {\n \"optional\": true\n },\n \"@openai/agents\": {\n \"optional\": true\n }\n },\n \"author\": \"Sentrial\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/sentrialdev/Sentrial.git\",\n \"directory\": \"packages/typescript-sdk\"\n },\n \"homepage\": \"https://sentrial.com\",\n \"bugs\": {\n \"url\": \"https://github.com/sentrialdev/Sentrial/issues\"\n },\n \"engines\": {\n \"node\": \">=18\"\n },\n \"devDependencies\": {\n \"@sentrial/otlp\": \"workspace:*\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.1.11\"\n }\n}\n","/**\n * Bounded-Map eviction policy shared by the SDK's per-session registries (#561).\n *\n * Both the OTLP session registry (otlp-transport.ts) and the cost/token accumulator (client.ts)\n * keep an insertion-ordered `Map<string, { createdAt }>` that must not leak entries for sessions\n * that never finalize (crashes, abandoned streams, event-only paths). The policy is identical: a\n * cheap TTL sweep (stop at the first still-fresh entry, since createdAt is monotonic with insertion\n * order) followed by a hard cap backstop evicting the oldest while still over `maxSize`.\n */\nexport function evictStaleEntries<V extends { createdAt: number }>(\n map: Map<string, V>,\n opts: { ttlMs: number; maxSize: number; onCapEvict?: (id: string, value: V) => void }\n): void {\n const cutoff = Date.now() - opts.ttlMs;\n for (const [id, value] of map) {\n if (value.createdAt >= cutoff) break; // insertion-ordered: everything after is at least as new\n // TTL sweep is intentionally SILENT (matches the Python policy's cap-only on_cap_evict): an\n // idle-past-TTL session is expected cleanup, not the \"created but never finished\" signal.\n map.delete(id);\n }\n while (map.size >= opts.maxSize) {\n const oldest = map.keys().next().value;\n if (oldest === undefined) break;\n const value = map.get(oldest);\n map.delete(oldest);\n // Over-cap eviction fires the callback — this is the actionable signal that sessions are being\n // created faster than they finish (a missing finish()/completeSession()).\n if (value) opts.onCapEvict?.(oldest, value);\n }\n}\n","/**\n * Containment seam — the outcome-level enforcement point of the cross-SDK\n * \"monitoring never crashes the host app\" contract (the TS twin of the Python\n * SDK's `_wire.contained` decorators).\n *\n * Repeated review passes each found a new sibling of the same hostile-input class: user\n * objects whose property getters / Proxy traps / toString / toJSON / valueOf throw,\n * escaping a tracking call through whichever access the per-pattern sweeps hadn't\n * guarded yet. That surface is unbounded, so per-pattern guards cannot close the class.\n * These wrappers restore the REST era's property at the METHOD level: the ENTIRE body of\n * every public tracking entrypoint on SentrialClient/Interaction runs inside one\n * try honoring `failSilently` — on ANY throw, with failSilently true (the default) the\n * failure is logged once per method and the method's degraded value is returned; with\n * failSilently false it rethrows/rejects unchanged.\n *\n * Interior guards (toText / safeJsonStringify / capStateSnapshot / the accumulate() and\n * toolError field guards) remain for DATA QUALITY — they let a tracking call still\n * SUCCEED with degraded values where this seam would drop the whole call.\n */\n\n/**\n * Method qualnames that have already emitted a containment warning. Keyed per METHOD\n * (not global — distinct failing entrypoints each stay visible; not per call — a hostile\n * object in a hot loop can't spam warnings). Tests may clear this via\n * `_resetContainedWarnings()`.\n */\nconst containedWarned = new Set<string>();\n\n/** Test hook: clear the per-method warn-once registry. */\nexport function _resetContainedWarnings(): void {\n containedWarned.clear();\n}\n\n/**\n * String(value) that never throws — the seam's own logging must be exception-free, and a\n * user-raised value's toString/Symbol.toPrimitive can itself be hostile (same last-resort\n * literal as the transport's toText / trackToolCall's asErrorString guards).\n */\nexport function safeString(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/**\n * Format a contained error as `Name: message` without letting hostile getters raise\n * (mirrors the Python `_warn_contained`'s guarded detail formatting).\n */\nexport function safeErrorDetail(err: unknown): string {\n try {\n if (err instanceof Error) {\n const name = typeof err.name === 'string' && err.name ? err.name : 'Error';\n return `${name}: ${safeString(err.message)}`;\n }\n return safeString(err);\n } catch {\n // A hostile `.name`/`.message` getter (or Symbol.hasInstance shenanigans) must not\n // make the seam's own logging throw.\n return '[unserializable]';\n }\n}\n\n/**\n * failSilently for a client (`obj.failSilently`) or an Interaction\n * (`obj.client.failSilently`). Unknown shapes default to true — never-crash is the\n * default posture. The WHOLE resolution chain is guarded: this runs on the seam's\n * catch-path, so it must be exception-free (a hostile subclass getter resolves to\n * true — contain by default). Mirrors the Python `_wire._resolve_fail_silently`.\n */\nexport function resolveFailSilently(obj: unknown): boolean {\n try {\n const o = obj as {\n failSilently?: unknown;\n client?: { failSilently?: unknown } | null;\n } | null;\n let fs: unknown = o?.failSilently;\n if (fs == null) fs = o?.client?.failSilently;\n if (fs == null) return true;\n return Boolean(fs);\n } catch {\n return true;\n }\n}\n\n/**\n * Log a containment: ONE console.warn per method name (the SDK's warn convention —\n * `Sentrial: ...` prefixed); later failures of the same method log at console.debug so\n * distinct failures stay visible without spam. Mirrors the Python `_warn_contained`.\n */\nexport function warnContained(qualname: string, err: unknown): void {\n const detail = safeErrorDetail(err);\n if (containedWarned.has(qualname)) {\n console.debug(`Sentrial: ${qualname} raised and was contained: ${detail}`);\n return;\n }\n containedWarned.add(qualname);\n console.warn(\n `Sentrial: ${qualname} raised and was contained — this tracking call was dropped ` +\n `(failSilently=true; construct the client with failSilently=false to raise ` +\n `instead): ${detail}`\n );\n}\n\n/**\n * A degraded-value factory. MUST be exception-free (plain constructions with guarded\n * reads only) — it runs on the seam's catch-path.\n */\nexport type DegradedFactory = (self: unknown, args: unknown[]) => unknown;\n\n/**\n * Wrap one public tracking method on a prototype with the containment seam.\n *\n * Handles sync and async methods uniformly: a synchronous throw is contained in place;\n * a returned promise gets a `.catch` that applies the same policy (so a\n * failSilently:false async method still REJECTS with the original error, unchanged).\n * The seam cannot double-log: it fires only for exceptions the inner code did not\n * already handle (safeRequest's own failSilently handling returns null, it does not\n * throw). Applied at module load (see the call sites at the bottom of client.ts) —\n * the structural equivalent of Python's `@contained()` decorations.\n */\nexport function containMethod(proto: object, name: string, degraded?: DegradedFactory): void {\n const record = proto as Record<string, unknown>;\n const original = record[name];\n if (typeof original !== 'function') {\n // A typo here would silently leave a method uncontained — fail loudly at module\n // load (deterministic: any test or import of the SDK catches it immediately).\n throw new TypeError(`containMethod: ${name} is not a method on the given prototype`);\n }\n const originalFn = original as (...a: unknown[]) => unknown;\n const qualname = `${(proto.constructor as { name?: string } | undefined)?.name ?? 'Object'}.${name}`;\n const wrapped = function (this: unknown, ...args: unknown[]): unknown {\n let result: unknown;\n try {\n result = originalFn.apply(this, args);\n } catch (err) {\n if (!resolveFailSilently(this)) throw err;\n warnContained(qualname, err);\n return degraded ? degraded(this, args) : undefined;\n }\n if (result instanceof Promise) {\n // The arrow captures the wrapper's lexical `this`, so no `const self = this` alias is needed.\n return result.catch((err: unknown) => {\n if (!resolveFailSilently(this)) throw err;\n warnContained(qualname, err);\n return degraded ? degraded(this, args) : undefined;\n });\n }\n return result;\n };\n // Preserve the method's name (stack traces / debuggers) and arity (fn.length) — plain\n // assignment would zero .length; original.length mirrors the unwrapped signature.\n Object.defineProperty(wrapped, 'name', { value: name, configurable: true });\n Object.defineProperty(wrapped, 'length', { value: original.length, configurable: true });\n // defineProperty (not assignment) keeps the wrapped method NON-enumerable, matching how\n // class methods natively sit on the prototype — a for...in over a client instance stays\n // identical pre/post wrapping (pass-27 informational).\n Object.defineProperty(record, name, { value: wrapped, writable: true, configurable: true });\n}\n","/** Optional agent assertions, never verified outcomes, review labels or instructions. */\nexport const AGENT_DIAGNOSTIC_EVENT = 'agent_diagnostic';\nexport const AGENT_DIAGNOSTIC_CATEGORIES = [\n 'missing_context',\n 'tool_problem',\n 'missing_capability',\n 'uncertain_completion',\n] as const;\nexport interface AgentDiagnosticInput {\n /** Opaque identifier reused for subsequent reports within this session. */\n reportId: string;\n category: (typeof AGENT_DIAGNOSTIC_CATEGORIES)[number];\n /** Both states are assertions by the agent, including claimed recovery. */\n state: 'reported' | 'recovered';\n description: string;\n /** Optional related OTel span IDs in this trace; these are unverified references. */\n relatedSpanIds?: readonly string[];\n}\nexport interface AgentDiagnostic extends Omit<AgentDiagnosticInput, 'relatedSpanIds'> {\n revision: 'agent-diagnostic-v1';\n origin: 'agent_report';\n relatedSpanIds: readonly string[];\n}\nconst fields = ['reportId', 'category', 'state', 'description', 'relatedSpanIds'];\nfunction dataObject(value: unknown, allowed: readonly string[]): Record<string, unknown> | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n if (![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;\n const descriptors = Object.getOwnPropertyDescriptors(value);\n if (\n Reflect.ownKeys(descriptors).some(\n (key) =>\n typeof key !== 'string' ||\n !allowed.includes(key) ||\n !('value' in descriptors[key]) ||\n !descriptors[key].enumerable\n )\n )\n return null;\n return Object.fromEntries(Object.entries(descriptors).map(([key, d]) => [key, d.value]));\n}\n\n/** Strict, bounded, detached data. Malformed reports never become authoritative errors. */\nexport function parseAgentDiagnostic(value: unknown): Readonly<AgentDiagnostic> | null {\n try {\n const row = dataObject(value, [...fields, 'revision', 'origin']);\n if (\n !row ||\n Object.keys(row).length !== 7 ||\n row.revision !== 'agent-diagnostic-v1' ||\n row.origin !== 'agent_report' ||\n typeof row.reportId !== 'string' ||\n // Avoid `$`, which also matches before a final newline in JavaScript.\n !/^[A-Za-z0-9]/.test(row.reportId) ||\n row.reportId.length > 128 ||\n /[^A-Za-z0-9._:-]/.test(row.reportId) ||\n !AGENT_DIAGNOSTIC_CATEGORIES.includes(row.category as AgentDiagnostic['category']) ||\n !['reported', 'recovered'].includes(row.state as string) ||\n typeof row.description !== 'string' ||\n // Union of Python/ECMAScript whitespace; neither SDK admits a blank report.\n // eslint-disable-next-line no-control-regex -- Python treats these separators as whitespace.\n /^[\\s\\u001c-\\u001f\\u0085]*$/u.test(row.description) ||\n row.description.length > 4096 ||\n new TextEncoder().encode(row.description).length > 4096 ||\n !Array.isArray(row.relatedSpanIds) ||\n row.relatedSpanIds.length > 8\n )\n return null;\n if (\n [...row.description].some((char) => {\n const cp = char.codePointAt(0)!;\n return cp >= 0xd800 && cp <= 0xdfff;\n })\n )\n return null;\n const refs = row.relatedSpanIds;\n const descriptors = Object.getOwnPropertyDescriptors(refs);\n if (\n Reflect.ownKeys(descriptors).length !== refs.length + 1 ||\n Array.from({ length: refs.length }, (_, i) => descriptors[String(i)]).some(\n (d) =>\n !d ||\n !('value' in d) ||\n !d.enumerable ||\n typeof d.value !== 'string' ||\n d.value.length !== 16 ||\n !/^[0-9a-f]{16}$/.test(d.value) ||\n /^0+$/.test(d.value)\n )\n )\n return null;\n const relatedSpanIds = Array.from({ length: refs.length }, (_, i) => descriptors[i].value);\n if (new Set(relatedSpanIds).size !== relatedSpanIds.length) return null;\n return Object.freeze({\n ...row,\n relatedSpanIds: Object.freeze(relatedSpanIds),\n }) as unknown as Readonly<AgentDiagnostic>;\n } catch {\n return null;\n }\n}\n\n/** SDK input constructor. Never reads accessors or coerces arbitrary objects to text. */\nexport function createAgentDiagnostic(input: AgentDiagnosticInput): Readonly<AgentDiagnostic> {\n try {\n const row = dataObject(input, fields);\n const result =\n row &&\n parseAgentDiagnostic({\n ...row,\n relatedSpanIds: row.relatedSpanIds === undefined ? [] : row.relatedSpanIds,\n revision: 'agent-diagnostic-v1',\n origin: 'agent_report',\n });\n if (result) return result;\n } catch {\n // SDK containment chooses fail-silent or strict behavior without echoing report content.\n }\n throw new Error('Invalid agent diagnostic');\n}\n","/**\n * Sentrial Client - Main SDK interface\n *\n * Captures agent sessions, tool calls, and metrics. This data powers:\n * - Signal detection (auto-detect patterns/anomalies)\n * - Root cause analysis (understand WHY agents fail)\n * - Code fixer (AI-suggested fixes with GitHub PRs)\n */\n\nimport { ApiError, NetworkError } from './errors.js';\nimport { DEFAULT_API_URL, OTEL_TRACES_PATH } from './constants.js';\nimport { randomId } from './random.js';\nimport { redactPayload, redactText, DEFAULT_FIELDS } from './redact.js';\nimport type { PiiConfig } from './redact.js';\nimport { createContextVar, type ContextVar } from './async-context.js';\nimport {\n _setSessionContextWithTokens,\n _restoreSessionContext,\n setDefaultClient,\n type SessionContextTokens,\n} from './wrappers.js';\nimport { TransportDiagnostics } from './transport-diagnostics.js';\nimport { EventBatcher } from './batcher.js';\nimport { LargeToolContentUploader } from './large-tool-content.js';\nimport {\n actionMetadata,\n createHandoffContext,\n createDispatchContext,\n handoffRecord,\n type ActionIdentity,\n type HandoffOutcome,\n} from './lineage.js';\nimport * as otlp from './otlp-transport.js';\nimport type {\n SentrialClientConfig,\n CreateSessionParams,\n CompleteSessionParams,\n TrackToolCallParams,\n TrackDecisionParams,\n TrackErrorParams,\n BeginParams,\n FinishParams,\n GetVariantParams,\n VariantAssignment,\n RecordExperimentExposureParams,\n ExperimentExposureReceipt,\n Session,\n Event,\n} from './types.js';\nimport { calculateOpenAICost, calculateAnthropicCost, calculateGoogleCost } from './cost.js';\nimport { evictStaleEntries } from './evict.js';\nimport { containMethod, safeString } from './contained.js';\nimport {\n AGENT_DIAGNOSTIC_EVENT,\n createAgentDiagnostic,\n type AgentDiagnosticInput,\n} from '@sentrial/otlp/agent-diagnostic';\n\n// Retry configuration (mirrors Python SDK)\nconst MAX_RETRIES = 3;\nconst INITIAL_BACKOFF_MS = 500;\nconst MAX_BACKOFF_MS = 8000;\nconst BACKOFF_MULTIPLIER = 2;\nconst RETRYABLE_STATUS_CODES = new Set([408, 429, 500, 502, 503, 504]);\nconst REQUEST_TIMEOUT_MS = 10_000;\n\n// OTLP ingest path — a 403 here almost always means the API key lacks the `events:write` scope the\n// route requires (in addition to `sessions:write`). Warn ONCE (module-level, not per request) so the\n// otherwise fail-silent transport doesn't lose data invisibly. Mirrors the Python SDK.\nlet otlpScopeWarningEmitted = false;\nfunction warnOtlpForbiddenOnce(): void {\n if (otlpScopeWarningEmitted) return;\n otlpScopeWarningEmitted = true;\n console.warn(\n \"Sentrial: OTLP transport got 403 — the API key is likely missing the required 'sessions:write' \" +\n \"and/or 'events:write' scope(s). Add them to the API key.\"\n );\n}\n\n// pii-config fetch — a 403 here means the API key lacks the `pii-config:read` scope the route\n// requires, so the SDK falls back to the minimal default redaction config (label mode, default\n// fields, builtin patterns) and the org's configured mode/fields/custom patterns are NOT applied.\n// Warn ONCE so this fail-silent fallback isn't invisible. Mirrors the Python SDK.\nlet piiConfigScopeWarningEmitted = false;\nfunction warnPiiConfigForbiddenOnce(): void {\n if (piiConfigScopeWarningEmitted) return;\n piiConfigScopeWarningEmitted = true;\n console.warn(\n \"Sentrial: pii-config fetch got 403 — the API key is missing the required 'pii-config:read' \" +\n 'scope, so default redaction is being used and your org PII settings (mode, fields, custom ' +\n 'patterns) are NOT applied.'\n );\n}\n\n/**\n * Sentrial Client for agent observability.\n *\n * Captures the data needed for automated root cause analysis and fix suggestions.\n *\n * @example\n * ```ts\n * const client = new SentrialClient({\n * apiKey: 'sentrial_live_xxx',\n * apiUrl: 'https://api.sentrial.com',\n * });\n *\n * // Create a session for an agent run\n * const sessionId = await client.createSession({\n * name: 'Support Request #123',\n * agentName: 'support-agent',\n * userId: 'user_123',\n * });\n *\n * // Track events\n * await client.trackToolCall({\n * sessionId,\n * toolName: 'search_kb',\n * toolInput: { query: 'password reset' },\n * toolOutput: { articles: ['KB-001'] },\n * });\n *\n * // Complete session with metrics\n * await client.completeSession({\n * sessionId,\n * success: true,\n * customMetrics: { customer_satisfaction: 90 },\n * });\n * ```\n *\n * @remarks\n * By default, the SDK fails silently (failSilently=true) to ensure\n * monitoring never crashes your application. Set failSilently=false\n * during development to see errors.\n *\n * Containment seam (the never-crash contract's enforcement point): every public tracking\n * entrypoint on this client (and on Interaction) is wrapped by `containMethod` (see\n * contained.ts and the application block at the bottom of this file) — the ENTIRE method\n * body runs inside one try honoring `failSilently`. Any exception escaping a method body\n * is caught at the method boundary: with failSilently=true (default) it is logged once per\n * method and the method's degraded value is returned (null / undefined / a degraded\n * Interaction); with failSilently=false it rethrows/rejects unchanged. Interior guards\n * (toText, safeJsonStringify, capStateSnapshot, accumulate's safeNum, the toolError field\n * accessors) remain for DATA QUALITY: they let a tracking call still succeed with degraded\n * values instead of the seam dropping the whole call. Mirrors the Python SDK's\n * `_wire.contained` decorations.\n */\nexport class SentrialClient {\n private readonly apiUrl: string;\n private readonly apiKey?: string;\n private readonly failSilently: boolean;\n private piiConfig?: PiiConfig;\n private piiConfigNeedsHydration: boolean = false;\n private piiHydrationPromise?: Promise<void>;\n private readonly _stateVar: ContextVar<Record<string, unknown>> = createContextVar<\n Record<string, unknown>\n >({});\n private readonly batcher?: EventBatcher;\n private readonly contentUploader?: LargeToolContentUploader;\n private readonly transportDiagnostics = new TransportDiagnostics();\n private readonly otlpSessions = new Map<string, otlp.OtlpSession>();\n /** Every ambient OTel traceId this client has EVER consumed for a session — permanent (bounded,\n * FIFO) because the server's otel-sess:<traceId> fallback key is permanent; see claimTraceId. */\n private readonly consumedAmbientTraceIds = new Set<string>();\n private pendingExperimentAssignmentIds: string[] = [];\n\n /** Per-session cost/token accumulator — populated by trackToolCall/trackDecision */\n private sessionAccumulators = new Map<\n string,\n {\n cost: number;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n /** When the entry was created (ms epoch), for TTL eviction of abandoned sessions (#561). */\n createdAt: number;\n }\n >();\n\n /**\n * Accumulators for sessions that never call completeSession() — e.g. a mastra stream the caller\n * abandons without consuming, so its onFinish never fires — are evicted after this TTL so their\n * bookkeeping can't linger. The TTL is measured from LAST ACTIVITY (accumulate() refreshes the\n * entry's timestamp), so an actively-tracking long-running session is never dropped — only\n * sessions idle for 2h expire. The accumulator is only a fallback for callers that don't pass\n * explicit cost/tokens to finish (#561).\n */\n private static readonly ACCUMULATOR_TTL_MS = 2 * 60 * 60 * 1000;\n\n /** Hard cap on live accumulator entries — backstop for the TTL sweep (#561). */\n private static readonly MAX_ACCUMULATORS = 10000;\n\n private get currentState(): Record<string, unknown> {\n return this._stateVar.get();\n }\n\n private set currentState(value: Record<string, unknown>) {\n this._stateVar.set(value);\n }\n\n constructor(config: SentrialClientConfig = {}) {\n this.apiUrl = (\n config.apiUrl ??\n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_URL : undefined) ??\n DEFAULT_API_URL\n ).replace(/\\/$/, '');\n\n this.apiKey =\n config.apiKey ?? (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_KEY : undefined);\n\n this.failSilently = config.failSilently ?? true;\n if (config.largeToolContent?.enabled)\n this.contentUploader = new LargeToolContentUploader(\n config.largeToolContent,\n this.apiUrl,\n this.apiKey\n );\n\n if (config.pii === true) {\n // Minimal default until we fetch the real config from the server\n this.piiConfig = { enabled: true };\n this.piiConfigNeedsHydration = true;\n } else if (config.pii && typeof config.pii === 'object') {\n this.piiConfig = config.pii;\n this.piiConfigNeedsHydration = false;\n }\n\n // Initialize event batcher if enabled\n if (config.batching?.enabled) {\n this.batcher = new EventBatcher(\n (method, url, body) => this.safeRequest(method, url, body),\n config.batching\n );\n }\n }\n\n /**\n * Fetch the organization's PII config from the server.\n *\n * Called lazily on the first request when `pii: true` was passed to the constructor.\n * Uses a single shared promise so concurrent requests don't trigger duplicate fetches.\n */\n private async hydratePiiConfig(): Promise<void> {\n if (!this.piiConfigNeedsHydration) return;\n\n // Deduplicate: if a fetch is already in flight, reuse its promise\n if (this.piiHydrationPromise) {\n await this.piiHydrationPromise;\n return;\n }\n\n this.piiHydrationPromise = (async () => {\n try {\n const headers: Record<string, string> = {};\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {\n method: 'GET',\n headers,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (response.ok) {\n const data = (await response.json()) as {\n config: {\n enabled: boolean;\n mode: string;\n fields: string[];\n builtinPatterns: Record<string, boolean>;\n customPatterns: Array<{ pattern: string; label: string }>;\n } | null;\n };\n if (data.config) {\n // Compile each custom pattern in its OWN try/catch so one server-stored pattern that is\n // invalid in JS (e.g. a Python-only construct) is skipped with a warning instead of\n // discarding the entire fetched config (mode/fields/other patterns).\n const compiledCustom: PiiConfig['customPatterns'] = [];\n for (const cp of data.config.customPatterns || []) {\n try {\n compiledCustom.push({ pattern: new RegExp(cp.pattern, 'g'), label: cp.label });\n } catch {\n console.warn(\n `Sentrial: skipping invalid custom PII pattern (label='${cp.label}'): ${cp.pattern}`\n );\n }\n }\n this.piiConfig = {\n // Default redaction ON when the server omits `enabled` (mirrors the Python\n // SDK's parse_pii_config `enabled=config.get('enabled', True)`) so a partial\n // response never silently disables PII redaction.\n enabled: data.config.enabled ?? true,\n mode: data.config.mode as PiiConfig['mode'],\n fields: data.config.fields as PiiConfig['fields'],\n builtinPatterns: data.config.builtinPatterns as PiiConfig['builtinPatterns'],\n customPatterns: compiledCustom,\n };\n }\n } else if (response.status === 403) {\n warnPiiConfigForbiddenOnce();\n }\n } catch {\n // Fail silently — keep the minimal default config (enabled: true)\n }\n this.piiConfigNeedsHydration = false;\n })();\n\n await this.piiHydrationPromise;\n }\n\n /**\n * Make an HTTP request with retry logic and exponential backoff.\n *\n * Retries on transient failures (network errors, timeouts, 429/5xx).\n * Up to MAX_RETRIES attempts with exponential backoff.\n */\n /**\n * Public wrapper around the retrying transport (exponential backoff, 10s timeout,\n * PII redaction, structured ApiError/NetworkError) so callers like Experiment reuse\n * it instead of hand-rolling a bare fetch (#482).\n */\n async apiRequest<T>(method: string, url: string, body?: unknown): Promise<T | null> {\n return this.safeRequest<T>(method, url, body);\n }\n\n private async safeRequest<T>(method: string, url: string, body?: unknown): Promise<T | null> {\n const diagnostics =\n method.toUpperCase() === 'POST' && url === `${this.apiUrl}${OTEL_TRACES_PATH}`\n ? this.transportDiagnostics\n : undefined;\n const finish = diagnostics?.startRequest();\n try {\n const result = await this.requestWithRetries<T>(method, url, body, diagnostics);\n finish?.(result === null ? 'failed' : 'returned');\n return result;\n } catch (error) {\n finish?.('failed');\n throw error;\n }\n }\n\n private async requestWithRetries<T>(\n method: string,\n url: string,\n body: unknown,\n diagnostics?: TransportDiagnostics\n ): Promise<T | null> {\n // Hydrate server-driven PII config before first request\n if (this.piiConfigNeedsHydration) {\n await this.hydratePiiConfig();\n }\n\n // Redaction + serialization are attempt-INVARIANT: a throw here (e.g. a circular or\n // hostile-toJSON body from a caller bypassing the transports' capStateSnapshot ingress) is\n // deterministic, so classifying it with the retryable network errors below would only burn\n // MAX_RETRIES backoff sleeps re-failing on the same body. Serialize ONCE, before the retry\n // loop, and fail fast to the failSilently path instead (NON-retryable).\n let serializedBody: string | undefined;\n try {\n // Apply PII redaction before serialization\n const finalBody =\n this.piiConfig && body && typeof body === 'object'\n ? redactPayload(body as Record<string, unknown>, this.piiConfig)\n : body;\n serializedBody = finalBody ? JSON.stringify(finalBody) : undefined;\n } catch (error) {\n const serializationError = error instanceof Error ? error : new Error(safeString(error));\n if (this.failSilently) {\n console.warn(\n `Sentrial: Request body serialization failed (${method} ${url}):`,\n serializationError.message\n );\n return null;\n }\n throw serializationError;\n }\n\n let lastError: Error | undefined;\n let backoff = INITIAL_BACKOFF_MS;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n const observation = diagnostics?.startAttempt(attempt > 0);\n const controller = new AbortController();\n let receivedHeaders = false;\n const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n // Keep the timeout active through body receipt, not just response headers.\n const response = await fetch(url, {\n method,\n headers,\n body: serializedBody,\n signal: controller.signal,\n });\n receivedHeaders = true;\n observation?.response(response.status, response.headers?.get('server-timing'));\n const responseBody = !response.ok ? await response.text() : '';\n if (!response.ok) {\n clearTimeout(timeoutId);\n observation?.finish('httpError');\n }\n\n // Read the response before retrying so attempts are bounded and connections released.\n if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {\n await this.sleep(backoff);\n backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);\n continue;\n }\n\n if (!response.ok) {\n // Surface the one-time actionable scope warning for the OTLP path before the generic\n // fail-silent handling below (which would otherwise swallow the 403 invisibly).\n if (response.status === 403 && url.endsWith(OTEL_TRACES_PATH)) {\n warnOtlpForbiddenOnce();\n }\n\n const errorBody = responseBody;\n let errorData: { error?: { message?: string; code?: string } | string } = {};\n try {\n errorData = JSON.parse(errorBody);\n } catch {\n // Ignore JSON parse errors\n }\n\n // V1/withV1Auth routes return the string form `{ error: '...' }`, while others use the\n // object form `{ error: { message, code } }` — handle both so the actionable server\n // message (e.g. a missing-scope hint) isn't discarded for a generic status line.\n const errorMessage =\n typeof errorData.error === 'string' ? errorData.error : errorData.error?.message;\n const errorCode = typeof errorData.error === 'object' ? errorData.error?.code : undefined;\n const error = new ApiError(\n errorMessage || `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n errorCode\n );\n\n if (this.failSilently) {\n console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);\n return null;\n }\n throw error;\n }\n\n const result = (await response.json()) as T;\n clearTimeout(timeoutId);\n observation?.finish('httpSuccess', result);\n return result;\n } catch (error) {\n clearTimeout(timeoutId);\n observation?.finish(\n controller.signal.aborted ? 'timeout' : receivedHeaders ? 'bodyError' : 'transportError'\n );\n // Non-retryable Sentrial errors — throw immediately\n if (error instanceof ApiError) {\n throw error;\n }\n\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Network / timeout errors are retryable\n if (attempt < MAX_RETRIES) {\n await this.sleep(backoff);\n backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);\n continue;\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n // All retries exhausted\n const networkError = new NetworkError(lastError?.message ?? 'Unknown network error', lastError);\n\n if (this.failSilently) {\n console.warn(\n `Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`,\n networkError.message\n );\n return null;\n }\n throw networkError;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private accumulate(\n sessionId: string,\n cost?: number,\n tokenCount?: number,\n toolOutput?: Record<string, unknown>\n ): void {\n let acc = this.sessionAccumulators.get(sessionId);\n if (acc) {\n // Refresh the TTL on activity (re-insert so the Map's insertion order stays monotonic with\n // the timestamp — the sweep's early-break shortcut depends on that). Without this, a\n // session accruing cost for >2h would have its accumulator evicted mid-session and its\n // totals silently reset.\n acc.createdAt = Date.now();\n this.sessionAccumulators.delete(sessionId);\n this.sessionAccumulators.set(sessionId, acc);\n }\n if (!acc) {\n // Bound the map: sessions that never call finish() (crashes, abandoned streams,\n // event-only code paths) would otherwise leak an entry forever in a long-lived\n // process (#496, #561). Sweep entries past the TTL, then evict the oldest while over\n // the cap — the canonical policy shared with the OTLP session registry.\n evictStaleEntries(this.sessionAccumulators, {\n ttlMs: SentrialClient.ACCUMULATOR_TTL_MS,\n maxSize: SentrialClient.MAX_ACCUMULATORS,\n // Loud, cap-only warning matching the Python SDK's _warn_evicted: an over-cap eviction means\n // sessions are being created but never completed (a missing finish()/completeSession()).\n onCapEvict: (id) =>\n console.warn(\n `Sentrial: session-accumulator cap (${SentrialClient.MAX_ACCUMULATORS}) reached; evicting the oldest ` +\n `un-completed session ${id} (its accrued cost/tokens are dropped). Sessions are being created ` +\n `but never completed — call completeSession()/finish().`\n ),\n });\n acc = {\n cost: 0,\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n createdAt: Date.now(),\n };\n this.sessionAccumulators.set(sessionId, acc);\n }\n // Fold cost/tokens using the EXACT same rule as the Python SDK's _wire.accumulate_event so the\n // two SDKs produce identical session accumulators (#478). Python gates the cost/total folds on\n // strictly-positive (`if cost > 0` / `token_count > 0`) after `_safe_number` coercion (finite\n // numbers only — NaN/±Infinity and non-numbers degrade to 0/skip), and folds the\n // prompt/completion breakdown through `_as_int` (truncate finite floats toward zero;\n // non-finite / non-numeric → 0). `safeNum` is the JS twin of `_safe_number`: a type-illegal\n // hostile object as estimatedCost/tokenCount would otherwise throw from the `> 0` ToPrimitive\n // coercion — this runs as trackToolCall's FIRST statement, so the containment seam would drop\n // the WHOLE call; degrading to not-accumulated keeps the call tracked.\n const safeNum = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0);\n const costNum = safeNum(cost);\n if (costNum > 0) acc.cost += costNum;\n const tokenNum = safeNum(tokenCount);\n if (tokenNum > 0) acc.totalTokens += tokenNum;\n // LLM wrappers store prompt/completion breakdown in toolOutput.tokens. `asInt` is the JS twin\n // of Python's `_as_int`: finite number → truncated toward zero, everything else → 0.\n const asInt = (v: unknown): number =>\n typeof v === 'number' && Number.isFinite(v) ? Math.trunc(v) : 0;\n // Guarded reads (mirrors Python _wire.accumulate_event's unbound dict.get): toolOutput is a\n // USER-supplied object whose `tokens`/`prompt`/`completion` property getters — or Proxy\n // get/ownKeys traps — can throw (pass-26). A throw here degrades to not-accumulated instead\n // of letting the seam drop the whole tracking call.\n try {\n const rawTokens = toolOutput?.tokens;\n if (rawTokens && typeof rawTokens === 'object' && !Array.isArray(rawTokens)) {\n const tokens = rawTokens as Record<string, unknown>;\n acc.promptTokens += asInt(tokens.prompt);\n acc.completionTokens += asInt(tokens.completion);\n }\n } catch {\n // Hostile getter/trap — skip the token-breakdown accumulation; the call still tracks.\n }\n }\n\n /**\n * Create a new session\n *\n * @param params - Session creation parameters\n * @returns Session ID, or null if the request failed and failSilently is true\n */\n /** Build the dependency bundle the OTLP transport needs (private members accessed here). */\n private otlpDeps(): otlp.OtlpDeps {\n return {\n apiUrl: this.apiUrl,\n request: (method, url, body) => this.safeRequest(method, url, body),\n enqueue: this.batcher\n ? (method, url, body) => this.batcher!.enqueue(method, url, body)\n : undefined,\n sessions: this.otlpSessions,\n contentUploader: this.contentUploader,\n consumedAmbientTraceIds: this.consumedAmbientTraceIds,\n redact: this.piiConfig?.enabled\n ? (text: string) => redactText(text, this.piiConfig!)\n : undefined,\n // The resolved field set the OTLP transport gates redaction on (parity with the REST path's\n // redactPayload, which redacts only config.fields ?? DEFAULT_FIELDS). An explicit empty\n // `fields: []` therefore redacts nothing, exactly as redactPayload does.\n redactFields: this.piiConfig?.enabled\n ? new Set(this.piiConfig.fields ?? DEFAULT_FIELDS)\n : undefined,\n };\n }\n\n async createSession(params: CreateSessionParams): Promise<string | null> {\n // Clear state from previous sessions to prevent memory leaks\n this.currentState = {};\n\n const assignmentIds = Array.from(\n new Set([...(params.experimentAssignmentIds || []), ...this.pendingExperimentAssignmentIds])\n );\n\n // The OTLP transport redacts the root-span name/input/metadata BEFORE posting. Hydrate the\n // org's server-driven PII config first so the very first session isn't redacted with only the\n // built-in default patterns. No-op when pii config doesn't need hydration.\n await this.hydratePiiConfig();\n const sessionId = await otlp.createSession(this.otlpDeps(), {\n name: params.name,\n agentName: params.agentName,\n userId: params.userId,\n convoId: params.convoId,\n input: params.input,\n model: params.model,\n assignmentIds,\n metadata: params.metadata,\n parentSessionId: params.parentSessionId,\n });\n if (sessionId && assignmentIds.length > 0) this.pendingExperimentAssignmentIds = [];\n return sessionId;\n }\n\n /**\n * Get a sticky production experiment variant for this user/conversation.\n *\n * The returned assignment id is automatically linked to the next session\n * created by this client. You can also pass `sessionId` to link immediately.\n */\n async getVariant(params: GetVariantParams): Promise<VariantAssignment> {\n const fallback: VariantAssignment = {\n active: false,\n reason: 'not_found',\n variant: params.defaultVariant || 'control',\n };\n\n const response = await this.safeRequest<VariantAssignment>(\n 'POST',\n `${this.apiUrl}/api/sdk/experiments/variant`,\n {\n ...params,\n sdkName: '@sentrial/sdk',\n }\n );\n\n if (!response) return fallback;\n\n if (\n (response.active || response.reason === 'holdout') &&\n response.assignmentId &&\n !params.sessionId\n ) {\n this.pendingExperimentAssignmentIds.push(response.assignmentId);\n }\n\n return response;\n }\n\n /** Report only after using this configuration. getVariant/createSession never imply exposure.\n * Returns null on fail-silent delivery failure; safe to retry with the same fields. */\n async recordExperimentExposure(\n params: RecordExperimentExposureParams\n ): Promise<ExperimentExposureReceipt | null> {\n await this.flush();\n return this.safeRequest<ExperimentExposureReceipt>(\n 'POST',\n `${this.apiUrl}/api/sdk/experiments/exposure`,\n params\n );\n }\n\n /**\n * Track a tool call event\n *\n * @param params - Tool call parameters\n * @returns `null` — the OTLP transport emits a span and does not return a synchronous event\n */\n async trackToolCall(params: TrackToolCallParams): Promise<Event | null> {\n this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount, params.toolOutput);\n\n // Snapshot the client state BEFORE the tool result is folded in (main's stateBefore/stateAfter\n // semantics, restored on the OTLP path via sentrial.event.state_before/_after).\n const stateBefore = { ...this.currentState };\n // Update current state\n this.currentState[`${params.toolName}_result`] = params.toolOutput;\n\n // Carry the STRUCTURED toolError (type + stack trace, not just message) so the primary\n // debugging payload survives the OTLP transport.\n const toolErrorObj =\n params.toolError && typeof params.toolError === 'object'\n ? (params.toolError as Record<string, unknown>)\n : undefined;\n // Narrow user-supplied error fields to strings instead of casting: a non-string\n // payload (e.g. `{ message: { code: 500 } }`) is serialized rather than emitted as a\n // type lie the OTLP schema would reject.\n const asErrorString = (value: unknown): string | undefined => {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n // A circular error payload must degrade, never throw out of trackToolCall (the\n // monitoring-never-crashes contract) — same String(v) fallback as the transport's toText.\n try {\n return String(value);\n } catch {\n // A hostile object can throw from toString/Symbol.toPrimitive too — the last-resort\n // fallback must be a literal, never another throw.\n return '[unserializable]';\n }\n }\n };\n // Fetch each toolError field through a guarded accessor: toolError is a user-supplied object\n // whose property getters (or Proxy get traps) can throw, and these reads previously happened\n // OUTSIDE asErrorString's try — a hostile field escaped trackToolCall (pass-26). A throwing\n // field degrades to undefined (that one detail is dropped); the event still emits.\n const errField = (key: string): unknown => {\n if (!toolErrorObj) return undefined;\n try {\n return toolErrorObj[key];\n } catch {\n return undefined;\n }\n };\n // Preserve the EXTRA toolError keys beyond the spec exception fields (message/type/name/\n // stack_trace/stackTrace) — e.g. { code, statusCode, retryAfter, provider }. Main stored the\n // whole toolError object verbatim; the exception event only carries the three standard fields,\n // so these ride the dedicated toolErrorDetails attr the server merges back onto events.toolError.\n // Each read is guarded (hostile getters must not escape trackToolCall).\n const errorDetails = ((): Record<string, unknown> | undefined => {\n if (!toolErrorObj) return undefined;\n let keys: string[];\n try {\n keys = Object.keys(toolErrorObj);\n } catch {\n return undefined;\n }\n const known = new Set(['message', 'type', 'name', 'stack_trace', 'stackTrace']);\n const out: Record<string, unknown> = {};\n for (const k of keys) {\n if (known.has(k)) continue;\n const v = errField(k);\n if (v !== undefined) out[k] = v;\n }\n return Object.keys(out).length ? out : undefined;\n })();\n await otlp.emitEvent(this.otlpDeps(), params.sessionId, {\n eventType: 'tool_call',\n toolName: params.toolName,\n input: params.toolInput,\n output: params.toolOutput,\n model: params.model,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n finishReasons: params.finishReason ? [params.finishReason] : undefined,\n totalTokens: params.tokenCount,\n cost: params.estimatedCost,\n // A toolError object present means the tool FAILED — mark the span errored even when the\n // caller omitted `message` (e.g. { code: 429 }), so the server reconstructs events.toolError\n // (incl. the extra keys) instead of dropping it. Default mirrors the legacy adapter's message.\n error: toolErrorObj\n ? (asErrorString(errField('message')) ?? 'Tool call reported an error')\n : undefined,\n // Accept idiomatic camelCase/JS-Error aliases so `{ type }`/`{ name }` and\n // `{ stack_trace }`/`{ stackTrace }` shapes aren't silently lossy.\n errorType: toolErrorObj ? asErrorString(errField('type') ?? errField('name')) : undefined,\n stackTrace: toolErrorObj\n ? asErrorString(errField('stack_trace') ?? errField('stackTrace'))\n : undefined,\n toolErrorDetails: errorDetails,\n reasoning: params.reasoning,\n metadata: params.metadata,\n stateBefore,\n stateAfter: { ...this.currentState },\n stateRole: 'context',\n // Real per-call timing (measured by the caller/wrappers) → the event span's start/end.\n durationMs: params.durationMs,\n startedAt: params.startedAt,\n });\n return null;\n }\n\n /**\n * Track an LLM decision event\n *\n * @param params - Decision parameters\n * @returns `null` — the OTLP transport emits a span and does not return a synchronous event\n */\n async trackDecision(params: TrackDecisionParams): Promise<Event | null> {\n this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount);\n // A decision does not mutate the client state — both snapshots are the current state\n // (main's semantics: stateBefore/stateAfter were always stamped, identical here).\n const stateSnapshot = { ...this.currentState };\n\n // The reasoning lives ONLY in sentrial.event.reasoning (→ events.reasoning). It is NOT also\n // passed as the event output, so no gen_ai.completion is emitted for a decision.\n await otlp.emitEvent(this.otlpDeps(), params.sessionId, {\n eventType: 'decision',\n contentKind: 'decision_context',\n totalTokens: params.tokenCount,\n cost: params.estimatedCost,\n reasoning: params.reasoning,\n alternativesConsidered: params.alternatives,\n confidence: params.confidence,\n metadata: params.metadata,\n stateBefore: stateSnapshot,\n stateAfter: { ...this.currentState },\n stateRole: 'context',\n durationMs: params.durationMs,\n startedAt: params.startedAt,\n });\n return null;\n }\n\n /**\n * Track an error event\n *\n * @param params - Error parameters\n * @returns `null` — the OTLP transport emits a span and does not return a synchronous event\n */\n async trackError(params: TrackErrorParams): Promise<Event | null> {\n // An error does not mutate the client state — both snapshots are the current state (main's\n // semantics restored on the OTLP path).\n const stateSnapshot = { ...this.currentState };\n await otlp.emitEvent(this.otlpDeps(), params.sessionId, {\n eventType: 'error',\n toolName: params.toolName,\n error: params.errorMessage,\n // Structured error details — persisted onto events.toolError.type/.stack_trace by the\n // ingest route.\n errorType: params.errorType,\n stackTrace: params.stackTrace,\n metadata: params.metadata,\n stateBefore: stateSnapshot,\n stateAfter: { ...this.currentState },\n stateRole: 'context',\n durationMs: params.durationMs,\n startedAt: params.startedAt,\n });\n return null;\n }\n\n /**\n * Update the current state\n *\n * @param key - State key\n * @param value - State value\n */\n updateState(key: string, value: unknown): void {\n this.currentState[key] = value;\n }\n\n /**\n * Set the user input for a session\n *\n * Re-posts the session's root span (start-only, no end time) with the updated\n * `gen_ai.input.messages` so the server refreshes `sessions.userInput` WITHOUT finalizing the\n * session — it stays running. Always resolves `null` under the OTLP transport (no synchronous session row).\n *\n * @param sessionId - Session ID\n * @param input - User input text\n * @returns `null` — the OTLP transport does not return a synchronous session row\n */\n async setInput(sessionId: string, input: string): Promise<Session | null> {\n await otlp.updateInput(this.otlpDeps(), sessionId, input);\n return null;\n }\n\n /**\n * Merge additional metadata into a running session (post-begin).\n *\n * No network call: the merged metadata is carried on the finalize re-post, where the server\n * merges it into the session's metadata (user keys top-level, later values win on a key\n * collision; the reserved `otel` diagnostics key is preserved server-side). String values are\n * redacted like begin()'s metadata.\n *\n * @param sessionId - Session ID\n * @param metadata - Metadata keys to merge into the session metadata\n */\n setSessionMetadata(sessionId: string, metadata: Record<string, unknown>): void {\n otlp.updateMetadata(this.otlpDeps(), sessionId, metadata);\n }\n\n /**\n * Set/override the session-level model for a running session (post-begin).\n *\n * No network call: carried on the finalize re-post as gen_ai.request.model and under the\n * stable `model` key in the session metadata (the sessions table has no model column).\n *\n * @param sessionId - Session ID\n * @param model - Model identifier (e.g. 'claude-sonnet-4-5')\n */\n setSessionModel(sessionId: string, model: string): void {\n otlp.updateModel(this.otlpDeps(), sessionId, model);\n }\n\n /**\n * Record an optional agent assertion, including claimed recovery. No model call,\n * automatic error status, state snapshot, or verified failure label is created.\n * Reuse reportId within this session for later observations; each is retained.\n */\n async reportDiagnostic(params: {\n sessionId: string;\n report: AgentDiagnosticInput;\n }): Promise<null> {\n const report = createAgentDiagnostic(params.report);\n await otlp.emitEvent(this.otlpDeps(), params.sessionId, {\n eventType: AGENT_DIAGNOSTIC_EVENT,\n metadata: { agentDiagnostic: report },\n });\n return null;\n }\n\n /** Track a generic event. */\n async trackEvent(params: {\n sessionId: string;\n eventType: string;\n eventData?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n }): Promise<Event | null> {\n // Lift the eventData keys that map to dedicated event COLUMNS out of the generic bag: on main\n // trackEvent Object.assign'd eventData onto the /api/sdk/events body, so these landed on their\n // columns (cost/tokens/toolName/tool I/O/reasoning/…) — NOT buried in metadata. Unrecognized keys\n // stay in metadata. Guarded reads (a hostile getter must not escape trackEvent).\n const data: Record<string, unknown> = { ...(params.eventData ?? {}) };\n const pick = (k: string): unknown => {\n if (!(k in data)) return undefined;\n try {\n const v = data[k];\n delete data[k];\n return v;\n } catch {\n return undefined;\n }\n };\n const asNum = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined);\n const asStr = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);\n const toolName = asStr(pick('toolName'));\n const toolInput = pick('toolInput');\n const toolOutput = pick('toolOutput');\n const toolErrorObj = ((): Record<string, unknown> | undefined => {\n const te = pick('toolError');\n if (te && typeof te === 'object') return te as Record<string, unknown>;\n // A SCALAR toolError (string/number — main stored it verbatim) becomes {message: <scalar>} so\n // it isn't silently discarded and still marks the event errored.\n return te != null ? { message: String(te) } : undefined;\n })();\n const cost = asNum(pick('estimatedCost'));\n const totalTokens = asNum(pick('tokenCount'));\n const promptTokens = asNum(pick('promptTokens'));\n const completionTokens = asNum(pick('completionTokens'));\n const model = asStr(pick('model'));\n const reasoning = asStr(pick('reasoning'));\n const confidence = asNum(pick('confidence'));\n const alternativesConsidered = ((): string[] | undefined => {\n const a = pick('alternativesConsidered');\n return Array.isArray(a) ? a.filter((x): x is string => typeof x === 'string') : undefined;\n })();\n const finishReason = asStr(pick('finishReason'));\n // toolError → the spec exception fields + the extra-keys blob (same split as trackToolCall).\n const errAt = (k: string): unknown => {\n try {\n return toolErrorObj?.[k];\n } catch {\n return undefined;\n }\n };\n const errorDetails = ((): Record<string, unknown> | undefined => {\n if (!toolErrorObj) return undefined;\n const known = new Set(['message', 'type', 'name', 'stack_trace', 'stackTrace']);\n const out: Record<string, unknown> = {};\n try {\n for (const k of Object.keys(toolErrorObj)) {\n if (!known.has(k)) {\n const v = errAt(k);\n if (v !== undefined) out[k] = v;\n }\n }\n } catch {\n /* hostile object → whatever we gathered */\n }\n return Object.keys(out).length ? out : undefined;\n })();\n\n // stateBefore/stateAfter passed through the generic eventData bag OVERRIDE the currentState\n // snapshot and land on the dedicated events.stateBefore/stateAfter columns — main did\n // Object.assign(payload, eventData) and those are recognized columns.\n const asObj = (v: unknown): Record<string, unknown> | undefined =>\n v && typeof v === 'object' && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;\n const stateBeforeOverride = asObj(pick('stateBefore'));\n const stateAfterOverride = asObj(pick('stateAfter'));\n\n // Remaining unrecognized eventData keys stay on events.metadata; the explicit metadata arg is\n // spread FLAT (main persisted events.metadata = params.metadata verbatim, so a consumer reads\n // events.metadata->>'key'), its keys winning over any leftover eventData key of the same name.\n const metadata: Record<string, unknown> = { ...data };\n if (params.metadata) Object.assign(metadata, params.metadata);\n // Generic events do not mutate the client state — the snapshots default to the current state\n // (main's semantics restored on the OTLP path) unless the caller overrode them via eventData.\n const stateSnapshot = { ...this.currentState };\n await otlp.emitEvent(this.otlpDeps(), params.sessionId, {\n eventType: params.eventType,\n toolName,\n input: toolInput,\n output: toolOutput,\n model,\n promptTokens,\n completionTokens,\n totalTokens,\n cost,\n reasoning,\n confidence,\n alternativesConsidered,\n finishReasons: finishReason ? [finishReason] : undefined,\n // A toolError object present means the event FAILED — mark the span errored even when it has no\n // string message (e.g. {code:429}), so the server reconstructs events.toolError (incl. the extra\n // keys) instead of dropping the whole payload. Mirrors trackToolCall + the Python track_event.\n error: toolErrorObj ? (asStr(errAt('message')) ?? 'Tool call reported an error') : undefined,\n errorType: toolErrorObj ? asStr(errAt('type') ?? errAt('name')) : undefined,\n stackTrace: toolErrorObj ? asStr(errAt('stack_trace') ?? errAt('stackTrace')) : undefined,\n toolErrorDetails: errorDetails,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n stateBefore: stateBeforeOverride ?? stateSnapshot,\n stateAfter: stateAfterOverride ?? { ...this.currentState },\n stateRole:\n stateAfterOverride === undefined && stateBeforeOverride === undefined\n ? 'context'\n : undefined,\n });\n return null;\n }\n\n /**\n * Complete a session with performance metrics\n *\n * This is the recommended way to close sessions for performance monitoring.\n *\n * @param params - Session completion parameters\n * @returns `null` — the OTLP transport re-posts the final root span and the server finalizes\n * the session asynchronously, so no session row is returned synchronously.\n *\n * @example\n * ```ts\n * await client.completeSession({\n * sessionId,\n * success: true,\n * estimatedCost: 0.023,\n * promptTokens: 1500,\n * completionTokens: 500,\n * totalTokens: 2000,\n * userInput: \"What's the weather in San Francisco?\",\n * assistantOutput: \"The weather in San Francisco is 65°F and sunny.\",\n * customMetrics: {\n * customer_satisfaction: 4.5,\n * order_value: 129.99,\n * },\n * });\n * ```\n */\n async completeSession(params: CompleteSessionParams): Promise<Session | null> {\n if (this.contentUploader) await this.contentUploader.drain(params.sessionId);\n // Flush queued events before completing the session so they arrive first\n if (this.batcher) {\n await this.batcher.flush();\n }\n\n // Auto-fill cost/tokens from accumulated event data if not explicitly provided\n const acc = this.sessionAccumulators.get(params.sessionId);\n if (acc) {\n if (params.estimatedCost === undefined && acc.cost > 0)\n params = { ...params, estimatedCost: acc.cost };\n if (params.promptTokens === undefined && acc.promptTokens > 0)\n params = { ...params, promptTokens: acc.promptTokens };\n if (params.completionTokens === undefined && acc.completionTokens > 0)\n params = { ...params, completionTokens: acc.completionTokens };\n if (params.totalTokens === undefined) {\n // Derive a CONSISTENT total. The accumulator's total comes from per-tool tokenCounts,\n // which is a DIFFERENT axis from an explicit finish({promptTokens, completionTokens}).\n // Filling total from the accumulator alone can produce total < prompt+completion (e.g.\n // one 7-token tool call + finish(prompt:12, completion:20) → total:7), corrupting the\n // session rollup. Take the max so total is never below the explicit prompt+completion\n // sum, while still honoring accumulated tool tokens when they exceed it.\n const explicitSum = (params.promptTokens ?? 0) + (params.completionTokens ?? 0);\n const derivedTotal = Math.max(acc.totalTokens, explicitSum);\n if (derivedTotal > 0) params = { ...params, totalTokens: derivedTotal };\n }\n }\n\n await otlp.finalize(this.otlpDeps(), params.sessionId, {\n success: params.success ?? true,\n output: params.assistantOutput ?? params.output,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n totalTokens: params.totalTokens,\n cost: params.estimatedCost,\n failureReason: params.failureReason,\n customMetrics: params.customMetrics,\n model: params.model,\n // Completion-time userInput becomes the structured gen_ai.input.messages on the final root\n // span; durationMs derives the span end time.\n userInput: params.userInput,\n durationMs: params.durationMs,\n });\n // Drop the accumulator ONLY after finalize settles — if finalize throws (failSilently:false),\n // leaving it lets a caller's retry of finish() re-derive the accumulated tool-call totals\n // instead of losing them.\n this.sessionAccumulators.delete(params.sessionId);\n return null;\n }\n\n /**\n * Flush any queued events immediately.\n *\n * Waits for pending content uploads before draining queued telemetry.\n */\n async flush(): Promise<void> {\n if (this.contentUploader) await this.contentUploader.drain();\n if (this.batcher) {\n await this.batcher.flush();\n }\n }\n\n /** Queue delivery health; null when batching is disabled. Contains no payloads. */\n getDeliveryDiagnostics() {\n return this.batcher?.diagnostics ?? null;\n }\n\n /** Content upload counts only; submitted bytes may still await server verification. */\n getContentUploadDiagnostics() {\n return this.contentUploader?.diagnostics() ?? null;\n }\n\n /** Bounded process-local OTLP request/response diagnostics, independent of batching. */\n getTransportDiagnostics() {\n return this.transportDiagnostics.snapshot();\n }\n\n /** Record delegation before dispatch, then pass the returned carrier to the worker. */\n async handoff(identity: ActionIdentity & { sessionId: string }): Promise<string> {\n if (!this.otlpSessions.has(identity.sessionId))\n throw new Error('Handoff requires an open session on this client');\n const carrier = createHandoffContext({ ...identity, parentSessionId: identity.sessionId });\n await otlp.emitEvent(this.otlpDeps(), identity.sessionId, {\n eventType: 'agent_handoff',\n metadata: { ...actionMetadata(identity), signalHandoff: JSON.parse(carrier) },\n });\n return carrier;\n }\n\n /** Emit before queueing work. This records intent, not queue delivery acknowledgment. */\n async dispatchHandoff(\n identity: ActionIdentity & { sessionId: string; dispatchId: string }\n ): Promise<string> {\n const carrier = createDispatchContext({ ...identity, parentSessionId: identity.sessionId });\n await this.recordHandoff(identity.sessionId, carrier, 'dispatched');\n return carrier;\n }\n\n async acceptHandoff(sessionId: string, carrier: string): Promise<void> {\n await this.recordHandoff(sessionId, carrier, 'accepted', sessionId);\n }\n\n /** Call before finishing the worker session. Outcome describes execution, not business success. */\n async finishHandoff(sessionId: string, carrier: string, outcome: HandoffOutcome): Promise<void> {\n await this.recordHandoff(sessionId, carrier, 'finished', sessionId, outcome);\n }\n\n /** Call in the parent only after receiving this worker's terminal result. */\n async receiveHandoff(\n sessionId: string,\n carrier: string,\n workerSessionId: string,\n outcome: HandoffOutcome\n ): Promise<void> {\n await this.recordHandoff(sessionId, carrier, 'received', workerSessionId, outcome);\n }\n\n private async recordHandoff(\n sessionId: string,\n carrier: string,\n phase: Parameters<typeof handoffRecord>[1],\n workerSessionId?: string,\n outcome?: HandoffOutcome\n ) {\n const record = handoffRecord(carrier, phase, workerSessionId, outcome);\n const session = this.otlpSessions.get(sessionId);\n if (!session) throw new Error('Handoff requires an open session on this client');\n if (phase === 'dispatched' || phase === 'received') {\n if (sessionId !== record.parentSessionId) throw new Error('Handoff parent mismatch');\n } else {\n const context = session.metadata?.signalHandoffContext as Record<string, unknown> | undefined;\n const expected = JSON.parse(carrier) as Record<string, unknown>;\n if (\n session.parentSessionId !== record.parentSessionId ||\n !context ||\n Object.keys(context).length !== Object.keys(expected).length ||\n Object.entries(expected).some(([key, value]) => context[key] !== value)\n )\n throw new Error('Worker must begin with this handoff context');\n }\n await otlp.emitEvent(this.otlpDeps(), sessionId, {\n eventType: `agent_handoff_${phase}`,\n metadata: { ...actionMetadata(record), signalHandoffRecord: record },\n });\n }\n\n /**\n * Shut down the event batcher, flushing remaining events.\n *\n * Call this before your process exits for a clean shutdown.\n * Cancels pending content uploads, then drains their fallback events and queued telemetry.\n */\n async shutdown(): Promise<void> {\n if (this.contentUploader) await this.contentUploader.shutdown();\n if (this.batcher) {\n await this.batcher.shutdown();\n }\n }\n\n /**\n * Begin tracking an interaction (simplified API)\n *\n * @param params - Interaction parameters\n * @returns Interaction object with finish() method\n *\n * @example\n * ```ts\n * const interaction = await client.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * convoId: 'convo_456',\n * });\n *\n * // ... do your agent work ...\n *\n * await interaction.finish({ output: responseText });\n * ```\n */\n async begin(params: BeginParams): Promise<Interaction> {\n const eventId = params.eventId ?? randomId();\n\n // Build metadata with optional fields. input and convoId are NOT folded in here: they are\n // emitted as their own first-class attributes (gen_ai.input.messages / gen_ai.conversation.id),\n // so copying them into the metadata bag would be the same value feeding two attributes. Only\n // event_id — which has no dedicated attribute — lives in metadata.\n // Guarded spread (the JS twin of the Python begin()'s safe_dict_copy): a hostile metadata\n // object (Proxy ownKeys/get traps, throwing getters) degrades to the cross-SDK\n // {_unserializable} marker so the session is STILL created — instead of the containment seam\n // dropping the whole begin() to a degraded Interaction.\n let fullMetadata: Record<string, unknown>;\n try {\n fullMetadata = params.metadata ? { ...params.metadata } : {};\n } catch {\n fullMetadata = { _unserializable: true };\n }\n fullMetadata.event_id = eventId;\n\n // Create session first (clears old state). An explicit params.name overrides the\n // auto-generated `{event}:{eventId[:8]}` session title.\n const sessionId = await this.createSession({\n name: params.name ?? `${params.event}:${eventId.slice(0, 8)}`,\n agentName: params.event,\n userId: params.userId,\n convoId: params.convoId,\n metadata: fullMetadata,\n parentSessionId: params.parentSessionId,\n input: params.input,\n model: params.model,\n });\n\n // Store input in current_state AFTER createSession clears old state\n if (params.input) {\n this.currentState.input = params.input;\n }\n\n // Auto-wire session context so wrapped LLM calls flow into this session\n let sessionTokens: SessionContextTokens | undefined;\n if (sessionId) {\n sessionTokens = _setSessionContextWithTokens(sessionId, this);\n }\n\n return new Interaction({\n client: this,\n sessionId,\n eventId,\n userId: params.userId,\n event: params.event,\n userInput: params.input,\n sessionTokens,\n });\n }\n\n // Cost calculation static methods for convenience\n static calculateOpenAICost = calculateOpenAICost;\n static calculateAnthropicCost = calculateAnthropicCost;\n static calculateGoogleCost = calculateGoogleCost;\n}\n\n// ============================================================================\n// Interaction Class\n// ============================================================================\n\ninterface InteractionConfig {\n client: SentrialClient;\n sessionId: string | null;\n eventId: string;\n userId: string;\n event: string;\n userInput?: string;\n sessionTokens?: SessionContextTokens;\n}\n\n/**\n * Represents an in-progress interaction that can be finished.\n *\n * Created by SentrialClient.begin() - provides a clean begin/finish API pattern.\n *\n * @remarks\n * If session creation failed (sessionId is null), all tracking methods\n * become no-ops to ensure your application continues running.\n */\nexport class Interaction {\n private readonly client: SentrialClient;\n private readonly sessionId: string | null;\n /** Event ID for this interaction */\n public readonly eventId: string;\n /** User ID for this interaction */\n public readonly userId: string;\n /** Event name for this interaction */\n public readonly event: string;\n private finished = false;\n private success = true;\n private failureReason?: string;\n private output?: string;\n private readonly userInput?: string;\n private readonly degraded: boolean;\n\n /** Context tokens for restoring previous session context on finish() */\n private sessionTokens?: SessionContextTokens;\n\n constructor(config: InteractionConfig) {\n this.client = config.client;\n this.sessionId = config.sessionId;\n this.eventId = config.eventId;\n this.userId = config.userId;\n this.event = config.event;\n this.userInput = config.userInput;\n this.degraded = config.sessionId === null;\n this.sessionTokens = config.sessionTokens;\n }\n\n /**\n * Set the output for this interaction\n *\n * This will be used when finish() is called.\n * Useful when you want to set the output but call finish() later.\n *\n * @param output - The output/response text\n */\n setOutput(output: string): void {\n this.output = output;\n }\n\n /**\n * Merge additional metadata into this interaction's session (post-begin).\n *\n * Carried on the finish() re-post: the server merges the keys into the session's metadata\n * top-level (later values win; the reserved `otel` diagnostics key is preserved).\n *\n * @param metadata - Metadata keys to merge into the session metadata\n */\n setMetadata(metadata: Record<string, unknown>): void {\n if (this.degraded) return;\n this.client.setSessionMetadata(this.sessionId!, metadata);\n }\n\n /**\n * Set/override the session-level model for this interaction (post-begin).\n *\n * Carried on the finish() re-post as gen_ai.request.model and under the stable `model` key in\n * the session metadata.\n *\n * @param model - Model identifier (e.g. 'claude-sonnet-4-5')\n */\n setModel(model: string): void {\n if (this.degraded) return;\n this.client.setSessionModel(this.sessionId!, model);\n }\n\n /**\n * Finish the interaction and record metrics\n *\n * @param params - Finish parameters\n * @returns `null` — the OTLP transport finalizes asynchronously server-side (also `null`\n * if degraded/already finished).\n *\n * @example\n * ```ts\n * await interaction.finish({\n * output: \"Here's the answer to your question...\",\n * success: true,\n * customMetrics: { satisfaction: 4.5 },\n * });\n * ```\n */\n async finish(params: FinishParams = {}): Promise<Session | null> {\n if (this.finished) return null;\n if (this.degraded) {\n this.finished = true;\n return null;\n }\n\n this.finished = true;\n\n // Use stored output if not provided\n const finalOutput = params.output ?? this.output;\n\n const result = await this.client.completeSession({\n sessionId: this.sessionId!,\n success: params.success ?? this.success,\n failureReason: params.failureReason ?? this.failureReason,\n estimatedCost: params.estimatedCost,\n customMetrics: params.customMetrics,\n // The transport owns the root start (before begin HTTP). Its default finish\n // timestamp includes that request and the final child; preserve explicit overrides.\n durationMs: params.durationMs,\n promptTokens: params.promptTokens,\n completionTokens: params.completionTokens,\n totalTokens: params.totalTokens,\n userInput: this.userInput,\n assistantOutput: finalOutput,\n });\n\n // Restore previous session context after completing (supports nesting)\n if (this.sessionTokens) {\n _restoreSessionContext(this.sessionTokens);\n this.sessionTokens = undefined;\n }\n\n return result;\n }\n\n /**\n * Track a tool call within this interaction\n */\n async trackToolCall(params: Omit<TrackToolCallParams, 'sessionId'>): Promise<Event | null> {\n if (this.degraded) return null;\n\n return this.client.trackToolCall({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /** Report an agent observation without changing this interaction's success or failure. */\n async reportDiagnostic(report: AgentDiagnosticInput): Promise<null> {\n if (this.degraded || this.finished) return null;\n return this.client.reportDiagnostic({ sessionId: this.sessionId!, report });\n }\n\n /**\n * Track an LLM decision within this interaction\n */\n async trackDecision(params: Omit<TrackDecisionParams, 'sessionId'>): Promise<Event | null> {\n if (this.degraded) return null;\n\n return this.client.trackDecision({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /**\n * Track an error within this interaction\n */\n async trackError(params: Omit<TrackErrorParams, 'sessionId'>): Promise<Event | null> {\n // Store failure info\n this.success = false;\n this.failureReason = params.errorMessage;\n\n if (this.degraded) return null;\n\n return this.client.trackError({\n ...params,\n sessionId: this.sessionId!,\n });\n }\n\n /**\n * Get the session ID (null if session creation failed)\n */\n getSessionId(): string | null {\n return this.sessionId;\n }\n\n /**\n * Check if the interaction is in a degraded state (session creation failed)\n */\n isDegraded(): boolean {\n return this.degraded;\n }\n}\n\n// ============================================================================\n// Containment seam application (see contained.ts and the class-level @remarks)\n// ============================================================================\n//\n// Every public tracking entrypoint is wrapped here, at module load — the structural\n// equivalent of the Python SDK's `@contained(...)` decorations on client.py. Degraded\n// returns mirror each method's EXISTING failure path (createSession's null sessionId,\n// track*'s null event, begin's degraded Interaction, void for the sync setters).\n// Method inventory MUST stay in sync with the Python surface (client.py):\n// create_session/get_variant/track_tool_call/track_decision/track_error/update_state/\n// set_session_metadata/set_session_model/set_input/track_event/complete_session/begin +\n// Interaction.set_output/set_metadata/set_model/finish/track_tool_call/track_decision/\n// track_error. (apiRequest/flush/shutdown are deliberately NOT wrapped — same as Python's\n// undecorated _safe_request/flush/shutdown; safeRequest has its own failSilently handling.)\n\n/** Degraded-value factories — MUST be exception-free (guarded reads only). */\nconst degradedNull = (): null => null;\n\nconst degradedVariant = (_self: unknown, args: unknown[]): VariantAssignment => {\n // getVariant's existing degraded response (the request-failed fallback shape). The\n // defaultVariant read is guarded — a hostile params object must not raise on the\n // seam's catch-path.\n let dv: unknown;\n try {\n dv = (args[0] as Record<string, unknown> | null | undefined)?.defaultVariant;\n } catch {\n dv = undefined;\n }\n return { active: false, reason: 'not_found', variant: typeof dv === 'string' ? dv : 'control' };\n};\n\nconst degradedBegin = (self: unknown, args: unknown[]): Interaction => {\n // begin()'s existing degraded shape: an Interaction with sessionId null, on which every\n // tracking method is a documented no-op (the `degraded` flag). Mirrors the Python\n // `_degraded_begin`. Each params read is guarded (hostile getters must not raise here).\n const p = args[0] as Record<string, unknown> | null | undefined;\n const str = (key: string): string => {\n try {\n const v = p ? p[key] : undefined;\n return typeof v === 'string' ? v : '';\n } catch {\n return '';\n }\n };\n return new Interaction({\n client: self as SentrialClient,\n sessionId: null,\n eventId: str('eventId') || randomId(),\n userId: str('userId'),\n event: str('event'),\n userInput: str('input') || undefined,\n });\n};\n\ncontainMethod(SentrialClient.prototype, 'createSession', degradedNull);\ncontainMethod(SentrialClient.prototype, 'getVariant', degradedVariant);\ncontainMethod(SentrialClient.prototype, 'recordExperimentExposure', degradedNull);\ncontainMethod(SentrialClient.prototype, 'trackToolCall', degradedNull);\ncontainMethod(SentrialClient.prototype, 'trackDecision', degradedNull);\ncontainMethod(SentrialClient.prototype, 'trackError', degradedNull);\ncontainMethod(SentrialClient.prototype, 'trackEvent', degradedNull);\ncontainMethod(SentrialClient.prototype, 'reportDiagnostic', degradedNull);\ncontainMethod(SentrialClient.prototype, 'updateState'); // sync, void\ncontainMethod(SentrialClient.prototype, 'setInput', degradedNull);\ncontainMethod(SentrialClient.prototype, 'setSessionMetadata'); // sync, void\ncontainMethod(SentrialClient.prototype, 'setSessionModel'); // sync, void\ncontainMethod(SentrialClient.prototype, 'completeSession', degradedNull);\ncontainMethod(SentrialClient.prototype, 'begin', degradedBegin);\n\ncontainMethod(Interaction.prototype, 'setOutput'); // sync, void\ncontainMethod(Interaction.prototype, 'setMetadata'); // sync, void\ncontainMethod(Interaction.prototype, 'setModel'); // sync, void\ncontainMethod(Interaction.prototype, 'finish', degradedNull);\ncontainMethod(Interaction.prototype, 'trackToolCall', degradedNull);\ncontainMethod(Interaction.prototype, 'trackDecision', degradedNull);\ncontainMethod(Interaction.prototype, 'trackError', degradedNull);\ncontainMethod(Interaction.prototype, 'reportDiagnostic', degradedNull);\n\n// ============================================================================\n// Module-level Simple API\n// ============================================================================\n\nlet defaultClient: SentrialClient | null = null;\n\nfunction getClient(): SentrialClient {\n if (!defaultClient) {\n defaultClient = new SentrialClient();\n }\n return defaultClient;\n}\n\n/**\n * Configure the default Sentrial client\n *\n * @param config - Client configuration\n *\n * @example\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({\n * apiKey: 'sentrial_live_xxx',\n * apiUrl: 'https://api.sentrial.com',\n * });\n * ```\n */\nexport function configure(config: SentrialClientConfig): void {\n defaultClient = new SentrialClient(config);\n // Sync to wrappers so wrapOpenAI/wrapAnthropic/wrapGoogle use the same client\n setDefaultClient(defaultClient);\n}\n\n/**\n * Begin tracking an interaction (module-level convenience function)\n *\n * This is a shorthand for new SentrialClient().begin(...).\n * Configure the client first with sentrial.configure() or set SENTRIAL_API_KEY env var.\n *\n * @param params - Interaction parameters\n * @returns Interaction object with finish() method\n *\n * @example\n * ```ts\n * import { sentrial } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: 'sentrial_live_xxx' });\n *\n * const interaction = await sentrial.begin({\n * userId: 'user_123',\n * event: 'chat_message',\n * input: message,\n * });\n *\n * // ... do your agent work ...\n *\n * await interaction.finish({ output: responseText });\n * ```\n */\nexport function begin(params: BeginParams): Promise<Interaction> {\n return getClient().begin(params);\n}\n\nexport function getVariant(params: GetVariantParams): Promise<VariantAssignment> {\n return getClient().getVariant(params);\n}\n\nexport function recordExperimentExposure(\n params: RecordExperimentExposureParams\n): Promise<ExperimentExposureReceipt | null> {\n return getClient().recordExperimentExposure(params);\n}\n\n/**\n * Simple API namespace for module-level usage\n */\nasync function flush(): Promise<void> {\n if (defaultClient) await defaultClient.flush();\n}\n\nasync function shutdown(): Promise<void> {\n if (defaultClient) await defaultClient.shutdown();\n}\n\nexport const sentrial = {\n configure,\n begin,\n getVariant,\n recordExperimentExposure,\n flush,\n shutdown,\n};\n","/**\n * Type definitions for the Sentrial TypeScript SDK\n */\n\nimport type { PiiConfig } from './redact.js';\nimport type { BatcherConfig } from './batcher.js';\nimport type { LargeToolContentConfig } from './large-tool-content.js';\n\n// ============================================================================\n// Enums\n// ============================================================================\n\n/**\n * Event types that can be tracked in a session\n */\nexport enum EventType {\n TOOL_CALL = 'tool_call',\n LLM_DECISION = 'llm_decision',\n STATE_CHANGE = 'state_change',\n ERROR = 'error',\n}\n\n/**\n * Session status values\n */\nexport type SessionStatus = 'running' | 'completed' | 'failed' | 'cancelled';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Configuration options for SentrialClient\n */\nexport interface SentrialClientConfig {\n /** API key for authentication (defaults to SENTRIAL_API_KEY env var) */\n apiKey?: string;\n /** URL of the Sentrial API server (defaults to SENTRIAL_API_URL env var or production) */\n apiUrl?: string;\n /**\n * If true (default), SDK errors are logged but won't crash your app.\n * Set to false during development to see full errors.\n */\n failSilently?: boolean;\n /**\n * PII redaction configuration.\n * - Pass a PiiConfig object for full control.\n * - Pass `true` to fetch the org's PII config from the server automatically.\n * NOTE: the server-config auto-fetch requires an API key with the elevated\n * `pii-config:read` scope, which product-issued keys do NOT carry by default.\n * Without that scope the fetch returns 403 and the SDK falls back to the\n * built-in default (label-mode) redaction rather than the org's configured\n * policy. Pass an explicit PiiConfig object if you need guaranteed redaction.\n */\n pii?: PiiConfig | boolean;\n /**\n * Event batching configuration.\n * When enabled, fire-and-forget tracking calls (trackToolCall, trackDecision,\n * trackError, trackEvent) are queued and flushed periodically instead of\n * being sent immediately. Session lifecycle calls (createSession,\n * completeSession) always bypass the batcher.\n */\n batching?: BatcherConfig;\n /** Opt-in bounded uploads of already-redacted tool input/output over 64 KiB.\n * Requires an enabled server/workspace. Failures retain an explicit loss marker. */\n largeToolContent?: LargeToolContentConfig;\n}\n\n// ============================================================================\n// Session Types\n// ============================================================================\n\n/**\n * Parameters for creating a new session\n */\nexport interface CreateSessionParams {\n /** Name of the session */\n name: string;\n /** Identifier for the agent type (used for grouping) */\n agentName: string;\n /** External user ID (for grouping sessions by end-user) */\n userId: string;\n /** Optional parent session ID for multi-agent hierarchies */\n parentSessionId?: string;\n /** Optional conversation ID to group related sessions into a thread */\n convoId?: string;\n /** Optional metadata */\n metadata?: Record<string, unknown>;\n /** Production experiment assignment ids to link to this session */\n experimentAssignmentIds?: string[];\n /**\n * Optional input captured at session creation. Emitted on the root span as the structured\n * gen_ai.input.messages (this SDK is OTLP-only).\n */\n input?: string;\n /** The (primary) requested model for the run, when known at creation (→ gen_ai.request.model). */\n model?: string;\n}\n\n/**\n * Parameters for completing a session\n */\nexport interface CompleteSessionParams {\n /** Session ID */\n sessionId: string;\n /** Whether the session successfully completed its goal (default: true) */\n success?: boolean;\n /** If success=false, why did it fail? */\n failureReason?: string;\n /** Total estimated cost in USD for this session */\n estimatedCost?: number;\n /** Custom metrics (e.g., { customer_satisfaction: 4.5, steps_taken: 3 }) */\n customMetrics?: Record<string, number>;\n /** Duration in milliseconds (auto-calculated if not provided) */\n durationMs?: number;\n /** Number of prompt/input tokens used */\n promptTokens?: number;\n /** Number of completion/output tokens used */\n completionTokens?: number;\n /** Total tokens used (prompt + completion) */\n totalTokens?: number;\n /** The user's original query/input for this session */\n userInput?: string;\n /** The final assistant response for this session */\n assistantOutput?: string;\n /** Alias for assistantOutput - the final output text */\n output?: string;\n /** The resolved model for the run, when only known at completion (overrides createSession's). */\n model?: string;\n}\n\n/**\n * Session data returned from the API\n */\nexport interface Session {\n id: string;\n agentId?: string;\n parentSessionId?: string;\n convoId?: string;\n name: string;\n agentName: string;\n userId: string;\n status: SessionStatus;\n totalEvents: number;\n\n // User interaction tracking\n userInput?: string;\n assistantOutput?: string;\n\n // Performance tracking\n success?: boolean;\n failureReason?: string;\n estimatedCost?: number;\n customMetrics?: Record<string, unknown>;\n score?: number;\n metricScores?: Record<string, unknown>;\n metricReasonings?: Record<string, string>;\n\n // Token tracking\n promptTokens?: number;\n completionTokens?: number;\n totalTokens?: number;\n\n // Timestamps\n durationMs?: number;\n metadata?: Record<string, unknown>;\n // ISO strings from the API (response.json() never revives Date) (#487, #569).\n // BREAKING (v0.5.0): previously (incorrectly) typed Date; parse with new Date(...) where a Date\n // is needed. Shipped under a minor bump per the SDK compatibility policy (see README).\n createdAt: string;\n updatedAt: string;\n completedAt?: string;\n}\n\n// ============================================================================\n// Event Types\n// ============================================================================\n\n/**\n * Parameters for tracking a tool call event\n */\nexport interface TrackToolCallParams {\n /** Session ID */\n sessionId: string;\n /** Name of the tool */\n toolName: string;\n /** Tool input data */\n toolInput: Record<string, unknown>;\n /** Tool output data */\n toolOutput: Record<string, unknown>;\n /** Optional reasoning for why this tool was called */\n reasoning?: string;\n /** Estimated cost in USD for this tool call */\n estimatedCost?: number;\n /**\n * Error details if the tool failed. Recognized keys persisted under the OTLP transport:\n * `message`, `type` (alias `name`), `stack_trace` (alias `stackTrace`) — other keys are\n * not persisted.\n */\n toolError?: Record<string, unknown>;\n /** Number of tokens used by this tool call (for LLM-based tools) */\n tokenCount?: number;\n /** Per-call prompt/completion token split (for LLM-based tools) — emitted as first-class fields. */\n promptTokens?: number;\n completionTokens?: number;\n /** The model used for this call (for LLM-based tools) — emitted as gen_ai.request.model. */\n model?: string;\n /** Why generation stopped for this call (e.g. 'stop', 'length', 'tool_calls'). */\n finishReason?: string;\n /** How long the tool call took (ms). Sets the event span's real duration (otherwise the event\n * is recorded as instantaneous). */\n durationMs?: number;\n /** When the tool call started (Date or ms epoch). Defaults to now − durationMs when only the\n * duration is known. */\n startedAt?: Date | number;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n traceId?: string;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for tracking an LLM decision event\n */\nexport interface TrackDecisionParams {\n /** Session ID */\n sessionId: string;\n /** Decision reasoning */\n reasoning: string;\n /** Alternative options considered */\n alternatives?: string[];\n /** Confidence score (0.0 to 1.0) */\n confidence?: number;\n /** Estimated cost in USD for this decision */\n estimatedCost?: number;\n /** Number of tokens used */\n tokenCount?: number;\n /** How long the decision took (ms). Sets the event span's real duration. */\n durationMs?: number;\n /** When the decision started (Date or ms epoch). */\n startedAt?: Date | number;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n traceId?: string;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Parameters for tracking an error event\n */\nexport interface TrackErrorParams {\n /** Session ID */\n sessionId: string;\n /** Error message */\n errorMessage: string;\n /** Type of error (e.g., \"ValueError\", \"APIError\") */\n errorType?: string;\n /** Name of the tool that caused the error (if applicable) */\n toolName?: string;\n /** Stack trace for debugging */\n stackTrace?: string;\n /** How long the failed operation ran before erroring (ms). Sets the event span's real duration. */\n durationMs?: number;\n /** When the failed operation started (Date or ms epoch). */\n startedAt?: Date | number;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n traceId?: string;\n /** Deprecated: accepted for backward compatibility but ignored — this SDK is OTLP-only. */\n spanId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Event data returned from the API\n */\nexport interface Event {\n id: string;\n sessionId: string;\n eventType: EventType;\n timestamp: string; // ISO string from the API, not a Date (#487)\n sequenceNumber: number;\n\n // Tool call data\n toolName?: string;\n toolInput?: Record<string, unknown>;\n toolOutput?: Record<string, unknown>;\n toolError?: Record<string, unknown>;\n\n // LLM decision data\n reasoning?: string;\n alternativesConsidered?: string[];\n confidence?: number;\n\n // State tracking (legacy transport only — not captured under the default OTLP transport)\n stateBefore?: Record<string, unknown>;\n stateAfter?: Record<string, unknown>;\n stateDiff?: Record<string, unknown>;\n\n // Performance tracking\n estimatedCost?: number;\n tokenCount?: number;\n\n // Metadata\n metadata?: Record<string, unknown>;\n traceId?: string;\n spanId?: string;\n}\n\n// ============================================================================\n// Raw V1 wire-response shapes\n// ============================================================================\n//\n// These describe the JSON exactly as the V1 REST API returns it (dates are ISO\n// strings — response.json() never revives Date — and every field can be null).\n// They are the single source of truth for what the API sends; the ergonomic\n// `Session`/`Event` types above are the client-facing views (non-null optionals).\n// The CLI consumes these directly (see cli.ts) rather than re-declaring them,\n// so the two can't drift (#489).\n\n/** A session object as returned by `GET /api/v1/sessions` (raw wire shape). */\nexport interface SessionResponse {\n id: string;\n name?: string | null;\n agentName?: string | null;\n agentSlug?: string | null;\n convoId?: string | null;\n userId?: string | null;\n status?: string | null;\n success?: boolean | null;\n failureReason?: string | null;\n totalEvents?: number | null;\n durationMs?: number | null;\n estimatedCost?: number | null;\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n score?: number | null;\n metricScores?: unknown;\n metricReasonings?: unknown;\n customMetrics?: unknown;\n userInput?: string | null;\n assistantOutput?: string | null;\n metadata?: unknown;\n detectedIssues?: unknown;\n createdAt?: string | null;\n completedAt?: string | null;\n}\n\n/** An event object as returned by `GET /api/v1/sessions/:id/events` (raw wire shape). */\nexport interface EventResponse {\n id: string;\n sessionId: string;\n eventType: string;\n sequenceNumber?: number | null;\n timestamp?: string | null;\n toolName?: string | null;\n toolInput?: unknown;\n toolOutput?: unknown;\n toolError?: unknown;\n reasoning?: string | null;\n alternativesConsidered?: unknown;\n confidence?: number | null;\n estimatedCost?: number | null;\n tokenCount?: number | null;\n metadata?: unknown;\n traceId?: string | null;\n spanId?: string | null;\n}\n\n// ============================================================================\n// Interaction Types (Simple API)\n// ============================================================================\n\n/**\n * Parameters for beginning an interaction\n */\nexport interface BeginParams {\n /** Parent session from a validated worker handoff. */\n parentSessionId?: string;\n /** External user ID for grouping sessions */\n userId: string;\n /** Event type/name (e.g., \"chat_message\", \"search_query\") */\n event: string;\n /** Optional input data for the interaction */\n input?: string;\n /** Optional custom event ID (auto-generated UUID if not provided) */\n eventId?: string;\n /** Optional conversation ID to group related interactions */\n convoId?: string;\n /** Optional additional metadata */\n metadata?: Record<string, unknown>;\n /**\n * Optional session title override. Defaults to the auto-generated `{event}:{eventId[:8]}` when\n * omitted. Redacted like the input before emission (→ sentrial.session.name).\n */\n name?: string;\n /** The (primary) requested model for the run, when known at begin (→ gen_ai.request.model). */\n model?: string;\n}\n\n/**\n * Parameters for finishing an interaction\n */\nexport interface FinishParams {\n /** Output/response from the interaction */\n output?: string;\n /** Whether the interaction succeeded (default: true) */\n success?: boolean;\n /** Reason for failure if success=false */\n failureReason?: string;\n /** Total estimated cost in USD */\n estimatedCost?: number;\n /** Custom metrics */\n customMetrics?: Record<string, number>;\n /** Number of prompt/input tokens used */\n promptTokens?: number;\n /** Number of completion/output tokens used */\n completionTokens?: number;\n /** Total tokens used */\n totalTokens?: number;\n /** Duration in milliseconds (auto-calculated from begin/finish if not provided) */\n durationMs?: number;\n}\n\n// ============================================================================\n// Production Experiment Types\n// ============================================================================\n\nexport interface ProductionVariantConfig {\n name: string;\n weight?: number;\n prompt?: string;\n systemPrompt?: string;\n model?: string;\n promptVersion?: string;\n toolConfig?: Record<string, unknown>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface GetVariantParams {\n /** Production experiment name configured in Sentrial */\n experimentName: string;\n /** Agent name this variant is for */\n agentName?: string;\n /** Required when the experiment assigns by user. Other IDs are not substitutes. */\n userId?: string;\n /** Required when the experiment assigns by conversation. */\n convoId?: string;\n /** Required when the experiment assigns by stable anonymous identity. */\n anonymousId?: string;\n /** Optional existing Sentrial session id */\n sessionId?: string;\n /** Variant to use when no active live experiment matches */\n defaultVariant?: string;\n /** Prompt version currently running in the customer app */\n promptVersion?: string;\n /** Model currently running in the customer app */\n model?: string;\n /** Extra assignment metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface VariantAssignment {\n active: boolean;\n reason: 'assigned' | 'holdout' | 'not_found' | 'missing_identity';\n /** Declared unit, or the required unit when reason is missing_identity. */\n assignmentUnit?: 'user' | 'convo' | 'anonymous';\n /** Historical fallback identities remain unknown; retries do not certify them. */\n identityProvenance?: 'declared' | 'legacy_unknown';\n experimentId?: string;\n assignmentId?: string;\n /** Frozen configuration digest; absent on legacy assignments. */\n configurationHash?: string;\n variant: string;\n variantConfig?: ProductionVariantConfig;\n prompt?: string;\n systemPrompt?: string;\n model?: string;\n promptVersion?: string;\n toolConfig?: Record<string, unknown>;\n}\n\n/** Explicit application report after using the returned configuration. */\nexport interface RecordExperimentExposureParams {\n assignmentId: string;\n sessionId: string;\n variant: string;\n configurationHash: string;\n}\n\nexport interface ExperimentExposureReceipt extends RecordExperimentExposureParams {\n /** Server arrival time, not provider invocation time. */\n recordedAt: string;\n source: 'sdk_reported';\n}\n\n// ============================================================================\n// Cost Calculation Types\n// ============================================================================\n\n/**\n * Parameters for cost calculation\n */\nexport interface CostParams {\n /** Model name (e.g., \"gpt-4\", \"claude-3-sonnet\") */\n model: string;\n /** Number of input tokens */\n inputTokens: number;\n /** Number of output tokens */\n outputTokens: number;\n}\n\n// ============================================================================\n// API Response Types\n// ============================================================================\n\n/**\n * Standard API response wrapper\n */\nexport interface ApiResponse<T> {\n data?: T;\n error?: {\n code: string;\n message: string;\n details?: Record<string, unknown>;\n };\n}\n","/**\n * Vercel AI SDK Integration for Sentrial\n *\n * Automatically traces AI SDK calls with full input/output logging, metrics, and tool execution.\n * Supports Vercel AI SDK v3 through v7.\n *\n * @example Simple Usage\n * ```ts\n * import { configureVercel, wrapAISDK } from '@sentrial/sdk';\n * import * as ai from 'ai';\n * import { openai } from '@ai-sdk/openai';\n *\n * configureVercel({\n * apiKey: process.env.SENTRIAL_API_KEY,\n * defaultAgent: 'my-ai-agent',\n * });\n *\n * const { generateText } = wrapAISDK(ai);\n *\n * // This automatically logs to Sentrial\n * const { text } = await generateText({\n * model: openai('gpt-4'),\n * prompt: 'What is the capital of France?',\n * });\n * ```\n *\n * @example Multi-Turn Conversations\n * ```ts\n * const { generateText } = wrapAISDK(ai, { convoId: 'convo_123' });\n *\n * // All calls are linked to the same conversation thread\n * const { text } = await generateText({\n * model: openai('gpt-4o'),\n * messages: conversationHistory,\n * });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { SentrialClient } from './client.js';\nimport { semconvKeys } from '@sentrial/otlp';\nimport { setDefaultClient, getDefaultClient } from './wrappers.js';\nimport { guessProvider, resolveTokensAndCost } from './cost.js';\nimport type { GetVariantParams, VariantAssignment } from './types.js';\n\n// ============================================================================\n// Types for AI SDK (we don't import to keep it as peer dep)\n// ============================================================================\n\n// Generic callable placeholder for the AI SDK surface we don't import (peer dep).\ntype AnyFunction = (...args: unknown[]) => unknown;\n\n// Accept provider-specific parameter types without importing a particular AI SDK version.\n// The public wrapper retains each original function, including its generic signatures.\ntype AISDKFunction = (...args: never[]) => unknown;\n\ntype AIModule = {\n generateText?: AISDKFunction;\n streamText?: AISDKFunction;\n generateObject?: AISDKFunction;\n streamObject?: AISDKFunction;\n experimental_generateText?: AISDKFunction;\n experimental_streamText?: AISDKFunction;\n experimental_generateObject?: AISDKFunction;\n experimental_streamObject?: AISDKFunction;\n};\n\ntype WrappedFunction<T, Stable extends PropertyKey, Experimental extends PropertyKey, Fallback> =\n T extends Record<Stable, infer F extends AISDKFunction>\n ? F\n : T extends Record<Experimental, infer F extends AISDKFunction>\n ? F\n : Fallback;\n\ntype WrappedAISDK<T extends AIModule> = {\n generateText: WrappedFunction<\n T,\n 'generateText',\n 'experimental_generateText',\n ReturnType<typeof wrapGenerateText>\n >;\n streamText: WrappedFunction<\n T,\n 'streamText',\n 'experimental_streamText',\n ReturnType<typeof wrapStreamText>\n >;\n generateObject: WrappedFunction<\n T,\n 'generateObject',\n 'experimental_generateObject',\n ReturnType<typeof wrapGenerateObject>\n >;\n streamObject: WrappedFunction<\n T,\n 'streamObject',\n 'experimental_streamObject',\n ReturnType<typeof wrapStreamObject>\n >;\n};\n\ntype WrapAISDKOptions = {\n client?: SentrialClient;\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n /** Session metadata, including explicit signalActionId / signalWorkflow when known.\n * Create a separate wrapper for each business action; no identity is inferred. */\n metadata?: Record<string, unknown>;\n};\n\ntype GenerateTextParams = {\n model: { modelId?: string; provider?: string } & Record<string, unknown>;\n prompt?: string;\n messages?: Array<{ role: string; content: string | unknown }>;\n system?: string;\n tools?: Record<string, { execute?: AnyFunction } & Record<string, unknown>>;\n maxTokens?: number;\n maxSteps?: number;\n temperature?: number;\n [key: string]: unknown;\n};\n\n/**\n * Usage info — supports both AI SDK v3/v4 (promptTokens/completionTokens)\n * and v5/v6 (inputTokens/outputTokens) field names.\n */\ntype UsageInfo = {\n // AI SDK v5/v6 naming\n inputTokens?: number;\n outputTokens?: number;\n // AI SDK v3/v4 naming (OpenAI-style)\n promptTokens?: number;\n completionTokens?: number;\n // Common\n totalTokens?: number;\n};\n\ntype StepResult = {\n text?: string;\n toolCalls?: Array<{ toolName: string; args: unknown }>;\n toolResults?: Array<{ toolName: string; result: unknown }>;\n usage?: UsageInfo;\n finishReason?: string;\n /** Per-step response metadata — v5+ modelId, v3/v4 model. */\n response?: { modelId?: string; model?: string };\n /** v6+: the model that produced this step (prepareStep can switch mid-call). */\n model?: { provider?: string; modelId?: string };\n};\n\ntype GenerateTextResult = {\n text: string;\n usage?: UsageInfo;\n finishReason?: string;\n toolCalls?: Array<{ toolName: string; args: unknown }>;\n toolResults?: Array<{ toolName: string; result: unknown }>;\n response?: { modelId?: string; id?: string };\n steps?: StepResult[];\n reasoning?: unknown;\n reasoningText?: string;\n sources?: unknown[];\n [key: string]: unknown;\n};\n\ntype StreamTextResult = {\n textStream: AsyncIterable<string>;\n fullStream?: AsyncIterable<unknown>;\n text?: string | Promise<string>;\n usage?: Promise<UsageInfo>;\n finishReason?: Promise<string>;\n toolCalls?: Promise<Array<{ toolName: string; args: unknown }>>;\n toolResults?: Promise<Array<{ toolName: string; result: unknown }>>;\n steps?: Promise<StepResult[]>;\n response?: Promise<{ modelId?: string; id?: string }>;\n [key: string]: unknown;\n};\n\n/** String or function that returns a string (for dynamic per-request values) */\ntype StringOrFn = string | (() => string | undefined);\n\nfunction resolveStringOrFn(value: StringOrFn | undefined): string | undefined;\nfunction resolveStringOrFn(value: StringOrFn | undefined, fallback: string): string;\nfunction resolveStringOrFn(value: StringOrFn | undefined, fallback?: string): string | undefined {\n if (typeof value === 'function') {\n // A throwing user callback (config.userId/convoId) must degrade to the fallback, not\n // fail the caller's generateText/streamText before the provider call runs.\n try {\n return value() ?? fallback;\n } catch {\n return fallback;\n }\n }\n return value ?? fallback;\n}\n\n/**\n * String() that never throws — errors/objects on the tracking path can carry hostile\n * toString/Symbol.toPrimitive; coercion must degrade to '[unserializable]', never throw\n * (the monitoring-never-crashes contract).\n */\nfunction safeString(value: unknown): string {\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/** JSON.stringify that never throws — cycles/hostile toJSON degrade via safeString. */\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? safeString(value);\n } catch {\n return safeString(value);\n }\n}\n\n/** Per-instance config passed through closures (not global) */\ntype WrapperConfig = {\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n metadata?: Record<string, unknown>;\n};\n\nfunction copyWrapperMetadata(value: Record<string, unknown> | undefined): Record<string, unknown> {\n try {\n return { ...value };\n } catch {\n return { _unserializable: true };\n }\n}\n\n// ============================================================================\n// Global Configuration (used only by configureVercel)\n// ============================================================================\n\nlet _defaultClient: SentrialClient | null = null;\nlet _globalConfig: WrapperConfig = {};\n\n/**\n * Configure the Sentrial SDK for Vercel AI SDK integration.\n *\n * @example\n * ```ts\n * import { configureVercel } from '@sentrial/sdk';\n *\n * configureVercel({\n * apiKey: process.env.SENTRIAL_API_KEY,\n * defaultAgent: 'my-chatbot',\n * userId: 'user_123',\n * });\n * ```\n */\nexport function configureVercel(config: {\n apiKey?: string;\n apiUrl?: string;\n defaultAgent?: string;\n userId?: StringOrFn;\n convoId?: StringOrFn;\n failSilently?: boolean;\n}): void {\n _defaultClient = new SentrialClient({\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n failSilently: config.failSilently ?? true,\n });\n // Sync to wrappers so all modules share the same client\n setDefaultClient(_defaultClient);\n _globalConfig = {\n defaultAgent: config.defaultAgent,\n userId: config.userId,\n convoId: config.convoId,\n };\n}\n\n/**\n * Resolve the client wrapAISDK should emit with. Precedence:\n * 1. the explicit client set by configureVercel()\n * 2. the shared default set by configure()/setDefaultClient() (so a top-level\n * configure({ apiKey }) reaches wrapAISDK)\n * 3. a fresh, unconfigured client as a last resort\n * @internal Exported for unit tests; not part of the public API.\n */\nexport function getClient(): SentrialClient {\n if (_defaultClient) return _defaultClient;\n const shared = getDefaultClient();\n if (shared) return shared;\n _defaultClient = new SentrialClient();\n return _defaultClient;\n}\n\n/**\n * Clear the configureVercel()-set client. Testing-only escape hatch so unit\n * tests can exercise the shared-default and fresh-client precedence branches\n * of {@link getClient} in isolation.\n * @internal\n */\nexport function __resetVercelClientForTests(): void {\n _defaultClient = null;\n}\n\n/**\n * Shared failure path for the non-streaming AI-SDK wrappers: record the error and complete\n * the session as failed. Awaited (the caller re-throws afterwards), so failure semantics live\n * in exactly one place across wrapGenerateText / wrapGenerateObject.\n */\nasync function failVercelSession(\n client: SentrialClient,\n sessionId: string,\n error: unknown,\n startTime: number\n): Promise<void> {\n const durationMs = Date.now() - startTime;\n const message = error instanceof Error ? error.message : 'Unknown error';\n\n // Isolate each POST (mirrors the streaming onError handlers): under failSilently:false a\n // trackError rejection must NOT short-circuit completeSession — that would leave the errored run\n // in 'running' (never finalized as failed) and mask the caller's original error with the\n // tracking error. The caller re-throws the original generation error afterwards regardless.\n await client\n .trackError({\n sessionId,\n errorType: error instanceof Error ? error.name : 'Error',\n errorMessage: message,\n })\n .catch(() => {});\n\n await client\n .completeSession({\n sessionId,\n success: false,\n failureReason: message,\n durationMs,\n })\n .catch(() => {});\n}\n\n// ============================================================================\n// Model & Provider Utilities\n// ============================================================================\n\nfunction extractModelInfo(model: GenerateTextParams['model'] | string): {\n modelId: string;\n provider: string;\n} {\n // Never-throw: runs BEFORE the provider call on a caller-supplied object — a hostile\n // property getter/non-string provider must degrade, never fail the user's call.\n try {\n // Gateway calling convention: the model can be a plain 'creator/model-name' STRING\n // (\"The AI SDK automatically uses the AI Gateway when you pass a model string\").\n if (typeof model === 'string') {\n return { modelId: model, provider: guessProvider(model) };\n }\n const modelId = model.modelId || ((model as Record<string, unknown>).id as string) || 'unknown';\n // AI SDK v5/v6 returns dotted providers like 'openai.chat', 'anthropic.messages'.\n // Extract the base name for cost calculation and consistent toolName naming.\n // 'gateway' is the router, not the creator — derive the real provider from the id.\n const rawProvider = typeof model.provider === 'string' ? model.provider : '';\n const base = rawProvider.split('.')[0];\n const provider = base && base !== 'gateway' ? base : guessProvider(modelId);\n return { modelId, provider };\n } catch {\n return { modelId: 'unknown', provider: 'unknown' };\n }\n}\n\nfunction extractContentText(content: unknown): string {\n // Never-throw: content is user-supplied (hostile getters/toJSON/cycles) and this runs\n // before the provider call — degrade to '[unserializable]' instead of throwing.\n try {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n // Vercel AI SDK content parts: [{type: \"text\", text: \"...\"}, ...]\n const textParts = content\n .filter(\n (part: Record<string, unknown>) =>\n part && part.type === 'text' && typeof part.text === 'string'\n )\n .map((part: Record<string, unknown>) => part.text as string);\n if (textParts.length > 0) return textParts.join('\\n');\n }\n return safeStringify(content);\n } catch {\n return '[unserializable]';\n }\n}\n\nfunction extractInput(params: GenerateTextParams): string {\n // Never-throw: params.messages is user-supplied (hostile iterables/getters) and this\n // runs before the provider call — degrade to '' instead of throwing.\n try {\n if (params.prompt) return params.prompt;\n if (params.messages && params.messages.length > 0) {\n const lastUserMessage = [...params.messages].reverse().find((m) => m.role === 'user');\n if (lastUserMessage) {\n return extractContentText(lastUserMessage.content);\n }\n // No user message found — fall back to last non-system message\n const lastNonSystem = [...params.messages].reverse().find((m) => m.role !== 'system');\n if (lastNonSystem) {\n return extractContentText(lastNonSystem.content);\n }\n return '';\n }\n return '';\n } catch {\n return '';\n }\n}\n\n// normalizeUsage now lives in cost.ts as the single canonical implementation shared with the\n// Mastra SDK (previously duplicated here and in mastra-sdk/utils.ts). Imported above.\n\n// resolveTokensAndCost (the shared \"normalize raw AI-SDK usage → prompt/completion/total → cost\"\n// derivation, including the Anthropic cachedInputTokens / Google reasoningTokens fold) now lives in\n// cost.ts as the single canonical implementation shared with the Mastra SDK (#484). Imported above.\n// The six call sites below keep their own model-fallback selection and trackToolCall bodies.\n\n// ============================================================================\n// Tool Wrapping\n// ============================================================================\n\n// wrapTools(tools, sessionId) is exactly wrapToolsAsync(tools, Promise.resolve(sessionId)) — the\n// sync path just has a concrete id instead of a pending one. Delegating avoids maintaining the\n// identical trackToolCall success/error bodies twice (#484). Tracking stays fire-and-forget; the\n// resolved-promise .then only defers the (already async) track by a microtask.\nfunction wrapTools(\n tools: Record<string, { execute?: AnyFunction } & Record<string, unknown>> | undefined,\n sessionId: string,\n client: SentrialClient\n): Record<string, { execute?: AnyFunction } & Record<string, unknown>> | undefined {\n if (!tools) return undefined;\n return wrapToolsAsync(tools, Promise.resolve(sessionId), client);\n}\n\nfunction wrapToolsAsync(\n tools: Record<string, { execute?: AnyFunction } & Record<string, unknown>>,\n sessionPromise: Promise<string | null>,\n client: SentrialClient\n): Record<string, { execute?: AnyFunction } & Record<string, unknown>> {\n // try-guarded: `tools` is caller-supplied and this runs (via wrappedParams) BEFORE the provider\n // call, so a malformed map (a null/undefined tool value, a hostile `execute` getter, a non-object\n // tools) must degrade to the original tools rather than throw before the call and orphan the\n // already-created session — same never-throw discipline as extractModelInfo/extractInput.\n try {\n const wrappedTools: Record<string, { execute?: AnyFunction } & Record<string, unknown>> = {};\n\n for (const [toolName, tool] of Object.entries(tools)) {\n if (tool && typeof tool.execute === 'function') {\n const originalExecute = tool.execute;\n wrappedTools[toolName] = {\n ...tool,\n execute: async (...args: unknown[]) => {\n const startTime = Date.now();\n\n // Execute the tool immediately — don't block on session creation.\n // Track asynchronously after the tool completes.\n try {\n const result = await originalExecute(...args);\n const durationMs = Date.now() - startTime;\n\n sessionPromise.then((sid) => {\n if (sid) {\n client\n .trackToolCall({\n sessionId: sid,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: result as Record<string, unknown>,\n reasoning: `Tool executed in ${durationMs}ms`,\n // Real timing as first-class fields (the reasoning string above is kept for\n // display parity): the event span carries start/end so the server derives the\n // true per-event duration instead of a zero-duration span.\n durationMs,\n startedAt: startTime,\n })\n .catch(() => {});\n }\n });\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n sessionPromise.then((sid) => {\n if (sid) {\n client\n .trackToolCall({\n sessionId: sid,\n toolName,\n toolInput: args[0] as Record<string, unknown>,\n toolOutput: {},\n toolError: {\n message: error instanceof Error ? error.message : 'Unknown error',\n },\n reasoning: `Tool failed after ${durationMs}ms`,\n // Real timing as first-class fields (see the success path above).\n durationMs,\n startedAt: startTime,\n })\n .catch(() => {});\n }\n });\n\n throw error;\n }\n },\n };\n } else {\n wrappedTools[toolName] = tool;\n }\n }\n\n return wrappedTools;\n } catch {\n // Malformed tools map — run the caller's provider with the original (un-instrumented) tools\n // instead of throwing before the call / leaking the created session.\n return tools;\n }\n}\n\n// ============================================================================\n// generateText Wrapper\n// ============================================================================\n\nfunction wrapGenerateText(\n originalFn: AnyFunction,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => Promise<GenerateTextResult> {\n return async (params: GenerateTextParams): Promise<GenerateTextResult> => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Contain createSession like every other tracking POST (and the streaming sibling's IIFE): a\n // rejection under failSilently:false must NOT throw before the provider call — fall through to\n // the un-instrumented call rather than turning a monitoring outage into a generation outage.\n let sessionId: string | null = null;\n try {\n sessionId = await client.createSession({\n name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n ...config.metadata,\n model: modelId,\n provider,\n function: 'generateText',\n ...(params.maxSteps ? { maxSteps: params.maxSteps } : {}),\n },\n });\n } catch {\n sessionId = null;\n }\n\n if (!sessionId) {\n return originalFn(params) as Promise<GenerateTextResult>;\n }\n\n // Fire-and-forget (matches the streaming wrappers): setInput is a best-effort enrichment POST,\n // so a transient failure must not throw out before the provider call — which, with the await\n // sitting outside the try below, would orphan the already-created running session.\n client.setInput(sessionId, input).catch(() => {});\n\n const wrappedParams = {\n ...params,\n tools: wrapTools(params.tools, sessionId, client),\n };\n\n try {\n const result = (await originalFn(wrappedParams)) as GenerateTextResult;\n const durationMs = Date.now() - startTime;\n\n // v5+ name the provider-reported model response.modelId; v3/v4 name it response.model.\n const resolvedModelId =\n result.response?.modelId ||\n (result.response as { model?: string } | undefined)?.model ||\n modelId;\n\n // AI SDK v5: result.usage is \"the token usage of the LAST step\" and totalUsage is \"the\n // total token usage of all steps\"; AI SDK 7.0 flipped usage to be the all-steps total and\n // deprecated totalUsage. Preferring totalUsage ?? usage is correct on every version\n // (matches the streamText onFinish path).\n const { promptTokens, completionTokens, totalTokens, cost } = resolveTokensAndCost(\n ((result as unknown as Record<string, unknown>).totalUsage as UsageInfo | undefined) ??\n result.usage,\n provider,\n resolvedModelId\n );\n\n // Track multi-step results (v4+ with maxSteps) — uses trackToolCall with\n // 'llm:provider:model' naming so data maps to proper API fields\n const steps = result.steps;\n let stepCostSum = 0;\n if (steps && steps.length >= 1) {\n const stepPromises = steps.map((step) => {\n // prepareStep can switch the model (and provider) mid-call — price and label each\n // step with its OWN model/provider. step.model.provider is v6+; otherwise infer\n // from the step model when it differs from the call-level one.\n const stepModel =\n step.model?.modelId ??\n step.response?.modelId ??\n step.response?.model ??\n resolvedModelId;\n const rawStepProvider = step.model?.provider?.split('.')[0];\n const stepProvider =\n rawStepProvider && rawStepProvider !== 'gateway'\n ? rawStepProvider\n : stepModel !== resolvedModelId || rawStepProvider === 'gateway'\n ? guessProvider(stepModel)\n : provider;\n const stepCost = resolveTokensAndCost(step.usage, stepProvider, stepModel);\n stepCostSum += stepCost.cost;\n return client\n .trackToolCall({\n sessionId,\n toolName: `llm:${stepProvider}:${stepModel}`,\n toolInput: { finishReason: step.finishReason },\n toolOutput: {\n text: step.text?.slice(0, 500),\n tokens: {\n prompt: stepCost.promptTokens,\n completion: stepCost.completionTokens,\n },\n },\n estimatedCost: stepCost.cost,\n tokenCount: stepCost.totalTokens,\n model: stepModel,\n promptTokens: stepCost.promptTokens,\n completionTokens: stepCost.completionTokens,\n finishReason: step.finishReason,\n metadata: {\n tool_calls: step.toolCalls?.map((tc) => tc.toolName),\n },\n })\n .catch(() => {});\n });\n await Promise.all(stepPromises);\n } else {\n await client\n .trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { finishReason: result.finishReason },\n toolOutput: {\n text: result.text?.slice(0, 500),\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n model: resolvedModelId,\n promptTokens,\n completionTokens,\n finishReason: result.finishReason,\n metadata: {\n tool_calls: result.toolCalls?.map((tc) => tc.toolName),\n },\n })\n .catch(() => {});\n }\n\n // .catch-guarded like the per-step trackToolCall and the streaming completeSession: a\n // completion-POST failure (only possible under failSilently:false) must NOT be caught below\n // as a generation failure — that would mislabel a succeeded run as failed AND discard the\n // caller's already-produced result by re-throwing.\n await client\n .completeSession({\n sessionId,\n success: true,\n output: result.text,\n durationMs,\n // With steps, sum the per-step costs (each priced at its own model) — pricing the\n // aggregate token total at the final step's model mis-bills model-switching runs.\n estimatedCost: stepCostSum > 0 ? stepCostSum : cost,\n promptTokens,\n completionTokens,\n totalTokens,\n model: resolvedModelId,\n })\n .catch(() => {});\n\n return result;\n } catch (error) {\n await failVercelSession(client, sessionId, error, startTime);\n throw error;\n }\n };\n}\n\n// ============================================================================\n// streamText Wrapper\n// ============================================================================\n\nfunction wrapStreamText(\n originalFn: AnyFunction,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => StreamTextResult {\n return (params: GenerateTextParams): StreamTextResult => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Session creation runs concurrently with the stream setup.\n // onStepFinish/onFinish callbacks await this before tracking.\n let sessionId: string | null = null;\n let sessionCompleted = false;\n let streamStepCostSum = 0;\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n (async () => {\n try {\n const id = await client.createSession({\n name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n ...config.metadata,\n model: modelId,\n provider,\n function: 'streamText',\n },\n });\n sessionId = id;\n if (id) {\n client.setInput(id, input).catch(() => {});\n }\n } catch {\n sessionId = null;\n }\n resolveSessionReady!();\n })();\n\n // Preserve user callbacks. AI SDK 7.0 renamed the callbacks to onStepEnd/onEnd; the SDK\n // resolves onEnd ?? onFinish, so we fold 7.0-named user callbacks into our injected aliases\n // (preferring the new names, matching the SDK's own precedence) so the session can't leak\n // as running when a caller uses the 7.0 names.\n const userOnStepFinish = ((params as Record<string, unknown>).onStepEnd ??\n params.onStepFinish) as ((step: Record<string, unknown>) => void) | undefined;\n const userOnFinish = ((params as Record<string, unknown>).onEnd ?? params.onFinish) as\n | ((event: Record<string, unknown>) => void)\n | undefined;\n const userOnError = params.onError as ((event: { error: Error }) => void) | undefined;\n\n // Inject our callbacks into the params — this is the v5/v6-compatible approach.\n // In v5/v6, StreamTextResult is a class with readonly getter properties;\n // mutating result.textStream silently fails. Callbacks are the only way.\n const wrappedParams = {\n ...params,\n // Remove the 7.0-named user callbacks (folded into ours above) so the SDK can't prefer them\n // over our injected tracking callbacks.\n onStepEnd: undefined,\n onEnd: undefined,\n tools: params.tools\n ? wrapToolsAsync(\n params.tools,\n sessionReady.then(() => sessionId),\n client\n )\n : undefined,\n\n onStepFinish: async (step: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId) {\n const stepModelInfo = step.model as { provider?: string; modelId?: string } | undefined;\n const resolvedStepModel =\n stepModelInfo?.modelId ??\n (step.response as { modelId?: string; model?: string })?.modelId ??\n (step.response as { model?: string })?.model ??\n modelId;\n // prepareStep can switch providers mid-call — derive the step's own provider.\n const rawStreamStepProvider = stepModelInfo?.provider?.split('.')[0];\n const stepProvider =\n rawStreamStepProvider && rawStreamStepProvider !== 'gateway'\n ? rawStreamStepProvider\n : resolvedStepModel !== modelId || rawStreamStepProvider === 'gateway'\n ? guessProvider(resolvedStepModel)\n : provider;\n const stepCost = resolveTokensAndCost(\n step.usage as UsageInfo | undefined,\n stepProvider,\n resolvedStepModel\n );\n streamStepCostSum += stepCost.cost;\n client\n .trackToolCall({\n sessionId,\n toolName: `llm:${stepProvider}:${resolvedStepModel}`,\n toolInput: { finishReason: step.finishReason },\n toolOutput: {\n text: (step.text as string)?.slice(0, 500),\n tokens: {\n prompt: stepCost.promptTokens,\n completion: stepCost.completionTokens,\n },\n },\n estimatedCost: stepCost.cost,\n tokenCount: stepCost.totalTokens,\n model: resolvedStepModel,\n promptTokens: stepCost.promptTokens,\n completionTokens: stepCost.completionTokens,\n finishReason: step.finishReason as string | undefined,\n metadata: {\n tool_calls: (step.toolCalls as { toolName: string }[] | undefined)?.map(\n (tc) => tc.toolName\n ),\n },\n })\n .catch(() => {});\n }\n if (userOnStepFinish) {\n try {\n userOnStepFinish(step);\n } catch {\n /* don't crash */\n }\n }\n },\n\n // Docs: \"onAbort is specifically called when a stream is aborted via AbortSignal\" — abort\n // fires neither onFinish nor onError, so without this the session leaks as running.\n onAbort: async (event: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n await client\n .completeSession({\n sessionId,\n success: false,\n failureReason: 'stream aborted',\n durationMs: Date.now() - startTime,\n })\n .catch(() => {});\n }\n const userOnAbort = (params as Record<string, unknown>).onAbort as\n | ((event: Record<string, unknown>) => void)\n | undefined;\n if (userOnAbort) {\n try {\n userOnAbort(event);\n } catch {\n /* don't crash the host app */\n }\n }\n },\n\n onFinish: async (event: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n // v5+ response.modelId; v3/v4 response.model.\n const resolvedModelId =\n (event.response as { modelId?: string; model?: string })?.modelId ??\n (event.response as { model?: string })?.model ??\n modelId;\n const text = (event.text as string) ?? '';\n\n // v5/v6: the event's totalUsage is the all-steps total while usage is last-step only\n // — do NOT simplify to event.usage. AI SDK 7.0 made the event's usage the all-steps\n // total as well (\"the onEnd callback ... follows the same result property changes\");\n // v3/v4 events have only usage (already the total). totalUsage ?? usage selects the\n // total on every version.\n const { promptTokens, completionTokens, totalTokens, cost } = resolveTokensAndCost(\n (event.totalUsage ?? event.usage) as UsageInfo | undefined,\n provider,\n resolvedModelId\n );\n\n await client\n .completeSession({\n sessionId,\n success: true,\n output: text,\n durationMs,\n // With steps, sum the per-step costs (each priced at its own model) — pricing the\n // aggregate total at one model mis-bills model-switching runs.\n estimatedCost: streamStepCostSum > 0 ? streamStepCostSum : cost,\n promptTokens,\n completionTokens,\n totalTokens,\n model: resolvedModelId,\n })\n .catch(() => {});\n }\n if (userOnFinish) {\n try {\n userOnFinish(event);\n } catch {\n /* don't crash */\n }\n }\n },\n\n onError: async (event: { error: Error }) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n const msg = event.error?.message ?? 'Unknown error';\n\n await client\n .trackError({\n sessionId,\n errorType: event.error?.name ?? 'Error',\n errorMessage: msg,\n })\n .catch(() => {});\n\n await client\n .completeSession({\n sessionId,\n success: false,\n failureReason: msg,\n durationMs,\n })\n .catch(() => {});\n }\n if (userOnError) {\n try {\n userOnError(event);\n } catch {\n /* don't crash */\n }\n }\n },\n };\n\n // streamText is synchronous in v5/v6 — returns the result immediately.\n // Wrap in try/catch: if it throws (invalid model, missing API key),\n // complete the orphaned session so it doesn't stay \"running\" forever.\n try {\n return originalFn(wrappedParams) as StreamTextResult;\n } catch (error) {\n sessionReady.then(() => {\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n const msg = error instanceof Error ? error.message : safeString(error);\n client\n .trackError({\n sessionId,\n errorType: error instanceof Error ? error.name : 'Error',\n errorMessage: msg,\n })\n .catch(() => {});\n client\n .completeSession({ sessionId, success: false, failureReason: msg, durationMs })\n .catch(() => {});\n }\n });\n throw error;\n }\n };\n}\n\n// ============================================================================\n// generateObject Wrapper\n// ============================================================================\n\nfunction wrapGenerateObject(\n originalFn: AnyFunction,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => Promise<unknown> {\n return async (params: GenerateTextParams): Promise<unknown> => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Contain createSession (mirrors wrapGenerateText and the streaming IIFE): a rejection under\n // failSilently:false must fall through to the un-instrumented call, not throw before the provider.\n let sessionId: string | null = null;\n try {\n sessionId = await client.createSession({\n name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n ...config.metadata,\n model: modelId,\n provider,\n function: 'generateObject',\n },\n });\n } catch {\n sessionId = null;\n }\n\n if (!sessionId) {\n return originalFn(params);\n }\n\n // Fire-and-forget (matches the streaming wrappers): a setInput failure must not throw out\n // before the provider call and orphan the already-created running session.\n client.setInput(sessionId, input).catch(() => {});\n\n try {\n const result = (await originalFn(params)) as {\n object?: unknown;\n usage?: UsageInfo;\n response?: { modelId?: string };\n };\n const durationMs = Date.now() - startTime;\n\n // v5+ name the provider-reported model response.modelId; v3/v4 name it response.model.\n const resolvedModelId =\n result.response?.modelId ||\n (result.response as { model?: string } | undefined)?.model ||\n modelId;\n const { promptTokens, completionTokens, totalTokens, cost } = resolveTokensAndCost(\n result.usage,\n provider,\n resolvedModelId\n );\n\n await client\n .trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { function: 'generateObject' },\n toolOutput: {\n object: result.object,\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n model: resolvedModelId,\n promptTokens,\n completionTokens,\n })\n .catch(() => {});\n\n // .catch-guarded so a completion-POST failure (failSilently:false only) can't be caught\n // below as a generation failure — mislabeling a succeeded run and discarding the result.\n await client\n .completeSession({\n sessionId,\n success: true,\n output: safeStringify(result.object),\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n model: resolvedModelId,\n })\n .catch(() => {});\n\n return result;\n } catch (error) {\n await failVercelSession(client, sessionId, error, startTime);\n throw error;\n }\n };\n}\n\n// ============================================================================\n// streamObject Wrapper\n// ============================================================================\n\n// streamObject exposes no onAbort hook (unlike streamText). To avoid leaking an aborted object\n// stream's session as \"running\", we finalize on the caller's AbortSignal directly (below), which\n// is robust regardless of whether the AI SDK also surfaces the abort via onError.\nfunction wrapStreamObject(\n originalFn: AnyFunction,\n client: SentrialClient,\n config: WrapperConfig\n): (params: GenerateTextParams) => unknown {\n return (params: GenerateTextParams): unknown => {\n const startTime = Date.now();\n const { modelId, provider } = extractModelInfo(params.model);\n const input = extractInput(params);\n\n // Session creation runs concurrently\n let sessionId: string | null = null;\n let sessionCompleted = false;\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n (async () => {\n try {\n const id = await client.createSession({\n name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`,\n agentName: config.defaultAgent ?? 'vercel-ai-sdk',\n userId: resolveStringOrFn(config.userId, 'anonymous'),\n convoId: resolveStringOrFn(config.convoId),\n metadata: {\n ...config.metadata,\n model: modelId,\n provider,\n function: 'streamObject',\n },\n });\n sessionId = id;\n if (id) {\n client.setInput(id, input).catch(() => {});\n }\n } catch {\n sessionId = null;\n }\n resolveSessionReady!();\n })();\n\n // streamObject has no onAbort hook, so finalize a cancelled object stream on the caller's\n // AbortSignal directly. The sessionCompleted guard makes this idempotent with onFinish/onError\n // (and with a duplicate abort event), so it never double-completes.\n // try-guarded: abortSignal is caller-supplied, so a hostile/malformed value (e.g. `{}` with no\n // addEventListener) must degrade to a no-op, never throw before the provider call — same\n // never-throw discipline as extractModelInfo/extractInput.\n try {\n const abortSignal = (params as { abortSignal?: AbortSignal }).abortSignal;\n if (abortSignal) {\n const finalizeAborted = (): void => {\n void sessionReady.then(() => {\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n client\n .completeSession({\n sessionId,\n success: false,\n failureReason: 'stream aborted',\n durationMs: Date.now() - startTime,\n })\n .catch(() => {});\n }\n });\n };\n if (abortSignal.aborted) finalizeAborted();\n else abortSignal.addEventListener('abort', finalizeAborted, { once: true });\n }\n } catch {\n /* malformed abortSignal — monitoring must not crash the caller */\n }\n\n // Preserve user callbacks\n const userOnFinish = params.onFinish as ((event: Record<string, unknown>) => void) | undefined;\n const userOnError = params.onError as ((event: { error: Error }) => void) | undefined;\n\n // Inject onFinish/onError callbacks — v5/v6 result.object is readonly,\n // so we can't intercept the promise chain. Callbacks are the only way.\n const wrappedParams = {\n ...params,\n\n onFinish: async (event: Record<string, unknown>) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n // v5+ response.modelId; v3/v4 response.model.\n const resolvedModelId =\n (event.response as { modelId?: string; model?: string })?.modelId ??\n (event.response as { model?: string })?.model ??\n modelId;\n\n const { promptTokens, completionTokens, totalTokens, cost } = resolveTokensAndCost(\n event.usage as UsageInfo | undefined,\n provider,\n resolvedModelId\n );\n\n // If the event has an error (schema validation failure), track it\n if (event.error) {\n const errMsg =\n event.error instanceof Error ? event.error.message : safeString(event.error);\n await client\n .trackError({\n sessionId,\n errorType: 'SchemaValidationError',\n errorMessage: errMsg,\n })\n .catch(() => {});\n }\n\n await client\n .trackToolCall({\n sessionId,\n toolName: `llm:${provider}:${resolvedModelId}`,\n toolInput: { function: 'streamObject' },\n toolOutput: {\n object: event.object,\n tokens: { prompt: promptTokens, completion: completionTokens },\n },\n estimatedCost: cost,\n tokenCount: totalTokens,\n model: resolvedModelId,\n promptTokens,\n completionTokens,\n })\n .catch(() => {});\n\n await client\n .completeSession({\n sessionId,\n success: !event.error,\n output: event.object != null ? safeStringify(event.object) : undefined,\n failureReason: event.error ? safeString(event.error) : undefined,\n durationMs,\n estimatedCost: cost,\n promptTokens,\n completionTokens,\n totalTokens,\n model: resolvedModelId,\n })\n .catch(() => {});\n }\n if (userOnFinish) {\n try {\n userOnFinish(event);\n } catch {\n /* don't crash */\n }\n }\n },\n\n onError: async (event: { error: Error }) => {\n await sessionReady;\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n const msg = event.error?.message ?? 'Unknown error';\n\n await client\n .trackError({\n sessionId,\n errorType: event.error?.name ?? 'Error',\n errorMessage: msg,\n })\n .catch(() => {});\n\n await client\n .completeSession({\n sessionId,\n success: false,\n failureReason: msg,\n durationMs,\n })\n .catch(() => {});\n }\n if (userOnError) {\n try {\n userOnError(event);\n } catch {\n /* don't crash */\n }\n }\n },\n };\n\n try {\n return originalFn(wrappedParams);\n } catch (error) {\n sessionReady.then(() => {\n if (sessionId && !sessionCompleted) {\n sessionCompleted = true;\n const durationMs = Date.now() - startTime;\n const msg = error instanceof Error ? error.message : safeString(error);\n client\n .trackError({\n sessionId,\n errorType: error instanceof Error ? error.name : 'Error',\n errorMessage: msg,\n })\n .catch(() => {});\n client\n .completeSession({ sessionId, success: false, failureReason: msg, durationMs })\n .catch(() => {});\n }\n });\n throw error;\n }\n };\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Wrap the Vercel AI SDK to automatically trace all AI calls.\n *\n * @param ai - The imported `ai` module from the Vercel AI SDK\n * @param options - Optional configuration (client, defaultAgent, userId, convoId)\n * @returns Wrapped versions of the AI SDK functions\n *\n * @example Using with configureVercel\n * ```ts\n * configureVercel({ apiKey: process.env.SENTRIAL_API_KEY, defaultAgent: 'my-agent' });\n * const { generateText, streamText } = wrapAISDK(ai);\n * ```\n *\n * @example Multi-turn conversations\n * ```ts\n * const { generateText } = wrapAISDK(ai, { convoId: 'convo_123' });\n * ```\n *\n * @example Using with existing SentrialClient\n * ```ts\n * const client = new SentrialClient({ apiKey: process.env.SENTRIAL_API_KEY });\n * const { generateText } = wrapAISDK(ai, { client, defaultAgent: 'my-agent' });\n * ```\n */\nexport function wrapAISDK<T extends AIModule>(ai: T, options?: WrapAISDKOptions): WrappedAISDK<T>;\nexport function wrapAISDK(\n ai: AIModule,\n options?: WrapAISDKOptions\n): {\n generateText: ReturnType<typeof wrapGenerateText>;\n streamText: ReturnType<typeof wrapStreamText>;\n generateObject: ReturnType<typeof wrapGenerateObject>;\n streamObject: ReturnType<typeof wrapStreamObject>;\n} {\n const client = options?.client ?? getClient();\n\n // Create per-instance config — never mutate global state\n const config: WrapperConfig = {\n defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,\n userId: options?.userId ?? _globalConfig.userId,\n convoId: options?.convoId ?? _globalConfig.convoId,\n metadata: copyWrapperMetadata(options?.metadata),\n };\n\n // ai@3.0 shipped the core functions under experimental_* names (\"Move AI Core functions\n // out of experimental\" landed in 3.1) — accept both so the claimed v3 support is real.\n // The runtime adapters forward parameters and results; only their internal view is erased.\n const generateTextFn = (ai.generateText ?? ai.experimental_generateText) as\n | AnyFunction\n | undefined;\n const streamTextFn = (ai.streamText ?? ai.experimental_streamText) as AnyFunction | undefined;\n const generateObjectFn = (ai.generateObject ?? ai.experimental_generateObject) as\n | AnyFunction\n | undefined;\n const streamObjectFn = (ai.streamObject ?? ai.experimental_streamObject) as\n | AnyFunction\n | undefined;\n\n return {\n generateText: generateTextFn\n ? wrapGenerateText(generateTextFn, client, config)\n : wrapGenerateText(\n () => Promise.reject(new Error('generateText not available')),\n client,\n config\n ),\n streamText: streamTextFn\n ? wrapStreamText(streamTextFn, client, config)\n : wrapStreamText(\n // Throw (like the async fallbacks reject) so the wrapper's try/catch completes the\n // already-created session — a silent empty-stream fallback would leak it as \"running\".\n () => {\n throw new Error('streamText not available');\n },\n client,\n config\n ),\n generateObject: generateObjectFn\n ? wrapGenerateObject(generateObjectFn, client, config)\n : wrapGenerateObject(\n () => Promise.reject(new Error('generateObject not available')),\n client,\n config\n ),\n streamObject: streamObjectFn\n ? wrapStreamObject(streamObjectFn, client, config)\n : wrapStreamObject(\n () => {\n throw new Error('streamObject not available');\n },\n client,\n config\n ),\n };\n}\n\n// Export types for consumers\nexport type { GenerateTextParams, GenerateTextResult, StreamTextResult };\n\n// ============================================================================\n// Production A/B variant helper\n// ============================================================================\n\n/**\n * Options shape accepted by {@link getVariantForAISDK} — a permissive subset of\n * an AI SDK `generateText` / `streamText` call. Extra keys are preserved, so it\n * composes with whatever options you already build.\n */\nexport type AISDKVariantOptions = {\n model?: unknown;\n system?: string;\n prompt?: string;\n messages?: unknown;\n experimental_telemetry?: { isEnabled?: boolean; metadata?: Record<string, unknown> };\n [key: string]: unknown;\n};\n\n/**\n * Resolve a production A/B variant and merge it into Vercel AI SDK call options.\n *\n * Pairs `client.getVariant()` with a `generateText` / `streamText` call: it\n * fetches the sticky assignment for this user/convo, applies the variant's\n * `systemPrompt`, `prompt`, and (via `modelResolver`, since the AI SDK needs a\n * model *object*, not a string) `model`, and tags the call's\n * `experimental_telemetry.metadata` with the experiment + variant so the\n * exposure is visible on the resulting session. Returns the merged options\n * (spread straight into your `generateText` call) and the raw assignment so you\n * can branch on `assignment.variant`.\n *\n * The assignment is recorded server-side by `getVariant` regardless of whether a\n * variant config is applied, so a `reason: 'holdout'` / `'not_found'` result is\n * safe — the base options pass through unchanged (sticky control).\n *\n * @example\n * ```ts\n * import { openai } from '@ai-sdk/openai';\n * import { wrapAISDK, getVariantForAISDK } from '@sentrial/sdk';\n * import * as ai from 'ai';\n *\n * const { generateText } = wrapAISDK(ai);\n * const { options, assignment } = await getVariantForAISDK(\n * { experimentName: 'support-prompt-v2', userId, convoId },\n * { model: openai('gpt-4o-mini'), system: 'You are a support agent.', prompt: userQuery },\n * { modelResolver: (id) => openai(id) }\n * );\n * const { text } = await generateText(options); // runs the assigned variant, tracked\n * ```\n */\nexport async function getVariantForAISDK<T extends AISDKVariantOptions>(\n variantParams: GetVariantParams,\n baseOptions: T,\n opts: { modelResolver?: (modelId: string) => unknown; client?: SentrialClient } = {}\n): Promise<{ options: T; assignment: VariantAssignment }> {\n const client = opts.client ?? getClient();\n const assignment = await client.getVariant(variantParams);\n\n const options: AISDKVariantOptions = { ...baseOptions };\n const config = assignment.variantConfig;\n\n const systemPrompt = assignment.systemPrompt ?? config?.systemPrompt;\n if (systemPrompt) options.system = systemPrompt;\n\n // Only override `prompt` when the caller isn't using `messages` — the two are\n // mutually exclusive in the AI SDK.\n const prompt = assignment.prompt ?? config?.prompt;\n if (prompt && options.messages === undefined) options.prompt = prompt;\n\n // The AI SDK takes a model *object*; the variant carries a model id string.\n // Apply only when the caller provides a resolver to build the object.\n const modelId = assignment.model ?? config?.model;\n if (modelId && opts.modelResolver) options.model = opts.modelResolver(modelId);\n\n // Surface the exposure on the call's telemetry so the variant shows up on the\n // session wrapAISDK creates — merged, never clobbering caller-set metadata.\n const existing = options.experimental_telemetry;\n const promptVersion = assignment.promptVersion ?? config?.promptVersion;\n options.experimental_telemetry = {\n ...existing,\n metadata: {\n ...existing?.metadata,\n // Annotation-only keys from the canonical map (\"experimentAnnotation\" block): they decorate\n // the CUSTOMER's own Vercel telemetry; Sentrial ingest links the assignment via assignmentIds.\n [semconvKeys.experimentAnnotation.experiment]: variantParams.experimentName,\n [semconvKeys.experimentAnnotation.variant]: assignment.variant,\n ...(assignment.assignmentId\n ? { [semconvKeys.experimentAnnotation.assignmentId]: assignment.assignmentId }\n : {}),\n ...(promptVersion ? { [semconvKeys.experimentAnnotation.promptVersion]: promptVersion } : {}),\n },\n };\n\n return { options: options as T, assignment };\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any --\n * This module wraps the untyped Claude Agent SDK `query()` generator and its tool-callback inputs,\n * whose runtime shapes the third-party package does not export as types. `any` is the boundary type\n * at those hook sites; disabling the rule file-wide keeps the wrapper readable without sacrificing\n * real type safety (the wrapper's own public surface stays typed). */\n/**\n * Sentrial wrapper for the Claude Agent SDK (Claude Code).\n *\n * Wraps the `query()` async generator to automatically track sessions,\n * tool calls, tokens, and costs to Sentrial.\n *\n * @example\n * ```ts\n * import { query } from '@anthropic-ai/claude-agent-sdk';\n * import { SentrialClient, wrapClaudeAgent } from '@sentrial/sdk';\n *\n * const client = new SentrialClient({ apiKey: process.env.SENTRIAL_API_KEY });\n *\n * const trackedQuery = wrapClaudeAgent(query, {\n * client,\n * defaultAgent: 'my-agent',\n * userId: 'user-123',\n * });\n *\n * for await (const message of trackedQuery({ prompt: 'Fix the tests' })) {\n * // Messages pass through unchanged\n * console.log(message.type);\n * }\n * ```\n */\n\nimport type { SentrialClient } from './client.js';\nimport { extractAnthropicUsage } from './cost.js';\n\n// ============================================================================\n// Types (local — Claude Agent SDK is a peer dep, not imported at runtime)\n// ============================================================================\n\ntype HookCallback = (\n input: any,\n toolUseID: string | undefined,\n options: { signal: AbortSignal }\n) => Promise<any>;\n\ninterface HookCallbackMatcher {\n matcher?: string;\n hooks: HookCallback[];\n}\n\ninterface QueryOptions {\n hooks?: Record<string, HookCallbackMatcher[]>;\n [key: string]: unknown;\n}\n\ninterface QueryParams {\n prompt: string | AsyncIterable<any>;\n options?: QueryOptions;\n}\n\ntype QueryFunction = (params: QueryParams) => AsyncGenerator<any, void>;\n\n// ============================================================================\n// Wrapper Options\n// ============================================================================\n\nexport interface WrapClaudeAgentOptions {\n /** Sentrial client instance for tracking */\n client: SentrialClient;\n /** Default agent name (used in session creation). Defaults to 'claude-agent'. */\n defaultAgent?: string;\n /** User ID to associate with sessions. Defaults to 'anonymous'. */\n userId?: string;\n /** Conversation ID to group related sessions into a thread. */\n convoId?: string;\n /** Extra metadata to include in every session. */\n extraMetadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Wrap the Claude Agent SDK's `query()` function to automatically track\n * sessions, tool calls, tokens, and costs to Sentrial.\n *\n * @param queryFn - The original `query()` function from `@anthropic-ai/claude-agent-sdk`\n * @param wrapOptions - Configuration for the wrapper\n * @returns A new function with the same signature as `query()`\n */\nexport function wrapClaudeAgent(\n queryFn: QueryFunction,\n wrapOptions: WrapClaudeAgentOptions\n): QueryFunction {\n const {\n client,\n defaultAgent = 'claude-agent',\n userId = 'anonymous',\n convoId,\n extraMetadata,\n } = wrapOptions;\n\n return function wrappedQuery(params: QueryParams): AsyncGenerator<any, void> {\n const { prompt, options = {} } = params;\n const startTime = Date.now();\n let sessionId: string | null = null;\n\n // Session-ready gate: hooks await this before checking sessionId.\n // This prevents hooks that fire during the createSession HTTP request\n // from silently skipping because sessionId is still null.\n let resolveSessionReady: () => void;\n const sessionReady = new Promise<void>((resolve) => {\n resolveSessionReady = resolve;\n });\n\n // Build the session name from the prompt\n const sessionName =\n typeof prompt === 'string'\n ? `${defaultAgent}: ${prompt.slice(0, 100)}`\n : `${defaultAgent} session`;\n\n // Collect pending tool call promises so we can flush before completing\n const pendingToolCalls: Promise<unknown>[] = [];\n\n // --- PostToolUse hook for tracking successful tool calls ---\n const sentrialToolHook: HookCallbackMatcher = {\n hooks: [\n async (input: any, toolUseID: string | undefined, _opts: { signal: AbortSignal }) => {\n await sessionReady;\n // Skip once the session is finalized: in streaming-input mode the session completes on\n // the FIRST result, but the query keeps yielding later turns whose tool hooks would\n // otherwise track against a finalized session AND grow pendingToolCalls unbounded.\n if (!sessionId || completed) return;\n // NOTE: the installed @anthropic-ai/claude-agent-sdk does NOT emit `agent_id` (or\n // `parent_tool_use_id`) on PostToolUse/PostToolUseFailure hook inputs — only on\n // SubagentStart/Stop and the can_use_tool request — so this guard is a forward-compat\n // no-op today. Consequence/known limitation: subagent-internal tool calls that fire these\n // hooks are recorded on the parent session (the hook payload carries no subagent\n // identifier to filter on, unlike the Python wrapper's message-stream parent_tool_use_id).\n if (input?.agent_id) return;\n const toolOutput =\n input?.tool_response && typeof input.tool_response === 'object'\n ? (input.tool_response as Record<string, unknown>)\n : { response: input?.tool_response ?? null };\n\n const p = client\n .trackToolCall({\n sessionId,\n toolName: input?.tool_name ?? 'unknown',\n toolInput: input?.tool_input ?? {},\n toolOutput,\n metadata: { tool_use_id: toolUseID },\n })\n .catch(() => {});\n pendingToolCalls.push(p);\n },\n ],\n };\n\n // --- PostToolUseFailure hook for tracking failed tool calls ---\n const sentrialToolFailureHook: HookCallbackMatcher = {\n hooks: [\n async (input: any, toolUseID: string | undefined, _opts: { signal: AbortSignal }) => {\n await sessionReady;\n // Skip once the session is finalized: in streaming-input mode the session completes on\n // the FIRST result, but the query keeps yielding later turns whose tool hooks would\n // otherwise track against a finalized session AND grow pendingToolCalls unbounded.\n if (!sessionId || completed) return;\n // NOTE: the installed @anthropic-ai/claude-agent-sdk does NOT emit `agent_id` (or\n // `parent_tool_use_id`) on PostToolUse/PostToolUseFailure hook inputs — only on\n // SubagentStart/Stop and the can_use_tool request — so this guard is a forward-compat\n // no-op today. Consequence/known limitation: subagent-internal tool calls that fire these\n // hooks are recorded on the parent session (the hook payload carries no subagent\n // identifier to filter on, unlike the Python wrapper's message-stream parent_tool_use_id).\n if (input?.agent_id) return;\n const p = client\n .trackToolCall({\n sessionId,\n toolName: input?.tool_name ?? 'unknown',\n toolInput: input?.tool_input ?? {},\n toolOutput: {},\n toolError: { message: input?.error ?? 'unknown error' },\n metadata: { tool_use_id: toolUseID },\n })\n .catch(() => {});\n pendingToolCalls.push(p);\n },\n ],\n };\n\n // Merge hooks: preserve existing user hooks, append ours\n const mergedHooks: Record<string, HookCallbackMatcher[]> = {\n ...(options.hooks ?? {}),\n };\n\n const existingPostToolUse = mergedHooks.PostToolUse ?? [];\n mergedHooks.PostToolUse = [...existingPostToolUse, sentrialToolHook];\n\n const existingPostToolUseFailure = mergedHooks.PostToolUseFailure ?? [];\n mergedHooks.PostToolUseFailure = [...existingPostToolUseFailure, sentrialToolFailureHook];\n\n const mergedOptions: QueryOptions = {\n ...options,\n hooks: mergedHooks,\n };\n\n // Call the original query function with merged options\n const generator = queryFn({ prompt, options: mergedOptions });\n\n // Wrap the generator to observe messages\n let completed = false;\n const tracked = (async function* () {\n try {\n for await (const message of generator) {\n // --- Observe init message: create session ---\n if (message.type === 'system' && message.subtype === 'init') {\n const metadata: Record<string, unknown> = {\n model: message.model,\n tools: message.tools,\n cwd: message.cwd,\n mcp_servers: message.mcp_servers,\n sdk_session_id: message.session_id,\n ...(extraMetadata ?? {}),\n };\n\n // Await session creation so sessionId is available for tool call hooks\n try {\n sessionId = await client.createSession({\n name: sessionName,\n agentName: defaultAgent,\n userId,\n convoId,\n metadata,\n // First-class model field (also kept in metadata alongside tools/cwd context).\n model: typeof message.model === 'string' ? message.model : undefined,\n });\n } catch {\n // Degrade to passthrough if session creation fails\n sessionId = null;\n }\n // Unblock hooks waiting for session creation (success or failure)\n resolveSessionReady!();\n }\n\n // --- Observe result message: complete session ---\n // Await completeSession here because result is the terminal message —\n // no more yields follow, and short-lived processes may exit immediately.\n // Streaming-input mode emits one result message PER TURN in a single query() stream\n // — guard so only the first completes the session (mirrors the Python guard).\n if (message.type === 'result' && sessionId && !completed) {\n completed = true;\n const isError = !!message.is_error;\n const cacheCreation =\n (message.usage as { cache_creation_input_tokens?: number } | undefined)\n ?.cache_creation_input_tokens ?? 0;\n const cacheRead =\n (message.usage as { cache_read_input_tokens?: number } | undefined)\n ?.cache_read_input_tokens ?? 0;\n // Cache-inclusive input + completion via the canonical home in cost.ts\n // (extractAnthropicUsage → anthropicCacheInclusiveInputTokens), so this wrapper can't\n // drift from extractAnthropicUsage / the streaming wrapper and gets the stricter\n // finite-guard for free. NOTE (documented scope mismatch): usage counts only the\n // top-level agent loop while total_cost_usd includes subagents — whole-tree token\n // accounting would need modelUsage, which we don't read.\n const { promptTokens: inputTokens, completionTokens: outputTokens } =\n extractAnthropicUsage(message.usage as Record<string, unknown> | undefined);\n\n let failureReason: string | undefined;\n if (isError) {\n const apiErrorStatus = (message as { api_error_status?: number | null })\n .api_error_status;\n // Array.isArray (not just truthy): `completed` is already true, so a wrong-shaped\n // `errors` (e.g. a string from a CLI version mismatch) reaching `.join` would throw\n // AFTER the guards, rethrowing into the user's for-await AND orphaning the session.\n if (Array.isArray(message.errors) && message.errors.length > 0) {\n failureReason = message.errors.join('; ');\n } else if (apiErrorStatus != null) {\n // Since CLI v2.1.110 a failing API call yields is_error=true with\n // subtype \"success\" and the HTTP status in api_error_status.\n failureReason = `API error ${apiErrorStatus}`;\n } else {\n failureReason = message.subtype;\n }\n }\n\n // Denied tools never execute, so neither PostToolUse hook fires — the result\n // message's permission_denials list is the only record. Track them as errored\n // tool calls (parity with the Python wrapper's is_error tool_result path).\n const denials = (message as { permission_denials?: Array<Record<string, unknown>> })\n .permission_denials;\n if (Array.isArray(denials)) {\n for (const denial of denials) {\n // Per-entry guard (mirrors the Array.isArray(errors) defense above): `completed`\n // is already true, so a hostile/malformed entry (e.g. a null from a CLI version\n // mismatch) whose property access threw would escape the completeSession try/catch\n // to the outer catch — which, seeing !completed=false, would skip finalization and\n // rethrow into the consumer AND orphan the session.\n if (!denial || typeof denial !== 'object') continue;\n const p = client\n .trackToolCall({\n sessionId,\n toolName: (denial.tool_name as string) ?? 'unknown',\n toolInput: (denial.tool_input as Record<string, unknown>) ?? {},\n toolOutput: {},\n toolError: { message: 'Permission denied' },\n metadata: denial.tool_use_id ? { tool_use_id: denial.tool_use_id } : undefined,\n })\n .catch(() => {});\n pendingToolCalls.push(p);\n }\n }\n\n // Flush pending tool call tracking before completing the session, then clear the\n // buffer so it can't retain settled promises for the life of a persistent query.\n await Promise.allSettled(pendingToolCalls);\n pendingToolCalls.length = 0;\n\n try {\n await client.completeSession({\n sessionId,\n success: !isError,\n failureReason,\n estimatedCost: message.total_cost_usd,\n promptTokens: inputTokens,\n completionTokens: outputTokens,\n totalTokens: inputTokens + outputTokens,\n durationMs: message.duration_ms ?? Date.now() - startTime,\n userInput: typeof prompt === 'string' ? prompt : undefined,\n output: message.result,\n customMetrics: {\n num_turns: message.num_turns ?? 0,\n duration_api_ms: message.duration_api_ms ?? 0,\n // Cache splits, tracked separately per the cost-tracking docs.\n ...(cacheCreation ? { cache_creation_input_tokens: cacheCreation } : {}),\n ...(cacheRead ? { cache_read_input_tokens: cacheRead } : {}),\n },\n });\n } catch {\n // Swallow — never break the agent due to tracking failures\n }\n }\n\n yield message;\n }\n } catch (error) {\n // If the generator throws, complete the session as failed\n if (sessionId && !completed) {\n completed = true;\n await Promise.allSettled(pendingToolCalls);\n try {\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: error instanceof Error ? error.message : String(error),\n durationMs: Date.now() - startTime,\n // Preserve the prompt on failed runs too (parity with the result path) — the\n // failed/interrupted sessions are exactly the ones most useful for debugging.\n userInput: typeof prompt === 'string' ? prompt : undefined,\n });\n } catch {\n // Never break the agent due to tracking failures\n }\n }\n throw error;\n } finally {\n // Early close (consumer breaks out of for-await / .return()) skips both the result\n // branch and the catch — complete the session here so it can't dangle as running\n // (mirrors the Python _track_message_stream finally guard).\n if (sessionId && !completed) {\n completed = true;\n await Promise.allSettled(pendingToolCalls);\n try {\n await client.completeSession({\n sessionId,\n success: false,\n failureReason: 'Session interrupted (generator closed early)',\n durationMs: Date.now() - startTime,\n userInput: typeof prompt === 'string' ? prompt : undefined,\n });\n } catch {\n // Never break the agent due to tracking failures\n }\n }\n }\n })();\n\n // The Agent SDK's query() returns a Query — an AsyncGenerator carrying control methods\n // (interrupt, setPermissionMode, setModel, supportedModels, mcpServerStatus, ...).\n // Returning the bare wrapper generator would strip them (mirrors the Python proxy\n // methods on _tracked_generator), so delegate everything except iteration to the\n // underlying Query.\n return new Proxy(tracked, {\n get(target, prop) {\n if (prop in target) {\n const value = Reflect.get(target, prop);\n return typeof value === 'function' ? value.bind(target) : value;\n }\n const value = Reflect.get(generator as object, prop);\n return typeof value === 'function' ? value.bind(generator) : value;\n },\n has(target, prop) {\n return prop in target || prop in (generator as object);\n },\n }) as AsyncGenerator<any, void>;\n };\n}\n","/**\n * OpenAI Agents SDK Integration for Sentrial\n *\n * Observes the OpenAI Agents SDK's (`@openai/agents`) tracing pipeline and maps\n * every trace onto a Sentrial session via the `begin()`/Interaction API:\n *\n * - **trace start** → `begin()` (agent/event name from the root agent span,\n * falling back to the trace's workflow name; session title from the trace name)\n * - **generation / response spans** → per-call LLM events (`llm:{model}` tool name\n * convention, prompt/completion tokens from usage, cost via the pricing tables,\n * duration from the span's start/end timestamps)\n * - **function spans** → `trackToolCall` (name, parsed args, result, errors)\n * - **handoff spans** → `trackDecision` (\"Handoff from X to Y\")\n * - **guardrail spans** → `trackError` when triggered, `trackDecision` when passed\n * - **trace end** → `finish()` (aggregated tokens/cost, last LLM output as the\n * session output, failure state from root agent span errors / triggered guardrails)\n *\n * Distinction from {@link wrapOpenAI}:\n * `wrapOpenAI` wraps a RAW `openai` client instance so each LLM call is tracked\n * inside a session YOU manage with `begin()`. This module instead observes the\n * OpenAI **Agents SDK** runtime (`Agent` + `run`) from the outside via its\n * documented tracing-processor hook — each agent run becomes a full Sentrial\n * session automatically, including tool calls, handoffs, and guardrails. If you\n * use the Agents SDK, prefer this module; don't combine both on the same run or\n * LLM calls will be double-counted.\n *\n * Verified against the official OpenAI Agents JS SDK\n * (https://openai.github.io/openai-agents-js/ and the `@openai/agents-core`\n * tracing sources): `TracingProcessor` is `onTraceStart/onTraceEnd/onSpanStart/\n * onSpanEnd(…): Promise<void>` + `shutdown(timeout?)`/`forceFlush()`; `Trace`\n * exposes camelCase `traceId/name/groupId/metadata`; `Span` exposes\n * `traceId/spanId/parentId/startedAt/endedAt/error/spanData` with ISO-string\n * timestamps and a `SpanError { message, data? }`; `SpanData` fields are\n * snake_case (`from_agent`, `to_agent`, `mcp_data`, usage `input_tokens`/\n * `output_tokens`; `ResponseSpanData` carries the raw Responses API payload on\n * `_response`). Registration is `addTraceProcessor(processor)` (additive — the\n * default OpenAI-dashboard exporter keeps running alongside).\n *\n * The `@openai/agents` package stays an optional peer dependency: like\n * `wrapAISDK(ai)`, the module never imports it — you hand `addTraceProcessor`\n * (or the module namespace that carries it) to {@link instrumentOpenAIAgents}.\n *\n * @example\n * ```ts\n * import { Agent, run, addTraceProcessor } from '@openai/agents';\n * import { sentrial, instrumentOpenAIAgents } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: process.env.SENTRIAL_API_KEY });\n * instrumentOpenAIAgents({ addTraceProcessor }, { userId: 'user_123' });\n *\n * const agent = new Agent({ name: 'Assistant', instructions: 'Be helpful.' });\n * const result = await run(agent, 'Write a haiku about recursion.');\n * // The run is now a Sentrial session with LLM events, tool calls, handoffs.\n * ```\n *\n * @packageDocumentation\n */\n\nimport { SentrialClient, Interaction } from './client.js';\nimport { calculateCostByProvider } from './cost.js';\n\n// ============================================================================\n// Duck types for the Agents SDK tracing surface (kept type-only — @openai/agents\n// is an optional peer dependency, mirroring vercel.ts's AIModule approach)\n// ============================================================================\n\n/** The subset of `Trace` this processor reads (camelCase, per agents-core). */\nexport type AgentsTrace = {\n traceId?: string;\n name?: string;\n groupId?: string | null;\n metadata?: Record<string, unknown>;\n};\n\n/** `SpanError` shape (`span.error`). */\nexport type AgentsSpanError = {\n message?: string;\n data?: Record<string, unknown>;\n};\n\n/** The subset of `Span` this processor reads (camelCase getters, ISO timestamps). */\nexport type AgentsSpan = {\n traceId?: string;\n spanId?: string;\n parentId?: string | null;\n startedAt?: string | null;\n endedAt?: string | null;\n error?: AgentsSpanError | null;\n spanData?: Record<string, unknown> & { type?: string };\n};\n\n/** The one entry point we need from `@openai/agents` / `@openai/agents-core`. */\nexport type AgentsModuleLike = {\n addTraceProcessor: (processor: unknown) => void;\n};\n\nexport interface OpenAIAgentsProcessorOptions {\n /** Explicit SentrialClient. Defaults to a client from the environment (SENTRIAL_API_KEY). */\n client?: SentrialClient;\n /** Explicit agent/event name override (defaults to the root agent span's name per run). */\n defaultAgent?: string;\n /** User ID to associate with sessions (default 'anonymous'). */\n userId?: string;\n /** Extra metadata merged into every session. */\n extraMetadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Safe coercion helpers (monitoring-never-crashes contract; see vercel.ts twins)\n// ============================================================================\n\n/** String() that never throws — hostile toString degrades to '[unserializable]'. */\nfunction safeString(value: unknown): string {\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/** Read a property without letting a hostile getter/Proxy trap throw. */\nfunction safeGet(obj: unknown, key: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n try {\n return (obj as Record<string, unknown>)[key];\n } catch {\n return undefined;\n }\n}\n\n/** Coerce to a non-empty exact string, else undefined. */\nfunction cleanString(value: unknown, limit = 500): string | undefined {\n if (value === null || value === undefined) return undefined;\n const s = safeString(value).slice(0, limit);\n return s.length > 0 ? s : undefined;\n}\n\n/** Coerce a token count to a finite non-negative number, else 0. */\nfunction safeCount(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0;\n}\n\n/** Shallow-copy a metadata-ish object; hostile shapes degrade to the marker. */\nfunction safeRecordCopy(value: unknown): Record<string, unknown> {\n if (value === null || value === undefined) return {};\n if (typeof value !== 'object') return {};\n try {\n return { ...(value as Record<string, unknown>) };\n } catch {\n return { _unserializable: true };\n }\n}\n\n/** Parse a span's ISO timestamp to ms epoch (the `startedAt` param convention). */\nfunction parseIsoMs(value: unknown): number | undefined {\n if (typeof value !== 'string' || !value) return undefined;\n const ms = Date.parse(value);\n return Number.isFinite(ms) ? ms : undefined;\n}\n\nfunction spanTiming(span: AgentsSpan): { startedAt?: number; durationMs?: number } {\n const startedAt = parseIsoMs(safeGet(span, 'startedAt'));\n const endedAt = parseIsoMs(safeGet(span, 'endedAt'));\n const durationMs =\n startedAt !== undefined && endedAt !== undefined ? Math.max(0, endedAt - startedAt) : undefined;\n return { startedAt, durationMs };\n}\n\n/** Extract `span.error` as a toolError-shaped record, or undefined. */\nfunction spanError(span: AgentsSpan): Record<string, unknown> | undefined {\n const error = safeGet(span, 'error');\n if (!error) return undefined;\n const message = cleanString(safeGet(error, 'message')) ?? 'Unknown error';\n const data = safeRecordCopy(safeGet(error, 'data'));\n return Object.keys(data).length > 0 ? { message, data } : { message };\n}\n\n/** Max characters of LLM/tool content folded into an event (LangChain-parity cap). */\nconst CONTENT_PREVIEW_CHARS = 2000;\n\n// Cap on concurrently-tracked traces. A trace whose onTraceEnd never fires\n// (aborted/crashed run, host/exporter error before trace end) would otherwise\n// leak its TraceState for the process lifetime. Mirrors google-adk.ts's\n// MAX_INFLIGHT_RUNS guard so both processors bound in-flight state identically.\nconst MAX_INFLIGHT_TRACES = 10_000;\n\n/** FunctionSpanData.input is the tool's arguments as a JSON string — decode when possible. */\nfunction parseToolInput(value: unknown): Record<string, unknown> {\n if (value === null || value === undefined) return {};\n if (typeof value === 'object' && !Array.isArray(value)) {\n return safeRecordCopy(value);\n }\n if (typeof value === 'string') {\n if (!value) return {};\n try {\n const parsed = JSON.parse(value);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return { input: parsed };\n } catch {\n return { input: value.slice(0, CONTENT_PREVIEW_CHARS) };\n }\n }\n return { input: safeString(value).slice(0, CONTENT_PREVIEW_CHARS) };\n}\n\n/** Coerce FunctionSpanData.output to the record shape trackToolCall expects. */\nfunction wrapToolOutput(value: unknown): Record<string, unknown> {\n if (value === null || value === undefined) return {};\n if (typeof value === 'object' && !Array.isArray(value)) {\n return safeRecordCopy(value);\n }\n return { output: safeString(value).slice(0, CONTENT_PREVIEW_CHARS) };\n}\n\n/** Last textual assistant content from a GenerationSpanData.output message array. */\nfunction lastMessageText(output: unknown): string | undefined {\n if (!Array.isArray(output)) return undefined;\n for (let i = output.length - 1; i >= 0; i--) {\n const content = safeGet(output[i], 'content');\n if (typeof content === 'string' && content) {\n return content.slice(0, CONTENT_PREVIEW_CHARS);\n }\n }\n return undefined;\n}\n\n/**\n * First textual prompt from a GenerationSpanData.input / Responses input array.\n * Prefers the first `user`-role message, else the first message with string\n * content; a bare string input is returned as-is. Mirrors `lastMessageText`\n * (string content only) so structured content-part inputs degrade to `undefined`\n * rather than throwing.\n */\nfunction firstMessageText(messages: unknown): string | undefined {\n if (typeof messages === 'string')\n return messages ? messages.slice(0, CONTENT_PREVIEW_CHARS) : undefined;\n if (!Array.isArray(messages)) return undefined;\n let fallback: string | undefined;\n for (const message of messages) {\n const content = safeGet(message, 'content');\n if (typeof content !== 'string' || !content) continue;\n if (safeGet(message, 'role') === 'user') return content.slice(0, CONTENT_PREVIEW_CHARS);\n if (fallback === undefined) fallback = content.slice(0, CONTENT_PREVIEW_CHARS);\n }\n return fallback;\n}\n\n// ============================================================================\n// Per-trace accumulator\n// ============================================================================\n\ntype TraceState = {\n traceId: string;\n traceName?: string;\n groupId?: string;\n traceMetadata: Record<string, unknown>;\n /** Once-only begin (stored as a promise so concurrent spans share one session). */\n interactionPromise: Promise<Interaction> | null;\n rootAgent?: string;\n promptTokens: number;\n completionTokens: number;\n totalCost: number;\n llmCalls: number;\n toolCalls: number;\n handoffs: number;\n lastOutput?: string;\n failureReason?: string;\n};\n\n// ============================================================================\n// Processor\n// ============================================================================\n\n/**\n * Duck-typed `TracingProcessor` (from `@openai/agents-core`) that mirrors OpenAI\n * Agents SDK runs into Sentrial sessions.\n *\n * Register it via {@link instrumentOpenAIAgents}, or directly:\n * ```ts\n * import { addTraceProcessor } from '@openai/agents';\n * addTraceProcessor(new SentrialOpenAIAgentsProcessor({ client }));\n * ```\n *\n * Every callback body is containment-wrapped: the Agents SDK awaits these\n * methods inline on the user's run path, so a tracking failure must never\n * reject into the host agent — it is swallowed instead (the SentrialClient/\n * Interaction methods additionally carry their own containment seam).\n *\n * Performance tip: construct the client with `batching: { enabled: true }` so\n * `track*` calls enqueue instead of awaiting a network round-trip per span.\n * (The TS BatcherConfig takes an object — a bare `batching: true` is silently\n * ignored; the Python SDK's `batching=True` bool shorthand has no TS twin.)\n */\nexport class SentrialOpenAIAgentsProcessor {\n private clientRef?: SentrialClient;\n private readonly defaultAgent?: string;\n private readonly userId: string;\n private readonly extraMetadata: Record<string, unknown>;\n private readonly traces = new Map<string, TraceState>();\n private tracesCapWarned = false;\n\n constructor(options: OpenAIAgentsProcessorOptions = {}) {\n this.clientRef = options.client;\n this.defaultAgent = options.defaultAgent;\n this.userId = options.userId ?? 'anonymous';\n this.extraMetadata = safeRecordCopy(options.extraMetadata);\n }\n\n // -- TracingProcessor interface (all containment-wrapped) ------------------\n\n /** Optional TracingProcessor lifecycle hook — nothing to start. */\n start(): void {}\n\n async onTraceStart(trace: AgentsTrace): Promise<void> {\n try {\n this.handleTraceStart(trace);\n } catch {\n /* tracking must never crash the agent run */\n }\n }\n\n async onTraceEnd(trace: AgentsTrace): Promise<void> {\n try {\n await this.handleTraceEnd(trace);\n } catch {\n /* contained */\n }\n }\n\n async onSpanStart(span: AgentsSpan): Promise<void> {\n try {\n await this.handleSpanStart(span);\n } catch {\n /* contained */\n }\n }\n\n async onSpanEnd(span: AgentsSpan): Promise<void> {\n try {\n await this.handleSpanEnd(span);\n } catch {\n /* contained */\n }\n }\n\n /**\n * Called at application stop: finishes any still-open sessions as interrupted\n * and flushes the client's event queue (if batching).\n */\n async shutdown(_timeout?: number): Promise<void> {\n try {\n const states = Array.from(this.traces.values());\n this.traces.clear();\n for (const state of states) {\n if (state.interactionPromise) {\n // Per-state containment (mirrors the Python twin's per-state safe_track):\n // one rejecting finish (e.g. a failSilently:false client) must not abort\n // the remaining states or skip the flush below.\n try {\n await this.finishState(state, 'Trace not ended before processor shutdown');\n } catch {\n /* contained — keep finishing the other dangling states */\n }\n }\n }\n await this.flushClient();\n } catch {\n /* contained */\n }\n }\n\n /** Forces immediate processing of queued events (no-op without batching). */\n async forceFlush(): Promise<void> {\n try {\n await this.flushClient();\n } catch {\n /* contained */\n }\n }\n\n // -- Internals -------------------------------------------------------------\n\n private resolveClient(): SentrialClient {\n // Explicit client, or an env-configured one (SENTRIAL_API_KEY) constructed\n // lazily on first use and reused for the processor's lifetime.\n return (this.clientRef ??= new SentrialClient());\n }\n\n private async flushClient(): Promise<void> {\n const client = this.resolveClient();\n if (typeof client.flush === 'function') await client.flush();\n }\n\n private handleTraceStart(trace: AgentsTrace): void {\n const traceId = cleanString(safeGet(trace, 'traceId'));\n if (!traceId) return;\n const groupId = safeGet(trace, 'groupId');\n // Bound the map so a trace whose onTraceEnd never fires (crash/abort mid-run)\n // can't grow it forever. Map iteration is insertion order, so the first key is\n // the oldest. Warn once — a leaking host would otherwise spam every new trace.\n if (this.traces.size >= MAX_INFLIGHT_TRACES) {\n const oldest = this.traces.keys().next().value;\n if (oldest !== undefined) this.traces.delete(oldest);\n if (!this.tracesCapWarned) {\n this.tracesCapWarned = true;\n console.warn(\n `[sentrial] SentrialOpenAIAgentsProcessor is tracking over ${MAX_INFLIGHT_TRACES} traces; ` +\n `evicting the oldest. A trace whose onTraceEnd never fires leaks its state — this warns once.`\n );\n }\n }\n this.traces.set(traceId, {\n traceId,\n traceName: cleanString(safeGet(trace, 'name'), 200),\n groupId: typeof groupId === 'string' && groupId ? groupId : undefined,\n traceMetadata: safeRecordCopy(safeGet(trace, 'metadata')),\n interactionPromise: null,\n promptTokens: 0,\n completionTokens: 0,\n totalCost: 0,\n llmCalls: 0,\n toolCalls: 0,\n handoffs: 0,\n });\n }\n\n private stateFor(spanOrTrace: unknown): TraceState | undefined {\n const traceId = cleanString(safeGet(spanOrTrace, 'traceId'));\n return traceId ? this.traces.get(traceId) : undefined;\n }\n\n /**\n * Begin the Sentrial session for this trace exactly once. Event/agent name\n * preference: explicit `defaultAgent` > root agent span name > trace workflow\n * name > 'openai-agents'. The session title is the trace's workflow name.\n *\n * DELIBERATE cross-SDK divergence on begin failure: the rejected promise is\n * cached, so a trace whose begin() failed never retries (avoids a partial\n * session whose earlier spans were lost). The Python twin leaves\n * `interaction=None` and retries at the next span (favoring late recovery).\n * Both are documented choices, not drift.\n */\n private ensureInteraction(state: TraceState, agentHint?: string): Promise<Interaction> {\n if (!state.interactionPromise) {\n const event =\n this.defaultAgent ?? agentHint ?? state.rootAgent ?? state.traceName ?? 'openai-agents';\n const metadata: Record<string, unknown> = {\n ...this.extraMetadata,\n ...state.traceMetadata,\n integration: 'openai-agents',\n // Cross-reference to the OpenAI traces dashboard entry for this run.\n trace_id: state.traceId,\n };\n state.interactionPromise = this.resolveClient().begin({\n userId: this.userId,\n event,\n convoId: state.groupId,\n metadata,\n name: state.traceName,\n });\n }\n return state.interactionPromise;\n }\n\n private async handleSpanStart(span: AgentsSpan): Promise<void> {\n const state = this.stateFor(span);\n if (!state) return;\n const spanData = safeGet(span, 'spanData');\n if (safeGet(spanData, 'type') !== 'agent') return;\n const name = cleanString(safeGet(spanData, 'name'), 200);\n // The FIRST agent span is the run's starting agent — remember it and begin\n // the session under its name (handoff successors no-op here).\n if (name && !state.rootAgent) state.rootAgent = name;\n await this.ensureInteraction(state, name);\n }\n\n private async handleSpanEnd(span: AgentsSpan): Promise<void> {\n const state = this.stateFor(span);\n if (!state) return;\n const spanData = safeGet(span, 'spanData');\n const type = safeGet(spanData, 'type');\n\n if (type === 'generation') {\n const usage = safeRecordCopy(safeGet(spanData, 'usage'));\n await this.trackLLMEvent(state, span, {\n model: cleanString(safeGet(spanData, 'model'), 200),\n promptTokens: safeCount(usage.input_tokens) || safeCount(usage.prompt_tokens),\n completionTokens: safeCount(usage.output_tokens) || safeCount(usage.completion_tokens),\n content: lastMessageText(safeGet(spanData, 'output')),\n input: firstMessageText(safeGet(spanData, 'input')),\n });\n } else if (type === 'response') {\n // The DEFAULT Responses-API model path: the raw API payload rides on\n // `_response` (snake_case usage; `output_text` is openai-node's aggregate).\n const response = safeGet(spanData, '_response');\n const usage = safeRecordCopy(safeGet(response, 'usage'));\n const promptTokens = safeCount(usage.input_tokens);\n const completionTokens = safeCount(usage.output_tokens);\n // == null: a synthetic null payload is as empty as an absent one (matches\n // the Python twin's `is None` skip — no phantom llm:unknown event).\n if (response == null && !promptTokens && !completionTokens) return;\n await this.trackLLMEvent(state, span, {\n model: cleanString(safeGet(response, 'model'), 200),\n promptTokens,\n completionTokens,\n content: cleanString(safeGet(response, 'output_text'), CONTENT_PREVIEW_CHARS),\n // ResponseSpanData exposes the request input on the underscore-prefixed\n // `_input` (like `_response` above), NOT a public `input` field — reading\n // `input` here would always be undefined and silently drop the prompt.\n input: firstMessageText(safeGet(spanData, '_input')),\n });\n } else if (type === 'function') {\n await this.trackFunction(state, span, spanData);\n } else if (type === 'handoff') {\n await this.trackHandoff(state, span, spanData);\n } else if (type === 'guardrail') {\n await this.trackGuardrail(state, span, spanData);\n } else if (type === 'agent') {\n // Agent spans carry no output, but a failed run attaches its SpanError to\n // the agent span — capture it as the session's failure reason.\n const error = spanError(span);\n if (error && !state.failureReason) {\n state.failureReason = safeString(error.message);\n }\n }\n // Other span types (custom, mcp_tools, speech, transcription, speech_group,\n // task, turn) carry no fields Sentrial models — ignored.\n }\n\n private async trackLLMEvent(\n state: TraceState,\n span: AgentsSpan,\n llm: {\n model?: string;\n promptTokens: number;\n completionTokens: number;\n content?: string;\n input?: string;\n }\n ): Promise<void> {\n const ix = await this.ensureInteraction(state);\n const cost =\n llm.promptTokens || llm.completionTokens\n ? calculateCostByProvider('', llm.model ?? '', llm.promptTokens, llm.completionTokens)\n : 0;\n\n state.llmCalls += 1;\n state.promptTokens += llm.promptTokens;\n state.completionTokens += llm.completionTokens;\n state.totalCost += cost;\n if (llm.content) state.lastOutput = llm.content;\n\n const { startedAt, durationMs } = spanTiming(span);\n await ix.trackToolCall({\n toolName: `llm:${llm.model ?? 'unknown'}`,\n toolInput: llm.input ? { model: llm.model, input: llm.input } : { model: llm.model },\n toolOutput: {\n content: llm.content ?? '',\n tokens: { prompt: llm.promptTokens, completion: llm.completionTokens },\n },\n toolError: spanError(span),\n model: llm.model,\n promptTokens: llm.promptTokens || undefined,\n completionTokens: llm.completionTokens || undefined,\n tokenCount: llm.promptTokens + llm.completionTokens || undefined,\n estimatedCost: cost,\n durationMs,\n startedAt,\n metadata: { span_id: cleanString(safeGet(span, 'spanId')) },\n });\n }\n\n private async trackFunction(\n state: TraceState,\n span: AgentsSpan,\n spanData: unknown\n ): Promise<void> {\n const ix = await this.ensureInteraction(state);\n state.toolCalls += 1;\n const { startedAt, durationMs } = spanTiming(span);\n const metadata: Record<string, unknown> = { span_id: cleanString(safeGet(span, 'spanId')) };\n const mcpData = safeGet(spanData, 'mcp_data');\n // The JS SDK documents mcp_data as a string; route it through safeString anyway\n // so a hostile non-conforming value degrades to the '[unserializable]' marker\n // instead of blowing up serialization downstream and dropping the event.\n if (mcpData !== undefined && mcpData !== null) metadata.mcp_data = safeString(mcpData);\n await ix.trackToolCall({\n toolName: cleanString(safeGet(spanData, 'name'), 200) ?? 'unknown_tool',\n toolInput: parseToolInput(safeGet(spanData, 'input')),\n toolOutput: wrapToolOutput(safeGet(spanData, 'output')),\n toolError: spanError(span),\n durationMs,\n startedAt,\n metadata,\n });\n }\n\n private async trackHandoff(\n state: TraceState,\n span: AgentsSpan,\n spanData: unknown\n ): Promise<void> {\n const ix = await this.ensureInteraction(state);\n state.handoffs += 1;\n const fromAgent = cleanString(safeGet(spanData, 'from_agent'), 200);\n const toAgent = cleanString(safeGet(spanData, 'to_agent'), 200) ?? 'unknown';\n const { startedAt, durationMs } = spanTiming(span);\n await ix.trackDecision({\n reasoning: fromAgent ? `Handoff from ${fromAgent} to ${toAgent}` : `Handoff to ${toAgent}`,\n durationMs,\n startedAt,\n metadata: {\n span_id: cleanString(safeGet(span, 'spanId')),\n from_agent: fromAgent,\n to_agent: toAgent,\n },\n });\n }\n\n private async trackGuardrail(\n state: TraceState,\n span: AgentsSpan,\n spanData: unknown\n ): Promise<void> {\n const ix = await this.ensureInteraction(state);\n const name = cleanString(safeGet(spanData, 'name'), 200) ?? 'unknown';\n // Boolean coercion never invokes user code in JS (unlike Python truthiness).\n const triggered = Boolean(safeGet(spanData, 'triggered'));\n const { startedAt, durationMs } = spanTiming(span);\n const metadata = {\n span_id: cleanString(safeGet(span, 'spanId')),\n guardrail: name,\n triggered,\n };\n if (triggered) {\n // A tripwire aborts the run (the SDK throws *GuardrailTripwireTriggered to\n // the caller) — record a first-class error; Interaction.trackError also\n // stores the failure so finish() defaults to success=false.\n await ix.trackError({\n errorMessage: `Guardrail '${name}' triggered`,\n errorType: 'guardrail_triggered',\n durationMs,\n startedAt,\n metadata,\n });\n } else {\n await ix.trackDecision({\n reasoning: `Guardrail '${name}' passed`,\n durationMs,\n startedAt,\n metadata,\n });\n }\n }\n\n private async handleTraceEnd(trace: AgentsTrace): Promise<void> {\n const traceId = cleanString(safeGet(trace, 'traceId'));\n if (!traceId) return;\n const state = this.traces.get(traceId);\n if (!state) return;\n this.traces.delete(traceId);\n // Begin-if-never-begun so even a span-less trace records a session.\n await this.ensureInteraction(state);\n await this.finishState(state);\n }\n\n private async finishState(state: TraceState, failureReason?: string): Promise<void> {\n if (!state.interactionPromise) return;\n const ix = await state.interactionPromise;\n const reason = failureReason ?? state.failureReason;\n const customMetrics: Record<string, number> = {};\n if (state.llmCalls) customMetrics.llm_calls = state.llmCalls;\n if (state.toolCalls) customMetrics.tool_calls = state.toolCalls;\n if (state.handoffs) customMetrics.handoffs = state.handoffs;\n const hasUsage = state.llmCalls > 0;\n await ix.finish({\n output: state.lastOutput,\n // `success` stays undefined on the clean path so finish() falls back to the\n // Interaction's stored state (false when a triggered guardrail trackError'd).\n success: reason !== undefined ? false : undefined,\n failureReason: reason,\n estimatedCost: hasUsage ? state.totalCost : undefined,\n promptTokens: hasUsage ? state.promptTokens : undefined,\n completionTokens: hasUsage ? state.completionTokens : undefined,\n totalTokens: hasUsage ? state.promptTokens + state.completionTokens : undefined,\n customMetrics: Object.keys(customMetrics).length > 0 ? customMetrics : undefined,\n });\n }\n}\n\n// Idempotency registry for instrumentOpenAIAgents: addTraceProcessor is ADDITIVE\n// on the Agents SDK side, so registering twice would mirror every run into TWO\n// Sentrial sessions. Keyed per (addTraceProcessor function, client) pair — the\n// function (not the wrapper object, which callers often build as a fresh literal)\n// identifies the agents namespace; `null` stands for the env-default client.\nlet registeredProcessors = new WeakMap<\n AgentsModuleLike['addTraceProcessor'],\n Map<SentrialClient | null, SentrialOpenAIAgentsProcessor>\n>();\nlet duplicateInstrumentWarned = false;\n\n/** Test hook: forget prior registrations (and re-arm the warn-once). */\nexport function _resetInstrumentationRegistry(): void {\n registeredProcessors = new WeakMap();\n duplicateInstrumentWarned = false;\n}\n\n/**\n * Construct a {@link SentrialOpenAIAgentsProcessor} and register it with the\n * OpenAI Agents SDK. Pass the SDK's `addTraceProcessor` (or any object carrying\n * it, e.g. the whole `@openai/agents` module namespace) — the peer dependency is\n * never imported here, mirroring `wrapAISDK(ai)`.\n *\n * Idempotent per (agents module, client) pair: calling this again with the same\n * `addTraceProcessor` and the same `options.client` (or again with no client —\n * the env-default) returns the FIRST registered processor instead of registering\n * a duplicate, which would mirror every run into two sessions. The first\n * duplicate call emits a console warning (silent thereafter).\n *\n * @example\n * ```ts\n * import * as agents from '@openai/agents';\n * import { instrumentOpenAIAgents } from '@sentrial/sdk';\n *\n * instrumentOpenAIAgents(agents, { userId: 'user_123' });\n * ```\n *\n * @param agents - The `@openai/agents` module namespace (or `{ addTraceProcessor }`).\n * @param options - Processor options (client, defaultAgent, userId, extraMetadata).\n * @returns The registered processor (keep it to call `shutdown()`/`forceFlush()`).\n */\nexport function instrumentOpenAIAgents(\n agents: AgentsModuleLike,\n options: OpenAIAgentsProcessorOptions = {}\n): SentrialOpenAIAgentsProcessor {\n const addTraceProcessor = agents?.addTraceProcessor;\n if (typeof addTraceProcessor !== 'function') {\n throw new TypeError(\n 'instrumentOpenAIAgents(agents, options) expects the @openai/agents module ' +\n '(or an object with its addTraceProcessor function) as the first argument. ' +\n \"Install the SDK with: npm install '@openai/agents'\"\n );\n }\n const clientKey = options.client ?? null;\n const byClient = registeredProcessors.get(addTraceProcessor);\n const existing = byClient?.get(clientKey);\n if (existing) {\n if (!duplicateInstrumentWarned) {\n duplicateInstrumentWarned = true;\n console.warn(\n 'Sentrial: instrumentOpenAIAgents() was already called for this agents module ' +\n 'and client — returning the existing processor (a second registration would ' +\n 'mirror every run into duplicate sessions).'\n );\n }\n return existing;\n }\n const processor = new SentrialOpenAIAgentsProcessor(options);\n addTraceProcessor(processor);\n const map = byClient ?? new Map<SentrialClient | null, SentrialOpenAIAgentsProcessor>();\n map.set(clientKey, processor);\n registeredProcessors.set(addTraceProcessor, map);\n return processor;\n}\n","/**\n * Google Agent Development Kit (ADK) Integration for Sentrial — TypeScript twin\n * of the Python `sentrial.google_adk` module, ported for `adk-js` (`@google/adk`).\n *\n * Maps a Google ADK **run** (one `Runner.runAsync` invocation) onto a single\n * Sentrial session via the `begin()`/Interaction API. The attachment surface is a\n * Runner-level **Plugin** — ADK's `BasePlugin` fires globally for the *entire*\n * invocation across *every* agent, tool, and model call, so one run → one Sentrial\n * session automatically (the ADK equivalent of the OpenAI Agents SDK's global\n * trace processor in {@link SentrialOpenAIAgentsProcessor}).\n *\n * Mapping (mirrors the Python google_adk.py):\n *\n * - **run start** (`beforeRunCallback`) → `begin()` — agent/event name from the\n * root agent, `convoId` from the ADK session id, `userId` from the ADK session\n * (overridable), input from the run's `userContent`.\n * - **model calls** (`beforeModelCallback` → `afterModelCallback`) → per-call LLM\n * event via `trackToolCall` (`llm:{model}` convention, model from\n * `LlmRequest.model`, prompt/completion tokens from `LlmResponse.usageMetadata`\n * — `promptTokenCount` / `candidatesTokenCount` — cost via the pricing tables,\n * duration from the before/after timestamps). Streaming *partial* responses are\n * skipped so tokens aren't double-counted; the final response carries the usage.\n * - **tool calls** (`beforeToolCallback` → `afterToolCallback`) → `trackToolCall`\n * (tool name/args/result, duration from before/after).\n * - **errors** (`onToolErrorCallback` / `onModelErrorCallback` and any\n * `LlmResponse.errorCode`) → `trackError` (the session finishes `success=false`).\n * - **run end** (`afterRunCallback`) → `finish()` with rolled-up tokens/cost and\n * the last model output; custom metrics `llm_calls` / `tool_calls`.\n *\n * Distinction from {@link wrapGoogle}:\n * `wrapGoogle` wraps a RAW `@google/genai` model so each `generateContent` call is\n * tracked inside a session YOU manage with `begin()`. This module instead observes\n * the ADK **runtime** (`Agent` + `Runner`) from the outside via its documented\n * Plugin hooks — each ADK run becomes a full Sentrial session automatically. If you\n * use ADK, prefer this module; don't combine both on the same run (LLM calls would\n * be double-counted).\n *\n * The `@google/adk` package stays an OPTIONAL peer dependency: this module NEVER\n * imports it. ADK's `PluginManager` dispatches to plugin callbacks BY NAME and\n * dedups by `.name`, and a plain object structurally satisfies `BasePlugin`, so a\n * duck-typed class needs no `BasePlugin` subclass — the module imports cleanly with\n * or without ADK installed. You attach it via `plugins: [new SentrialAdkPlugin()]`\n * at Runner construction, or after the fact with {@link instrumentAdk}.\n *\n * Verified against the official ADK-JS source (github.com/google/adk-js, package\n * `@google/adk`):\n * - `core/src/plugins/base_plugin.ts` — plugin callbacks take a SINGLE camelCase\n * params object (NOT Python's keyword args): `beforeRunCallback({invocationContext})`,\n * `afterRunCallback({invocationContext})`, `beforeModelCallback({callbackContext, llmRequest})`,\n * `afterModelCallback({callbackContext, llmResponse})`,\n * `onModelErrorCallback({callbackContext, llmRequest, error})`,\n * `beforeToolCallback({tool, toolArgs, toolContext})`,\n * `afterToolCallback({tool, toolArgs, toolContext, result})`,\n * `onToolErrorCallback({tool, toolArgs, toolContext, error})`. Returning a\n * non-`undefined` value SHORT-CIRCUITS the run, so every callback returns\n * `undefined` (pure observer).\n * - `core/src/plugins/plugin_manager.ts` — `runCallbacks` CATCHES a throwing\n * callback and RE-THROWS a wrapped error that HALTS the agent run; containment\n * inside every callback body is therefore mandatory. `registerPlugin` throws on a\n * duplicate `.name`; `getPlugin(name)` retrieves by name.\n * - `core/src/models/llm_response.ts` — `content?: Content` (`.parts[].text`),\n * `usageMetadata` (`promptTokenCount` / `candidatesTokenCount`), `partial`,\n * `errorCode` / `errorMessage`.\n * - `core/src/models/llm_request.ts` — `model?: string`.\n * - `core/src/agents/invocation_context.ts` — `invocationId`, `agent` (`.name`),\n * `session` (`.id` / `.appName` / `.userId`), `appName`/`userId` getters, `userContent`.\n * - `core/src/agents/readonly_context.ts` — the callback `Context` exposes getters\n * `invocationId` / `agentName` / `sessionId` / `userId` / `userContent` and the\n * public `invocationContext`.\n * - `core/src/runner/runner.ts` — `plugins?: BasePlugin[]` at construction;\n * `runner.pluginManager.registerPlugin(plugin)`.\n *\n * @example\n * ```ts\n * import { InMemoryRunner } from '@google/adk';\n * import { LlmAgent } from '@google/adk';\n * import { sentrial, SentrialAdkPlugin } from '@sentrial/sdk';\n *\n * sentrial.configure({ apiKey: process.env.SENTRIAL_API_KEY });\n *\n * const agent = new LlmAgent({ name: 'assistant', model: 'gemini-2.0-flash' });\n * const runner = new InMemoryRunner({ agent, plugins: [new SentrialAdkPlugin()] });\n * // Every runner.runAsync(...) is now a Sentrial session with LLM events,\n * // tool calls, and token/cost rollups.\n * ```\n *\n * @packageDocumentation\n */\n\nimport { SentrialClient, Interaction } from './client.js';\nimport { calculateCostByProvider } from './cost.js';\n\n// ============================================================================\n// Duck types for the ADK plugin surface (kept type-only — @google/adk is an\n// optional peer dependency, mirroring openai-agents.ts's AgentsModuleLike).\n// Every field is optional/unknown so a hostile or partial shape never breaks\n// type-checking; the safe accessors below defend at runtime.\n// ============================================================================\n\n/** The subset of ADK `Content` (`@google/genai`) this plugin reads. */\nexport type AdkContent = { parts?: Array<{ text?: unknown } | unknown> } | string | unknown;\n\n/** The subset of ADK `InvocationContext` this plugin reads (run lifecycle). */\nexport type AdkInvocationContext = {\n invocationId?: string;\n agent?: { name?: string };\n session?: { id?: string; appName?: string; userId?: string };\n appName?: string;\n userId?: string;\n userContent?: AdkContent;\n};\n\n/** The subset of the ADK callback `Context` this plugin reads (model/tool calls). */\nexport type AdkCallbackContext = {\n invocationId?: string;\n agentName?: string;\n sessionId?: string;\n userId?: string;\n userContent?: AdkContent;\n invocationContext?: AdkInvocationContext;\n};\n\n/** The subset of ADK `LlmRequest` this plugin reads. */\nexport type AdkLlmRequest = { model?: string };\n\n/** The subset of ADK `LlmResponse` this plugin reads. */\nexport type AdkLlmResponse = {\n content?: AdkContent;\n usageMetadata?: { promptTokenCount?: unknown; candidatesTokenCount?: unknown };\n partial?: unknown;\n errorCode?: unknown;\n errorMessage?: unknown;\n};\n\n/** The subset of ADK `BaseTool` this plugin reads. */\nexport type AdkTool = { name?: string };\n\n/** The Runner surface {@link instrumentAdk} needs (`runner.pluginManager.registerPlugin`). */\nexport type AdkRunnerLike = {\n pluginManager?: {\n registerPlugin?: (plugin: unknown) => void;\n getPlugin?: (name: string) => unknown;\n };\n};\n\nexport interface AdkPluginOptions {\n /** Explicit SentrialClient. Defaults to a client from the environment (SENTRIAL_API_KEY). */\n client?: SentrialClient;\n /** ADK plugin name (default 'sentrial'); ADK requires unique plugin names. */\n name?: string;\n /** Explicit agent/event name override (defaults to the root agent's name per run). */\n defaultAgent?: string;\n /** User ID override (defaults to the ADK session's userId, then 'anonymous'). */\n userId?: string;\n /** Extra metadata merged into every session. */\n extraMetadata?: Record<string, unknown>;\n}\n\n// Default ADK Plugin name. ADK's PluginManager dedups plugins by `.name` (a second\n// plugin with the same name throws), which is exactly the idempotency we want.\nconst PLUGIN_NAME = 'sentrial';\n\n/** Max characters of model/tool content folded into a per-call event (openai-agents parity). */\nconst CONTENT_PREVIEW_CHARS = 2000;\n\n// Cap on in-flight tool-timing entries per run so a tool whose after/error callback never\n// fires (host crash mid-tool) can't grow the map without bound (Python parity).\nconst MAX_INFLIGHT_TOOLS = 1000;\n\n// Cap on the per-run model-timing stack so a model call whose after/error callback never fires\n// (aborted/torn-down streaming run, or an ADK build lacking on_model_error) can't grow it without\n// bound — same evict-oldest-when-full policy as MAX_INFLIGHT_TOOLS. Python parity (_MAX_INFLIGHT_MODELS).\nconst MAX_INFLIGHT_MODELS = 1000;\n\n// Cap on live per-run states so a host that starts runs which never reach afterRunCallback\n// (crash/abort mid-run) can't grow the `runs` map without bound — same evict-oldest-when-full\n// policy as the per-run toolStarts cap above (Map iteration is insertion order, so the first key\n// is the oldest). Higher than MAX_INFLIGHT_TOOLS because this is the top-level cross-invocation\n// map, not a per-run one. Python parity (_MAX_INFLIGHT_RUNS).\nconst MAX_INFLIGHT_RUNS = 10_000;\n\nconst INSTALL_HINT =\n \"Install the ADK with: npm install '@google/adk' (the Plugin surface lives on the Runner).\";\n\n// ============================================================================\n// Safe coercion helpers (monitoring-never-crashes contract; twins of openai-agents.ts)\n// ============================================================================\n\n/** String() that never throws — hostile toString degrades to '[unserializable]'. */\nfunction safeString(value: unknown): string {\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/** Read a property without letting a hostile getter/Proxy trap throw. */\nfunction safeGet(obj: unknown, key: string): unknown {\n if (obj === null || obj === undefined) return undefined;\n try {\n return (obj as Record<string, unknown>)[key];\n } catch {\n return undefined;\n }\n}\n\n/** Coerce to a non-empty exact string, else undefined. */\nfunction cleanString(value: unknown, limit = 500): string | undefined {\n if (value === null || value === undefined) return undefined;\n const s = safeString(value).slice(0, limit);\n return s.length > 0 ? s : undefined;\n}\n\n/** Coerce a token count to a finite non-negative number, else 0. */\nfunction safeCount(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : 0;\n}\n\n/** Shallow-copy a metadata-ish object; hostile shapes degrade to the marker. */\nfunction safeRecordCopy(value: unknown): Record<string, unknown> {\n if (value === null || value === undefined) return {};\n if (typeof value !== 'object') return {};\n try {\n return { ...(value as Record<string, unknown>) };\n } catch {\n return { _unserializable: true };\n }\n}\n\n/** `type(error).name` without letting a hostile getter/metaclass throw. */\nfunction safeTypeName(error: unknown): string {\n const ctorName = safeGet(safeGet(error, 'constructor'), 'name');\n const name = cleanString(ctorName, 200);\n return name ?? 'Error';\n}\n\n/**\n * Extract the assistant text from an ADK `Content` (`.parts` where each part may\n * carry `.text`) or a plain string. Non-text parts (function calls/responses) are\n * skipped. Returns undefined when there is no usable text.\n */\nfunction contentText(content: unknown): string | undefined {\n if (content === null || content === undefined) return undefined;\n if (typeof content === 'string') return cleanString(content, CONTENT_PREVIEW_CHARS);\n const parts = safeGet(content, 'parts');\n if (!Array.isArray(parts)) return undefined;\n const chunks: string[] = [];\n for (const part of parts) {\n // Skip reasoning parts (Part.thought=true when includeThoughts is enabled); those are\n // surfaced separately via reasoningText so chain-of-thought maps to the reasoning field.\n if (safeGet(part, 'thought')) continue;\n const text = safeGet(part, 'text');\n if (typeof text === 'string' && text) chunks.push(text);\n }\n const joined = chunks.join('');\n return joined ? joined.slice(0, CONTENT_PREVIEW_CHARS) : undefined;\n}\n\n/**\n * The reasoning (chain-of-thought) text from an ADK `Content`: the `.text` of parts flagged\n * `thought: true` (present only when the model was called with `includeThoughts`). Returns\n * undefined when there is no reasoning part.\n */\nfunction reasoningText(content: unknown): string | undefined {\n if (content === null || content === undefined || typeof content === 'string') return undefined;\n const parts = safeGet(content, 'parts');\n if (!Array.isArray(parts)) return undefined;\n const chunks: string[] = [];\n for (const part of parts) {\n if (!safeGet(part, 'thought')) continue;\n const text = safeGet(part, 'text');\n if (typeof text === 'string' && text) chunks.push(text);\n }\n const joined = chunks.join('');\n return joined ? joined.slice(0, CONTENT_PREVIEW_CHARS) : undefined;\n}\n\n/**\n * `[promptTokens, completionTokens]` from an ADK `LlmResponse.usageMetadata`\n * (`promptTokenCount` / `candidatesTokenCount`). Degrades to `[0, 0]`.\n */\nfunction usageTokens(usageMetadata: unknown): [number, number] {\n if (usageMetadata === null || usageMetadata === undefined) return [0, 0];\n return [\n safeCount(safeGet(usageMetadata, 'promptTokenCount')),\n safeCount(safeGet(usageMetadata, 'candidatesTokenCount')),\n ];\n}\n\n/** Coerce an ADK tool result (`Record<string, unknown>` per the callback contract) to a record. */\nfunction wrapToolOutput(result: unknown): Record<string, unknown> {\n if (result === null || result === undefined) return {};\n if (typeof result === 'object' && !Array.isArray(result)) return safeRecordCopy(result);\n return { output: safeString(result).slice(0, CONTENT_PREVIEW_CHARS) };\n}\n\n// ============================================================================\n// Per-run accumulator\n// ============================================================================\n\ntype RunState = {\n invocationId: string;\n agentName?: string;\n convoId?: string;\n appName?: string;\n userId?: string;\n pendingInput?: string;\n /** Once-only begin (stored as a promise so concurrent callbacks share one session). */\n interactionPromise: Promise<Interaction> | null;\n finished: boolean;\n model?: string;\n promptTokens: number;\n completionTokens: number;\n totalCost: number;\n llmCalls: number;\n toolCalls: number;\n lastOutput?: string;\n failureReason?: string;\n /** Stack of {startedAt, model} pushed in beforeModel, popped in afterModel/onModelError.\n * LIFO correlation: under truly-concurrent ParallelAgent model calls the model-name/duration\n * LABEL can mis-correlate (a pop may pair with a different call's push); token/cost VALUES stay\n * correct (they ride the response). Inherent + matches the Python twin — accepted limitation. */\n modelStarts: Array<{ startedAt: number; model?: string }>;\n /** toolContext object → startedAt ms; the Context instance is stable across a tool's callbacks. */\n toolStarts: Map<unknown, number>;\n};\n\n// ============================================================================\n// Plugin\n// ============================================================================\n\n/**\n * Duck-typed Google ADK `BasePlugin` that mirrors ADK runs into Sentrial sessions.\n *\n * Register it on a `Runner`:\n * ```ts\n * const runner = new InMemoryRunner({ agent, plugins: [new SentrialAdkPlugin()] });\n * ```\n * or attach it to an existing runner with {@link instrumentAdk}.\n *\n * This is a *plain* class implementing the `BasePlugin` callback protocol — it is\n * intentionally NOT a `BasePlugin` subclass, because ADK's `PluginManager`\n * dispatches to callbacks by name and dedups by `.name`, so subclassing would force\n * a hard `@google/adk` import (breaking the \"optional peer\" contract and\n * import-without-ADK). Every callback body is containment-wrapped: ADK's\n * PluginManager re-throws a callback error and HALTS the run, so a tracking failure\n * must never escape — it is swallowed instead. Every callback returns `undefined`\n * (a pure observer never short-circuits the model/tool call).\n *\n * Performance tip: construct the client with `batching: { enabled: true }` so\n * `track*` calls enqueue instead of awaiting a network round-trip per event. ADK\n * invokes these callbacks inline on the run's event loop.\n */\nexport class SentrialAdkPlugin {\n /** PluginManager reads `.name` for dedup/logging — a real string, always present. */\n readonly name: string;\n private clientRef?: SentrialClient;\n private readonly defaultAgent?: string;\n private readonly userIdOption?: string;\n private readonly extraMetadata: Record<string, unknown>;\n private readonly runs = new Map<string, RunState>();\n /** One-time guard so the runs-cap eviction warns at most once (like the duplicate-instrument warn). */\n private runsCapWarned = false;\n\n constructor(options: AdkPluginOptions = {}) {\n this.name = cleanString(options.name, 200) ?? PLUGIN_NAME;\n this.clientRef = options.client;\n this.defaultAgent = options.defaultAgent;\n this.userIdOption = options.userId;\n this.extraMetadata = safeRecordCopy(options.extraMetadata);\n }\n\n // -- BasePlugin protocol (all containment-wrapped, all return undefined) ----\n\n async beforeRunCallback(params: { invocationContext: AdkInvocationContext }): Promise<undefined> {\n try {\n await this.handleRunStart(safeGet(params, 'invocationContext'));\n } catch {\n /* tracking must never crash the agent run */\n }\n return undefined;\n }\n\n async afterRunCallback(params: { invocationContext: AdkInvocationContext }): Promise<void> {\n try {\n await this.handleRunEnd(safeGet(params, 'invocationContext'));\n } catch {\n /* contained */\n }\n }\n\n async onRunErrorCallback(params: {\n invocationContext: AdkInvocationContext;\n error: unknown;\n }): Promise<void> {\n // ADK fires afterRunCallback ONLY on the success path; an unhandled exception that\n // aborts the run is routed here instead (adk-python `_notify_run_error`; adk-js mirrors it).\n // Without this, an errored run's begun session would be orphaned in status=running.\n try {\n await this.handleRunError(safeGet(params, 'invocationContext'), safeGet(params, 'error'));\n } catch {\n /* contained */\n }\n }\n\n async beforeModelCallback(params: {\n callbackContext: AdkCallbackContext;\n llmRequest: AdkLlmRequest;\n }): Promise<undefined> {\n try {\n await this.handleModelStart(\n safeGet(params, 'callbackContext'),\n safeGet(params, 'llmRequest')\n );\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n async afterModelCallback(params: {\n callbackContext: AdkCallbackContext;\n llmResponse: AdkLlmResponse;\n }): Promise<undefined> {\n try {\n await this.handleModelEnd(safeGet(params, 'callbackContext'), safeGet(params, 'llmResponse'));\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n async onModelErrorCallback(params: {\n callbackContext: AdkCallbackContext;\n llmRequest: AdkLlmRequest;\n error: unknown;\n }): Promise<undefined> {\n try {\n await this.handleModelError(\n safeGet(params, 'callbackContext'),\n safeGet(params, 'llmRequest'),\n safeGet(params, 'error')\n );\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n async beforeToolCallback(params: {\n tool: AdkTool;\n toolArgs: Record<string, unknown>;\n toolContext: AdkCallbackContext;\n }): Promise<undefined> {\n try {\n await this.handleToolStart(safeGet(params, 'toolContext'));\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n async afterToolCallback(params: {\n tool: AdkTool;\n toolArgs: Record<string, unknown>;\n toolContext: AdkCallbackContext;\n result: unknown;\n }): Promise<undefined> {\n try {\n await this.handleToolEnd(\n safeGet(params, 'tool'),\n safeGet(params, 'toolArgs'),\n safeGet(params, 'toolContext'),\n safeGet(params, 'result')\n );\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n async onToolErrorCallback(params: {\n tool: AdkTool;\n toolArgs: Record<string, unknown>;\n toolContext: AdkCallbackContext;\n error: unknown;\n }): Promise<undefined> {\n try {\n await this.handleToolError(\n safeGet(params, 'tool'),\n safeGet(params, 'toolArgs'),\n safeGet(params, 'toolContext'),\n safeGet(params, 'error')\n );\n } catch {\n /* contained */\n }\n return undefined;\n }\n\n // -- Client / registry -----------------------------------------------------\n\n private resolveClient(): SentrialClient {\n // Explicit client, or an env-configured one (SENTRIAL_API_KEY) constructed\n // lazily on first use and reused for the plugin's lifetime.\n return (this.clientRef ??= new SentrialClient());\n }\n\n /**\n * Fetch (or lazily create) the per-run state for an invocation id, back-filling\n * any run-identity fields not known when it was first created (a model/tool\n * callback can reach a run before beforeRun in some ADK flows).\n */\n private getOrCreateState(\n invocationId: string | undefined,\n fields: Partial<\n Pick<RunState, 'agentName' | 'convoId' | 'appName' | 'userId' | 'pendingInput'>\n > = {}\n ): RunState | undefined {\n if (!invocationId) return undefined;\n let state = this.runs.get(invocationId);\n if (!state) {\n // Bound the map: evict the oldest run if a prior run's afterRunCallback never fired\n // (crash/abort mid-run) so it can't grow forever. Map iteration is insertion order, so the\n // first key is the oldest. Warn once — a leaking host would otherwise spam every new run.\n if (this.runs.size >= MAX_INFLIGHT_RUNS) {\n const oldest = this.runs.keys().next().value;\n if (oldest !== undefined) this.runs.delete(oldest);\n if (!this.runsCapWarned) {\n this.runsCapWarned = true;\n console.warn(\n `[sentrial] SentrialAdkPlugin is tracking over ${MAX_INFLIGHT_RUNS} runs; evicting the ` +\n `oldest. A run whose afterRunCallback never fires leaks its state — this warns once.`\n );\n }\n }\n state = {\n invocationId,\n interactionPromise: null,\n finished: false,\n promptTokens: 0,\n completionTokens: 0,\n totalCost: 0,\n llmCalls: 0,\n toolCalls: 0,\n modelStarts: [],\n toolStarts: new Map(),\n };\n this.runs.set(invocationId, state);\n }\n if (state.agentName === undefined && fields.agentName) state.agentName = fields.agentName;\n if (state.convoId === undefined && fields.convoId) state.convoId = fields.convoId;\n if (state.appName === undefined && fields.appName) state.appName = fields.appName;\n if (state.userId === undefined && fields.userId) state.userId = fields.userId;\n if (state.pendingInput === undefined && fields.pendingInput)\n state.pendingInput = fields.pendingInput;\n return state;\n }\n\n private stateFromInvocationContext(ic: unknown): RunState | undefined {\n const invocationId = cleanString(safeGet(ic, 'invocationId'));\n if (!invocationId) return undefined;\n const agent = safeGet(ic, 'agent');\n const session = safeGet(ic, 'session');\n return this.getOrCreateState(invocationId, {\n agentName: cleanString(safeGet(agent, 'name'), 200),\n convoId: cleanString(safeGet(session, 'id'), 200),\n appName:\n cleanString(safeGet(session, 'appName'), 200) ?? cleanString(safeGet(ic, 'appName'), 200),\n userId:\n cleanString(safeGet(session, 'userId'), 200) ?? cleanString(safeGet(ic, 'userId'), 200),\n pendingInput: contentText(safeGet(ic, 'userContent')),\n });\n }\n\n private stateFromCallbackContext(cc: unknown): RunState | undefined {\n const invocationId = cleanString(safeGet(cc, 'invocationId'));\n if (!invocationId) return undefined;\n const ic = safeGet(cc, 'invocationContext');\n const session = safeGet(ic, 'session');\n return this.getOrCreateState(invocationId, {\n agentName: cleanString(safeGet(cc, 'agentName'), 200),\n convoId:\n cleanString(safeGet(cc, 'sessionId'), 200) ?? cleanString(safeGet(session, 'id'), 200),\n appName:\n cleanString(safeGet(ic, 'appName'), 200) ?? cleanString(safeGet(session, 'appName'), 200),\n userId: cleanString(safeGet(cc, 'userId'), 200),\n pendingInput: contentText(safeGet(cc, 'userContent')),\n });\n }\n\n /**\n * Begin the Sentrial session for this run exactly once. Event/agent name\n * preference: explicit `defaultAgent` > root agent name > 'google-adk'. The ADK\n * session id becomes `convoId`; the ADK session `userId` (or the configured\n * override) becomes the Sentrial user.\n *\n * DELIBERATE (matches the openai-agents.ts TS twin): the interaction promise is\n * cached, so a run whose `begin()` REJECTED never retries (avoids a partial\n * session whose earlier events were lost). The Python google_adk leaves the\n * interaction unset and retries at the next callback — both are documented,\n * per-SDK choices, not drift.\n */\n private ensureInteraction(state: RunState): Promise<Interaction> {\n if (!state.interactionPromise) {\n const event = this.defaultAgent ?? state.agentName ?? 'google-adk';\n const metadata: Record<string, unknown> = {\n ...this.extraMetadata,\n integration: 'google-adk',\n invocation_id: state.invocationId,\n };\n if (state.appName) metadata.app_name = state.appName;\n state.interactionPromise = this.resolveClient().begin({\n userId: this.userIdOption ?? state.userId ?? 'anonymous',\n event,\n input: state.pendingInput,\n convoId: state.convoId,\n metadata,\n name: event,\n });\n }\n return state.interactionPromise;\n }\n\n // -- Run lifecycle ---------------------------------------------------------\n\n private async handleRunStart(ic: unknown): Promise<void> {\n const state = this.stateFromInvocationContext(ic);\n if (state) await this.ensureInteraction(state);\n }\n\n private async handleRunEnd(ic: unknown): Promise<void> {\n const invocationId = cleanString(safeGet(ic, 'invocationId'));\n const state = invocationId ? this.runs.get(invocationId) : undefined;\n if (!state) return;\n // Begin-if-never-begun so even a call-less run records a session.\n await this.ensureInteraction(state);\n await this.finishState(state);\n }\n\n private async handleRunError(ic: unknown, error: unknown): Promise<void> {\n const invocationId = cleanString(safeGet(ic, 'invocationId'));\n const state = invocationId ? this.runs.get(invocationId) : undefined;\n if (!state) return;\n const message =\n cleanString(safeGet(error, 'message'), CONTENT_PREVIEW_CHARS) ??\n cleanString(error, CONTENT_PREVIEW_CHARS) ??\n 'run error';\n const errorType = safeTypeName(error);\n // Begin-if-never-begun so even a run that aborts before any model/tool call\n // still records a (failed) session.\n await this.ensureInteraction(state);\n const firstError = state.failureReason === undefined;\n if (firstError) state.failureReason = message;\n const ix = await state.interactionPromise;\n // Only emit an error event when no model/tool error already recorded this abort,\n // so the same underlying exception isn't double-reported.\n if (firstError && ix) {\n await ix.trackError({\n errorMessage: message,\n errorType,\n metadata: { invocation_id: state.invocationId },\n });\n }\n await this.finishState(state);\n }\n\n // -- Model events ----------------------------------------------------------\n\n private async handleModelStart(cc: unknown, llmRequest: unknown): Promise<void> {\n const state = this.stateFromCallbackContext(cc);\n if (!state) return;\n // Bound the stack: drop the oldest entry if a prior model call's after/error callback never\n // fired (aborted/torn-down streaming run) so it can't grow forever — mirrors the toolStarts cap.\n if (state.modelStarts.length >= MAX_INFLIGHT_MODELS) {\n state.modelStarts.shift();\n }\n // LIFO push; the matching pop is in handleModelEnd/onModelError. Under parallel model calls the\n // model-name/duration label is best-effort (may mis-correlate); token/cost values stay correct.\n state.modelStarts.push({\n startedAt: Date.now(),\n model: cleanString(safeGet(llmRequest, 'model'), 200),\n });\n await this.ensureInteraction(state);\n }\n\n private async handleModelEnd(cc: unknown, llmResponse: unknown): Promise<void> {\n const invocationId = cleanString(safeGet(cc, 'invocationId'));\n const state =\n (invocationId ? this.runs.get(invocationId) : undefined) ?? this.stateFromCallbackContext(cc);\n if (!state) return;\n // Streaming yields many partial LlmResponses then one final; skip partials so\n // tokens (which ride the final response) aren't double-counted and the start\n // isn't popped prematurely. (The `if` truthiness check never invokes user code.)\n if (safeGet(llmResponse, 'partial')) return;\n\n // LIFO pop (paired with handleModelStart's push): under parallel model calls this may pair with\n // a different call's push, so model/duration is best-effort; token/cost values stay correct.\n const start = state.modelStarts.pop();\n const [promptTokens, completionTokens] = usageTokens(safeGet(llmResponse, 'usageMetadata'));\n const model = start?.model ?? cleanString(safeGet(llmResponse, 'model'), 200);\n const contentObj = safeGet(llmResponse, 'content');\n const content = contentText(contentObj);\n const reasoning = reasoningText(contentObj);\n const errorCode = cleanString(safeGet(llmResponse, 'errorCode'));\n const errorMessage = cleanString(safeGet(llmResponse, 'errorMessage'));\n let toolError: Record<string, unknown> | undefined;\n if (errorCode || errorMessage) {\n toolError = { message: errorMessage ?? errorCode ?? 'model error' };\n }\n\n await this.emitLlmEvent(state, {\n model,\n promptTokens,\n completionTokens,\n content,\n startedAt: start?.startedAt,\n toolError,\n reasoning,\n });\n\n if (toolError) {\n if (state.failureReason === undefined) state.failureReason = safeString(toolError.message);\n const ix = await state.interactionPromise;\n if (ix) {\n await ix.trackError({\n errorMessage: safeString(toolError.message),\n errorType: errorCode ?? 'model_error',\n metadata: { invocation_id: state.invocationId, model },\n });\n }\n }\n }\n\n private async handleModelError(cc: unknown, llmRequest: unknown, error: unknown): Promise<void> {\n const invocationId = cleanString(safeGet(cc, 'invocationId'));\n const state =\n (invocationId ? this.runs.get(invocationId) : undefined) ?? this.stateFromCallbackContext(cc);\n if (!state) return;\n const start = state.modelStarts.pop();\n const model = start?.model ?? cleanString(safeGet(llmRequest, 'model'), 200);\n const message =\n cleanString(safeGet(error, 'message'), CONTENT_PREVIEW_CHARS) ??\n cleanString(error, CONTENT_PREVIEW_CHARS) ??\n 'model error';\n const errorType = safeTypeName(error);\n\n await this.emitLlmEvent(state, {\n model,\n promptTokens: 0,\n completionTokens: 0,\n content: undefined,\n startedAt: start?.startedAt,\n toolError: { message, type: errorType },\n });\n\n if (state.failureReason === undefined) state.failureReason = message;\n const ix = await state.interactionPromise;\n if (ix) {\n await ix.trackError({\n errorMessage: message,\n errorType,\n metadata: { invocation_id: state.invocationId, model },\n });\n }\n }\n\n private async emitLlmEvent(\n state: RunState,\n llm: {\n model?: string;\n promptTokens: number;\n completionTokens: number;\n content?: string;\n startedAt?: number;\n toolError?: Record<string, unknown>;\n reasoning?: string;\n }\n ): Promise<void> {\n const ix = await this.ensureInteraction(state);\n const cost =\n llm.promptTokens || llm.completionTokens\n ? calculateCostByProvider('', llm.model ?? '', llm.promptTokens, llm.completionTokens)\n : 0;\n const durationMs =\n llm.startedAt !== undefined ? Math.max(0, Date.now() - llm.startedAt) : undefined;\n\n state.llmCalls += 1;\n state.promptTokens += llm.promptTokens;\n state.completionTokens += llm.completionTokens;\n state.totalCost += cost;\n if (llm.model) state.model = llm.model;\n if (llm.content) state.lastOutput = llm.content;\n\n if (!ix) return;\n await ix.trackToolCall({\n toolName: `llm:${llm.model ?? 'unknown'}`,\n toolInput: { model: llm.model },\n toolOutput: {\n content: llm.content ?? '',\n tokens: { prompt: llm.promptTokens, completion: llm.completionTokens },\n },\n toolError: llm.toolError,\n model: llm.model,\n promptTokens: llm.promptTokens || undefined,\n completionTokens: llm.completionTokens || undefined,\n tokenCount: llm.promptTokens + llm.completionTokens || undefined,\n estimatedCost: cost,\n durationMs,\n startedAt: llm.startedAt,\n reasoning: llm.reasoning,\n metadata: { invocation_id: state.invocationId },\n });\n }\n\n // -- Tool events -----------------------------------------------------------\n\n private async handleToolStart(toolContext: unknown): Promise<void> {\n const state = this.stateFromCallbackContext(toolContext);\n if (!state) return;\n // Bound the in-flight map: drop the oldest entry if a prior tool's after/error\n // callback never fired (host crash mid-tool) so it can't grow forever.\n if (state.toolStarts.size >= MAX_INFLIGHT_TOOLS) {\n const oldest = state.toolStarts.keys().next().value;\n if (oldest !== undefined) state.toolStarts.delete(oldest);\n }\n // Key on the toolContext object identity — stable across a tool's before/after\n // callbacks, so parallel tools are correlated correctly.\n state.toolStarts.set(toolContext, Date.now());\n await this.ensureInteraction(state);\n }\n\n private popToolTiming(\n state: RunState,\n toolContext: unknown\n ): { startedAt?: number; durationMs?: number } {\n const startedAt = state.toolStarts.get(toolContext);\n if (startedAt === undefined) return {};\n state.toolStarts.delete(toolContext);\n return { startedAt, durationMs: Math.max(0, Date.now() - startedAt) };\n }\n\n private async handleToolEnd(\n tool: unknown,\n toolArgs: unknown,\n toolContext: unknown,\n result: unknown\n ): Promise<void> {\n const invocationId = cleanString(safeGet(toolContext, 'invocationId'));\n const state =\n (invocationId ? this.runs.get(invocationId) : undefined) ??\n this.stateFromCallbackContext(toolContext);\n if (!state) return;\n const { startedAt, durationMs } = this.popToolTiming(state, toolContext);\n state.toolCalls += 1;\n const ix = await this.ensureInteraction(state);\n if (!ix) return;\n await ix.trackToolCall({\n toolName: cleanString(safeGet(tool, 'name'), 200) ?? 'unknown_tool',\n toolInput: safeRecordCopy(toolArgs),\n toolOutput: wrapToolOutput(result),\n durationMs,\n startedAt,\n metadata: { invocation_id: state.invocationId },\n });\n }\n\n private async handleToolError(\n tool: unknown,\n toolArgs: unknown,\n toolContext: unknown,\n error: unknown\n ): Promise<void> {\n const invocationId = cleanString(safeGet(toolContext, 'invocationId'));\n const state =\n (invocationId ? this.runs.get(invocationId) : undefined) ??\n this.stateFromCallbackContext(toolContext);\n if (!state) return;\n const { startedAt, durationMs } = this.popToolTiming(state, toolContext);\n state.toolCalls += 1;\n const toolName = cleanString(safeGet(tool, 'name'), 200) ?? 'unknown_tool';\n const message =\n cleanString(safeGet(error, 'message'), CONTENT_PREVIEW_CHARS) ??\n cleanString(error, CONTENT_PREVIEW_CHARS) ??\n 'tool error';\n const errorType = safeTypeName(error);\n if (state.failureReason === undefined) state.failureReason = message;\n const ix = await this.ensureInteraction(state);\n if (!ix) return;\n await ix.trackToolCall({\n toolName,\n toolInput: safeRecordCopy(toolArgs),\n toolOutput: { error: message },\n toolError: { message, type: errorType },\n durationMs,\n startedAt,\n metadata: { invocation_id: state.invocationId },\n });\n await ix.trackError({\n errorMessage: message,\n errorType,\n toolName,\n metadata: { invocation_id: state.invocationId },\n });\n }\n\n // -- Finish ----------------------------------------------------------------\n\n private async finishState(state: RunState, failureReason?: string): Promise<void> {\n if (state.finished) return;\n state.finished = true;\n // Forget the run regardless of whether it began (aborted/call-less runs included).\n this.runs.delete(state.invocationId);\n if (!state.interactionPromise) return;\n const ix = await state.interactionPromise;\n const reason = failureReason ?? state.failureReason;\n const customMetrics: Record<string, number> = {};\n if (state.llmCalls) customMetrics.llm_calls = state.llmCalls;\n if (state.toolCalls) customMetrics.tool_calls = state.toolCalls;\n const hasUsage = state.llmCalls > 0;\n await ix.finish({\n output: state.lastOutput,\n // `success` stays undefined on the clean path so finish() defers to the\n // Interaction's stored state (false when a trackError already fired).\n success: reason !== undefined ? false : undefined,\n failureReason: reason,\n estimatedCost: hasUsage ? state.totalCost : undefined,\n promptTokens: hasUsage ? state.promptTokens : undefined,\n completionTokens: hasUsage ? state.completionTokens : undefined,\n totalTokens: hasUsage ? state.promptTokens + state.completionTokens : undefined,\n customMetrics: Object.keys(customMetrics).length > 0 ? customMetrics : undefined,\n });\n }\n}\n\n// ============================================================================\n// Idempotent Runner registration\n// ============================================================================\n\n// Idempotency registry for instrumentAdk, keyed by the runner OBJECT via a WeakMap\n// (not id(runner)): ADK's PluginManager throws on a duplicate plugin name, so\n// re-instrumenting the same runner must return the existing plugin instead of\n// registering a second one (which would mirror every run into two sessions). A\n// WeakMap keyed by the runner drops the entry when the runner is collected.\nlet registeredPlugins = new WeakMap<object, SentrialAdkPlugin>();\nlet duplicateInstrumentWarned = false;\n\n/** Test hook: forget prior registrations (and re-arm the warn-once). */\nexport function _resetInstrumentationRegistry(): void {\n registeredPlugins = new WeakMap();\n duplicateInstrumentWarned = false;\n}\n\n/**\n * Register a {@link SentrialAdkPlugin} on an already-constructed ADK `Runner`.\n *\n * Convenience for when you didn't pass `plugins: [new SentrialAdkPlugin()]` at\n * Runner construction. Pass the runner (its `pluginManager.registerPlugin` is the\n * only surface used) — the `@google/adk` peer is never imported here, mirroring\n * `instrumentOpenAIAgents(agents)`.\n *\n * Idempotent per runner: calling it again on the same runner returns the FIRST\n * registered plugin instead of registering a duplicate (which ADK would reject on\n * the duplicate name anyway, and which would mirror every run into two sessions).\n * The first duplicate call emits a console warning (silent thereafter).\n *\n * @example\n * ```ts\n * import { InMemoryRunner } from '@google/adk';\n * import { instrumentAdk } from '@sentrial/sdk';\n *\n * const runner = new InMemoryRunner({ agent });\n * instrumentAdk(runner, { userId: 'user_123' });\n * ```\n *\n * @param runner - An ADK `Runner` exposing `pluginManager.registerPlugin`.\n * @param options - Plugin options (client, name, defaultAgent, userId, extraMetadata).\n * @returns The registered plugin.\n */\nexport function instrumentAdk(\n runner: AdkRunnerLike,\n options: AdkPluginOptions = {}\n): SentrialAdkPlugin {\n const pm = safeGet(runner, 'pluginManager');\n const register = safeGet(pm, 'registerPlugin');\n if (typeof register !== 'function') {\n throw new TypeError(\n 'instrumentAdk(runner, options) expects an ADK Runner exposing ' +\n 'pluginManager.registerPlugin as the first argument. ' +\n INSTALL_HINT\n );\n }\n\n // Try to use the runner itself as a weak key. A non-object runner can't be a weak\n // key — the getPlugin-scan below is the real idempotency backstop (it adopts an\n // already-registered SentrialAdkPlugin), so correctness never depends on the cache.\n const cacheKey = runner && typeof runner === 'object' ? (runner as object) : undefined;\n\n const existing = cacheKey ? registeredPlugins.get(cacheKey) : undefined;\n if (existing) {\n if (!duplicateInstrumentWarned) {\n duplicateInstrumentWarned = true;\n console.warn(\n 'Sentrial: instrumentAdk() was already called for this runner — returning the ' +\n 'existing plugin (a second registration would mirror every run into duplicate sessions).'\n );\n }\n return existing;\n }\n\n const name = cleanString(options.name, 200) ?? PLUGIN_NAME;\n\n // A plugin with our name may already be present (e.g. passed via plugins:[...] at\n // construction); ADK's registerPlugin throws on a duplicate name, so scan first via\n // getPlugin and adopt an existing SentrialAdkPlugin rather than crashing the caller.\n const getPlugin = safeGet(pm, 'getPlugin');\n if (typeof getPlugin === 'function') {\n let found: unknown;\n try {\n found = (getPlugin as (n: string) => unknown).call(pm, name);\n } catch {\n found = undefined;\n }\n if (found instanceof SentrialAdkPlugin) {\n if (cacheKey) registeredPlugins.set(cacheKey, found);\n return found;\n }\n }\n\n const plugin = new SentrialAdkPlugin({ ...options, name });\n (register as (p: unknown) => void).call(pm, plugin);\n if (cacheKey) registeredPlugins.set(cacheKey, plugin);\n return plugin;\n}\n","/**\n * Sentrial Decorators - Easy instrumentation for AI agents\n *\n * Provides decorators and higher-order functions for automatic tracking:\n * - withTool: Track tool/function calls\n * - withSession: Create session boundaries around agent runs\n *\n * @example Using higher-order functions (works everywhere)\n * ```ts\n * import { withTool, withSession, configure } from '@sentrial/sdk';\n *\n * configure({ apiKey: 'sentrial_live_xxx' });\n *\n * // Wrap a tool function\n * const searchWeb = withTool('search', async (query: string) => {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * });\n *\n * // Wrap an agent session\n * const handleRequest = withSession('support-agent', async (userId: string, message: string) => {\n * const results = await searchWeb(message);\n * return processResults(results);\n * });\n * ```\n *\n * @example Using TypeScript decorators (requires experimentalDecorators)\n * ```ts\n * import { Tool, Session } from '@sentrial/sdk';\n *\n * class MyAgent {\n * @Tool('search')\n * async searchWeb(query: string) {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * }\n *\n * @Session('support-agent')\n * async handleRequest(userId: string, message: string) {\n * const results = await this.searchWeb(message);\n * return processResults(results);\n * }\n * }\n * ```\n */\n\nimport { SentrialClient, Interaction } from './client.js';\nimport {\n setSessionContext,\n clearSessionContext,\n getSessionContext,\n setDefaultClient,\n getDefaultClient,\n _setSessionContextWithTokens,\n _restoreSessionContext,\n type SessionContextTokens,\n} from './wrappers.js';\nimport { createContextVar, type ContextToken } from './async-context.js';\n\n// Re-export context functions\nexport { setSessionContext, clearSessionContext, getSessionContext };\n\n// ============================================================================\n// Context Management\n// ============================================================================\n\nconst _currentInteraction = createContextVar<Interaction | null>(null);\n\n/**\n * Get the default client, creating one if needed\n */\nfunction getClient(): SentrialClient | null {\n // Resolve straight from the shared wrappers registry (the single source of truth\n // that configure()/configureVercel() write to) rather than caching a local mirror\n // of it (#481). On first use with nothing configured, lazily create + register one.\n const configuredClient = getDefaultClient();\n if (configuredClient) return configuredClient;\n\n try {\n const client = new SentrialClient();\n setDefaultClient(client);\n return client;\n } catch {\n return null;\n }\n}\n\n/**\n * Set the default client for decorators\n */\nexport function setClient(client: SentrialClient): void {\n setDefaultClient(client);\n}\n\n/**\n * Get the current session ID (if inside a session context)\n */\nexport function getCurrentSessionId(): string | null {\n return getSessionContext();\n}\n\n/**\n * Get the current interaction (if inside a session context)\n */\nexport function getCurrentInteraction(): Interaction | null {\n return _currentInteraction.get();\n}\n\n// ============================================================================\n// withTool - Higher-order function for tool tracking\n// ============================================================================\n\ntype AsyncFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => Promise<TReturn>;\ntype SyncFunction<TArgs extends unknown[], TReturn> = (...args: TArgs) => TReturn;\n\n/**\n * Wrap a function to automatically track it as a tool call.\n *\n * When called within a session context, automatically tracks:\n * - tool_input: Function arguments\n * - tool_output: Return value\n * - duration: Execution time\n * - errors: Any exceptions raised\n *\n * @param name - Name of the tool\n * @param fn - The function to wrap\n * @returns Wrapped function with automatic tracking\n *\n * @example\n * ```ts\n * const searchWeb = withTool('search', async (query: string) => {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * });\n *\n * // Use within a session - automatically tracked\n * const results = await searchWeb('AI agents');\n * ```\n */\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: AsyncFunction<TArgs, TReturn>\n): AsyncFunction<TArgs, TReturn>;\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: SyncFunction<TArgs, TReturn>\n): SyncFunction<TArgs, TReturn>;\nexport function withTool<TArgs extends unknown[], TReturn>(\n name: string,\n fn: (...args: TArgs) => TReturn | Promise<TReturn>\n): (...args: TArgs) => TReturn | Promise<TReturn> {\n // Check if function is async\n const isAsync = fn.constructor.name === 'AsyncFunction';\n\n if (isAsync) {\n return async function (...args: TArgs): Promise<TReturn> {\n return trackToolAsync(name, fn as AsyncFunction<TArgs, TReturn>, args);\n };\n } else {\n return function (...args: TArgs): TReturn {\n return trackToolSync(name, fn as SyncFunction<TArgs, TReturn>, args);\n };\n }\n}\n\n// Shared tool-call/error tracking used by trackToolAsync, trackToolSync, and the @tool\n// class-method decorator — previously triplicated verbatim (#483).\nfunction trackToolSuccess(\n client: ReturnType<typeof getClient>,\n sessionId: ReturnType<typeof getSessionContext>,\n toolName: string,\n toolInput: Record<string, unknown>,\n result: unknown,\n durationMs: number\n): void {\n if (client && sessionId) {\n // The tool already SUCCEEDED — nothing on this tracking path may throw back into the\n // caller (a hostile result would otherwise convert success into the error branch,\n // firing a spurious trackError and losing the caller's result). serializeOutput() is\n // itself never-throw; this try is the belt-and-braces layer for the synchronous\n // prelude of trackToolCall().\n try {\n client\n .trackToolCall({\n sessionId,\n toolName,\n toolInput,\n toolOutput: serializeOutput(result),\n metadata: { duration_ms: durationMs },\n })\n .catch((err) => {\n console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);\n });\n } catch {\n // Tracking degrades; the caller still gets the successful result.\n }\n }\n}\n\nfunction trackToolFailure(\n client: ReturnType<typeof getClient>,\n sessionId: ReturnType<typeof getSessionContext>,\n toolName: string,\n error: unknown,\n durationMs: number\n): void {\n if (client && sessionId) {\n // Users can `throw` arbitrary values, including objects whose name/message/stack\n // getters themselves throw — error introspection must never replace the original\n // error on its way back to the caller.\n try {\n client\n .trackError({\n sessionId,\n errorMessage: safeErrorPart(error, 'message') ?? safeString(error),\n errorType: safeErrorPart(error, 'name') ?? 'Error',\n toolName,\n stackTrace: safeErrorPart(error, 'stack'),\n metadata: { duration_ms: durationMs },\n })\n .catch(() => {});\n } catch {\n // Tracking degrades; the caller's original error still propagates.\n }\n }\n}\n\nasync function trackToolAsync<TArgs extends unknown[], TReturn>(\n toolName: string,\n fn: AsyncFunction<TArgs, TReturn>,\n args: TArgs\n): Promise<TReturn> {\n const startTime = Date.now();\n // buildToolInput is never-throw: it runs before the try (and before getClient()), so a\n // hostile argument degrades to a marker input and the user's tool ALWAYS runs.\n const toolInput = buildToolInput(args);\n\n const client = getClient();\n const sessionId = getSessionContext();\n\n try {\n const result = await fn(...args);\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n trackToolSuccess(client, sessionId, toolName, toolInput, result, durationMs);\n }\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n trackToolFailure(client, sessionId, toolName, error, durationMs);\n }\n\n throw error;\n }\n}\n\nfunction trackToolSync<TArgs extends unknown[], TReturn>(\n toolName: string,\n fn: SyncFunction<TArgs, TReturn>,\n args: TArgs\n): TReturn {\n const startTime = Date.now();\n // buildToolInput is never-throw (see trackToolAsync) — the user's tool ALWAYS runs.\n const toolInput = buildToolInput(args);\n\n const client = getClient();\n const sessionId = getSessionContext();\n\n try {\n const result = fn(...args);\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n trackToolSuccess(client, sessionId, toolName, toolInput, result, durationMs);\n }\n\n return result;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n\n if (client && sessionId) {\n trackToolFailure(client, sessionId, toolName, error, durationMs);\n }\n\n throw error;\n }\n}\n\n// ============================================================================\n// withSession - Higher-order function for session management\n// ============================================================================\n\ninterface SessionOptions {\n /** Parameter index or name for user ID (default: looks for 'userId' or first param) */\n userIdParam?: string | number;\n /** Parameter index or name for input (default: looks for 'input', 'message', 'query') */\n inputParam?: string | number;\n}\n\n/**\n * Wrap a function to automatically manage a session around it.\n *\n * Automatically:\n * - Creates a session when the function is called\n * - Sets up context for withTool and wrapped LLM calls\n * - Captures output from return value\n * - Completes the session when the function returns\n * - Marks as failed if an exception is raised\n *\n * @param agentName - Name of the agent\n * @param fn - The function to wrap\n * @param options - Session options\n * @returns Wrapped function with automatic session management\n *\n * @example\n * ```ts\n * const handleRequest = withSession('support-agent', async (userId: string, message: string) => {\n * // Session automatically created\n * // All withTool calls and wrapped LLM calls are tracked\n * const response = await processRequest(message);\n * return response; // Captured as output\n * });\n *\n * await handleRequest('user_123', 'Hello!');\n * ```\n */\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: AsyncFunction<TArgs, TReturn>,\n options?: SessionOptions\n): AsyncFunction<TArgs, TReturn>;\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: SyncFunction<TArgs, TReturn>,\n options?: SessionOptions\n): AsyncFunction<TArgs, TReturn>; // Session always returns a Promise\nexport function withSession<TArgs extends unknown[], TReturn>(\n agentName: string,\n fn: (...args: TArgs) => TReturn | Promise<TReturn>,\n options: SessionOptions = {}\n): (...args: TArgs) => Promise<TReturn> {\n return async function (...args: TArgs): Promise<TReturn> {\n return runWithSession(agentName, options, args, () => fn(...args));\n };\n}\n\nfunction extractParams(\n args: unknown[],\n options: SessionOptions\n): { userId: string; userInput: string | undefined } {\n let userId = 'anonymous';\n let userInput: string | undefined;\n\n // Extract userId. safeString(): a hostile arg (throwing toString/Symbol.toPrimitive) must\n // degrade, never throw out of the tracked function's invocation before begin() even runs.\n if (typeof options.userIdParam === 'number') {\n userId = safeString(args[options.userIdParam] ?? 'anonymous');\n } else if (typeof options.userIdParam === 'string') {\n // Can't access by name in JS, use index 0\n userId = safeString(args[0] ?? 'anonymous');\n } else {\n // Default: first arg is userId\n userId = safeString(args[0] ?? 'anonymous');\n }\n\n // Extract input\n if (typeof options.inputParam === 'number') {\n userInput = safeString(args[options.inputParam] ?? '');\n } else {\n // Default: second arg is input\n const input = args[1];\n if (typeof input === 'string') {\n userInput = input;\n } else if (Array.isArray(input)) {\n try {\n userInput = JSON.stringify(input).slice(0, 500);\n } catch {\n // A circular argument must degrade, never throw out of the tracked function's\n // invocation (the monitoring-never-crashes contract).\n try {\n userInput = String(input).slice(0, 500);\n } catch {\n // A hostile element can throw from toString/Symbol.toPrimitive too — the last-resort\n // fallback must be a literal, never another throw.\n userInput = '[unserializable]';\n }\n }\n }\n }\n\n return { userId, userInput };\n}\n\n/**\n * Shared implementation for `withSession` and the `@TrackSession` decorator.\n *\n * Creates an interaction, installs the session/interaction context (with\n * nesting support), invokes `run`, captures the output, and finishes the\n * interaction — restoring the previous context in `finally`. When no client is\n * configured it simply runs `run` untracked.\n */\n/**\n * Best-effort coercion of an arbitrary agent return value into a string output for\n * session telemetry. Strings pass through; objects prefer a `response`/`output`\n * field; anything else is stringified and capped at 1000 chars. Returns undefined\n * only for null/undefined so `finish()` omits the field (#591).\n */\nexport function extractOutput(result: unknown): string | undefined {\n // Never-throw: the result is user-controlled — a hostile `in` trap (Proxy), throwing\n // property getter, or throwing toString/Symbol.toPrimitive must degrade to the\n // '[unserializable]' literal, not divert a SUCCESSFUL run into the failure branch.\n try {\n if (typeof result === 'string') return result;\n if (result && typeof result === 'object') {\n if ('response' in result) return safeString((result as { response: unknown }).response);\n if ('output' in result) return safeString((result as { output: unknown }).output);\n }\n if (result !== null && result !== undefined) return safeString(result).slice(0, 1000);\n return undefined;\n } catch {\n return '[unserializable]';\n }\n}\n\nasync function runWithSession<TReturn>(\n agentName: string,\n options: SessionOptions,\n args: unknown[],\n run: () => TReturn | Promise<TReturn>\n): Promise<TReturn> {\n const client = getClient();\n if (!client) {\n // No client, just run the function\n return run() as Promise<TReturn>;\n }\n\n // Extract userId and input from args\n const { userId, userInput } = extractParams(args, options);\n\n // Create interaction\n const interaction = await client.begin({\n userId,\n event: agentName,\n input: userInput,\n });\n\n // Save previous context and set new one (supports nesting)\n const sessionId = interaction.getSessionId();\n let sessionTokens: SessionContextTokens | undefined;\n if (sessionId) {\n sessionTokens = _setSessionContextWithTokens(sessionId, client);\n }\n const interactionToken = _currentInteraction.set(interaction);\n\n try {\n const result = await run();\n await interaction.finish({ output: extractOutput(result), success: true });\n return result;\n } catch (error) {\n // Users can `throw` arbitrary values — guarded introspection so a hostile name/message\n // getter can't replace the original error (or skip finish()) on its way back out.\n await interaction.finish({\n success: false,\n failureReason: `${safeErrorPart(error, 'name') ?? 'Error'}: ${\n safeErrorPart(error, 'message') ?? safeString(error)\n }`,\n });\n throw error;\n } finally {\n // Restore previous context (supports nesting)\n if (sessionTokens) {\n _restoreSessionContext(sessionTokens);\n }\n _currentInteraction.reset(interactionToken);\n }\n}\n\n// ============================================================================\n// TypeScript Decorators (requires experimentalDecorators)\n// ============================================================================\n\n/**\n * Method decorator to track a method as a tool call.\n *\n * @param name - Name of the tool (optional, defaults to method name)\n *\n * @example\n * ```ts\n * class MyAgent {\n * @Tool('search')\n * async searchWeb(query: string) {\n * return await fetch(`/search?q=${query}`).then(r => r.json());\n * }\n * }\n * ```\n */\nexport function Tool(name?: string): MethodDecorator {\n return function (\n _target: object,\n propertyKey: string | symbol,\n descriptor: PropertyDescriptor\n ): PropertyDescriptor {\n const originalMethod = descriptor.value;\n const toolName = name ?? String(propertyKey);\n\n // Delegate to the shared async tool-tracking path (buildToolInput → run →\n // trackToolSuccess / trackToolFailure) instead of re-inlining it (#483). The\n // closure re-binds `this` so the method still runs against its instance.\n descriptor.value = async function (...args: unknown[]) {\n return trackToolAsync(\n toolName,\n ((...callArgs: unknown[]) => originalMethod.apply(this, callArgs)) as AsyncFunction<\n unknown[],\n unknown\n >,\n args\n );\n };\n\n return descriptor;\n };\n}\n\n/**\n * Method decorator to wrap a method in a session.\n *\n * @param agentName - Name of the agent (optional, defaults to class name)\n * @param options - Session options\n *\n * @example\n * ```ts\n * class MyAgent {\n * @TrackSession('support-agent')\n * async handleRequest(userId: string, message: string) {\n * // Session automatically created\n * const results = await this.searchWeb(message);\n * return processResults(results);\n * }\n * }\n * ```\n */\nexport function TrackSession(agentName?: string, options?: SessionOptions): MethodDecorator {\n return function (\n target: object,\n _propertyKey: string | symbol,\n descriptor: PropertyDescriptor\n ): PropertyDescriptor {\n const originalMethod = descriptor.value;\n const agent = agentName ?? target.constructor.name;\n\n descriptor.value = async function (this: unknown, ...args: unknown[]) {\n return runWithSession(agent, options ?? {}, args, () => originalMethod.apply(this, args));\n };\n\n return descriptor;\n };\n}\n\n// ============================================================================\n// SessionContext - Manual session control\n// ============================================================================\n\n/**\n * Manual session context for when you need more control.\n *\n * @example\n * ```ts\n * const ctx = new SessionContext({\n * userId: 'user_123',\n * agent: 'my-agent',\n * input: 'Hello!',\n * });\n *\n * await ctx.start();\n * try {\n * // All tool calls are tracked\n * const result = await myTool(...);\n * ctx.setOutput(result);\n * await ctx.finish();\n * } catch (error) {\n * await ctx.finish({ success: false, error: error.message });\n * }\n * ```\n */\nexport class SessionContext {\n private readonly userId: string;\n private readonly agent: string;\n private readonly input?: string;\n private readonly client: SentrialClient | null;\n private interaction: Interaction | null = null;\n private output?: string;\n private sessionTokens?: SessionContextTokens;\n private interactionToken?: ContextToken<Interaction | null>;\n\n constructor(options: { userId: string; agent: string; input?: string; client?: SentrialClient }) {\n this.userId = options.userId;\n this.agent = options.agent;\n this.input = options.input;\n this.client = options.client ?? getClient();\n }\n\n /**\n * Start the session\n */\n async start(): Promise<this> {\n if (!this.client) return this;\n\n this.interaction = await this.client.begin({\n userId: this.userId,\n event: this.agent,\n input: this.input,\n });\n\n const sessionId = this.interaction.getSessionId();\n if (sessionId) {\n this.sessionTokens = _setSessionContextWithTokens(sessionId, this.client);\n }\n this.interactionToken = _currentInteraction.set(this.interaction);\n\n return this;\n }\n\n /**\n * Set the output for this session\n */\n setOutput(output: string): void {\n this.output = output;\n }\n\n /**\n * Finish the session\n */\n async finish(options?: { success?: boolean; error?: string }): Promise<void> {\n if (this.interaction) {\n await this.interaction.finish({\n output: this.output,\n success: options?.success ?? true,\n failureReason: options?.error,\n });\n }\n\n // Restore previous context (supports nesting)\n if (this.sessionTokens) {\n _restoreSessionContext(this.sessionTokens);\n }\n if (this.interactionToken) {\n _currentInteraction.reset(this.interactionToken);\n }\n }\n\n /**\n * Get the session ID\n */\n get sessionId(): string | null {\n return this.interaction?.getSessionId() ?? null;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Coerce an arbitrary user value to a string without ever throwing — a hostile\n * toString/Symbol.toPrimitive degrades to the cross-SDK '[unserializable]' literal.\n */\nfunction safeString(value: unknown): string {\n try {\n return String(value);\n } catch {\n return '[unserializable]';\n }\n}\n\n/**\n * Read `error[key]` without ever throwing. Users can `throw` arbitrary values (Proxies,\n * objects with throwing getters), so even `.name`/`.message`/`.stack` access on the\n * tracking path must be guarded. Returns undefined when the field is absent or hostile.\n */\nfunction safeErrorPart(error: unknown, key: 'name' | 'message' | 'stack'): string | undefined {\n try {\n const raw = (error as Record<'name' | 'message' | 'stack', unknown>)[key];\n if (raw === null || raw === undefined) return undefined;\n return typeof raw === 'string' ? raw : safeString(raw);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Maximum nesting depth for decorator-ingress serialization. Matches MAX_REDACT_DEPTH in\n * redact.ts (and the Python `_MAX_SERIALIZE_DEPTH`): far above any real payload, far below\n * the stack limit — a deeper branch degrades to the `{ _unserializable: true }` marker\n * instead of blowing the stack out of a tracking call.\n */\nconst MAX_SERIALIZE_DEPTH = 256;\n\nfunction buildToolInput(args: unknown[]): Record<string, unknown> {\n // Never-throw: this runs BEFORE the wrapped tool — a hostile argument must degrade to a\n // marker input, never prevent the user's tool from running (with or without a client).\n try {\n if (args.length === 0) {\n return {};\n }\n if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null) {\n return serializeValue(args[0]) as Record<string, unknown>;\n }\n return {\n args: args.map((arg) => serializeValue(arg)),\n };\n } catch {\n return { _unserializable: true };\n }\n}\n\nfunction serializeValue(value: unknown): unknown {\n return serializeValueGuarded(value, new Set(), 0);\n}\n\n/**\n * Cycle- and depth-safe serializer for user-supplied tool inputs/outputs, matching\n * `deepMapStringsGuarded` in redact.ts: a cyclic branch or one nested deeper than\n * MAX_SERIALIZE_DEPTH becomes the cross-SDK `{ _unserializable: true }` marker, a hostile\n * getter/Proxy trap degrades to '[unserializable]' for that property, and the last-resort\n * String() fallback is itself guarded. Never throws — a RangeError (or a user getter's\n * throw) out of a tracking call would violate the monitoring-never-crashes contract.\n */\nfunction serializeValueGuarded(value: unknown, ancestors: Set<object>, depth: number): unknown {\n if (value === null || value === undefined) {\n return value;\n }\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return value;\n }\n if (Array.isArray(value)) {\n // Same marker convention as cycles: a deeper-than-cap branch degrades, never overflows.\n if (depth >= MAX_SERIALIZE_DEPTH) return { _unserializable: true };\n if (ancestors.has(value)) return { _unserializable: true };\n ancestors.add(value);\n try {\n // .map() reads elements through possible Proxy get traps — contained here.\n return value.map((item) => serializeValueGuarded(item, ancestors, depth + 1));\n } catch {\n return { _unserializable: true };\n } finally {\n ancestors.delete(value);\n }\n }\n if (typeof value === 'object') {\n if (depth >= MAX_SERIALIZE_DEPTH) return { _unserializable: true };\n if (ancestors.has(value)) return { _unserializable: true };\n ancestors.add(value);\n try {\n const result: Record<string, unknown> = {};\n // Object.keys (not Object.entries): entries reads EVERY getter up front, so one\n // hostile getter would nuke all siblings. Per-key access lets each property degrade\n // independently.\n for (const key of Object.keys(value)) {\n try {\n result[key] = serializeValueGuarded(\n (value as Record<string, unknown>)[key],\n ancestors,\n depth + 1\n );\n } catch {\n result[key] = '[unserializable]';\n }\n }\n return result;\n } catch {\n // Object.keys itself can throw (hostile Proxy ownKeys trap).\n return { _unserializable: true };\n } finally {\n ancestors.delete(value);\n }\n }\n try {\n return String(value).slice(0, 1000);\n } catch {\n return `<${typeof value}>`;\n }\n}\n\nfunction serializeOutput(value: unknown): Record<string, unknown> {\n // Never-throw: a hostile result from a SUCCESSFUL tool must degrade to a marker — the\n // caller always receives the real result, tracking only degrades.\n try {\n if (value === null || value === undefined) {\n return { result: null };\n }\n if (typeof value === 'object' && !Array.isArray(value)) {\n return serializeValue(value) as Record<string, unknown>;\n }\n return { result: serializeValue(value) };\n } catch {\n return { _unserializable: true };\n }\n}\n","/**\n * Sentrial Experiment Context\n *\n * Provides context-aware configuration for experiments. When running experiments\n * via the CLI, the system prompt and other experiment config is automatically\n * available to your agent code via simple function calls.\n *\n * @example\n * ```ts\n * import { getSystemPrompt } from '@sentrial/sdk';\n *\n * async function myAgent(query: string): Promise<string> {\n * // When running normally: returns your default\n * // When running experiment: returns the variant's system prompt\n * const systemPrompt = getSystemPrompt('You are a helpful assistant.');\n *\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: systemPrompt },\n * { role: 'user', content: query },\n * ],\n * });\n *\n * return response.choices[0].message.content!;\n * }\n * ```\n *\n * That's it! No other changes needed. The experiment runner handles setting the context.\n */\n\n// ============================================================================\n// Experiment Context Types\n// ============================================================================\n\n/**\n * Context for the current experiment run.\n */\nexport interface ExperimentContext {\n /** The system prompt for this variant */\n systemPrompt: string;\n /** Name of the variant being tested */\n variantName: string;\n /** The experiment ID */\n experimentId: string;\n /** The test case session ID (if applicable) */\n testCaseId?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Context Storage (async-safe via AsyncLocalStorage in Node.js)\n// ============================================================================\n\nimport { createContextVar } from './async-context.js';\n\nconst _experimentContext = createContextVar<ExperimentContext | null>(null);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Get the current system prompt.\n *\n * When running an experiment, this returns the variant's system prompt.\n * Otherwise, returns your default.\n *\n * @param defaultPrompt - The default system prompt to use when not in experiment mode.\n * This should be your normal production prompt.\n * @returns The system prompt to use.\n *\n * @example\n * ```ts\n * import { getSystemPrompt } from '@sentrial/sdk';\n *\n * async function myAgent(query: string) {\n * // Just wrap your existing prompt with getSystemPrompt()\n * const prompt = getSystemPrompt('You are a helpful weather assistant.');\n *\n * const response = await openai.chat.completions.create({\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: prompt },\n * { role: 'user', content: query },\n * ],\n * });\n *\n * return response.choices[0].message.content;\n * }\n * ```\n */\nexport function getSystemPrompt(defaultPrompt?: string): string {\n const ctx = _experimentContext.get();\n if (ctx?.systemPrompt) {\n return ctx.systemPrompt;\n }\n return defaultPrompt ?? '';\n}\n\n/**\n * Get the full experiment context if running in experiment mode.\n *\n * Returns null if not running an experiment.\n *\n * @returns ExperimentContext with variant info, or null.\n *\n * @example\n * ```ts\n * import { getExperimentContext } from '@sentrial/sdk';\n *\n * const ctx = getExperimentContext();\n * if (ctx) {\n * console.log(`Running variant: ${ctx.variantName}`);\n * }\n * ```\n */\nexport function getExperimentContext(): ExperimentContext | null {\n return _experimentContext.get();\n}\n\n/**\n * Check if currently running in experiment mode.\n *\n * @returns True if running via experiment runner, false otherwise.\n */\nexport function isExperimentMode(): boolean {\n return _experimentContext.get() !== null;\n}\n\n/**\n * Get the current variant name if in experiment mode.\n *\n * @returns The variant name (e.g., \"Control\", \"Variant A\"), or null.\n */\nexport function getVariantName(): string | null {\n return _experimentContext.get()?.variantName ?? null;\n}\n\n/**\n * Get the current experiment ID if in experiment mode.\n *\n * @returns The experiment ID, or null.\n */\nexport function getExperimentId(): string | null {\n return _experimentContext.get()?.experimentId ?? null;\n}\n\n// ============================================================================\n// Internal Functions (used by experiment runner)\n// ============================================================================\n\n/**\n * Set the experiment context. Called by the experiment runner before running agent.\n *\n * @internal This is an internal function - users don't need to call this directly.\n */\nexport function setExperimentContext(context: ExperimentContext): void {\n _experimentContext.set(context);\n}\n\n/**\n * Clear the experiment context. Called by the experiment runner after agent completes.\n *\n * @internal This is an internal function - users don't need to call this directly.\n */\nexport function clearExperimentContext(): void {\n _experimentContext.set(null);\n}\n","/**\n * Sentrial Experiments - Run prompt A/B tests with results synced to Sentrial\n *\n * @example\n * ```ts\n * import { Experiment } from '@sentrial/sdk';\n *\n * const experiment = new Experiment('exp_abc123');\n *\n * // Get config\n * await experiment.load();\n * console.log(`Testing ${experiment.variants.length} variants on ${experiment.testCases.length} inputs`);\n *\n * // Run with your agent function\n * await experiment.run(async (testCase, variant, tracker) => {\n * // Create a session for this run\n * const interaction = await client.begin({\n * userId: 'experiment',\n * event: 'my-agent',\n * input: testCase.userInput,\n * });\n * tracker.setResultSessionId(interaction.getSessionId()!);\n *\n * // Run your agent with the variant's system prompt\n * const response = await runAgent(testCase.userInput, variant.systemPrompt);\n *\n * await interaction.finish({ output: response, success: true });\n * });\n * ```\n *\n * @example Manual run tracking\n * ```ts\n * const experiment = new Experiment('exp_abc123');\n * await experiment.load();\n *\n * for (const variant of experiment.variants) {\n * for (const testCase of experiment.testCases) {\n * const tracker = await experiment.trackRun(variant.name, testCase.sessionId);\n * try {\n * const sessionId = await runAgent(testCase.userInput, variant.systemPrompt);\n * tracker.setResultSessionId(sessionId);\n * await tracker.complete();\n * } catch (error) {\n * await tracker.fail(error.message);\n * }\n * }\n * }\n * ```\n */\n\nimport { SentrialClient } from './client.js';\nimport { setExperimentContext, clearExperimentContext } from './context.js';\nimport { DEFAULT_API_URL } from './constants.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * A prompt variant to test.\n */\nexport interface ExperimentVariant {\n /** Name of the variant */\n name: string;\n /** The system prompt for this variant */\n systemPrompt: string;\n /** Optional description */\n description?: string;\n}\n\n/**\n * A test case (input) to run against each variant.\n */\nexport interface ExperimentTestCase {\n /** Session ID of the original session */\n sessionId: string;\n /** The user input to test */\n userInput: string;\n}\n\n/**\n * Result of a single experiment run.\n */\nexport interface ExperimentRunResult {\n /** Name of the variant */\n variantName: string;\n /** Session ID of the original test case */\n testCaseSessionId: string;\n /** Session ID of the result session (if created) */\n resultSessionId?: string;\n /** Whether the run succeeded */\n success: boolean;\n /** Error message if failed */\n errorMessage?: string;\n}\n\n/**\n * Aggregated experiment results.\n */\nexport interface ExperimentResults {\n experimentId: string;\n status: string;\n variants: Array<{\n name: string;\n successRate: number;\n avgScore?: number;\n avgCost?: number;\n avgDuration?: number;\n runCount: number;\n }>;\n winner?: string;\n}\n\n// ============================================================================\n// Run Tracker\n// ============================================================================\n\n/**\n * Tracks a single experiment run.\n *\n * Created by Experiment.trackRun() - use this to manually track runs.\n */\nexport class ExperimentRunTracker {\n private readonly experiment: Experiment;\n private readonly variantName: string;\n private readonly baseSessionId: string;\n private runId?: string;\n private resultSessionId?: string;\n private _success = true;\n private _errorMessage?: string;\n\n /** @internal */\n constructor(experiment: Experiment, variantName: string, baseSessionId: string) {\n this.experiment = experiment;\n this.variantName = variantName;\n this.baseSessionId = baseSessionId;\n }\n\n /**\n * Start the run - creates a run record via API.\n */\n async start(): Promise<this> {\n try {\n const response = await this.experiment.request<{ run?: { id: string } }>(\n 'POST',\n `/api/experiments/${this.experiment.experimentId}/runs`,\n {\n variantName: this.variantName,\n baseSessionId: this.baseSessionId,\n }\n );\n this.runId = response?.run?.id;\n } catch {\n // Ignore errors starting the run\n }\n return this;\n }\n\n /**\n * Set the session ID of the result session.\n */\n setResultSessionId(sessionId: string): void {\n this.resultSessionId = sessionId;\n }\n\n /**\n * Mark the run as complete.\n */\n async complete(): Promise<void> {\n if (!this.runId) return;\n\n try {\n await this.experiment.request(\n 'PATCH',\n `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,\n {\n status: 'completed',\n resultSessionId: this.resultSessionId,\n }\n );\n } catch {\n // Ignore errors completing the run\n }\n }\n\n /**\n * Mark the run as failed.\n */\n async fail(errorMessage: string): Promise<void> {\n this._success = false;\n this._errorMessage = errorMessage;\n\n if (!this.runId) return;\n\n try {\n await this.experiment.request(\n 'PATCH',\n `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,\n {\n status: 'failed',\n resultSessionId: this.resultSessionId,\n errorMessage,\n }\n );\n } catch {\n // Ignore errors failing the run\n }\n }\n\n /**\n * Get the result of this run.\n */\n getResult(): ExperimentRunResult {\n return {\n variantName: this.variantName,\n testCaseSessionId: this.baseSessionId,\n resultSessionId: this.resultSessionId,\n success: this._success,\n errorMessage: this._errorMessage,\n };\n }\n}\n\n// ============================================================================\n// Experiment Class\n// ============================================================================\n\ntype AgentFunction = (\n testCase: ExperimentTestCase,\n variant: ExperimentVariant,\n tracker: ExperimentRunTracker\n) => Promise<void>;\n\n/**\n * Run experiments with results synced to Sentrial.\n *\n * @example\n * ```ts\n * const experiment = new Experiment('exp_abc123');\n *\n * await experiment.load();\n * console.log(`Testing ${experiment.variants.length} variants`);\n *\n * await experiment.run(async (testCase, variant, tracker) => {\n * const sessionId = await runMyAgent(testCase.userInput, variant.systemPrompt);\n * tracker.setResultSessionId(sessionId);\n * });\n * ```\n */\nexport class Experiment {\n /** The experiment ID */\n readonly experimentId: string;\n\n /** @internal */\n readonly client: SentrialClient;\n /** @internal */\n readonly apiUrl: string;\n /** @internal */\n readonly apiKey?: string;\n\n private config?: Record<string, unknown>;\n private _variants?: ExperimentVariant[];\n private _testCases?: ExperimentTestCase[];\n\n /**\n * Create an experiment instance.\n *\n * @param experimentId - The experiment ID from Sentrial dashboard\n * @param options - Configuration options\n */\n constructor(\n experimentId: string,\n options: {\n apiKey?: string;\n apiUrl?: string;\n } = {}\n ) {\n this.experimentId = experimentId;\n this.apiUrl = (\n options.apiUrl ??\n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_URL : undefined) ??\n DEFAULT_API_URL\n ).replace(/\\/$/, '');\n this.apiKey =\n options.apiKey ??\n (typeof process !== 'undefined' ? process.env?.SENTRIAL_API_KEY : undefined);\n this.client = new SentrialClient({\n apiKey: this.apiKey,\n apiUrl: this.apiUrl,\n failSilently: false, // We want errors for experiments\n });\n }\n\n /**\n * Make an HTTP request to the API\n * @internal\n */\n async request<T>(method: string, path: string, body?: unknown): Promise<T | null> {\n // Route through the client's retrying transport (backoff, timeout, PII redaction,\n // structured errors) instead of a bare fetch — the client was already built with\n // failSilently:false precisely so experiment calls surface errors (#482).\n return this.client.apiRequest<T>(method, `${this.apiUrl}${path}`, body);\n }\n\n /**\n * Load the experiment configuration from the API.\n */\n async load(): Promise<this> {\n interface ExperimentConfig {\n experiment?: {\n name?: string;\n status?: string;\n variants?: Array<{\n name: string;\n systemPrompt?: string;\n description?: string;\n }>;\n testCases?: Array<{\n sessionId: string;\n userInput?: string;\n }>;\n };\n }\n\n const response = await this.request<ExperimentConfig>(\n 'GET',\n `/api/experiments/${this.experimentId}/runs`\n );\n\n if (!response?.experiment) {\n throw new Error(`Failed to load experiment config for ${this.experimentId}`);\n }\n\n this.config = response.experiment;\n\n // Parse variants\n const variants = this.config.variants as\n | Array<{ name: string; systemPrompt?: string; description?: string }>\n | undefined;\n this._variants =\n variants?.map((v) => ({\n name: v.name ?? '',\n systemPrompt: v.systemPrompt ?? '',\n description: v.description,\n })) ?? [];\n\n // Parse test cases (only include those with input)\n const testCases = this.config.testCases as\n | Array<{ sessionId: string; userInput?: string }>\n | undefined;\n this._testCases =\n testCases\n ?.filter((tc) => tc.userInput)\n ?.map((tc) => ({\n sessionId: tc.sessionId ?? '',\n userInput: tc.userInput ?? '',\n })) ?? [];\n\n return this;\n }\n\n /**\n * Get experiment name.\n */\n get name(): string {\n return (this.config?.name as string) ?? '';\n }\n\n /**\n * Get experiment status.\n */\n get status(): string {\n return (this.config?.status as string) ?? '';\n }\n\n /**\n * Get list of variants to test.\n */\n get variants(): ExperimentVariant[] {\n if (!this._variants) {\n throw new Error('Experiment not loaded. Call load() first.');\n }\n return this._variants;\n }\n\n /**\n * Get list of test cases.\n */\n get testCases(): ExperimentTestCase[] {\n if (!this._testCases) {\n throw new Error('Experiment not loaded. Call load() first.');\n }\n return this._testCases;\n }\n\n /**\n * Get a specific variant by name.\n */\n getVariant(name: string): ExperimentVariant | undefined {\n return this.variants.find((v) => v.name === name);\n }\n\n /**\n * Create a run tracker for manual experiment runs.\n */\n async trackRun(variantName: string, baseSessionId: string): Promise<ExperimentRunTracker> {\n const tracker = new ExperimentRunTracker(this, variantName, baseSessionId);\n await tracker.start();\n return tracker;\n }\n\n /**\n * Run the experiment with all variants and test cases.\n *\n * @param agentFn - Function that runs your agent with a test case and variant\n * @param options - Run options\n * @returns List of run results\n */\n async run(\n agentFn: AgentFunction,\n options: {\n /** Run test cases in parallel */\n parallel?: boolean;\n /** Max parallel workers */\n maxWorkers?: number;\n } = {}\n ): Promise<ExperimentRunResult[]> {\n const { parallel = false, maxWorkers = 4 } = options;\n\n if (!this._variants || !this._testCases) {\n await this.load();\n }\n\n const results: ExperimentRunResult[] = [];\n const totalRuns = this.variants.length * this.testCases.length;\n let completed = 0;\n\n console.log(`Running experiment: ${this.name || this.experimentId}`);\n console.log(` Variants: ${this.variants.length}`);\n console.log(` Test cases: ${this.testCases.length}`);\n console.log(` Total runs: ${totalRuns}`);\n console.log();\n\n const runSingle = async (\n variant: ExperimentVariant,\n testCase: ExperimentTestCase\n ): Promise<ExperimentRunResult> => {\n const tracker = await this.trackRun(variant.name, testCase.sessionId);\n\n // Set experiment context so getSystemPrompt() returns the variant prompt\n setExperimentContext({\n systemPrompt: variant.systemPrompt,\n variantName: variant.name,\n experimentId: this.experimentId,\n testCaseId: testCase.sessionId,\n });\n\n try {\n await agentFn(testCase, variant, tracker);\n await tracker.complete();\n return tracker.getResult();\n } catch (error) {\n // Guarded coercion: agentFn is user code and can throw arbitrary hostile values —\n // a String()/getter throw here would skip tracker.fail() and escape the runner.\n let errorMessage: string;\n try {\n errorMessage = error instanceof Error ? error.message : String(error);\n } catch {\n errorMessage = '[unserializable]';\n }\n await tracker.fail(errorMessage);\n return tracker.getResult();\n } finally {\n clearExperimentContext();\n }\n };\n\n if (parallel) {\n // Run in parallel with limited concurrency\n const queue: Array<() => Promise<void>> = [];\n\n for (const variant of this.variants) {\n for (const testCase of this.testCases) {\n queue.push(async () => {\n const result = await runSingle(variant, testCase);\n results.push(result);\n completed++;\n const status = result.success ? '✓' : '✗';\n console.log(\n ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`\n );\n });\n }\n }\n\n // Process queue with limited concurrency\n const executing: Promise<void>[] = [];\n for (const task of queue) {\n const promise = task().then(() => {\n executing.splice(executing.indexOf(promise), 1);\n });\n executing.push(promise);\n\n if (executing.length >= maxWorkers) {\n await Promise.race(executing);\n }\n }\n await Promise.all(executing);\n } else {\n // Run sequentially\n for (const variant of this.variants) {\n for (const testCase of this.testCases) {\n const result = await runSingle(variant, testCase);\n results.push(result);\n completed++;\n const status = result.success ? '✓' : '✗';\n console.log(\n ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`\n );\n }\n }\n }\n\n console.log();\n console.log('Experiment complete!');\n console.log(` Successful: ${results.filter((r) => r.success).length}/${totalRuns}`);\n console.log(` Failed: ${results.filter((r) => !r.success).length}/${totalRuns}`);\n\n return results;\n }\n\n /**\n * Reset all runs for this experiment (for re-running).\n */\n async reset(): Promise<boolean> {\n try {\n const response = await this.request('DELETE', `/api/experiments/${this.experimentId}/runs`);\n\n if (response !== null) {\n // Clear cached config\n this.config = undefined;\n this._variants = undefined;\n this._testCases = undefined;\n return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n /**\n * Fetch aggregated results from the API.\n */\n async getResults(): Promise<ExperimentResults | null> {\n try {\n const response = await this.request<ExperimentResults>(\n 'GET',\n `/api/experiments/${this.experimentId}/results`\n );\n return response;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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","names":["hashValue","_createHash","getCreateHash","hashValue","fields","keys","flat","value","present","uuid","fields","context","spanError","fields","safeString","record","safeString","keys","record","context","EventType","safeString","safeStringify","_defaultClient","getClient","safeStringify","safeString","getClient","value","safeString","span","trace","CONTENT_PREVIEW_CHARS","safeString","safeGet","cleanString","safeCount","safeRecordCopy","wrapToolOutput","fields","duplicateInstrumentWarned","safeGet","duplicateInstrumentWarned","cleanString","getClient","safeString","context"]}
|