risicare 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/frameworks/instructor.cjs +45 -17
  2. package/dist/frameworks/instructor.cjs.map +1 -1
  3. package/dist/frameworks/instructor.js +47 -17
  4. package/dist/frameworks/instructor.js.map +1 -1
  5. package/dist/frameworks/langchain.cjs +73 -6
  6. package/dist/frameworks/langchain.cjs.map +1 -1
  7. package/dist/frameworks/langchain.d.cts +20 -4
  8. package/dist/frameworks/langchain.d.ts +20 -4
  9. package/dist/frameworks/langchain.js +75 -6
  10. package/dist/frameworks/langchain.js.map +1 -1
  11. package/dist/frameworks/langgraph.cjs +73 -6
  12. package/dist/frameworks/langgraph.cjs.map +1 -1
  13. package/dist/frameworks/langgraph.js +75 -6
  14. package/dist/frameworks/langgraph.js.map +1 -1
  15. package/dist/frameworks/llamaindex.cjs +41 -14
  16. package/dist/frameworks/llamaindex.cjs.map +1 -1
  17. package/dist/frameworks/llamaindex.js +43 -14
  18. package/dist/frameworks/llamaindex.js.map +1 -1
  19. package/dist/index.cjs +1494 -67
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +436 -1
  22. package/dist/index.d.ts +436 -1
  23. package/dist/index.js +1515 -67
  24. package/dist/index.js.map +1 -1
  25. package/dist/providers/anthropic/index.cjs +74 -24
  26. package/dist/providers/anthropic/index.cjs.map +1 -1
  27. package/dist/providers/anthropic/index.js +76 -24
  28. package/dist/providers/anthropic/index.js.map +1 -1
  29. package/dist/providers/bedrock/index.cjs +81 -24
  30. package/dist/providers/bedrock/index.cjs.map +1 -1
  31. package/dist/providers/bedrock/index.js +83 -24
  32. package/dist/providers/bedrock/index.js.map +1 -1
  33. package/dist/providers/cerebras/index.cjs +78 -25
  34. package/dist/providers/cerebras/index.cjs.map +1 -1
  35. package/dist/providers/cerebras/index.js +80 -25
  36. package/dist/providers/cerebras/index.js.map +1 -1
  37. package/dist/providers/cohere/index.cjs +95 -25
  38. package/dist/providers/cohere/index.cjs.map +1 -1
  39. package/dist/providers/cohere/index.js +97 -25
  40. package/dist/providers/cohere/index.js.map +1 -1
  41. package/dist/providers/google/index.cjs +77 -25
  42. package/dist/providers/google/index.cjs.map +1 -1
  43. package/dist/providers/google/index.js +79 -25
  44. package/dist/providers/google/index.js.map +1 -1
  45. package/dist/providers/groq/index.cjs +80 -25
  46. package/dist/providers/groq/index.cjs.map +1 -1
  47. package/dist/providers/groq/index.js +82 -25
  48. package/dist/providers/groq/index.js.map +1 -1
  49. package/dist/providers/huggingface/index.cjs +80 -25
  50. package/dist/providers/huggingface/index.cjs.map +1 -1
  51. package/dist/providers/huggingface/index.js +82 -25
  52. package/dist/providers/huggingface/index.js.map +1 -1
  53. package/dist/providers/mistral/index.cjs +72 -24
  54. package/dist/providers/mistral/index.cjs.map +1 -1
  55. package/dist/providers/mistral/index.js +74 -24
  56. package/dist/providers/mistral/index.js.map +1 -1
  57. package/dist/providers/ollama/index.cjs +83 -25
  58. package/dist/providers/ollama/index.cjs.map +1 -1
  59. package/dist/providers/ollama/index.js +85 -25
  60. package/dist/providers/ollama/index.js.map +1 -1
  61. package/dist/providers/openai/index.cjs +1429 -28
  62. package/dist/providers/openai/index.cjs.map +1 -1
  63. package/dist/providers/openai/index.js +1447 -28
  64. package/dist/providers/openai/index.js.map +1 -1
  65. package/dist/providers/together/index.cjs +80 -25
  66. package/dist/providers/together/index.cjs.map +1 -1
  67. package/dist/providers/together/index.js +82 -25
  68. package/dist/providers/together/index.js.map +1 -1
  69. package/dist/providers/vercel-ai/index.cjs +45 -17
  70. package/dist/providers/vercel-ai/index.cjs.map +1 -1
  71. package/dist/providers/vercel-ai/index.js +47 -17
  72. package/dist/providers/vercel-ai/index.js.map +1 -1
  73. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/groq/index.ts","../../../src/ids.ts","../../../src/noop.ts","../../../src/globals.ts","../../../src/context/storage.ts","../../../src/utils/log.ts","../../../src/client.ts","../../../src/utils/pricing.ts","../../../src/context/dedup.ts","../../../src/providers/groq/patch.ts"],"sourcesContent":["export { patchGroq } from './patch.js';\n","/**\n * ID generation for traces and spans.\n *\n * Trace IDs: 32 lowercase hex characters (16 random bytes)\n * Span IDs: 16 lowercase hex characters (8 random bytes)\n *\n * Uses crypto.randomBytes for cryptographically secure randomness.\n */\n\nimport { randomBytes } from 'node:crypto';\n\nconst HEX_REGEX_32 = /^[0-9a-f]{32}$/;\nconst HEX_REGEX_16 = /^[0-9a-f]{16}$/;\n\nexport function generateTraceId(): string {\n return randomBytes(16).toString('hex');\n}\n\nexport function generateSpanId(): string {\n return randomBytes(8).toString('hex');\n}\n\nexport function generateAgentId(prefix?: string): string {\n const suffix = randomBytes(8).toString('hex');\n return prefix ? `${prefix}-${suffix}` : suffix;\n}\n\nexport function validateTraceId(id: string): boolean {\n return HEX_REGEX_32.test(id);\n}\n\nexport function validateSpanId(id: string): boolean {\n return HEX_REGEX_16.test(id);\n}\n","/**\n * No-op implementations for the disabled path.\n *\n * When tracing is disabled, all operations return these no-op objects\n * to maintain zero overhead. No allocations, no side effects.\n */\n\nimport { SpanKind, SpanStatus, type SpanPayload } from './types.js';\n\n/**\n * A frozen no-op span that silently ignores all operations.\n * Used when SDK is disabled to avoid overhead.\n */\nexport const NOOP_SPAN = Object.freeze({\n traceId: '00000000000000000000000000000000',\n spanId: '0000000000000000',\n parentSpanId: undefined,\n name: 'noop',\n kind: SpanKind.INTERNAL,\n startTime: '',\n startHrtime: 0,\n endTime: undefined,\n status: SpanStatus.UNSET,\n statusMessage: undefined,\n attributes: Object.freeze({}) as Record<string, unknown>,\n events: Object.freeze([]) as readonly [],\n links: Object.freeze([]) as readonly [],\n sessionId: undefined,\n agentId: undefined,\n agentName: undefined,\n semanticPhase: undefined,\n llmProvider: undefined,\n llmModel: undefined,\n llmPromptTokens: undefined,\n llmCompletionTokens: undefined,\n llmTotalTokens: undefined,\n llmCostUsd: undefined,\n toolName: undefined,\n toolSuccess: undefined,\n isEnded: true,\n durationMs: 0,\n\n setAttribute() { return this; },\n setAttributes() { return this; },\n setStatus() { return this; },\n addEvent() { return this; },\n addLink() { return this; },\n recordException() { return this; },\n setLlmFields() { return this; },\n setToolFields() { return this; },\n end() {},\n toPayload(): SpanPayload {\n return {\n traceId: this.traceId,\n spanId: this.spanId,\n name: this.name,\n kind: this.kind,\n startTime: this.startTime,\n status: this.status,\n attributes: {},\n events: [],\n links: [],\n };\n },\n});\n\nexport type NoopSpan = typeof NOOP_SPAN;\n","/**\n * Shared state via globalThis — ensures all entry point bundles share\n * the same singleton instances.\n *\n * Problem: tsup with `splitting: false` gives each entry point (index,\n * openai, anthropic, vercel-ai) its own copy of module-level variables.\n * This means `init()` from 'risicare' sets a tracer that 'risicare/openai'\n * can't see — breaking all provider instrumentation silently.\n *\n * Solution: Store all mutable singletons on globalThis with a namespaced\n * prefix. Every bundle reads/writes the same global slots.\n *\n * This pattern is used by React, OpenTelemetry, and other SDKs that must\n * share state across independently bundled entry points.\n *\n * @internal\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst G = globalThis as any;\nconst PREFIX = '__risicare_';\n\n// ─── Client & Tracer ────────────────────────────────────────────────────────\n\nexport function getClient(): unknown {\n return G[PREFIX + 'client'];\n}\n\nexport function setClient(client: unknown): void {\n G[PREFIX + 'client'] = client;\n}\n\nexport function getTracer(): unknown {\n return G[PREFIX + 'tracer'];\n}\n\nexport function setTracer(tracer: unknown): void {\n G[PREFIX + 'tracer'] = tracer;\n}\n\n// ─── Context Storage ────────────────────────────────────────────────────────\n\nexport function getContextStorage(): AsyncLocalStorage<unknown> {\n if (!G[PREFIX + 'ctx']) {\n G[PREFIX + 'ctx'] = new AsyncLocalStorage();\n }\n return G[PREFIX + 'ctx'];\n}\n\n// ─── Span Registry ──────────────────────────────────────────────────────────\n\nexport function getRegistry(): Map<string, unknown> {\n if (!G[PREFIX + 'registry']) {\n G[PREFIX + 'registry'] = new Map();\n }\n return G[PREFIX + 'registry'];\n}\n\nexport function getOpCount(): number {\n return G[PREFIX + 'opcount'] ?? 0;\n}\n\nexport function setOpCount(n: number): void {\n G[PREFIX + 'opcount'] = n;\n}\n\n// ─── Debug Flag ─────────────────────────────────────────────────────────────\n\nexport function getDebug(): boolean {\n return G[PREFIX + 'debug'] ?? false;\n}\n\nexport function setDebugFlag(enabled: boolean): void {\n G[PREFIX + 'debug'] = enabled;\n}\n","/**\n * AsyncLocalStorage-based context propagation.\n *\n * Uses a single AsyncLocalStorage instance with a composite state object.\n * This is simpler and more performant than multiple separate stores.\n *\n * Node.js AsyncLocalStorage automatically propagates through:\n * - Promise / async-await\n * - setTimeout / setImmediate\n * - EventEmitter callbacks\n * - process.nextTick\n * - async generators (unlike Python's contextvars!)\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Span } from '../span.js';\nimport type { SemanticPhase } from '../types.js';\nimport { getContextStorage } from '../globals.js';\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\nexport interface SessionContext {\n sessionId: string;\n userId?: string;\n metadata?: Record<string, unknown>;\n parentSessionId?: string;\n turnNumber?: number;\n}\n\nexport interface AgentContext {\n agentId: string;\n agentName?: string;\n agentRole?: string;\n agentType?: string;\n parentAgentId?: string;\n version?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface ContextState {\n session?: SessionContext;\n agent?: AgentContext;\n span?: Span;\n phase?: SemanticPhase;\n /** When true, provider instrumentors skip span creation (framework is handling it). */\n _suppressProviderInstrumentation?: boolean;\n}\n\n// ─── Storage Accessor ────────────────────────────────────────────────────────\n\nfunction storage(): AsyncLocalStorage<ContextState> {\n return getContextStorage() as AsyncLocalStorage<ContextState>;\n}\n\n// ─── Core Operations ─────────────────────────────────────────────────────────\n\n/**\n * Get the current context state, or empty object if outside any context.\n */\nexport function getContext(): ContextState {\n return storage().getStore() ?? {};\n}\n\n/**\n * Run a callback within a new context scope.\n * The new scope inherits from the parent, with overrides applied.\n */\nexport function runWithContext<T>(overrides: Partial<ContextState>, fn: () => T): T {\n const parent = getContext();\n const merged: ContextState = { ...parent, ...overrides };\n return storage().run(merged, fn);\n}\n\n/**\n * Run an async callback within a new context scope.\n */\nexport function runWithContextAsync<T>(overrides: Partial<ContextState>, fn: () => Promise<T>): Promise<T> {\n const parent = getContext();\n const merged: ContextState = { ...parent, ...overrides };\n return storage().run(merged, fn);\n}\n\n// ─── Context Accessors ───────────────────────────────────────────────────────\n\nexport function getCurrentSession(): SessionContext | undefined {\n return getContext().session;\n}\n\nexport function getCurrentAgent(): AgentContext | undefined {\n return getContext().agent;\n}\n\nexport function getCurrentSpan(): Span | undefined {\n return getContext().span;\n}\n\nexport function getCurrentPhase(): SemanticPhase | undefined {\n return getContext().phase;\n}\n\nexport function getCurrentSessionId(): string | undefined {\n return getContext().session?.sessionId;\n}\n\nexport function getCurrentAgentId(): string | undefined {\n return getContext().agent?.agentId;\n}\n\nexport function getCurrentTraceId(): string | undefined {\n return getContext().span?.traceId;\n}\n\nexport function getCurrentSpanId(): string | undefined {\n return getContext().span?.spanId;\n}\n\nexport function getCurrentParentSpanId(): string | undefined {\n return getContext().span?.parentSpanId;\n}\n\n/**\n * Get all current context as a plain object (for debugging/serialization).\n */\nexport function getCurrentContext(): Record<string, unknown> {\n const ctx = getContext();\n return {\n session: ctx.session ? {\n sessionId: ctx.session.sessionId,\n userId: ctx.session.userId,\n ...(ctx.session.parentSessionId !== undefined ? { parentSessionId: ctx.session.parentSessionId } : {}),\n ...(ctx.session.turnNumber !== undefined ? { turnNumber: ctx.session.turnNumber } : {}),\n ...(ctx.session.metadata !== undefined ? { metadata: ctx.session.metadata } : {}),\n } : null,\n agent: ctx.agent ? {\n agentId: ctx.agent.agentId,\n agentName: ctx.agent.agentName,\n agentRole: ctx.agent.agentRole,\n agentType: ctx.agent.agentType,\n ...(ctx.agent.parentAgentId !== undefined ? { parentAgentId: ctx.agent.parentAgentId } : {}),\n ...(ctx.agent.version !== undefined ? { version: ctx.agent.version } : {}),\n ...(ctx.agent.metadata !== undefined ? { metadata: ctx.agent.metadata } : {}),\n } : null,\n span: ctx.span ? { spanId: ctx.span.spanId, traceId: ctx.span.traceId } : null,\n phase: ctx.phase ?? null,\n };\n}\n","/**\n * Internal logger for the Risicare SDK.\n *\n * Centralizes all diagnostic output so that:\n * - Debug messages are gated by a single flag (zero-cost when disabled)\n * - Warnings always fire (operational alerts like queue full)\n * - All output goes to stderr with a consistent [risicare] prefix\n * - A future custom logger callback can be added in one place\n */\n\nimport { getDebug, setDebugFlag } from '../globals.js';\n\n/**\n * Enable or disable debug logging. Called once during init().\n * @internal\n */\nexport function setDebug(enabled: boolean): void {\n setDebugFlag(enabled);\n}\n\n/**\n * Log a debug message. Only outputs when debug mode is enabled.\n * @internal\n */\nexport function debug(msg: string): void {\n if (getDebug()) {\n process.stderr.write(`[risicare] ${msg}\\n`);\n }\n}\n\n/**\n * Log a warning. Always outputs regardless of debug mode.\n * Use sparingly — only for operational issues the user should see.\n * @internal\n */\nexport function warn(msg: string): void {\n process.stderr.write(`[risicare] WARNING: ${msg}\\n`);\n}\n","/**\n * RisicareClient — singleton client managing SDK lifecycle.\n *\n * Handles initialization, shutdown, and the connection between\n * the Tracer and the export pipeline (batch processor + HTTP exporter).\n *\n * Usage:\n * import { init, shutdown } from 'risicare';\n * init({ apiKey: 'rsk-...' }); // API key determines project\n * // ... instrument code ...\n * await shutdown(); // flush remaining spans\n */\n\nimport { type RisicareConfig, resolveConfig } from './config.js';\nimport { Tracer } from './tracer.js';\nimport { BatchSpanProcessor } from './exporters/batch.js';\nimport { HttpExporter } from './exporters/http.js';\nimport { ConsoleExporter } from './exporters/console.js';\nimport { SpanKind, SpanStatus } from './types.js';\nimport type { SpanExporter } from './exporters/base.js';\nimport { setDebug, debug } from './utils/log.js';\nimport {\n getClient as getGlobalClient,\n setClient as setGlobalClient,\n getTracer as getGlobalTracer,\n setTracer as setGlobalTracer,\n} from './globals.js';\n\n// ─── Client Class ───────────────────────────────────────────────────────────\n\nclass RisicareClient {\n readonly config: ReturnType<typeof resolveConfig>;\n readonly processor: BatchSpanProcessor;\n readonly tracer: Tracer;\n private _shutdownPromise: Promise<void> | undefined;\n private _shutdownHandlers: { signal: string; handler: () => void }[] = [];\n\n constructor(config?: Partial<RisicareConfig>) {\n this.config = resolveConfig(config);\n\n // API key format validation\n if (this.config.apiKey && !this.config.apiKey.startsWith('rsk-')) {\n debug('Warning: API key should start with \"rsk-\". Got: ' + this.config.apiKey.slice(0, 4) + '...');\n }\n\n // Build exporter chain\n let exporter: SpanExporter;\n if (this.config.debug && !this.config.apiKey) {\n exporter = new ConsoleExporter();\n } else if (this.config.apiKey) {\n exporter = new HttpExporter({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId || undefined,\n environment: this.config.environment || undefined,\n compress: this.config.compress,\n });\n } else {\n // No API key and not debug — use console as fallback\n exporter = new ConsoleExporter();\n }\n\n this.processor = new BatchSpanProcessor({\n exporters: [exporter],\n batchSize: this.config.batchSize,\n batchTimeoutMs: this.config.batchTimeoutMs,\n maxQueueSize: this.config.maxQueueSize,\n debug: this.config.debug,\n });\n\n this.tracer = new Tracer({\n onSpanEnd: (span) => this.processor.onSpanEnd(span),\n sampleRate: this.config.sampleRate,\n enabled: this.config.enabled,\n traceContent: this.config.traceContent,\n });\n\n // Start the batch processor (enables span queuing and periodic flushing)\n this.processor.start();\n\n // Register shutdown hooks\n this._registerShutdownHooks();\n\n // Enable internal debug logging if configured\n setDebug(this.config.debug);\n debug(`Initialized: enabled=${this.config.enabled}, endpoint=${this.config.endpoint}`);\n }\n\n get enabled(): boolean {\n return this.tracer.enabled;\n }\n\n set enabled(value: boolean) {\n this.tracer.enabled = value;\n }\n\n // Audit #6: Promise-based shutdown dedup (fixes TOCTOU race condition)\n async shutdown(): Promise<void> {\n if (this._shutdownPromise) return this._shutdownPromise;\n this._shutdownPromise = this._doShutdown();\n return this._shutdownPromise;\n }\n\n private async _doShutdown(): Promise<void> {\n debug('Shutting down...');\n\n // Audit #3: Remove process listeners to prevent leak\n for (const { signal, handler } of this._shutdownHandlers) {\n process.removeListener(signal, handler);\n }\n this._shutdownHandlers = [];\n\n await this.processor.shutdown();\n }\n\n async flush(): Promise<void> {\n await this.processor.flush();\n }\n\n private _registerShutdownHooks(): void {\n const onShutdown = () => {\n // Audit #3: Add 5s timeout to prevent hanging on signal\n const timeout = setTimeout(() => process.exit(1), 5000);\n timeout.unref();\n this.shutdown().catch(() => {}).finally(() => clearTimeout(timeout));\n };\n\n const signals = ['beforeExit', 'SIGTERM', 'SIGINT'];\n for (const signal of signals) {\n process.once(signal, onShutdown);\n this._shutdownHandlers.push({ signal, handler: onShutdown });\n }\n }\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * Initialize the Risicare SDK. Call once at application startup.\n *\n * @example\n * import { init } from 'risicare';\n * init({ apiKey: 'rsk-...', serviceName: 'my-agent', environment: 'production' });\n */\nexport function init(config?: Partial<RisicareConfig>): void {\n if (getGlobalClient()) {\n debug('Already initialized. Call shutdown() first to re-initialize.');\n return;\n }\n\n const client = new RisicareClient(config);\n setGlobalClient(client);\n setGlobalTracer(client.tracer);\n}\n\n/**\n * Gracefully shut down the SDK. Flushes pending spans before resolving.\n */\nexport async function shutdown(): Promise<void> {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client) return;\n await client.shutdown();\n setGlobalClient(undefined);\n setGlobalTracer(undefined);\n}\n\n/**\n * Flush all pending spans without shutting down.\n */\nexport async function flush(): Promise<void> {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client) return;\n await client.flush();\n}\n\n/**\n * Enable tracing at runtime.\n */\nexport function enable(): void {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (client) client.enabled = true;\n}\n\n/**\n * Disable tracing at runtime. Spans will not be created or exported.\n */\nexport function disable(): void {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (client) client.enabled = false;\n}\n\n/**\n * Check whether tracing is currently enabled.\n */\nexport function isEnabled(): boolean {\n const client = getGlobalClient() as RisicareClient | undefined;\n return client?.enabled ?? false;\n}\n\n/**\n * Get the global tracer instance. Returns undefined if not initialized.\n */\nexport function getTracer(): Tracer | undefined {\n return getGlobalTracer() as Tracer | undefined;\n}\n\n/**\n * Get the global tracer, or throw if not initialized.\n * @internal Used by decorators and providers that require an active tracer.\n */\nexport function requireTracer(): Tracer {\n const tracer = getGlobalTracer() as Tracer | undefined;\n if (!tracer) {\n throw new Error(\n 'Risicare SDK not initialized. Call init() before using tracing features.',\n );\n }\n return tracer;\n}\n\n/**\n * Check whether content tracing (prompt/completion capture) is enabled.\n */\nexport function getTraceContent(): boolean {\n const tracer = getGlobalTracer() as Tracer | undefined;\n return tracer?.traceContent ?? true;\n}\n\n/**\n * Get SDK metrics: exported spans, dropped spans, failed exports, queue stats.\n * Returns zero-valued metrics if SDK is not initialized.\n */\nexport function getMetrics() {\n const client = getGlobalClient() as RisicareClient | undefined;\n return client?.processor.getMetrics() ?? {\n exportedSpans: 0,\n droppedSpans: 0,\n failedExports: 0,\n queueSize: 0,\n queueCapacity: 0,\n queueUtilization: 0,\n };\n}\n\n// ─── reportError ──────────────────────────────────────────────────────────\n\n/**\n * Report a caught exception to the self-healing pipeline.\n *\n * Creates an error span that triggers diagnosis and fix generation.\n * This function never throws and is non-blocking.\n *\n * @param error - The caught exception (Error object or string)\n * @param options - Optional attributes and context overrides\n */\nexport function reportError(\n error: unknown,\n options?: { name?: string; attributes?: Record<string, unknown> },\n): void {\n try {\n const tracer = getTracer();\n if (!tracer) return;\n\n const err = error instanceof Error ? error : new Error(String(error));\n const spanName = options?.name ?? `error:${err.constructor.name}`;\n\n tracer.startSpan({ name: spanName, kind: SpanKind.INTERNAL }, (span) => {\n span.setStatus(SpanStatus.ERROR, err.message);\n span.setAttribute('error', true);\n span.setAttribute('error.type', err.constructor.name);\n span.setAttribute('error.message', err.message.slice(0, 2000));\n if (err.stack) span.setAttribute('error.stack', err.stack.slice(0, 4000));\n span.setAttribute('risicare.reported_error', true);\n if (options?.attributes) {\n for (const [k, v] of Object.entries(options.attributes)) {\n span.setAttribute(k, v);\n }\n }\n });\n } catch {\n // Never crash the host application\n debug('reportError failed');\n }\n}\n\n// ─── score ─────────────────────────────────────────────────────────────────\n\n/**\n * Record a custom evaluation score on a trace.\n *\n * Sends the score to the server in a fire-and-forget fashion.\n * This function never throws and is non-blocking.\n *\n * @param traceId - The trace to score\n * @param name - Score name (e.g., \"accuracy\", \"user_satisfaction\")\n * @param value - Score value between 0.0 and 1.0 inclusive\n * @param options - Optional span_id and comment\n */\nexport function score(\n traceId: string,\n name: string,\n value: number,\n options?: { spanId?: string; comment?: string },\n): void {\n try {\n if (typeof value !== 'number' || value < 0.0 || value > 1.0) {\n debug(`score: value must be in [0.0, 1.0], got ${value}. Score not sent.`);\n return;\n }\n if (!traceId || !name) {\n debug('score: traceId and name are required');\n return;\n }\n\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client?.enabled || !client.config.apiKey) return;\n\n const endpoint = client.config.endpoint.replace(/\\/$/, '');\n const url = `${endpoint}/api/v1/scores`;\n const body = JSON.stringify({\n trace_id: traceId,\n name,\n score: value,\n source: 'sdk',\n ...(options?.spanId && { span_id: options.spanId }),\n ...(options?.comment && { comment: options.comment }),\n });\n\n // Fire-and-forget — never blocks caller\n fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${client.config.apiKey}`,\n },\n body,\n }).catch((err) => debug(`score: send failed: ${err}`));\n } catch {\n // Never crash the host application\n debug('score failed');\n }\n}\n","/**\n * Token cost calculation table.\n *\n * Prices are per 1M tokens. Update monthly.\n * Source: provider pricing pages.\n */\n\ninterface ModelPricing {\n input: number; // USD per 1M input tokens\n output: number; // USD per 1M output tokens\n}\n\nconst PRICING: Record<string, ModelPricing> = {\n // OpenAI\n 'gpt-4o': { input: 2.50, output: 10.00 },\n 'gpt-4o-mini': { input: 0.15, output: 0.60 },\n 'gpt-4-turbo': { input: 10.00, output: 30.00 },\n 'gpt-4': { input: 30.00, output: 60.00 },\n 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },\n 'o1': { input: 15.00, output: 60.00 },\n 'o1-mini': { input: 3.00, output: 12.00 },\n 'o3-mini': { input: 1.10, output: 4.40 },\n\n // Anthropic\n 'claude-opus-4-5-20251101': { input: 15.00, output: 75.00 },\n 'claude-sonnet-4-5-20250929': { input: 3.00, output: 15.00 },\n 'claude-haiku-4-5-20251001': { input: 0.80, output: 4.00 },\n 'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },\n 'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },\n 'claude-3-opus-20240229': { input: 15.00, output: 75.00 },\n\n // Google\n 'gemini-2.0-flash': { input: 0.10, output: 0.40 },\n 'gemini-1.5-pro': { input: 1.25, output: 5.00 },\n 'gemini-1.5-flash': { input: 0.075, output: 0.30 },\n\n // Groq\n 'llama-3.3-70b-versatile': { input: 0.59, output: 0.79 },\n 'llama-3.1-8b-instant': { input: 0.05, output: 0.08 },\n 'mixtral-8x7b-32768': { input: 0.24, output: 0.24 },\n\n // DeepSeek\n 'deepseek-chat': { input: 0.14, output: 0.28 },\n 'deepseek-reasoner': { input: 0.55, output: 2.19 },\n\n // Together.ai (open-source models)\n 'meta-llama/llama-3.3-70b-instruct-turbo': { input: 0.88, output: 0.88 },\n 'meta-llama/meta-llama-3.1-8b-instruct-turbo': { input: 0.18, output: 0.18 },\n 'meta-llama/llama-3.2-3b-instruct-turbo': { input: 0.06, output: 0.06 },\n 'qwen/qwen2.5-7b-instruct-turbo': { input: 0.20, output: 0.20 },\n 'mistralai/mistral-small-24b-instruct-2501': { input: 0.20, output: 0.20 },\n 'mistralai/mixtral-8x7b-instruct-v0.1': { input: 0.60, output: 0.60 },\n 'deepseek-ai/deepseek-v3': { input: 0.27, output: 1.10 },\n};\n\n/**\n * Calculate cost in USD for a model's token usage.\n * Returns undefined if model is not in pricing table.\n */\nexport function calculateCost(\n model: string,\n promptTokens: number,\n completionTokens: number,\n): number | undefined {\n const pricing = PRICING[model] ?? PRICING[model.toLowerCase()];\n if (!pricing) return undefined;\n\n const inputCost = (promptTokens / 1_000_000) * pricing.input;\n const outputCost = (completionTokens / 1_000_000) * pricing.output;\n return inputCost + outputCost;\n}\n\n/**\n * Check if a model has pricing data.\n */\nexport function hasPricing(model: string): boolean {\n return model in PRICING || model.toLowerCase() in PRICING;\n}\n","/**\n * Double-tracing prevention for framework integrations.\n *\n * When a framework integration (e.g., LlamaIndex handler) creates its own\n * LLM span, the underlying provider proxy (e.g., patchOpenAI) would also\n * create a duplicate span. This module provides suppression:\n *\n * - Framework integrations SET suppression via suppressProviderInstrumentation()\n * - Provider proxies CHECK via isProviderInstrumentationSuppressed() and skip\n *\n * Scoped to AsyncLocalStorage — concurrent calls are independent.\n */\n\nimport { getContext, runWithContext } from './storage.js';\n\n/**\n * Run a callback with provider instrumentation suppressed.\n *\n * During this callback, all provider instrumentors (patchOpenAI, etc.) will\n * skip span creation. The framework is responsible for creating the span.\n *\n * @param fn - The function to run with suppression active\n * @returns The function's return value\n */\nexport function suppressProviderInstrumentation<T>(fn: () => T): T {\n return runWithContext({ _suppressProviderInstrumentation: true }, fn);\n}\n\n/**\n * Check if provider instrumentation should be suppressed.\n *\n * Called by provider instrumentors as an early-exit guard. When true,\n * the provider calls the original method directly without creating a span.\n */\nexport function isProviderInstrumentationSuppressed(): boolean {\n return getContext()._suppressProviderInstrumentation === true;\n}\n","/**\n * Groq SDK (groq-sdk) Proxy-based instrumentation.\n *\n * Groq's API is OpenAI-compatible (chat.completions.create). This instrumentor\n * follows the exact same pattern as the OpenAI one but labels spans as \"groq\".\n *\n * Usage:\n * import Groq from 'groq-sdk';\n * import { patchGroq } from 'risicare/groq';\n * const groq = patchGroq(new Groq({ apiKey: '...' }));\n */\n\nimport { requireTracer } from '../../client.js';\nimport { SpanKind } from '../../types.js';\nimport { calculateCost } from '../../utils/pricing.js';\nimport { debug } from '../../utils/log.js';\nimport { isProviderInstrumentationSuppressed } from '../../context/dedup.js';\nimport type { Span } from '../../span.js';\n\nfunction enrichFromResponse(span: Span, response: Record<string, unknown>): void {\n try {\n const model = response.model as string | undefined;\n const usage = response.usage as Record<string, number> | undefined;\n\n if (model) span.setLlmFields({ model });\n\n if (usage) {\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 = (model ?? span.llmModel)\n ? calculateCost(model ?? span.llmModel ?? '', promptTokens, completionTokens)\n : undefined;\n\n span.setLlmFields({ promptTokens, completionTokens, totalTokens, costUsd: cost });\n }\n\n // Groq includes x_groq.usage for detailed timing\n const xGroq = response.x_groq as Record<string, unknown> | undefined;\n if (xGroq?.usage) {\n const groqUsage = xGroq.usage as Record<string, number>;\n if (groqUsage.queue_time != null) span.setAttribute('groq.queue_time', groqUsage.queue_time);\n if (groqUsage.prompt_time != null) span.setAttribute('groq.prompt_time', groqUsage.prompt_time);\n if (groqUsage.completion_time != null) span.setAttribute('groq.completion_time', groqUsage.completion_time);\n }\n } catch {\n // Never fail enrichment\n }\n}\n\nfunction createCompletionProxy(originalCreate: Function): Function {\n return function patchedCreate(this: unknown, ...args: unknown[]) {\n if (isProviderInstrumentationSuppressed()) {\n return originalCreate.apply(this, args);\n }\n\n let tracer;\n try {\n tracer = requireTracer();\n } catch {\n debug('Tracer not initialized — call init() before using patchGroq()');\n return originalCreate.apply(this, args);\n }\n\n const params = (args[0] ?? {}) as Record<string, unknown>;\n const model = (params.model as string) ?? 'unknown';\n const isStream = !!params.stream;\n\n return tracer.startSpan(\n { name: 'groq.chat.completions.create', kind: SpanKind.LLM_CALL, attributes: { 'llm.request.model': model, 'llm.stream': isStream } },\n (span) => {\n span.setLlmFields({ provider: 'groq', model });\n\n const result = originalCreate.apply(this, args);\n\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<Record<string, unknown>>).then((response) => {\n if (!isStream && response) {\n enrichFromResponse(span, response);\n }\n return response;\n });\n }\n\n return result;\n },\n );\n };\n}\n\n/**\n * Wrap a Groq client instance with tracing instrumentation.\n *\n * Returns a Proxy that intercepts chat.completions.create.\n * The original client is NOT modified.\n *\n * @param client - A Groq SDK client instance\n * @returns A proxied client with automatic tracing\n */\nexport function patchGroq<T extends object>(client: T): T {\n return new Proxy(client, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (prop === 'chat' && value && typeof value === 'object') {\n return new Proxy(value as object, {\n get(chatTarget, chatProp, chatReceiver) {\n const chatValue = Reflect.get(chatTarget, chatProp, chatReceiver);\n\n if (chatProp === 'completions' && chatValue && typeof chatValue === 'object') {\n return new Proxy(chatValue as object, {\n get(compTarget, compProp, compReceiver) {\n const compValue = Reflect.get(compTarget, compProp, compReceiver);\n\n if (compProp === 'create' && typeof compValue === 'function') {\n return createCompletionProxy(compValue.bind(compTarget));\n }\n\n return compValue;\n },\n });\n }\n\n return chatValue;\n },\n });\n }\n\n return value;\n },\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/globals.ts","../../../src/utils/log.ts","../../../src/providers/groq/index.ts","../../../src/ids.ts","../../../src/noop.ts","../../../src/context/storage.ts","../../../src/exporters/batch.ts","../../../src/exporters/http.ts","../../../src/client.ts","../../../src/utils/pricing.ts","../../../src/providers/groq/patch.ts","../../../src/context/dedup.ts"],"sourcesContent":["/**\n * Shared state via globalThis — ensures all entry point bundles share\n * the same singleton instances.\n *\n * Problem: tsup with `splitting: false` gives each entry point (index,\n * openai, anthropic, vercel-ai) its own copy of module-level variables.\n * This means `init()` from 'risicare' sets a tracer that 'risicare/openai'\n * can't see — breaking all provider instrumentation silently.\n *\n * Solution: Store all mutable singletons on globalThis with a namespaced\n * prefix. Every bundle reads/writes the same global slots.\n *\n * This pattern is used by React, OpenTelemetry, and other SDKs that must\n * share state across independently bundled entry points.\n *\n * @internal\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst G = globalThis as any;\nconst PREFIX = '__risicare_';\n\n// ─── Client & Tracer ────────────────────────────────────────────────────────\n\nexport function getClient(): unknown {\n return G[PREFIX + 'client'];\n}\n\nexport function setClient(client: unknown): void {\n G[PREFIX + 'client'] = client;\n}\n\nexport function getTracer(): unknown {\n return G[PREFIX + 'tracer'];\n}\n\nexport function setTracer(tracer: unknown): void {\n G[PREFIX + 'tracer'] = tracer;\n}\n\n// ─── Context Storage ────────────────────────────────────────────────────────\n\nexport function getContextStorage(): AsyncLocalStorage<unknown> {\n if (!G[PREFIX + 'ctx']) {\n G[PREFIX + 'ctx'] = new AsyncLocalStorage();\n }\n return G[PREFIX + 'ctx'];\n}\n\n// ─── Span Registry ──────────────────────────────────────────────────────────\n\nexport function getRegistry(): Map<string, unknown> {\n if (!G[PREFIX + 'registry']) {\n G[PREFIX + 'registry'] = new Map();\n }\n return G[PREFIX + 'registry'];\n}\n\nexport function getOpCount(): number {\n return G[PREFIX + 'opcount'] ?? 0;\n}\n\nexport function setOpCount(n: number): void {\n G[PREFIX + 'opcount'] = n;\n}\n\n// ─── Debug Flag ─────────────────────────────────────────────────────────────\n\nexport function getDebug(): boolean {\n return G[PREFIX + 'debug'] ?? false;\n}\n\nexport function setDebugFlag(enabled: boolean): void {\n G[PREFIX + 'debug'] = enabled;\n}\n\n// ─── Fix Runtime ────────────────────────────────────────────────────────\n\nexport function getGlobalFixRuntime(): unknown {\n return G[PREFIX + 'fix_runtime'];\n}\n\nexport function setGlobalFixRuntime(runtime: unknown): void {\n G[PREFIX + 'fix_runtime'] = runtime;\n}\n","/**\n * Internal logger for the Risicare SDK.\n *\n * Centralizes all diagnostic output so that:\n * - Debug messages are gated by a single flag (zero-cost when disabled)\n * - Warnings always fire (operational alerts like queue full)\n * - All output goes to stderr with a consistent [risicare] prefix\n * - A future custom logger callback can be added in one place\n */\n\nimport { getDebug, setDebugFlag } from '../globals.js';\n\n/**\n * Enable or disable debug logging. Called once during init().\n * @internal\n */\nexport function setDebug(enabled: boolean): void {\n setDebugFlag(enabled);\n}\n\n/**\n * Log a debug message. Only outputs when debug mode is enabled.\n * @internal\n */\nexport function debug(msg: string): void {\n if (getDebug()) {\n process.stderr.write(`[risicare] ${msg}\\n`);\n }\n}\n\n/**\n * Log a warning. Always outputs regardless of debug mode.\n * Use sparingly — only for operational issues the user should see.\n * @internal\n */\nexport function warn(msg: string): void {\n process.stderr.write(`[risicare] WARNING: ${msg}\\n`);\n}\n","export { patchGroq } from './patch.js';\n","/**\n * ID generation for traces and spans.\n *\n * Trace IDs: 32 lowercase hex characters (16 random bytes)\n * Span IDs: 16 lowercase hex characters (8 random bytes)\n *\n * Uses crypto.randomBytes for cryptographically secure randomness.\n */\n\nimport { randomBytes } from 'node:crypto';\n\nconst HEX_REGEX_32 = /^[0-9a-f]{32}$/;\nconst HEX_REGEX_16 = /^[0-9a-f]{16}$/;\n\nexport function generateTraceId(): string {\n return randomBytes(16).toString('hex');\n}\n\nexport function generateSpanId(): string {\n return randomBytes(8).toString('hex');\n}\n\nexport function generateAgentId(prefix?: string): string {\n const suffix = randomBytes(8).toString('hex');\n return prefix ? `${prefix}-${suffix}` : suffix;\n}\n\nexport function validateTraceId(id: string): boolean {\n return HEX_REGEX_32.test(id);\n}\n\nexport function validateSpanId(id: string): boolean {\n return HEX_REGEX_16.test(id);\n}\n","/**\n * No-op implementations for the disabled path.\n *\n * When tracing is disabled, all operations return these no-op objects\n * to maintain zero overhead. No allocations, no side effects.\n */\n\nimport { SpanKind, SpanStatus, type SpanPayload } from './types.js';\n\n/**\n * A frozen no-op span that silently ignores all operations.\n * Used when SDK is disabled to avoid overhead.\n */\nexport const NOOP_SPAN = Object.freeze({\n traceId: '00000000000000000000000000000000',\n spanId: '0000000000000000',\n parentSpanId: undefined,\n name: 'noop',\n kind: SpanKind.INTERNAL,\n startTime: '',\n startHrtime: 0,\n endTime: undefined,\n status: SpanStatus.UNSET,\n statusMessage: undefined,\n attributes: Object.freeze({}) as Record<string, unknown>,\n events: Object.freeze([]) as readonly [],\n links: Object.freeze([]) as readonly [],\n sessionId: undefined,\n agentId: undefined,\n agentName: undefined,\n semanticPhase: undefined,\n llmProvider: undefined,\n llmModel: undefined,\n llmPromptTokens: undefined,\n llmCompletionTokens: undefined,\n llmTotalTokens: undefined,\n llmCostUsd: undefined,\n toolName: undefined,\n toolSuccess: undefined,\n isEnded: true,\n durationMs: 0,\n\n setAttribute() { return this; },\n setAttributes() { return this; },\n setStatus() { return this; },\n addEvent() { return this; },\n addLink() { return this; },\n recordException() { return this; },\n setLlmFields() { return this; },\n setToolFields() { return this; },\n end() {},\n toPayload(): SpanPayload {\n return {\n traceId: this.traceId,\n spanId: this.spanId,\n name: this.name,\n kind: this.kind,\n startTime: this.startTime,\n status: this.status,\n attributes: {},\n events: [],\n links: [],\n };\n },\n});\n\nexport type NoopSpan = typeof NOOP_SPAN;\n","/**\n * AsyncLocalStorage-based context propagation.\n *\n * Uses a single AsyncLocalStorage instance with a composite state object.\n * This is simpler and more performant than multiple separate stores.\n *\n * Node.js AsyncLocalStorage automatically propagates through:\n * - Promise / async-await\n * - setTimeout / setImmediate\n * - EventEmitter callbacks\n * - process.nextTick\n * - async generators (unlike Python's contextvars!)\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Span } from '../span.js';\nimport type { SemanticPhase } from '../types.js';\nimport { getContextStorage } from '../globals.js';\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\nexport interface SessionContext {\n sessionId: string;\n userId?: string;\n metadata?: Record<string, unknown>;\n parentSessionId?: string;\n turnNumber?: number;\n}\n\nexport interface AgentContext {\n agentId: string;\n agentName?: string;\n agentRole?: string;\n agentType?: string;\n parentAgentId?: string;\n version?: number;\n metadata?: Record<string, unknown>;\n}\n\nexport interface ContextState {\n session?: SessionContext;\n agent?: AgentContext;\n span?: Span;\n phase?: SemanticPhase;\n /** When true, provider instrumentors skip span creation (framework is handling it). */\n _suppressProviderInstrumentation?: boolean;\n /**\n * Pre-allocated trace ID from getTraceContext() when no span exists yet.\n * Ensures that getTraceContext().traceId matches the next span created\n * in this context — preventing the \"trace ID mismatch\" bug.\n */\n _rootTraceId?: string;\n}\n\n// ─── Storage Accessor ────────────────────────────────────────────────────────\n\nfunction storage(): AsyncLocalStorage<ContextState> {\n return getContextStorage() as AsyncLocalStorage<ContextState>;\n}\n\n// ─── Core Operations ─────────────────────────────────────────────────────────\n\n/**\n * Get the current context state, or empty object if outside any context.\n */\nexport function getContext(): ContextState {\n return storage().getStore() ?? {};\n}\n\n/**\n * Run a callback within a new context scope.\n * The new scope inherits from the parent, with overrides applied.\n */\nexport function runWithContext<T>(overrides: Partial<ContextState>, fn: () => T): T {\n const parent = getContext();\n const merged: ContextState = { ...parent, ...overrides };\n return storage().run(merged, fn);\n}\n\n/**\n * Run an async callback within a new context scope.\n */\nexport function runWithContextAsync<T>(overrides: Partial<ContextState>, fn: () => Promise<T>): Promise<T> {\n const parent = getContext();\n const merged: ContextState = { ...parent, ...overrides };\n return storage().run(merged, fn);\n}\n\n// ─── Context Accessors ───────────────────────────────────────────────────────\n\nexport function getCurrentSession(): SessionContext | undefined {\n return getContext().session;\n}\n\nexport function getCurrentAgent(): AgentContext | undefined {\n return getContext().agent;\n}\n\nexport function getCurrentSpan(): Span | undefined {\n return getContext().span;\n}\n\nexport function getCurrentPhase(): SemanticPhase | undefined {\n return getContext().phase;\n}\n\nexport function getCurrentSessionId(): string | undefined {\n return getContext().session?.sessionId;\n}\n\nexport function getCurrentAgentId(): string | undefined {\n return getContext().agent?.agentId;\n}\n\nexport function getCurrentTraceId(): string | undefined {\n return getContext().span?.traceId;\n}\n\nexport function getCurrentSpanId(): string | undefined {\n return getContext().span?.spanId;\n}\n\nexport function getCurrentParentSpanId(): string | undefined {\n return getContext().span?.parentSpanId;\n}\n\n/**\n * Get all current context as a plain object (for debugging/serialization).\n */\nexport function getCurrentContext(): Record<string, unknown> {\n const ctx = getContext();\n return {\n session: ctx.session ? {\n sessionId: ctx.session.sessionId,\n userId: ctx.session.userId,\n ...(ctx.session.parentSessionId !== undefined ? { parentSessionId: ctx.session.parentSessionId } : {}),\n ...(ctx.session.turnNumber !== undefined ? { turnNumber: ctx.session.turnNumber } : {}),\n ...(ctx.session.metadata !== undefined ? { metadata: ctx.session.metadata } : {}),\n } : null,\n agent: ctx.agent ? {\n agentId: ctx.agent.agentId,\n agentName: ctx.agent.agentName,\n agentRole: ctx.agent.agentRole,\n agentType: ctx.agent.agentType,\n ...(ctx.agent.parentAgentId !== undefined ? { parentAgentId: ctx.agent.parentAgentId } : {}),\n ...(ctx.agent.version !== undefined ? { version: ctx.agent.version } : {}),\n ...(ctx.agent.metadata !== undefined ? { metadata: ctx.agent.metadata } : {}),\n } : null,\n span: ctx.span ? { spanId: ctx.span.spanId, traceId: ctx.span.traceId } : null,\n phase: ctx.phase ?? null,\n };\n}\n","/**\n * Batch span processor.\n *\n * Collects spans and exports them in batches based on:\n * - Batch size threshold (default: 100 spans)\n * - Time interval (default: 1000ms)\n *\n * Node.js is single-threaded — no locks needed.\n * Timer is unref()'d so it doesn't prevent process exit.\n */\n\nimport type { Span } from '../span.js';\nimport { ExportResult, type SpanExporter } from './base.js';\nimport { debug, warn } from '../utils/log.js';\n\nexport interface BatchProcessorOptions {\n exporters: SpanExporter[];\n batchSize?: number;\n batchTimeoutMs?: number;\n maxQueueSize?: number;\n debug?: boolean;\n}\n\nexport class BatchSpanProcessor {\n private readonly _exporters: SpanExporter[];\n private readonly _batchSize: number;\n private readonly _batchTimeoutMs: number;\n private readonly _maxQueueSize: number;\n private readonly _debug: boolean;\n\n private _queue: Span[] = [];\n private _timer: ReturnType<typeof setInterval> | null = null;\n private _started = false;\n private _flushing = false;\n private _beforeExitHandler: (() => void) | null = null;\n\n // Retry tracking for failed batches (Audit #5)\n private _retryCounts = new Map<string, number>();\n private static readonly MAX_RETRIES = 3;\n\n // Metrics\n droppedSpans = 0;\n exportedSpans = 0;\n failedExports = 0;\n\n constructor(options: BatchProcessorOptions) {\n this._exporters = [...options.exporters];\n this._batchSize = options.batchSize ?? 100;\n this._batchTimeoutMs = options.batchTimeoutMs ?? 1000;\n this._maxQueueSize = options.maxQueueSize ?? 10000;\n this._debug = options.debug ?? false;\n }\n\n start(): void {\n if (this._started) return;\n this._started = true;\n\n // Periodic flush timer — unref so it doesn't keep process alive\n this._timer = setInterval(() => {\n void this._exportBatch();\n }, this._batchTimeoutMs);\n this._timer.unref();\n\n // Flush on process exit (Audit #3: use .once, not .on; await, not void)\n this._beforeExitHandler = () => {\n void this.shutdown();\n };\n process.once('beforeExit', this._beforeExitHandler);\n }\n\n async shutdown(timeoutMs = 5000): Promise<void> {\n if (!this._started) return;\n this._started = false;\n\n if (this._timer) {\n clearInterval(this._timer);\n this._timer = null;\n }\n\n // Remove process listener to prevent leak (Audit #3)\n if (this._beforeExitHandler) {\n process.removeListener('beforeExit', this._beforeExitHandler);\n this._beforeExitHandler = null;\n }\n\n // Final flush with timeout\n const flushPromise = this._exportBatch();\n const timeoutPromise = new Promise<void>((resolve) => setTimeout(resolve, timeoutMs));\n await Promise.race([flushPromise, timeoutPromise]);\n\n // Shutdown exporters\n for (const exporter of this._exporters) {\n try {\n await exporter.shutdown();\n } catch (e) {\n debug(`Error shutting down ${exporter.name}: ${e}`);\n }\n }\n\n // Clean up retry tracking\n this._retryCounts.clear();\n\n debug(\n `BatchSpanProcessor shutdown. Exported: ${this.exportedSpans}, ` +\n `Dropped: ${this.droppedSpans}, Failed: ${this.failedExports}`\n );\n }\n\n onSpanEnd(span: Span): void {\n if (!this._started) return;\n\n // Check queue capacity\n if (this._queue.length >= this._maxQueueSize) {\n this.droppedSpans++;\n if (this.droppedSpans === 1 || this.droppedSpans % 1000 === 0) {\n warn(`Span queue full (${this._maxQueueSize}). ${this.droppedSpans} spans dropped so far.`);\n }\n return;\n }\n\n this._queue.push(span);\n\n // Trigger immediate flush if batch size reached\n if (this._queue.length >= this._batchSize) {\n void this._exportBatch();\n }\n }\n\n async flush(timeoutMs = 5000): Promise<boolean> {\n if (!this._started) return true;\n\n const start = Date.now();\n\n // Wait for in-flight exports to complete AND queue to drain\n while (this._queue.length > 0 || this._flushing) {\n if (!this._flushing && this._queue.length > 0) {\n await this._exportBatch();\n } else {\n // Wait a tick for the in-flight export to finish\n await new Promise((r) => setTimeout(r, 1));\n }\n if (Date.now() - start > timeoutMs) return false;\n }\n\n return true;\n }\n\n getMetrics() {\n return {\n exportedSpans: this.exportedSpans,\n droppedSpans: this.droppedSpans,\n failedExports: this.failedExports,\n queueSize: this._queue.length,\n queueCapacity: this._maxQueueSize,\n queueUtilization: this._queue.length / this._maxQueueSize,\n };\n }\n\n private async _exportBatch(): Promise<void> {\n if (this._flushing || this._queue.length === 0) return;\n this._flushing = true;\n\n try {\n // Take up to batchSize spans\n const batch = this._queue.splice(0, this._batchSize);\n if (batch.length === 0) return;\n\n debug(`Exporting batch of ${batch.length} spans`);\n\n // Export to all exporters — count once, not per exporter\n let batchExported = false;\n for (const exporter of this._exporters) {\n try {\n const result = await exporter.export(batch);\n if (result === ExportResult.SUCCESS) {\n if (!batchExported) {\n this.exportedSpans += batch.length;\n batchExported = true;\n // Clear retry counts for successfully exported spans\n for (const span of batch) {\n this._retryCounts.delete(span.spanId);\n }\n }\n } else {\n this.failedExports++;\n }\n } catch (e) {\n this.failedExports++;\n debug(`Export to ${exporter.name} failed: ${e}`);\n }\n }\n\n // Audit #5: Re-queue failed batches with retry limit\n if (!batchExported) {\n const retryable = batch.filter(span => {\n const count = (this._retryCounts.get(span.spanId) ?? 0) + 1;\n if (count > BatchSpanProcessor.MAX_RETRIES) {\n this._retryCounts.delete(span.spanId);\n this.droppedSpans++;\n return false;\n }\n this._retryCounts.set(span.spanId, count);\n return true;\n });\n if (retryable.length > 0) {\n this._queue.unshift(...retryable);\n debug(`Re-queued ${retryable.length} spans for retry (${batch.length - retryable.length} dropped after max retries)`);\n }\n }\n } finally {\n this._flushing = false;\n }\n }\n}\n","/**\n * HTTP exporter for sending spans to the Risicare gateway.\n *\n * Uses native fetch (Node.js 18+), with:\n * - Automatic retry with exponential backoff\n * - Circuit breaker with half-open probe state\n * - Gzip compression for large payloads\n * - Bearer token authentication\n */\n\nimport type { Span } from '../span.js';\nimport type { IngestRequest } from '../types.js';\nimport { ExportResult, type SpanExporter } from './base.js';\nimport { debug, warn } from '../utils/log.js';\n\nconst SDK_VERSION = '0.1.1';\n\nexport interface HttpExporterOptions {\n endpoint: string;\n apiKey?: string;\n projectId?: string;\n environment?: string;\n timeoutMs?: number;\n maxRetries?: number;\n compress?: boolean;\n}\n\nexport class HttpExporter implements SpanExporter {\n readonly name = 'http';\n\n private readonly _endpoint: string;\n private readonly _apiKey: string | undefined;\n private readonly _projectId: string | undefined;\n private readonly _environment: string | undefined;\n private readonly _timeoutMs: number;\n private readonly _maxRetries: number;\n private readonly _compress: boolean;\n\n // Circuit breaker\n private _consecutiveFailures = 0;\n private _circuitOpenUntil = 0;\n private readonly _circuitBreakerThreshold = 3;\n private readonly _circuitBreakerCooldownMs = 30_000;\n\n constructor(options: HttpExporterOptions) {\n this._endpoint = options.endpoint.replace(/\\/+$/, '');\n this._apiKey = options.apiKey;\n this._projectId = options.projectId;\n this._environment = options.environment;\n this._timeoutMs = options.timeoutMs ?? 5000;\n this._maxRetries = options.maxRetries ?? 3;\n this._compress = options.compress ?? false;\n }\n\n async export(spans: Span[]): Promise<ExportResult> {\n if (spans.length === 0) return ExportResult.SUCCESS;\n\n // Circuit breaker: skip if open\n const now = Date.now();\n let isHalfOpen = false;\n if (this._consecutiveFailures >= this._circuitBreakerThreshold) {\n if (now < this._circuitOpenUntil) {\n return ExportResult.FAILURE;\n }\n // Cooldown expired — probe with single request (Audit #7: half-open state)\n isHalfOpen = true;\n }\n\n const body: IngestRequest = {\n spans: spans.map((s) => s.toPayload()),\n };\n if (this._projectId) body.projectId = this._projectId;\n if (this._environment) body.environment = this._environment;\n\n // Half-open: single probe request. Normal: full retry loop.\n const maxAttempts = isHalfOpen ? 1 : this._maxRetries;\n\n // Retry loop with exponential backoff\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const result = await this._sendRequest(body);\n\n if (result === ExportResult.SUCCESS) {\n this._consecutiveFailures = 0;\n return result;\n }\n\n // Backoff: 100ms, 200ms, 400ms\n if (attempt < maxAttempts - 1) {\n await sleep(100 * Math.pow(2, attempt));\n }\n }\n\n // All retries failed — update circuit breaker\n this._consecutiveFailures++;\n if (this._consecutiveFailures >= this._circuitBreakerThreshold) {\n this._circuitOpenUntil = Date.now() + this._circuitBreakerCooldownMs;\n warn(\n `HTTP exporter circuit breaker opened after ${this._consecutiveFailures} failures. ` +\n `Cooldown: ${this._circuitBreakerCooldownMs / 1000}s`\n );\n }\n\n return ExportResult.FAILURE;\n }\n\n private async _sendRequest(body: IngestRequest): Promise<ExportResult> {\n const url = `${this._endpoint}/v1/spans`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': `risicare-js/${SDK_VERSION} node/${process.version}`,\n };\n if (this._apiKey) {\n headers['Authorization'] = `Bearer ${this._apiKey}`;\n }\n\n let payload: string | Uint8Array = JSON.stringify(body);\n\n if (this._compress && payload.length > 1024) {\n try {\n const { gzipSync } = await import('node:zlib');\n payload = gzipSync(Buffer.from(payload));\n headers['Content-Encoding'] = 'gzip';\n } catch (e) {\n // Audit #16: log compression failures instead of silently swallowing\n debug(`Gzip compression failed, sending uncompressed: ${e}`);\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this._timeoutMs);\n\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body: payload,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (response.status < 300) {\n return ExportResult.SUCCESS;\n } else if (response.status === 408 || response.status === 504) {\n debug(`HTTP export timeout: ${response.status}`);\n return ExportResult.TIMEOUT;\n }\n debug(`HTTP export failed: ${response.status}`);\n return ExportResult.FAILURE;\n } catch (e) {\n debug(`HTTP export error: ${e}`);\n return ExportResult.FAILURE;\n }\n }\n\n shutdown(): void {\n // Native fetch doesn't need cleanup\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * RisicareClient — singleton client managing SDK lifecycle.\n *\n * Handles initialization, shutdown, and the connection between\n * the Tracer and the export pipeline (batch processor + HTTP exporter).\n *\n * Usage:\n * import { init, shutdown } from 'risicare';\n * init({ apiKey: 'rsk-...' }); // API key determines project\n * // ... instrument code ...\n * await shutdown(); // flush remaining spans\n */\n\nimport { type RisicareConfig, resolveConfig } from './config.js';\nimport { Tracer } from './tracer.js';\nimport { BatchSpanProcessor } from './exporters/batch.js';\nimport { HttpExporter } from './exporters/http.js';\nimport { ConsoleExporter } from './exporters/console.js';\nimport { SpanKind, SpanStatus } from './types.js';\nimport type { SpanExporter } from './exporters/base.js';\nimport { setDebug, debug } from './utils/log.js';\nimport {\n getClient as getGlobalClient,\n setClient as setGlobalClient,\n getTracer as getGlobalTracer,\n setTracer as setGlobalTracer,\n} from './globals.js';\n\n// ─── Client Class ───────────────────────────────────────────────────────────\n\nclass RisicareClient {\n readonly config: ReturnType<typeof resolveConfig>;\n readonly processor: BatchSpanProcessor;\n readonly tracer: Tracer;\n private _shutdownPromise: Promise<void> | undefined;\n private _shutdownHandlers: { signal: string; handler: () => void }[] = [];\n\n constructor(config?: Partial<RisicareConfig>) {\n this.config = resolveConfig(config);\n\n // API key format validation\n if (this.config.apiKey && !this.config.apiKey.startsWith('rsk-')) {\n debug('Warning: API key should start with \"rsk-\". Got: ' + this.config.apiKey.slice(0, 4) + '...');\n }\n\n // Build exporter chain\n let exporter: SpanExporter;\n if (this.config.debug && !this.config.apiKey) {\n exporter = new ConsoleExporter();\n } else if (this.config.apiKey) {\n exporter = new HttpExporter({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n projectId: this.config.projectId || undefined,\n environment: this.config.environment || undefined,\n compress: this.config.compress,\n });\n } else {\n // No API key and not debug — use console as fallback\n exporter = new ConsoleExporter();\n }\n\n this.processor = new BatchSpanProcessor({\n exporters: [exporter],\n batchSize: this.config.batchSize,\n batchTimeoutMs: this.config.batchTimeoutMs,\n maxQueueSize: this.config.maxQueueSize,\n debug: this.config.debug,\n });\n\n this.tracer = new Tracer({\n onSpanEnd: (span) => this.processor.onSpanEnd(span),\n sampleRate: this.config.sampleRate,\n enabled: this.config.enabled,\n traceContent: this.config.traceContent,\n });\n\n // Start the batch processor (enables span queuing and periodic flushing)\n this.processor.start();\n\n // Start FixRuntime for self-healing fix application.\n // Loads active fixes from /api/v1/fixes/active, caches with TTL,\n // background-refreshes every 60s. Provider patches call interceptCall()\n // to apply fixes before LLM calls.\n if (this.config.apiKey && this.config.enabled) {\n try {\n const { initFixRuntime } = require('./runtime/runtime.js') as typeof import('./runtime/runtime.js');\n initFixRuntime({\n apiEndpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n enabled: true,\n debug: this.config.debug,\n });\n } catch {\n // FixRuntime failure is non-fatal — tracing still works\n }\n }\n\n // Register shutdown hooks\n this._registerShutdownHooks();\n\n // Enable internal debug logging if configured\n setDebug(this.config.debug);\n debug(`Initialized: enabled=${this.config.enabled}, endpoint=${this.config.endpoint}`);\n }\n\n get enabled(): boolean {\n return this.tracer.enabled;\n }\n\n set enabled(value: boolean) {\n this.tracer.enabled = value;\n }\n\n // Audit #6: Promise-based shutdown dedup (fixes TOCTOU race condition)\n async shutdown(): Promise<void> {\n if (this._shutdownPromise) return this._shutdownPromise;\n this._shutdownPromise = this._doShutdown();\n return this._shutdownPromise;\n }\n\n private async _doShutdown(): Promise<void> {\n debug('Shutting down...');\n\n // Audit #3: Remove process listeners to prevent leak\n for (const { signal, handler } of this._shutdownHandlers) {\n process.removeListener(signal, handler);\n }\n this._shutdownHandlers = [];\n\n await this.processor.shutdown();\n }\n\n async flush(): Promise<void> {\n await this.processor.flush();\n }\n\n private _registerShutdownHooks(): void {\n const onShutdown = () => {\n // Audit #3: Add 5s timeout to prevent hanging on signal\n const timeout = setTimeout(() => process.exit(1), 5000);\n timeout.unref();\n this.shutdown().catch(() => {}).finally(() => clearTimeout(timeout));\n };\n\n const signals = ['beforeExit', 'SIGTERM', 'SIGINT'];\n for (const signal of signals) {\n process.once(signal, onShutdown);\n this._shutdownHandlers.push({ signal, handler: onShutdown });\n }\n }\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * Initialize the Risicare SDK. Call once at application startup.\n *\n * @example\n * import { init } from 'risicare';\n * init({ apiKey: 'rsk-...', serviceName: 'my-agent', environment: 'production' });\n */\nexport function init(config?: Partial<RisicareConfig>): void {\n if (getGlobalClient()) {\n debug('Already initialized. Call shutdown() first to re-initialize.');\n return;\n }\n\n const client = new RisicareClient(config);\n setGlobalClient(client);\n setGlobalTracer(client.tracer);\n}\n\n/**\n * Gracefully shut down the SDK. Flushes pending spans before resolving.\n */\nexport async function shutdown(): Promise<void> {\n // Stop FixRuntime first (stops background refresh)\n try {\n const { shutdownFixRuntime } = require('./runtime/runtime.js') as typeof import('./runtime/runtime.js');\n shutdownFixRuntime();\n } catch {\n // FixRuntime may not be initialized\n }\n\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client) return;\n await client.shutdown();\n setGlobalClient(undefined);\n setGlobalTracer(undefined);\n}\n\n/**\n * Flush all pending spans without shutting down.\n */\nexport async function flush(): Promise<void> {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client) return;\n await client.flush();\n}\n\n/**\n * Enable tracing at runtime.\n */\nexport function enable(): void {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (client) client.enabled = true;\n}\n\n/**\n * Disable tracing at runtime. Spans will not be created or exported.\n */\nexport function disable(): void {\n const client = getGlobalClient() as RisicareClient | undefined;\n if (client) client.enabled = false;\n}\n\n/**\n * Check whether tracing is currently enabled.\n */\nexport function isEnabled(): boolean {\n const client = getGlobalClient() as RisicareClient | undefined;\n return client?.enabled ?? false;\n}\n\n/**\n * Get the global tracer instance. Returns undefined if not initialized.\n */\nexport function getTracer(): Tracer | undefined {\n return getGlobalTracer() as Tracer | undefined;\n}\n\n/**\n * Get the global tracer, or throw if not initialized.\n * @internal Used by decorators and providers that require an active tracer.\n */\nexport function requireTracer(): Tracer {\n const tracer = getGlobalTracer() as Tracer | undefined;\n if (!tracer) {\n throw new Error(\n 'Risicare SDK not initialized. Call init() before using tracing features.',\n );\n }\n return tracer;\n}\n\n/**\n * Check whether content tracing (prompt/completion capture) is enabled.\n */\nexport function getTraceContent(): boolean {\n const tracer = getGlobalTracer() as Tracer | undefined;\n return tracer?.traceContent ?? true;\n}\n\n/**\n * Get SDK metrics: exported spans, dropped spans, failed exports, queue stats.\n * Returns zero-valued metrics if SDK is not initialized.\n */\nexport function getMetrics() {\n const client = getGlobalClient() as RisicareClient | undefined;\n return client?.processor.getMetrics() ?? {\n exportedSpans: 0,\n droppedSpans: 0,\n failedExports: 0,\n queueSize: 0,\n queueCapacity: 0,\n queueUtilization: 0,\n };\n}\n\n// ─── reportError ──────────────────────────────────────────────────────────\n\n// Error dedup: SHA256 fingerprint → timestamp. Prevents retry loops from\n// creating N duplicate diagnoses. Matches Python SDK's 5-minute TTL.\nconst _ERROR_DEDUP_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst _ERROR_DEDUP_MAX = 1000;\nconst _recentErrors = new Map<string, number>(); // fingerprint → Date.now()\n\nfunction _errorFingerprint(err: Error): string {\n const raw = `${err.constructor?.name ?? 'Error'}:${String(err.message ?? '').slice(0, 200)}`;\n const { createHash } = require('node:crypto') as typeof import('node:crypto');\n return createHash('sha256').update(raw).digest('hex').slice(0, 16);\n}\n\nfunction _isDuplicateError(fingerprint: string): boolean {\n const now = Date.now();\n // Evict expired entries\n for (const [fp, ts] of _recentErrors) {\n if (now - ts > _ERROR_DEDUP_TTL_MS) _recentErrors.delete(fp);\n else break; // Map iterates in insertion order\n }\n if (_recentErrors.has(fingerprint)) return true;\n // Enforce max size\n if (_recentErrors.size >= _ERROR_DEDUP_MAX) {\n const oldest = _recentErrors.keys().next().value;\n if (oldest !== undefined) _recentErrors.delete(oldest);\n }\n _recentErrors.set(fingerprint, now);\n return false;\n}\n\n/**\n * Report a caught exception to the self-healing pipeline.\n *\n * Creates an error span that triggers diagnosis and fix generation.\n * Deduplicates identical errors within a 5-minute window (SHA256 fingerprint).\n * This function never throws and is non-blocking.\n *\n * @param error - The caught exception (Error object or string)\n * @param options - Optional attributes and context overrides\n */\nexport function reportError(\n error: unknown,\n options?: { name?: string; attributes?: Record<string, unknown> },\n): void {\n try {\n const tracer = getTracer();\n if (!tracer) return;\n\n const err = error instanceof Error ? error : new Error(String(error ?? 'unknown'));\n const spanName = options?.name ?? `error:${err.constructor.name}`;\n\n // Dedup: suppress identical errors within TTL (matches Python SDK)\n const fp = _errorFingerprint(err);\n if (_isDuplicateError(fp)) {\n debug(`reportError: duplicate suppressed (fp=${fp.slice(0, 8)})`);\n return;\n }\n\n tracer.startSpan({ name: spanName, kind: SpanKind.INTERNAL }, (span) => {\n span.setStatus(SpanStatus.ERROR, err.message);\n span.setAttribute('error', true);\n span.setAttribute('error.type', err.constructor.name);\n span.setAttribute('error.message', err.message.slice(0, 2000));\n if (err.stack) span.setAttribute('error.stack', err.stack.slice(0, 4000));\n span.setAttribute('risicare.reported_error', true);\n if (options?.attributes) {\n for (const [k, v] of Object.entries(options.attributes)) {\n span.setAttribute(k, v);\n }\n }\n });\n } catch {\n // Never crash the host application\n debug('reportError failed');\n }\n}\n\n// ─── score ─────────────────────────────────────────────────────────────────\n\n/**\n * Record a custom evaluation score on a trace.\n *\n * Sends the score to the server in a fire-and-forget fashion.\n * This function never throws and is non-blocking.\n *\n * @param traceId - The trace to score\n * @param name - Score name (e.g., \"accuracy\", \"user_satisfaction\")\n * @param value - Score value between 0.0 and 1.0 inclusive\n * @param options - Optional span_id and comment\n */\nexport function score(\n traceId: string,\n name: string,\n value: number,\n options?: { spanId?: string; comment?: string },\n): void {\n try {\n if (typeof value !== 'number' || value < 0.0 || value > 1.0) {\n debug(`score: value must be in [0.0, 1.0], got ${value}. Score not sent.`);\n return;\n }\n if (!traceId || !name) {\n debug('score: traceId and name are required');\n return;\n }\n\n const client = getGlobalClient() as RisicareClient | undefined;\n if (!client?.enabled || !client.config.apiKey) return;\n\n const endpoint = client.config.endpoint.replace(/\\/$/, '');\n const url = `${endpoint}/api/v1/scores`;\n const body = JSON.stringify({\n trace_id: traceId,\n name,\n score: value,\n source: 'sdk',\n ...(options?.spanId && { span_id: options.spanId }),\n ...(options?.comment && { comment: options.comment }),\n });\n\n // Fire-and-forget — never blocks caller\n fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${client.config.apiKey}`,\n },\n body,\n }).catch((err) => debug(`score: send failed: ${err}`));\n } catch {\n // Never crash the host application\n debug('score failed');\n }\n}\n","/**\n * Token cost calculation table.\n *\n * Prices are per 1M tokens. Update monthly.\n * Source: provider pricing pages.\n */\n\ninterface ModelPricing {\n input: number; // USD per 1M input tokens\n output: number; // USD per 1M output tokens\n}\n\nconst PRICING: Record<string, ModelPricing> = {\n // OpenAI\n 'gpt-4o': { input: 2.50, output: 10.00 },\n 'gpt-4o-mini': { input: 0.15, output: 0.60 },\n 'gpt-4-turbo': { input: 10.00, output: 30.00 },\n 'gpt-4': { input: 30.00, output: 60.00 },\n 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },\n 'o1': { input: 15.00, output: 60.00 },\n 'o1-mini': { input: 3.00, output: 12.00 },\n 'o3-mini': { input: 1.10, output: 4.40 },\n\n // Anthropic\n 'claude-opus-4-5-20251101': { input: 15.00, output: 75.00 },\n 'claude-sonnet-4-5-20250929': { input: 3.00, output: 15.00 },\n 'claude-haiku-4-5-20251001': { input: 0.80, output: 4.00 },\n 'claude-3-5-sonnet-20241022': { input: 3.00, output: 15.00 },\n 'claude-3-haiku-20240307': { input: 0.25, output: 1.25 },\n 'claude-3-opus-20240229': { input: 15.00, output: 75.00 },\n\n // Google\n 'gemini-2.0-flash': { input: 0.10, output: 0.40 },\n 'gemini-1.5-pro': { input: 1.25, output: 5.00 },\n 'gemini-1.5-flash': { input: 0.075, output: 0.30 },\n\n // Groq\n 'llama-3.3-70b-versatile': { input: 0.59, output: 0.79 },\n 'llama-3.1-8b-instant': { input: 0.05, output: 0.08 },\n 'mixtral-8x7b-32768': { input: 0.24, output: 0.24 },\n\n // DeepSeek\n 'deepseek-chat': { input: 0.14, output: 0.28 },\n 'deepseek-reasoner': { input: 0.55, output: 2.19 },\n\n // Together.ai (open-source models)\n 'meta-llama/llama-3.3-70b-instruct-turbo': { input: 0.88, output: 0.88 },\n 'meta-llama/meta-llama-3.1-8b-instruct-turbo': { input: 0.18, output: 0.18 },\n 'meta-llama/llama-3.2-3b-instruct-turbo': { input: 0.06, output: 0.06 },\n 'qwen/qwen2.5-7b-instruct-turbo': { input: 0.20, output: 0.20 },\n 'mistralai/mistral-small-24b-instruct-2501': { input: 0.20, output: 0.20 },\n 'mistralai/mixtral-8x7b-instruct-v0.1': { input: 0.60, output: 0.60 },\n 'deepseek-ai/deepseek-v3': { input: 0.27, output: 1.10 },\n};\n\n/**\n * Calculate cost in USD for a model's token usage.\n * Returns undefined if model is not in pricing table.\n */\nexport function calculateCost(\n model: string,\n promptTokens: number,\n completionTokens: number,\n): number | undefined {\n const pricing = PRICING[model] ?? PRICING[model.toLowerCase()];\n if (!pricing) return undefined;\n\n const inputCost = (promptTokens / 1_000_000) * pricing.input;\n const outputCost = (completionTokens / 1_000_000) * pricing.output;\n return inputCost + outputCost;\n}\n\n/**\n * Check if a model has pricing data.\n */\nexport function hasPricing(model: string): boolean {\n return model in PRICING || model.toLowerCase() in PRICING;\n}\n","/**\n * Groq SDK (groq-sdk) Proxy-based instrumentation.\n *\n * Groq's API is OpenAI-compatible (chat.completions.create). This instrumentor\n * follows the exact same pattern as the OpenAI one but labels spans as \"groq\".\n *\n * Usage:\n * import Groq from 'groq-sdk';\n * import { patchGroq } from 'risicare/groq';\n * const groq = patchGroq(new Groq({ apiKey: '...' }));\n */\n\nimport { requireTracer } from '../../client.js';\nimport { SpanKind } from '../../types.js';\nimport { calculateCost } from '../../utils/pricing.js';\nimport { debug } from '../../utils/log.js';\nimport { isProviderInstrumentationSuppressed } from '../../context/dedup.js';\nimport type { Span } from '../../span.js';\n\n/**\n * Extract request-time attributes from the API call parameters.\n */\nfunction enrichSpanFromRequest(span: Span, params: Record<string, unknown>): void {\n const model = params.model as string | undefined;\n if (model) {\n span.setAttribute('gen_ai.system', 'groq');\n span.setAttribute('gen_ai.request.model', model);\n }\n if (params.temperature !== undefined) span.setAttribute('gen_ai.request.temperature', params.temperature);\n if (params.max_tokens !== undefined) span.setAttribute('gen_ai.request.max_tokens', params.max_tokens);\n if (params.top_p !== undefined) span.setAttribute('gen_ai.request.top_p', params.top_p);\n if (params.frequency_penalty !== undefined) span.setAttribute('gen_ai.request.frequency_penalty', params.frequency_penalty);\n if (params.presence_penalty !== undefined) span.setAttribute('gen_ai.request.presence_penalty', params.presence_penalty);\n if (params.stream !== undefined) span.setAttribute('llm.stream', !!params.stream);\n}\n\n/**\n * Extract response-time attributes from the API response.\n */\nfunction enrichFromResponse(span: Span, response: Record<string, unknown>): void {\n try {\n const model = response.model as string | undefined;\n const usage = response.usage as Record<string, number> | undefined;\n\n if (model) {\n span.setLlmFields({ model });\n span.setAttribute('gen_ai.response.model', model);\n }\n\n // Response ID\n if (response.id) span.setAttribute('gen_ai.response.id', response.id);\n\n // Finish reasons from choices (OpenAI-compatible)\n const choices = response.choices as Array<Record<string, unknown>> | undefined;\n if (Array.isArray(choices) && choices.length > 0) {\n const reasons = choices.map(c => c.finish_reason).filter(Boolean);\n if (reasons.length > 0) span.setAttribute('gen_ai.response.finish_reasons', reasons);\n }\n\n if (usage) {\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 = (model ?? span.llmModel)\n ? calculateCost(model ?? span.llmModel ?? '', promptTokens, completionTokens)\n : undefined;\n\n span.setLlmFields({ promptTokens, completionTokens, totalTokens, costUsd: cost });\n\n // Also set as gen_ai attributes for worker column promotion\n span.setAttribute('gen_ai.usage.prompt_tokens', promptTokens);\n span.setAttribute('gen_ai.usage.completion_tokens', completionTokens);\n span.setAttribute('gen_ai.usage.total_tokens', totalTokens);\n if (cost !== undefined) span.setAttribute('llm.cost.total_usd', cost);\n }\n\n // Groq includes x_groq.usage for detailed timing\n const xGroq = response.x_groq as Record<string, unknown> | undefined;\n if (xGroq?.usage) {\n const groqUsage = xGroq.usage as Record<string, number>;\n if (groqUsage.queue_time != null) span.setAttribute('groq.queue_time', groqUsage.queue_time);\n if (groqUsage.prompt_time != null) span.setAttribute('groq.prompt_time', groqUsage.prompt_time);\n if (groqUsage.completion_time != null) span.setAttribute('groq.completion_time', groqUsage.completion_time);\n }\n } catch {\n // Never fail enrichment\n }\n}\n\nfunction createCompletionProxy(originalCreate: Function): Function {\n return function patchedCreate(this: unknown, ...args: unknown[]) {\n if (isProviderInstrumentationSuppressed()) {\n return originalCreate.apply(this, args);\n }\n\n let tracer;\n try {\n tracer = requireTracer();\n } catch {\n debug('Tracer not initialized — call init() before using patchGroq()');\n return originalCreate.apply(this, args);\n }\n\n const params = (args[0] ?? {}) as Record<string, unknown>;\n const model = (params.model as string) ?? 'unknown';\n const isStream = !!params.stream;\n\n return tracer.startSpan(\n { name: 'groq.chat.completions.create', kind: SpanKind.LLM_CALL, attributes: { 'llm.request.model': model, 'llm.stream': isStream } },\n (span) => {\n span.setLlmFields({ provider: 'groq', model });\n enrichSpanFromRequest(span, params);\n\n const result = originalCreate.apply(this, args);\n\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return (result as Promise<Record<string, unknown>>).then((response) => {\n if (!isStream && response) {\n enrichFromResponse(span, response);\n }\n return response;\n });\n }\n\n return result;\n },\n );\n };\n}\n\n/**\n * Wrap a Groq client instance with tracing instrumentation.\n *\n * Returns a Proxy that intercepts chat.completions.create.\n * The original client is NOT modified.\n *\n * @param client - A Groq SDK client instance\n * @returns A proxied client with automatic tracing\n */\nexport function patchGroq<T extends object>(client: T): T {\n return new Proxy(client, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (prop === 'chat' && value && typeof value === 'object') {\n return new Proxy(value as object, {\n get(chatTarget, chatProp, chatReceiver) {\n const chatValue = Reflect.get(chatTarget, chatProp, chatReceiver);\n\n if (chatProp === 'completions' && chatValue && typeof chatValue === 'object') {\n return new Proxy(chatValue as object, {\n get(compTarget, compProp, compReceiver) {\n const compValue = Reflect.get(compTarget, compProp, compReceiver);\n\n if (compProp === 'create' && typeof compValue === 'function') {\n return createCompletionProxy(compValue.bind(compTarget));\n }\n\n return compValue;\n },\n });\n }\n\n return chatValue;\n },\n });\n }\n\n return value;\n },\n });\n}\n","/**\n * Double-tracing prevention for framework integrations.\n *\n * When a framework integration (e.g., LlamaIndex handler) creates its own\n * LLM span, the underlying provider proxy (e.g., patchOpenAI) would also\n * create a duplicate span. This module provides suppression:\n *\n * - Framework integrations SET suppression via suppressProviderInstrumentation()\n * - Provider proxies CHECK via isProviderInstrumentationSuppressed() and skip\n *\n * Scoped to AsyncLocalStorage — concurrent calls are independent.\n */\n\nimport { getContext, runWithContext } from './storage.js';\n\n/**\n * Run a callback with provider instrumentation suppressed.\n *\n * During this callback, all provider instrumentors (patchOpenAI, etc.) will\n * skip span creation. The framework is responsible for creating the span.\n *\n * @param fn - The function to run with suppression active\n * @returns The function's return value\n */\nexport function suppressProviderInstrumentation<T>(fn: () => T): T {\n return runWithContext({ _suppressProviderInstrumentation: true }, fn);\n}\n\n/**\n * Check if provider instrumentation should be suppressed.\n *\n * Called by provider instrumentors as an early-exit guard. When true,\n * the provider calls the original method directly without creating a span.\n */\nexport function isProviderInstrumentationSuppressed(): boolean {\n return getContext()._suppressProviderInstrumentation === true;\n}\n"],"mappings":"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","names":[]}
@@ -1,3 +1,45 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // src/globals.ts
7
+ import { AsyncLocalStorage } from "async_hooks";
8
+ function getTracer() {
9
+ return G[PREFIX + "tracer"];
10
+ }
11
+ function getContextStorage() {
12
+ if (!G[PREFIX + "ctx"]) {
13
+ G[PREFIX + "ctx"] = new AsyncLocalStorage();
14
+ }
15
+ return G[PREFIX + "ctx"];
16
+ }
17
+ function getDebug() {
18
+ return G[PREFIX + "debug"] ?? false;
19
+ }
20
+ var G, PREFIX;
21
+ var init_globals = __esm({
22
+ "src/globals.ts"() {
23
+ "use strict";
24
+ G = globalThis;
25
+ PREFIX = "__risicare_";
26
+ }
27
+ });
28
+
29
+ // src/utils/log.ts
30
+ function debug(msg) {
31
+ if (getDebug()) {
32
+ process.stderr.write(`[risicare] ${msg}
33
+ `);
34
+ }
35
+ }
36
+ var init_log = __esm({
37
+ "src/utils/log.ts"() {
38
+ "use strict";
39
+ init_globals();
40
+ }
41
+ });
42
+
1
43
  // src/ids.ts
2
44
  import { randomBytes } from "crypto";
3
45
 
@@ -71,24 +113,8 @@ var NOOP_SPAN = Object.freeze({
71
113
  }
72
114
  });
73
115
 
74
- // src/globals.ts
75
- import { AsyncLocalStorage } from "async_hooks";
76
- var G = globalThis;
77
- var PREFIX = "__risicare_";
78
- function getTracer() {
79
- return G[PREFIX + "tracer"];
80
- }
81
- function getContextStorage() {
82
- if (!G[PREFIX + "ctx"]) {
83
- G[PREFIX + "ctx"] = new AsyncLocalStorage();
84
- }
85
- return G[PREFIX + "ctx"];
86
- }
87
- function getDebug() {
88
- return G[PREFIX + "debug"] ?? false;
89
- }
90
-
91
116
  // src/context/storage.ts
117
+ init_globals();
92
118
  function storage() {
93
119
  return getContextStorage();
94
120
  }
@@ -96,15 +122,15 @@ function getContext() {
96
122
  return storage().getStore() ?? {};
97
123
  }
98
124
 
99
- // src/utils/log.ts
100
- function debug(msg) {
101
- if (getDebug()) {
102
- process.stderr.write(`[risicare] ${msg}
103
- `);
104
- }
105
- }
125
+ // src/exporters/batch.ts
126
+ init_log();
127
+
128
+ // src/exporters/http.ts
129
+ init_log();
106
130
 
107
131
  // src/client.ts
132
+ init_log();
133
+ init_globals();
108
134
  function requireTracer() {
109
135
  const tracer = getTracer();
110
136
  if (!tracer) {
@@ -114,6 +140,7 @@ function requireTracer() {
114
140
  }
115
141
  return tracer;
116
142
  }
143
+ var _ERROR_DEDUP_TTL_MS = 5 * 60 * 1e3;
117
144
 
118
145
  // src/utils/pricing.ts
119
146
  var PRICING = {
@@ -161,23 +188,52 @@ function calculateCost(model, promptTokens, completionTokens) {
161
188
  return inputCost + outputCost;
162
189
  }
163
190
 
191
+ // src/providers/groq/patch.ts
192
+ init_log();
193
+
164
194
  // src/context/dedup.ts
165
195
  function isProviderInstrumentationSuppressed() {
166
196
  return getContext()._suppressProviderInstrumentation === true;
167
197
  }
168
198
 
169
199
  // src/providers/groq/patch.ts
200
+ function enrichSpanFromRequest(span, params) {
201
+ const model = params.model;
202
+ if (model) {
203
+ span.setAttribute("gen_ai.system", "groq");
204
+ span.setAttribute("gen_ai.request.model", model);
205
+ }
206
+ if (params.temperature !== void 0) span.setAttribute("gen_ai.request.temperature", params.temperature);
207
+ if (params.max_tokens !== void 0) span.setAttribute("gen_ai.request.max_tokens", params.max_tokens);
208
+ if (params.top_p !== void 0) span.setAttribute("gen_ai.request.top_p", params.top_p);
209
+ if (params.frequency_penalty !== void 0) span.setAttribute("gen_ai.request.frequency_penalty", params.frequency_penalty);
210
+ if (params.presence_penalty !== void 0) span.setAttribute("gen_ai.request.presence_penalty", params.presence_penalty);
211
+ if (params.stream !== void 0) span.setAttribute("llm.stream", !!params.stream);
212
+ }
170
213
  function enrichFromResponse(span, response) {
171
214
  try {
172
215
  const model = response.model;
173
216
  const usage = response.usage;
174
- if (model) span.setLlmFields({ model });
217
+ if (model) {
218
+ span.setLlmFields({ model });
219
+ span.setAttribute("gen_ai.response.model", model);
220
+ }
221
+ if (response.id) span.setAttribute("gen_ai.response.id", response.id);
222
+ const choices = response.choices;
223
+ if (Array.isArray(choices) && choices.length > 0) {
224
+ const reasons = choices.map((c) => c.finish_reason).filter(Boolean);
225
+ if (reasons.length > 0) span.setAttribute("gen_ai.response.finish_reasons", reasons);
226
+ }
175
227
  if (usage) {
176
228
  const promptTokens = usage.prompt_tokens ?? 0;
177
229
  const completionTokens = usage.completion_tokens ?? 0;
178
230
  const totalTokens = usage.total_tokens ?? promptTokens + completionTokens;
179
231
  const cost = model ?? span.llmModel ? calculateCost(model ?? span.llmModel ?? "", promptTokens, completionTokens) : void 0;
180
232
  span.setLlmFields({ promptTokens, completionTokens, totalTokens, costUsd: cost });
233
+ span.setAttribute("gen_ai.usage.prompt_tokens", promptTokens);
234
+ span.setAttribute("gen_ai.usage.completion_tokens", completionTokens);
235
+ span.setAttribute("gen_ai.usage.total_tokens", totalTokens);
236
+ if (cost !== void 0) span.setAttribute("llm.cost.total_usd", cost);
181
237
  }
182
238
  const xGroq = response.x_groq;
183
239
  if (xGroq?.usage) {
@@ -208,6 +264,7 @@ function createCompletionProxy(originalCreate) {
208
264
  { name: "groq.chat.completions.create", kind: "llm_call" /* LLM_CALL */, attributes: { "llm.request.model": model, "llm.stream": isStream } },
209
265
  (span) => {
210
266
  span.setLlmFields({ provider: "groq", model });
267
+ enrichSpanFromRequest(span, params);
211
268
  const result = originalCreate.apply(this, args);
212
269
  if (result && typeof result.then === "function") {
213
270
  return result.then((response) => {