evlog 2.19.1 → 2.20.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 (190) hide show
  1. package/README.md +30 -1
  2. package/dist/adapters/axiom.d.mts +1 -1
  3. package/dist/adapters/axiom.mjs +2 -2
  4. package/dist/adapters/better-stack.d.mts +1 -1
  5. package/dist/adapters/better-stack.mjs +2 -2
  6. package/dist/adapters/datadog.d.mts +1 -1
  7. package/dist/adapters/datadog.mjs +2 -2
  8. package/dist/adapters/fs.d.mts +1 -1
  9. package/dist/adapters/fs.mjs +1 -1
  10. package/dist/adapters/hyperdx.d.mts +1 -1
  11. package/dist/adapters/hyperdx.mjs +1 -1
  12. package/dist/adapters/memory.d.mts +1 -1
  13. package/dist/adapters/memory.mjs +1 -1
  14. package/dist/adapters/otlp.d.mts +1 -1
  15. package/dist/adapters/otlp.mjs +2 -2
  16. package/dist/adapters/posthog.d.mts +1 -1
  17. package/dist/adapters/posthog.mjs +2 -2
  18. package/dist/adapters/sentry.d.mts +1 -1
  19. package/dist/adapters/sentry.mjs +2 -2
  20. package/dist/ai/index.d.mts +27 -10
  21. package/dist/ai/index.d.mts.map +1 -1
  22. package/dist/ai/index.mjs +80 -15
  23. package/dist/ai/index.mjs.map +1 -1
  24. package/dist/asyncStorageScope-DnF7Xk8l.mjs +81 -0
  25. package/dist/asyncStorageScope-DnF7Xk8l.mjs.map +1 -0
  26. package/dist/{audit-BQt8yAHo.mjs → audit-BXCYMjcC.mjs} +31 -93
  27. package/dist/audit-BXCYMjcC.mjs.map +1 -0
  28. package/dist/{audit-D7v6JHj0.d.mts → audit-BZrUvjfW.d.mts} +71 -53
  29. package/dist/audit-BZrUvjfW.d.mts.map +1 -0
  30. package/dist/audit-action.d.mts +2 -0
  31. package/dist/audit-action.mjs +74 -0
  32. package/dist/audit-action.mjs.map +1 -0
  33. package/dist/audit.d.mts +2 -0
  34. package/dist/audit.mjs +3 -0
  35. package/dist/better-auth/index.d.mts +1 -1
  36. package/dist/browser.d.mts +1 -1
  37. package/dist/{index-EvnrXvQM.d.mts → catalog-1cQnNxR2.d.mts} +4 -3
  38. package/dist/catalog-1cQnNxR2.d.mts.map +1 -0
  39. package/dist/catalog.d.mts +2 -0
  40. package/dist/catalog.mjs +205 -0
  41. package/dist/catalog.mjs.map +1 -0
  42. package/dist/{define-DTQpu4f6.d.mts → define-Ct2QyrE9.d.mts} +3 -3
  43. package/dist/{define-DTQpu4f6.d.mts.map → define-Ct2QyrE9.d.mts.map} +1 -1
  44. package/dist/{drain-fDb-eNwz.mjs → drain-Dl2ZJKRA.mjs} +3 -3
  45. package/dist/{drain-fDb-eNwz.mjs.map → drain-Dl2ZJKRA.mjs.map} +1 -1
  46. package/dist/elysia/index.d.mts +8 -7
  47. package/dist/elysia/index.d.mts.map +1 -1
  48. package/dist/elysia/index.mjs +11 -8
  49. package/dist/elysia/index.mjs.map +1 -1
  50. package/dist/{enrich-error-stack-next.node-Dgm_rCf5.mjs → enrich-error-stack-next.node-KB0Oap7R.mjs} +1 -1
  51. package/dist/{enrich-error-stack-next.node-Dgm_rCf5.mjs.map → enrich-error-stack-next.node-KB0Oap7R.mjs.map} +1 -1
  52. package/dist/{enricher-CBRmQw6e.d.mts → enricher-BOzt2FCi.d.mts} +2 -2
  53. package/dist/{enricher-CBRmQw6e.d.mts.map → enricher-BOzt2FCi.d.mts.map} +1 -1
  54. package/dist/enrichers.d.mts +2 -2
  55. package/dist/{error-CpghjrkY.d.mts → error-BRymuv8U.d.mts} +2 -2
  56. package/dist/{error-CpghjrkY.d.mts.map → error-BRymuv8U.d.mts.map} +1 -1
  57. package/dist/error.d.mts +1 -1
  58. package/dist/{errors-BLU4Tfpe.d.mts → errors-C7qA6CAi.d.mts} +2 -2
  59. package/dist/{errors-BLU4Tfpe.d.mts.map → errors-C7qA6CAi.d.mts.map} +1 -1
  60. package/dist/eve/index.d.mts +72 -0
  61. package/dist/eve/index.d.mts.map +1 -0
  62. package/dist/eve/index.mjs +593 -0
  63. package/dist/eve/index.mjs.map +1 -0
  64. package/dist/express/index.d.mts +4 -4
  65. package/dist/express/index.d.mts.map +1 -1
  66. package/dist/express/index.mjs +3 -3
  67. package/dist/express/index.mjs.map +1 -1
  68. package/dist/fastify/index.d.mts +4 -4
  69. package/dist/fastify/index.d.mts.map +1 -1
  70. package/dist/fastify/index.mjs +4 -2
  71. package/dist/fastify/index.mjs.map +1 -1
  72. package/dist/fork-S9gpZmF8.mjs +72 -0
  73. package/dist/fork-S9gpZmF8.mjs.map +1 -0
  74. package/dist/hono/index.d.mts +3 -3
  75. package/dist/hono/index.d.mts.map +1 -1
  76. package/dist/hono/index.mjs +3 -6
  77. package/dist/hono/index.mjs.map +1 -1
  78. package/dist/{http-ChVS9GYc.mjs → http-pKfd8UDu.mjs} +2 -2
  79. package/dist/{http-ChVS9GYc.mjs.map → http-pKfd8UDu.mjs.map} +1 -1
  80. package/dist/http.d.mts +1 -1
  81. package/dist/index.d.mts +9 -9
  82. package/dist/index.mjs +3 -203
  83. package/dist/{instrumentation-create-BrjQtSKD.d.mts → instrumentation-create-gSDk0leX.d.mts} +2 -2
  84. package/dist/{instrumentation-create-BrjQtSKD.d.mts.map → instrumentation-create-gSDk0leX.d.mts.map} +1 -1
  85. package/dist/{integration-DYp2uw8O.mjs → integration-CeL90DtN.mjs} +3 -2
  86. package/dist/{integration-DYp2uw8O.mjs.map → integration-CeL90DtN.mjs.map} +1 -1
  87. package/dist/{logger-mHIWxBhJ.d.mts → logger-CxWz7QJ3.d.mts} +7 -3
  88. package/dist/logger-CxWz7QJ3.d.mts.map +1 -0
  89. package/dist/logger.d.mts +2 -2
  90. package/dist/logger.mjs +2 -2
  91. package/dist/{fork-CgGlAaHa.mjs → middleware-Cdebyfdt.mjs} +3 -69
  92. package/dist/middleware-Cdebyfdt.mjs.map +1 -0
  93. package/dist/{middleware-B_k4Mrzg.d.mts → middleware-fbtEs6bq.d.mts} +3 -3
  94. package/dist/middleware-fbtEs6bq.d.mts.map +1 -0
  95. package/dist/nestjs/index.d.mts +5 -5
  96. package/dist/nestjs/index.d.mts.map +1 -1
  97. package/dist/nestjs/index.mjs +4 -3
  98. package/dist/nestjs/index.mjs.map +1 -1
  99. package/dist/next/client.d.mts +1 -1
  100. package/dist/next/index.d.mts +5 -5
  101. package/dist/next/index.d.mts.map +1 -1
  102. package/dist/next/index.mjs +3 -3
  103. package/dist/next/index.mjs.map +1 -1
  104. package/dist/next/instrumentation/create.d.mts +1 -1
  105. package/dist/next/instrumentation/create.mjs +3 -1
  106. package/dist/next/instrumentation/create.mjs.map +1 -1
  107. package/dist/next/instrumentation.d.mts +1 -1
  108. package/dist/next/stream.d.mts +1 -1
  109. package/dist/nitro/errorHandler.mjs +9 -4
  110. package/dist/nitro/errorHandler.mjs.map +1 -1
  111. package/dist/nitro/module.d.mts +2 -2
  112. package/dist/nitro/module.mjs +1 -1
  113. package/dist/nitro/plugin.mjs +2 -2
  114. package/dist/nitro/v3/errorHandler.d.mts +0 -10
  115. package/dist/nitro/v3/errorHandler.mjs +13 -3
  116. package/dist/nitro/v3/errorHandler.mjs.map +1 -1
  117. package/dist/nitro/v3/index.d.mts +2 -2
  118. package/dist/nitro/v3/module.d.mts +1 -1
  119. package/dist/nitro/v3/module.mjs +1 -1
  120. package/dist/nitro/v3/plugin.mjs +2 -2
  121. package/dist/nitro/v3/useLogger.d.mts +2 -2
  122. package/dist/nitro/v3/useLogger.d.mts.map +1 -1
  123. package/dist/nitro/v3/useLogger.mjs.map +1 -1
  124. package/dist/{nitro-_Hda8Deo.d.mts → nitro-CcN7HDnr.d.mts} +2 -2
  125. package/dist/{nitro-_Hda8Deo.d.mts.map → nitro-CcN7HDnr.d.mts.map} +1 -1
  126. package/dist/{nitro-ClRZLD1g.mjs → nitro-DYFCSlho.mjs} +30 -3
  127. package/dist/nitro-DYFCSlho.mjs.map +1 -0
  128. package/dist/{nitroConfigBridge-BkVWnSV3.mjs → nitroConfigBridge-lHPsE5zG.mjs} +8 -6
  129. package/dist/nitroConfigBridge-lHPsE5zG.mjs.map +1 -0
  130. package/dist/{nodeResponse-CIEEbrNE.mjs → nodeResponse-Dsl7gZUf.mjs} +1 -1
  131. package/dist/{nodeResponse-CIEEbrNE.mjs.map → nodeResponse-Dsl7gZUf.mjs.map} +1 -1
  132. package/dist/nuxt/module.d.mts +1 -1
  133. package/dist/nuxt/module.mjs +2 -2
  134. package/dist/orpc/index.d.mts +4 -4
  135. package/dist/orpc/index.d.mts.map +1 -1
  136. package/dist/orpc/index.mjs +2 -2
  137. package/dist/orpc/index.mjs.map +1 -1
  138. package/dist/{package-CNV_CXs8.mjs → package-Drb3wqqt.mjs} +2 -2
  139. package/dist/package-Drb3wqqt.mjs.map +1 -0
  140. package/dist/{parseError-BeBXEd2V.d.mts → parseError-BmoJ7XeF.d.mts} +2 -2
  141. package/dist/parseError-BmoJ7XeF.d.mts.map +1 -0
  142. package/dist/react-router/index.d.mts +4 -4
  143. package/dist/react-router/index.d.mts.map +1 -1
  144. package/dist/react-router/index.mjs +3 -2
  145. package/dist/react-router/index.mjs.map +1 -1
  146. package/dist/register-disk-snippet-B5svlT8-.mjs +21 -0
  147. package/dist/register-disk-snippet-B5svlT8-.mjs.map +1 -0
  148. package/dist/runtime/client/log.d.mts +1 -1
  149. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  150. package/dist/runtime/server/useLogger.d.mts +1 -1
  151. package/dist/runtime/server/useLogger.mjs.map +1 -1
  152. package/dist/runtime/utils/parseError.d.mts +2 -2
  153. package/dist/{storage-7X37OToT.mjs → storage-BeFdoGTI.mjs} +1 -1
  154. package/dist/storage-BeFdoGTI.mjs.map +1 -0
  155. package/dist/stream.d.mts +1 -1
  156. package/dist/stream.d.mts.map +1 -1
  157. package/dist/stream.mjs +7 -1
  158. package/dist/stream.mjs.map +1 -1
  159. package/dist/sveltekit/index.d.mts +3 -4
  160. package/dist/sveltekit/index.d.mts.map +1 -1
  161. package/dist/sveltekit/index.mjs +6 -3
  162. package/dist/sveltekit/index.mjs.map +1 -1
  163. package/dist/toolkit.d.mts +7 -7
  164. package/dist/toolkit.d.mts.map +1 -1
  165. package/dist/toolkit.mjs +7 -6
  166. package/dist/types.d.mts +1 -1
  167. package/dist/{useLogger-Cfv8Ck8b.d.mts → useLogger-BupVLO9_.d.mts} +3 -3
  168. package/dist/useLogger-BupVLO9_.d.mts.map +1 -0
  169. package/dist/{utils-CJJG0ZYW.d.mts → utils-D_LY-J1k.d.mts} +2 -2
  170. package/dist/{utils-CJJG0ZYW.d.mts.map → utils-D_LY-J1k.d.mts.map} +1 -1
  171. package/dist/utils.d.mts +1 -1
  172. package/dist/vite/index.d.mts +1 -1
  173. package/dist/workers.d.mts +3 -3
  174. package/dist/workers.d.mts.map +1 -1
  175. package/dist/workers.mjs +1 -1
  176. package/dist/workers.mjs.map +1 -1
  177. package/package.json +25 -2
  178. package/dist/audit-BQt8yAHo.mjs.map +0 -1
  179. package/dist/audit-D7v6JHj0.d.mts.map +0 -1
  180. package/dist/fork-CgGlAaHa.mjs.map +0 -1
  181. package/dist/index-EvnrXvQM.d.mts.map +0 -1
  182. package/dist/index.mjs.map +0 -1
  183. package/dist/logger-mHIWxBhJ.d.mts.map +0 -1
  184. package/dist/middleware-B_k4Mrzg.d.mts.map +0 -1
  185. package/dist/nitro-ClRZLD1g.mjs.map +0 -1
  186. package/dist/nitroConfigBridge-BkVWnSV3.mjs.map +0 -1
  187. package/dist/package-CNV_CXs8.mjs.map +0 -1
  188. package/dist/parseError-BeBXEd2V.d.mts.map +0 -1
  189. package/dist/storage-7X37OToT.mjs.map +0 -1
  190. package/dist/useLogger-Cfv8Ck8b.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/ai/index.ts"],"sourcesContent":["import { gateway, wrapLanguageModel, bindTelemetryIntegration } from 'ai'\nimport type { GatewayModelId, TelemetryIntegration, OnStartEvent, OnToolCallFinishEvent, OnFinishEvent } from 'ai'\nimport type { LanguageModelV3, LanguageModelV3Middleware, LanguageModelV3StreamPart } from '@ai-sdk/provider'\nimport type { RequestLogger } from '../types'\n\n/**\n * Fine-grained control over tool call input capture.\n */\nexport interface ToolInputsOptions {\n /**\n * Max character length for the stringified input JSON.\n * Inputs exceeding this limit are truncated with a `…` suffix.\n */\n maxLength?: number\n /**\n * Custom transform applied to each captured input before storing.\n * Receives the parsed input and tool name; return value is stored.\n * Runs before `maxLength` truncation.\n */\n transform?: (input: unknown, toolName: string) => unknown\n}\n\n/**\n * Pricing entry for a single model: cost per 1 million tokens in dollars.\n */\nexport interface ModelCost {\n input: number\n output: number\n}\n\n/**\n * Options for `createAILogger` and `createAIMiddleware`.\n */\nexport interface AILoggerOptions {\n /**\n * When enabled, `toolCalls` contains `{ name, input }` objects instead of plain tool name strings.\n * Opt-in because inputs can be large and may contain sensitive data.\n *\n * - `true` — capture all inputs as-is\n * - `{ maxLength, transform }` — capture with truncation or custom transform\n * @default false\n */\n toolInputs?: boolean | ToolInputsOptions\n /**\n * Pricing map for estimating request cost.\n * Keys are model IDs (e.g. `'claude-sonnet-4.6'`, `'gpt-4o'`), values are\n * `{ input, output }` in dollars per 1M tokens.\n *\n * When provided, the wide event includes `ai.estimatedCost` (in dollars).\n *\n * @example\n * ```ts\n * const ai = createAILogger(log, {\n * cost: {\n * 'claude-sonnet-4.6': { input: 3, output: 15 },\n * 'gpt-4o': { input: 2.5, output: 10 },\n * },\n * })\n * ```\n */\n cost?: Record<string, ModelCost>\n}\n\n/**\n * Per-step token usage breakdown for multi-step agent runs.\n */\nexport interface AIStepUsage {\n model: string\n inputTokens: number\n outputTokens: number\n toolCalls?: string[]\n}\n\n/**\n * Tool execution detail captured via `TelemetryIntegration`.\n */\nexport interface AIToolExecution {\n name: string\n durationMs: number\n success: boolean\n error?: string\n}\n\n/**\n * Embedding metadata captured via `captureEmbed`.\n */\nexport interface AIEmbeddingData {\n model?: string\n tokens: number\n dimensions?: number\n count?: number\n}\n\n/**\n * Shape of the `ai` field written to the wide event, and the public\n * snapshot returned by `AILogger.getMetadata()`.\n *\n * `model` and `provider` are populated after the first model call.\n * They may be undefined when only `captureEmbed` has been called or\n * before any AI activity has happened.\n */\nexport interface AIEventData {\n calls: number\n model?: string\n models?: string[]\n provider?: string\n inputTokens: number\n outputTokens: number\n totalTokens: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n finishReason?: string\n toolCalls?: string[] | Array<{ name: string, input: unknown }>\n responseId?: string\n steps?: number\n stepsUsage?: AIStepUsage[]\n msToFirstChunk?: number\n msToFinish?: number\n tokensPerSecond?: number\n error?: string\n tools?: AIToolExecution[]\n totalDurationMs?: number\n embedding?: AIEmbeddingData\n estimatedCost?: number\n}\n\n/**\n * Public alias for the metadata snapshot returned by `AILogger.getMetadata()`.\n *\n * Mirrors the shape of the `ai` field on the wide event, so the same object\n * can be persisted, surfaced to end-users, or compared across runs.\n */\nexport type AIMetadata = AIEventData\n\n/**\n * Callback fired on every metadata update (per step, per embed, on error,\n * and on integration completion). Receives a structured snapshot.\n */\nexport type AIMetadataListener = (metadata: AIMetadata) => void\n\nexport interface AILogger {\n /**\n * Wrap a language model with evlog middleware.\n * All `generateText` and `streamText` calls using the wrapped model\n * are captured automatically into the wide event.\n *\n * Accepts a `LanguageModelV3` object or a model string (e.g. `'anthropic/claude-sonnet-4.6'`).\n * Strings are resolved via the AI SDK gateway.\n *\n * Also works with pre-wrapped models (e.g. from supermemory, guardrails):\n * `ai.wrap(withSupermemory(base, orgId))` composes correctly.\n *\n * @example\n * ```ts\n * const ai = createAILogger(log)\n * const model = ai.wrap('anthropic/claude-sonnet-4.6')\n *\n * // Also accepts a model object\n * const model = ai.wrap(anthropic('claude-sonnet-4.6'))\n * ```\n */\n wrap: (model: LanguageModelV3 | GatewayModelId) => LanguageModelV3\n\n /**\n * Manually capture token usage from an `embed()` or `embedMany()` result.\n * Embedding models use a different type than language models, so they\n * cannot be wrapped with middleware.\n *\n * @example\n * ```ts\n * const { embedding, usage } = await embed({ model: embeddingModel, value: query })\n * ai.captureEmbed({ usage })\n *\n * // With model info (v2)\n * ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })\n *\n * // After embedMany\n * ai.captureEmbed({ usage, count: texts.length })\n * ```\n */\n captureEmbed: (result: {\n usage: { tokens: number }\n model?: string\n dimensions?: number\n count?: number\n }) => void\n\n /**\n * Get a snapshot of the current AI execution metadata.\n *\n * Returns the same structured object that is written to the `ai` field\n * of the wide event. Safe to call at any time — including inside the\n * AI SDK's `onFinish` callback, after `await generateText()`, or while a\n * stream is in progress.\n *\n * The returned snapshot is a fresh copy: mutating it does not affect\n * subsequent calls or the underlying state.\n *\n * @example Persist execution history after a run\n * ```ts\n * const ai = createAILogger(log, { cost: { ... } })\n *\n * await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })\n *\n * const metadata = ai.getMetadata()\n * await db.insert('ai_runs', metadata)\n * ```\n *\n * @example Surface usage to end-users in a streaming response\n * ```ts\n * const result = streamText({\n * model: ai.wrap('anthropic/claude-sonnet-4.6'),\n * messages,\n * onFinish: () => {\n * const { totalTokens, estimatedCost } = ai.getMetadata()\n * trackUsage(userId, { totalTokens, estimatedCost })\n * },\n * })\n * ```\n */\n getMetadata: () => AIMetadata\n\n /**\n * Get the current estimated cost in dollars.\n *\n * Returns `undefined` if no `cost` map was provided to `createAILogger`,\n * or if the model is not in the pricing map.\n *\n * Convenience for `getMetadata().estimatedCost`.\n *\n * @example\n * ```ts\n * const ai = createAILogger(log, {\n * cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },\n * })\n *\n * await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })\n *\n * console.log(`Cost: $${ai.getEstimatedCost()?.toFixed(4)}`)\n * ```\n */\n getEstimatedCost: () => number | undefined\n\n /**\n * Subscribe to metadata updates.\n *\n * The callback fires every time the underlying state flushes — once per\n * step (in multi-step agent runs), once per `captureEmbed` call, on\n * model errors, and once on `createEvlogIntegration`'s `onFinish`.\n *\n * Each invocation receives a fresh snapshot (same shape as `getMetadata`).\n * Returns an unsubscribe function.\n *\n * @example Stream incremental usage updates to the client\n * ```ts\n * const ai = createAILogger(log)\n *\n * ai.onUpdate((metadata) => {\n * pushToClient({ type: 'ai-progress', metadata })\n * })\n *\n * const result = streamText({ model: ai.wrap('...'), messages })\n * ```\n *\n * @example Cleanup\n * ```ts\n * const off = ai.onUpdate((metadata) => { ... })\n * // later\n * off()\n * ```\n */\n onUpdate: (callback: AIMetadataListener) => () => void\n\n /**\n * Internal accumulator state exposed for `createEvlogIntegration` to share.\n * @internal\n */\n _state: AccumulatorState\n}\n\ninterface UsageAccumulator {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n}\n\nfunction addUsage(\n acc: UsageAccumulator,\n usage: {\n inputTokens: { total: number | undefined, cacheRead?: number | undefined, cacheWrite?: number | undefined }\n outputTokens: { total: number | undefined, reasoning?: number | undefined }\n },\n): void {\n acc.inputTokens += usage.inputTokens.total ?? 0\n acc.outputTokens += usage.outputTokens.total ?? 0\n acc.cacheReadTokens += usage.inputTokens.cacheRead ?? 0\n acc.cacheWriteTokens += usage.inputTokens.cacheWrite ?? 0\n acc.reasoningTokens += usage.outputTokens.reasoning ?? 0\n}\n\n/**\n * When using `gateway('google/gemini-3-flash')`, the model object has\n * `provider: 'gateway'` and `modelId: 'google/gemini-3-flash'`.\n * This extracts the real provider and model name from the modelId.\n */\nfunction resolveProviderAndModel(provider: string, modelId: string): { provider: string, model: string } {\n if (provider !== 'gateway' || !modelId.includes('/')) {\n return { provider, model: modelId }\n }\n const slashIndex = modelId.indexOf('/')\n return {\n provider: modelId.slice(0, slashIndex),\n model: modelId.slice(slashIndex + 1),\n }\n}\n\n/**\n * Create the evlog AI middleware that captures AI SDK data into a wide event.\n *\n * Use this when you need explicit middleware composition with other wrappers\n * (e.g. supermemory, guardrails). For most cases, use `createAILogger` instead.\n *\n * Note: `captureEmbed` is not available with the raw middleware — use\n * `createAILogger` if you need embedding capture.\n *\n * @example Nuxt API route with supermemory\n * ```ts\n * import { createAIMiddleware } from 'evlog/ai'\n * import { wrapLanguageModel } from 'ai'\n *\n * export default defineEventHandler(async (event) => {\n * const log = useLogger(event)\n *\n * const model = wrapLanguageModel({\n * model: withSupermemory(base, orgId),\n * middleware: [createAIMiddleware(log, { toolInputs: true })],\n * })\n * })\n * ```\n */\nexport function createAIMiddleware(log: RequestLogger, options?: AILoggerOptions): LanguageModelV3Middleware {\n return buildMiddleware(log, options)\n}\n\n/**\n * Create an AI logger that captures AI SDK data into the wide event.\n *\n * Uses model middleware (`wrapLanguageModel`) to transparently intercept\n * all LLM calls. `onFinish` and `onStepFinish` remain free for user code.\n *\n * @example\n * ```ts\n * import { createAILogger } from 'evlog/ai'\n *\n * const log = useLogger(event)\n * const ai = createAILogger(log)\n * const model = ai.wrap('anthropic/claude-sonnet-4.6')\n *\n * const result = streamText({\n * model,\n * messages,\n * onFinish: ({ text }) => saveConversation(text),\n * })\n * ```\n *\n * @example Capture tool call inputs\n * ```ts\n * const ai = createAILogger(log, { toolInputs: true })\n * ```\n */\nexport function createAILogger(log: RequestLogger, options?: AILoggerOptions): AILogger {\n const state = createAccumulatorState(options)\n state._log = log\n const middleware = buildMiddlewareFromState(log, state)\n\n return {\n wrap: (model: LanguageModelV3 | GatewayModelId) => {\n const resolved = typeof model === 'string' ? gateway(model) : model\n return wrapLanguageModel({ model: resolved, middleware })\n },\n\n captureEmbed: (result: {\n usage: { tokens: number }\n model?: string\n dimensions?: number\n count?: number\n }) => {\n state.calls++\n state.usage.inputTokens += result.usage.tokens\n state.embedding = {\n tokens: (state.embedding?.tokens ?? 0) + result.usage.tokens,\n ...(result.model ? { model: result.model } : state.embedding?.model ? { model: state.embedding.model } : {}),\n ...(result.dimensions ? { dimensions: result.dimensions } : state.embedding?.dimensions ? { dimensions: state.embedding.dimensions } : {}),\n ...(result.count ? { count: (state.embedding?.count ?? 0) + result.count } : state.embedding?.count ? { count: state.embedding.count } : {}),\n }\n flushState(log, state)\n },\n\n getMetadata: () => buildMetadata(state),\n\n getEstimatedCost: () => computeEstimatedCost(state),\n\n onUpdate: (callback: AIMetadataListener) => {\n state.subscribers.add(callback)\n return () => {\n state.subscribers.delete(callback)\n }\n },\n\n _state: state,\n }\n}\n\n/**\n * Snapshot of how much of each cumulative array we've already sent to the\n * wide event. `flushState` reads it to compute the delta and updates it in\n * place; passing zero-watermarks to `buildMetadata` yields a full snapshot.\n */\ninterface Watermarks {\n toolCalls: number\n toolCallInputs: number\n stepsUsage: number\n toolExecutions: number\n models: Set<string>\n}\n\nfunction freshWatermarks(): Watermarks {\n return { toolCalls: 0, toolCallInputs: 0, stepsUsage: 0, toolExecutions: 0, models: new Set() }\n}\n\ninterface AccumulatorState {\n calls: number\n steps: number\n usage: UsageAccumulator\n models: string[]\n lastProvider: string | undefined\n allToolCalls: string[]\n allToolCallInputs: Array<{ name: string, input: unknown }>\n stepsUsage: AIStepUsage[]\n lastFinishReason: string | undefined\n lastMsToFirstChunk: number | undefined\n lastMsToFinish: number | undefined\n lastError: string | undefined\n lastResponseId: string | undefined\n toolInputs: boolean\n toolInputsOptions: ToolInputsOptions | undefined\n toolExecutions: AIToolExecution[]\n generationStartTime: number | undefined\n totalDurationMs: number | undefined\n embedding: AIEmbeddingData | undefined\n costMap: Record<string, ModelCost> | undefined\n subscribers: Set<AIMetadataListener>\n /** @internal What's already been written to the wide event. */\n _flushed: Watermarks\n /** @internal Logger reference for integration flush */\n _log?: RequestLogger\n}\n\nfunction resolveToolInputs(raw?: boolean | ToolInputsOptions): { enabled: boolean, options: ToolInputsOptions | undefined } {\n if (!raw) return { enabled: false, options: undefined }\n if (raw === true) return { enabled: true, options: undefined }\n return { enabled: true, options: raw }\n}\n\nfunction processToolInput(input: unknown, toolName: string, options: ToolInputsOptions | undefined): unknown {\n let value = input\n if (options?.transform) {\n value = options.transform(value, toolName)\n }\n if (options?.maxLength) {\n const str = typeof value === 'string' ? value : JSON.stringify(value)\n if (str.length > options.maxLength) {\n return `${str.slice(0, options.maxLength)}…`\n }\n }\n return value\n}\n\nfunction createAccumulatorState(options?: AILoggerOptions): AccumulatorState {\n const { enabled, options: captureOpts } = resolveToolInputs(options?.toolInputs)\n return {\n calls: 0,\n steps: 0,\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n reasoningTokens: 0,\n },\n models: [],\n lastProvider: undefined,\n allToolCalls: [],\n allToolCallInputs: [],\n stepsUsage: [],\n lastFinishReason: undefined,\n lastMsToFirstChunk: undefined,\n lastMsToFinish: undefined,\n lastError: undefined,\n lastResponseId: undefined,\n toolInputs: enabled,\n toolInputsOptions: captureOpts,\n toolExecutions: [],\n generationStartTime: undefined,\n totalDurationMs: undefined,\n embedding: undefined,\n costMap: options?.cost,\n subscribers: new Set(),\n _flushed: freshWatermarks(),\n }\n}\n\nfunction computeEstimatedCost(state: AccumulatorState): number | undefined {\n if (!state.costMap) return undefined\n const lastModel = state.models[state.models.length - 1]\n if (!lastModel) return undefined\n const pricing = state.costMap[lastModel]\n if (!pricing) return undefined\n const inputCost = (state.usage.inputTokens / 1_000_000) * pricing.input\n const outputCost = (state.usage.outputTokens / 1_000_000) * pricing.output\n const total = inputCost + outputCost\n return total > 0 ? Math.round(total * 1_000_000) / 1_000_000 : undefined\n}\n\n/**\n * Build the `ai.*` payload from the accumulator. Pass `since` to emit\n * only the array entries appended after that watermark (used by\n * `flushState` to avoid quadratic growth on the wide event, since evlog's\n * `mergeInto` concatenates arrays). The default — fresh watermarks —\n * yields the full cumulative snapshot used by `getMetadata()` and\n * delivered to `onUpdate` subscribers.\n */\nfunction buildMetadata(state: AccumulatorState, since: Watermarks = freshWatermarks()): AIMetadata {\n const lastModel = state.models[state.models.length - 1]\n\n const data: AIMetadata = {\n calls: state.calls,\n inputTokens: state.usage.inputTokens,\n outputTokens: state.usage.outputTokens,\n totalTokens: state.usage.inputTokens + state.usage.outputTokens,\n }\n\n if (lastModel) data.model = lastModel\n if (state.lastProvider) data.provider = state.lastProvider\n if (state.usage.cacheReadTokens > 0) data.cacheReadTokens = state.usage.cacheReadTokens\n if (state.usage.cacheWriteTokens > 0) data.cacheWriteTokens = state.usage.cacheWriteTokens\n if (state.usage.reasoningTokens > 0) data.reasoningTokens = state.usage.reasoningTokens\n if (state.lastFinishReason) data.finishReason = state.lastFinishReason\n if (state.lastResponseId) data.responseId = state.lastResponseId\n if (state.lastMsToFirstChunk !== undefined) data.msToFirstChunk = state.lastMsToFirstChunk\n if (state.lastMsToFinish !== undefined) {\n data.msToFinish = state.lastMsToFinish\n if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) {\n data.tokensPerSecond = Math.round((state.usage.outputTokens / state.lastMsToFinish) * 1000)\n }\n }\n if (state.lastError) data.error = state.lastError\n if (state.totalDurationMs !== undefined) data.totalDurationMs = state.totalDurationMs\n if (state.embedding) data.embedding = { ...state.embedding }\n const cost = computeEstimatedCost(state)\n if (cost !== undefined) data.estimatedCost = cost\n\n if (state.toolInputs) {\n if (state.allToolCallInputs.length > since.toolCallInputs) {\n data.toolCalls = state.allToolCallInputs.slice(since.toolCallInputs).map(t => ({ ...t }))\n }\n } else if (state.allToolCalls.length > since.toolCalls) {\n data.toolCalls = state.allToolCalls.slice(since.toolCalls)\n }\n\n if (state.steps > 1 && state.stepsUsage.length > since.stepsUsage) {\n data.steps = state.steps\n data.stepsUsage = state.stepsUsage\n .slice(since.stepsUsage)\n .map(s => ({ ...s, ...(s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}) }))\n }\n\n if (state.toolExecutions.length > since.toolExecutions) {\n data.tools = state.toolExecutions.slice(since.toolExecutions).map(t => ({ ...t }))\n }\n\n // `models` is only emitted once a second distinct model appears, then\n // deduplicated across flushes so the merged wide event stays unique.\n const uniqueModels = new Set(state.models)\n if (uniqueModels.size > 1) {\n const newModels = [...uniqueModels].filter(m => !since.models.has(m))\n if (newModels.length > 0) data.models = newModels\n }\n\n return data\n}\n\nfunction notifySubscribers(state: AccumulatorState, metadata: AIMetadata): void {\n if (state.subscribers.size === 0) return\n for (const subscriber of state.subscribers) {\n try {\n subscriber(metadata)\n } catch {\n // Subscribers must not break the AI flow.\n }\n }\n}\n\n/**\n * Push the accumulator's latest changes onto the wide event using delta\n * semantics for arrays, then notify subscribers with the full snapshot.\n */\nfunction flushState(log: RequestLogger, state: AccumulatorState): void {\n const flushed = state._flushed\n const data = buildMetadata(state, flushed)\n\n flushed.toolCalls = state.allToolCalls.length\n flushed.toolCallInputs = state.allToolCallInputs.length\n flushed.toolExecutions = state.toolExecutions.length\n if (data.stepsUsage) flushed.stepsUsage = state.stepsUsage.length\n if (data.models) for (const m of data.models) flushed.models.add(m)\n\n log.set({ ai: data } as Record<string, unknown>)\n\n if (state.subscribers.size > 0) {\n notifySubscribers(state, buildMetadata(state))\n }\n}\n\nfunction recordModel(state: AccumulatorState, provider: string, modelId: string, responseModelId?: string): void {\n const resolved = resolveProviderAndModel(provider, responseModelId ?? modelId)\n state.models.push(resolved.model)\n state.lastProvider = resolved.provider\n}\n\nfunction safeParseJSON(input: string): unknown {\n try {\n return JSON.parse(input)\n } catch {\n return input\n }\n}\n\nfunction recordError(log: RequestLogger, state: AccumulatorState, model: { provider: string, modelId: string }, error: unknown): void {\n state.calls++\n state.steps++\n recordModel(state, model.provider, model.modelId)\n state.lastFinishReason = 'error'\n state.lastError = error instanceof Error ? error.message : String(error)\n\n const resolved = resolveProviderAndModel(model.provider, model.modelId)\n state.stepsUsage.push({\n model: resolved.model,\n inputTokens: 0,\n outputTokens: 0,\n })\n\n flushState(log, state)\n}\n\nfunction buildMiddleware(log: RequestLogger, options?: AILoggerOptions): LanguageModelV3Middleware {\n const state = createAccumulatorState(options)\n state._log = log\n return buildMiddlewareFromState(log, state)\n}\n\nfunction buildMiddlewareFromState(log: RequestLogger, state: AccumulatorState): LanguageModelV3Middleware {\n return {\n specificationVersion: 'v3',\n wrapGenerate: async ({ doGenerate, model }) => {\n try {\n const result = await doGenerate()\n\n state.calls++\n state.steps++\n addUsage(state.usage, result.usage)\n recordModel(state, model.provider, model.modelId, result.response?.modelId)\n state.lastFinishReason = result.finishReason.unified\n\n if (result.response?.id) {\n state.lastResponseId = result.response.id\n }\n\n const stepToolCalls: string[] = []\n for (const item of result.content) {\n if (item.type === 'tool-call') {\n state.allToolCalls.push(item.toolName)\n stepToolCalls.push(item.toolName)\n if (state.toolInputs) {\n const raw = typeof item.input === 'string' ? safeParseJSON(item.input) : item.input\n state.allToolCallInputs.push({\n name: item.toolName,\n input: processToolInput(raw, item.toolName, state.toolInputsOptions),\n })\n }\n }\n }\n\n const resolvedModel = resolveProviderAndModel(model.provider, result.response?.modelId ?? model.modelId)\n state.stepsUsage.push({\n model: resolvedModel.model,\n inputTokens: result.usage.inputTokens.total ?? 0,\n outputTokens: result.usage.outputTokens.total ?? 0,\n ...(stepToolCalls.length > 0 ? { toolCalls: stepToolCalls } : {}),\n })\n\n flushState(log, state)\n return result\n } catch (error) {\n recordError(log, state, model, error)\n throw error\n }\n },\n\n wrapStream: async ({ doStream, model }) => {\n const streamStart = Date.now()\n let firstChunkTime: number | undefined\n\n let streamUsage: UsageAccumulator | undefined\n let streamFinishReason: string | undefined\n let streamModelId: string | undefined\n let streamResponseId: string | undefined\n const streamToolCalls: string[] = []\n const streamToolInputBuffers = new Map<string, { name: string, chunks: string[] }>()\n let streamError: string | undefined\n\n let doStreamResult: Awaited<ReturnType<typeof doStream>>\n try {\n doStreamResult = await doStream()\n } catch (error) {\n recordError(log, state, model, error)\n throw error\n }\n\n const { stream, ...rest } = doStreamResult\n\n const transformStream = new TransformStream<\n LanguageModelV3StreamPart,\n LanguageModelV3StreamPart\n >({\n transform(chunk, controller) {\n if (!firstChunkTime && chunk.type === 'text-delta') {\n firstChunkTime = Date.now()\n }\n\n if (chunk.type === 'tool-input-start') {\n streamToolCalls.push(chunk.toolName)\n if (state.toolInputs) {\n streamToolInputBuffers.set(chunk.id, { name: chunk.toolName, chunks: [] })\n }\n }\n\n if (chunk.type === 'tool-input-delta' && state.toolInputs) {\n const buffer = streamToolInputBuffers.get(chunk.id)\n if (buffer) {\n buffer.chunks.push(chunk.delta)\n }\n }\n\n if (chunk.type === 'tool-input-end' && state.toolInputs) {\n const buffer = streamToolInputBuffers.get(chunk.id)\n if (buffer) {\n const raw = safeParseJSON(buffer.chunks.join(''))\n state.allToolCallInputs.push({\n name: buffer.name,\n input: processToolInput(raw, buffer.name, state.toolInputsOptions),\n })\n streamToolInputBuffers.delete(chunk.id)\n }\n }\n\n if (chunk.type === 'finish') {\n streamUsage = {\n inputTokens: chunk.usage.inputTokens.total ?? 0,\n outputTokens: chunk.usage.outputTokens.total ?? 0,\n cacheReadTokens: chunk.usage.inputTokens.cacheRead ?? 0,\n cacheWriteTokens: chunk.usage.inputTokens.cacheWrite ?? 0,\n reasoningTokens: chunk.usage.outputTokens.reasoning ?? 0,\n }\n streamFinishReason = chunk.finishReason.unified\n }\n\n if (chunk.type === 'response-metadata') {\n if (chunk.modelId) streamModelId = chunk.modelId\n if (chunk.id) streamResponseId = chunk.id\n }\n\n if (chunk.type === 'error') {\n streamError = chunk.error instanceof Error ? chunk.error.message : String(chunk.error)\n }\n\n controller.enqueue(chunk)\n },\n\n flush() {\n state.calls++\n state.steps++\n\n if (streamUsage) {\n state.usage.inputTokens += streamUsage.inputTokens\n state.usage.outputTokens += streamUsage.outputTokens\n state.usage.cacheReadTokens += streamUsage.cacheReadTokens\n state.usage.cacheWriteTokens += streamUsage.cacheWriteTokens\n state.usage.reasoningTokens += streamUsage.reasoningTokens\n }\n\n recordModel(state, model.provider, model.modelId, streamModelId)\n state.lastFinishReason = streamFinishReason\n\n state.allToolCalls.push(...streamToolCalls)\n\n if (streamResponseId) {\n state.lastResponseId = streamResponseId\n }\n\n if (firstChunkTime) {\n state.lastMsToFirstChunk = firstChunkTime - streamStart\n }\n state.lastMsToFinish = Date.now() - streamStart\n\n if (streamError) state.lastError = streamError\n\n const resolvedModel = resolveProviderAndModel(model.provider, streamModelId ?? model.modelId)\n state.stepsUsage.push({\n model: resolvedModel.model,\n inputTokens: streamUsage?.inputTokens ?? 0,\n outputTokens: streamUsage?.outputTokens ?? 0,\n ...(streamToolCalls.length > 0 ? { toolCalls: [...streamToolCalls] } : {}),\n })\n\n flushState(log, state)\n },\n })\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n }\n },\n }\n}\n\n/**\n * Create an AI SDK `TelemetryIntegration` that captures tool execution\n * timing, errors, and total generation wall time into the wide event.\n *\n * Complements the middleware-based `createAILogger`: the middleware captures\n * token usage and streaming metrics at the model level, while the integration\n * captures application-level lifecycle events (tool execution, total duration).\n *\n * When passed an `AILogger`, shares its accumulator so both paths write to\n * the same `ai.*` field. Can also be used standalone with a `RequestLogger`.\n *\n * @example Combined with middleware (recommended)\n * ```ts\n * import { createAILogger, createEvlogIntegration } from 'evlog/ai'\n *\n * const log = useLogger(event)\n * const ai = createAILogger(log)\n *\n * const result = await generateText({\n * model: ai.wrap('anthropic/claude-sonnet-4.6'),\n * tools: { getWeather },\n * experimental_telemetry: {\n * isEnabled: true,\n * integrations: [createEvlogIntegration(ai)],\n * },\n * })\n * ```\n *\n * @example Standalone (no middleware wrapping)\n * ```ts\n * import { createEvlogIntegration } from 'evlog/ai'\n *\n * const integration = createEvlogIntegration(log)\n *\n * const result = await generateText({\n * model: openai('gpt-4o'),\n * experimental_telemetry: {\n * isEnabled: true,\n * integrations: [integration],\n * },\n * })\n * ```\n */\nexport function createEvlogIntegration(\n logOrAi: RequestLogger | AILogger,\n options?: AILoggerOptions,\n): TelemetryIntegration {\n let log: RequestLogger\n let state: AccumulatorState\n\n if ('_state' in logOrAi && logOrAi._state) {\n state = logOrAi._state\n log = state._log!\n } else {\n log = logOrAi as RequestLogger\n state = createAccumulatorState(options)\n state._log = log\n }\n\n class EvlogIntegration implements TelemetryIntegration {\n\n onStart(_event: OnStartEvent) {\n state.generationStartTime = Date.now()\n }\n\n onToolCallFinish(event: OnToolCallFinishEvent) {\n const execution: AIToolExecution = {\n name: (event.toolCall as { toolName: string }).toolName,\n durationMs: event.durationMs,\n success: event.success,\n }\n if (!event.success && event.error) {\n execution.error = event.error instanceof Error ? event.error.message : String(event.error)\n }\n state.toolExecutions.push(execution)\n }\n\n onFinish(_event: OnFinishEvent) {\n if (state.generationStartTime) {\n state.totalDurationMs = Date.now() - state.generationStartTime\n }\n flushState(log, state)\n }\n \n }\n\n return bindTelemetryIntegration(new EvlogIntegration())\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/ai/index.ts"],"sourcesContent":["import * as aiModule from 'ai'\nimport { gateway, wrapLanguageModel } from 'ai'\nimport type { GatewayModelId } from 'ai'\nimport type { LanguageModelV3, LanguageModelV3Middleware, LanguageModelV3StreamPart } from '@ai-sdk/provider'\nimport type { RequestLogger } from '../types'\n\nconst { bindTelemetryIntegration } = aiModule as {\n bindTelemetryIntegration?: (integration: EvlogTelemetryIntegration) => EvlogTelemetry\n}\n\n/** @internal v6 tool execution event shape. */\ninterface V6ToolCallFinishEvent {\n toolCall: { toolName: string }\n durationMs: number\n success: boolean\n error?: unknown\n}\n\n/** @internal v7 tool execution event shape. */\ninterface V7ToolExecutionEndEvent {\n toolCall: { toolName: string }\n toolExecutionMs: number\n toolOutput: { type: 'tool-result', output?: unknown } | { type: 'tool-error', error: unknown }\n}\n\n/** @internal v7 embedding telemetry event shape. */\ninterface V7EmbedEndEvent {\n modelId: string\n usage: { tokens: number }\n embeddings: Array<{ length?: number }>\n values: string[]\n}\n\n/**\n * Telemetry integration returned by {@link createEvlogIntegration}.\n * Implements hooks for AI SDK v6 (`onToolCallFinish`, `onFinish`) and v7\n * (`onToolExecutionEnd`, `onEnd`, `onEmbedEnd`, `onAbort`, `onError`).\n */\nexport type EvlogTelemetry = {\n onStart?: (event: unknown) => void | PromiseLike<void>\n /** AI SDK v6 — superseded by `onToolExecutionEnd` in v7. */\n onToolCallFinish?: (event: unknown) => void | PromiseLike<void>\n onToolExecutionEnd?: (event: unknown) => void | PromiseLike<void>\n /** AI SDK v6 — superseded by `onEnd` in v7. */\n onFinish?: (event: unknown) => void | PromiseLike<void>\n onEnd?: (event: unknown) => void | PromiseLike<void>\n onEmbedEnd?: (event: unknown) => void | PromiseLike<void>\n onAbort?: (event: unknown) => void | PromiseLike<void>\n onError?: (event: unknown) => void | PromiseLike<void>\n}\n\n/** @internal Concrete integration class — both v6 and v7 hook names. */\ninterface EvlogTelemetryIntegration extends EvlogTelemetry {}\n\n/**\n * Fine-grained control over tool call input capture.\n */\nexport interface ToolInputsOptions {\n /**\n * Max character length for the stringified input JSON.\n * Inputs exceeding this limit are truncated with a `…` suffix.\n */\n maxLength?: number\n /**\n * Custom transform applied to each captured input before storing.\n * Receives the parsed input and tool name; return value is stored.\n * Runs before `maxLength` truncation.\n */\n transform?: (input: unknown, toolName: string) => unknown\n}\n\n/**\n * Pricing entry for a single model: cost per 1 million tokens in dollars.\n */\nexport interface ModelCost {\n input: number\n output: number\n}\n\n/**\n * Options for `createAILogger` and `createAIMiddleware`.\n */\nexport interface AILoggerOptions {\n /**\n * When enabled, `toolCalls` contains `{ name, input }` objects instead of plain tool name strings.\n * Opt-in because inputs can be large and may contain sensitive data.\n *\n * - `true` — capture all inputs as-is\n * - `{ maxLength, transform }` — capture with truncation or custom transform\n * @default false\n */\n toolInputs?: boolean | ToolInputsOptions\n /**\n * Pricing map for estimating request cost.\n * Keys are model IDs (e.g. `'claude-sonnet-4.6'`, `'gpt-4o'`), values are\n * `{ input, output }` in dollars per 1M tokens.\n *\n * When provided, the wide event includes `ai.estimatedCost` (in dollars).\n *\n * @example\n * ```ts\n * const ai = createAILogger(log, {\n * cost: {\n * 'claude-sonnet-4.6': { input: 3, output: 15 },\n * 'gpt-4o': { input: 2.5, output: 10 },\n * },\n * })\n * ```\n */\n cost?: Record<string, ModelCost>\n}\n\n/**\n * Per-step token usage breakdown for multi-step agent runs.\n */\nexport interface AIStepUsage {\n model: string\n inputTokens: number\n outputTokens: number\n toolCalls?: string[]\n}\n\n/**\n * Tool execution detail captured via telemetry integration hooks.\n */\nexport interface AIToolExecution {\n name: string\n durationMs: number\n success: boolean\n error?: string\n}\n\n/**\n * Embedding metadata captured via `captureEmbed`.\n */\nexport interface AIEmbeddingData {\n model?: string\n tokens: number\n dimensions?: number\n count?: number\n}\n\n/**\n * Shape of the `ai` field written to the wide event, and the public\n * snapshot returned by `AILogger.getMetadata()`.\n *\n * `model` and `provider` are populated after the first model call.\n * They may be undefined when only `captureEmbed` has been called or\n * before any AI activity has happened.\n */\nexport interface AIEventData {\n calls: number\n model?: string\n models?: string[]\n provider?: string\n inputTokens: number\n outputTokens: number\n totalTokens: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n finishReason?: string\n toolCalls?: string[] | Array<{ name: string, input: unknown }>\n responseId?: string\n steps?: number\n stepsUsage?: AIStepUsage[]\n msToFirstChunk?: number\n msToFinish?: number\n tokensPerSecond?: number\n error?: string\n tools?: AIToolExecution[]\n totalDurationMs?: number\n embedding?: AIEmbeddingData\n estimatedCost?: number\n}\n\n/**\n * Public alias for the metadata snapshot returned by `AILogger.getMetadata()`.\n *\n * Mirrors the shape of the `ai` field on the wide event, so the same object\n * can be persisted, surfaced to end-users, or compared across runs.\n */\nexport type AIMetadata = AIEventData\n\n/**\n * Callback fired on every metadata update (per step, per embed, on error,\n * and on integration completion). Receives a structured snapshot.\n */\nexport type AIMetadataListener = (metadata: AIMetadata) => void\n\nexport interface AILogger {\n /**\n * Wrap a language model with evlog middleware.\n * All `generateText` and `streamText` calls using the wrapped model\n * are captured automatically into the wide event.\n *\n * Accepts a `LanguageModelV3` object or a model string (e.g. `'anthropic/claude-sonnet-4.6'`).\n * Strings are resolved via the AI SDK gateway.\n *\n * Also works with pre-wrapped models (e.g. from supermemory, guardrails):\n * `ai.wrap(withSupermemory(base, orgId))` composes correctly.\n *\n * @example\n * ```ts\n * const ai = createAILogger(log)\n * const model = ai.wrap('anthropic/claude-sonnet-4.6')\n *\n * // Also accepts a model object\n * const model = ai.wrap(anthropic('claude-sonnet-4.6'))\n * ```\n */\n wrap: (model: LanguageModelV3 | GatewayModelId) => LanguageModelV3\n\n /**\n * Manually capture token usage from an `embed()` or `embedMany()` result.\n * Embedding models use a different type than language models, so they\n * cannot be wrapped with middleware.\n *\n * @example\n * ```ts\n * const { embedding, usage } = await embed({ model: embeddingModel, value: query })\n * ai.captureEmbed({ usage })\n *\n * // With model info (v2)\n * ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })\n *\n * // After embedMany\n * ai.captureEmbed({ usage, count: texts.length })\n * ```\n */\n captureEmbed: (result: {\n usage: { tokens: number }\n model?: string\n dimensions?: number\n count?: number\n }) => void\n\n /**\n * Get a snapshot of the current AI execution metadata.\n *\n * Returns the same structured object that is written to the `ai` field\n * of the wide event. Safe to call at any time — including inside the\n * AI SDK's `onFinish` callback, after `await generateText()`, or while a\n * stream is in progress.\n *\n * The returned snapshot is a fresh copy: mutating it does not affect\n * subsequent calls or the underlying state.\n *\n * @example Persist execution history after a run\n * ```ts\n * const ai = createAILogger(log, { cost: { ... } })\n *\n * await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })\n *\n * const metadata = ai.getMetadata()\n * await db.insert('ai_runs', metadata)\n * ```\n *\n * @example Surface usage to end-users in a streaming response\n * ```ts\n * const result = streamText({\n * model: ai.wrap('anthropic/claude-sonnet-4.6'),\n * messages,\n * onFinish: () => {\n * const { totalTokens, estimatedCost } = ai.getMetadata()\n * trackUsage(userId, { totalTokens, estimatedCost })\n * },\n * })\n * ```\n */\n getMetadata: () => AIMetadata\n\n /**\n * Get the current estimated cost in dollars.\n *\n * Returns `undefined` if no `cost` map was provided to `createAILogger`,\n * or if the model is not in the pricing map.\n *\n * Convenience for `getMetadata().estimatedCost`.\n *\n * @example\n * ```ts\n * const ai = createAILogger(log, {\n * cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },\n * })\n *\n * await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })\n *\n * console.log(`Cost: $${ai.getEstimatedCost()?.toFixed(4)}`)\n * ```\n */\n getEstimatedCost: () => number | undefined\n\n /**\n * Subscribe to metadata updates.\n *\n * The callback fires every time the underlying state flushes — once per\n * step (in multi-step agent runs), once per `captureEmbed` call, on\n * model errors, and once on `createEvlogIntegration`'s `onFinish`.\n *\n * Each invocation receives a fresh snapshot (same shape as `getMetadata`).\n * Returns an unsubscribe function.\n *\n * @example Stream incremental usage updates to the client\n * ```ts\n * const ai = createAILogger(log)\n *\n * ai.onUpdate((metadata) => {\n * pushToClient({ type: 'ai-progress', metadata })\n * })\n *\n * const result = streamText({ model: ai.wrap('...'), messages })\n * ```\n *\n * @example Cleanup\n * ```ts\n * const off = ai.onUpdate((metadata) => { ... })\n * // later\n * off()\n * ```\n */\n onUpdate: (callback: AIMetadataListener) => () => void\n\n /**\n * Internal accumulator state exposed for `createEvlogIntegration` to share.\n * @internal\n */\n _state: AccumulatorState\n}\n\ninterface UsageAccumulator {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n reasoningTokens: number\n}\n\nfunction addUsage(\n acc: UsageAccumulator,\n usage: {\n inputTokens: { total: number | undefined, cacheRead?: number | undefined, cacheWrite?: number | undefined }\n outputTokens: { total: number | undefined, reasoning?: number | undefined }\n },\n): void {\n acc.inputTokens += usage.inputTokens.total ?? 0\n acc.outputTokens += usage.outputTokens.total ?? 0\n acc.cacheReadTokens += usage.inputTokens.cacheRead ?? 0\n acc.cacheWriteTokens += usage.inputTokens.cacheWrite ?? 0\n acc.reasoningTokens += usage.outputTokens.reasoning ?? 0\n}\n\n/**\n * When using `gateway('google/gemini-3-flash')`, the model object has\n * `provider: 'gateway'` and `modelId: 'google/gemini-3-flash'`.\n * This extracts the real provider and model name from the modelId.\n */\nfunction resolveProviderAndModel(provider: string, modelId: string): { provider: string, model: string } {\n if (provider !== 'gateway' || !modelId.includes('/')) {\n return { provider, model: modelId }\n }\n const slashIndex = modelId.indexOf('/')\n return {\n provider: modelId.slice(0, slashIndex),\n model: modelId.slice(slashIndex + 1),\n }\n}\n\n/**\n * Create the evlog AI middleware that captures AI SDK data into a wide event.\n *\n * Use this when you need explicit middleware composition with other wrappers\n * (e.g. supermemory, guardrails). For most cases, use `createAILogger` instead.\n *\n * Note: `captureEmbed` is not available with the raw middleware — use\n * `createAILogger` if you need embedding capture.\n *\n * @example Nuxt API route with supermemory\n * ```ts\n * import { createAIMiddleware } from 'evlog/ai'\n * import { wrapLanguageModel } from 'ai'\n *\n * export default defineEventHandler(async (event) => {\n * const log = useLogger(event)\n *\n * const model = wrapLanguageModel({\n * model: withSupermemory(base, orgId),\n * middleware: [createAIMiddleware(log, { toolInputs: true })],\n * })\n * })\n * ```\n */\nexport function createAIMiddleware(log: RequestLogger, options?: AILoggerOptions): LanguageModelV3Middleware {\n return buildMiddleware(log, options)\n}\n\n/**\n * Create an AI logger that captures AI SDK data into the wide event.\n *\n * Uses model middleware (`wrapLanguageModel`) to transparently intercept\n * all LLM calls. `onFinish` and `onStepFinish` remain free for user code.\n *\n * @example\n * ```ts\n * import { createAILogger } from 'evlog/ai'\n *\n * const log = useLogger(event)\n * const ai = createAILogger(log)\n * const model = ai.wrap('anthropic/claude-sonnet-4.6')\n *\n * const result = streamText({\n * model,\n * messages,\n * onFinish: ({ text }) => saveConversation(text),\n * })\n * ```\n *\n * @example Capture tool call inputs\n * ```ts\n * const ai = createAILogger(log, { toolInputs: true })\n * ```\n */\nexport function createAILogger(log: RequestLogger, options?: AILoggerOptions): AILogger {\n const state = createAccumulatorState(options)\n state._log = log\n const middleware = buildMiddlewareFromState(log, state)\n\n return {\n wrap: (model: LanguageModelV3 | GatewayModelId) => {\n const resolved = typeof model === 'string' ? gateway(model) : model\n return wrapLanguageModel({ model: resolved, middleware })\n },\n\n captureEmbed: (result: {\n usage: { tokens: number }\n model?: string\n dimensions?: number\n count?: number\n }) => {\n state.calls++\n state.usage.inputTokens += result.usage.tokens\n state.embedding = {\n tokens: (state.embedding?.tokens ?? 0) + result.usage.tokens,\n ...(result.model ? { model: result.model } : state.embedding?.model ? { model: state.embedding.model } : {}),\n ...(result.dimensions ? { dimensions: result.dimensions } : state.embedding?.dimensions ? { dimensions: state.embedding.dimensions } : {}),\n ...(result.count ? { count: (state.embedding?.count ?? 0) + result.count } : state.embedding?.count ? { count: state.embedding.count } : {}),\n }\n flushState(log, state)\n },\n\n getMetadata: () => buildMetadata(state),\n\n getEstimatedCost: () => computeEstimatedCost(state),\n\n onUpdate: (callback: AIMetadataListener) => {\n state.subscribers.add(callback)\n return () => {\n state.subscribers.delete(callback)\n }\n },\n\n _state: state,\n }\n}\n\n/**\n * Snapshot of how much of each cumulative array we've already sent to the\n * wide event. `flushState` reads it to compute the delta and updates it in\n * place; passing zero-watermarks to `buildMetadata` yields a full snapshot.\n */\ninterface Watermarks {\n toolCalls: number\n toolCallInputs: number\n stepsUsage: number\n toolExecutions: number\n models: Set<string>\n}\n\nfunction freshWatermarks(): Watermarks {\n return { toolCalls: 0, toolCallInputs: 0, stepsUsage: 0, toolExecutions: 0, models: new Set() }\n}\n\ninterface AccumulatorState {\n calls: number\n steps: number\n usage: UsageAccumulator\n models: string[]\n lastProvider: string | undefined\n allToolCalls: string[]\n allToolCallInputs: Array<{ name: string, input: unknown }>\n stepsUsage: AIStepUsage[]\n lastFinishReason: string | undefined\n lastMsToFirstChunk: number | undefined\n lastMsToFinish: number | undefined\n lastError: string | undefined\n lastResponseId: string | undefined\n toolInputs: boolean\n toolInputsOptions: ToolInputsOptions | undefined\n toolExecutions: AIToolExecution[]\n generationStartTime: number | undefined\n totalDurationMs: number | undefined\n embedding: AIEmbeddingData | undefined\n costMap: Record<string, ModelCost> | undefined\n subscribers: Set<AIMetadataListener>\n /** @internal What's already been written to the wide event. */\n _flushed: Watermarks\n /** @internal Logger reference for integration flush */\n _log?: RequestLogger\n}\n\nfunction resolveToolInputs(raw?: boolean | ToolInputsOptions): { enabled: boolean, options: ToolInputsOptions | undefined } {\n if (!raw) return { enabled: false, options: undefined }\n if (raw === true) return { enabled: true, options: undefined }\n return { enabled: true, options: raw }\n}\n\nfunction processToolInput(input: unknown, toolName: string, options: ToolInputsOptions | undefined): unknown {\n let value = input\n if (options?.transform) {\n value = options.transform(value, toolName)\n }\n if (options?.maxLength) {\n const str = typeof value === 'string' ? value : JSON.stringify(value)\n if (str.length > options.maxLength) {\n return `${str.slice(0, options.maxLength)}…`\n }\n }\n return value\n}\n\nfunction createAccumulatorState(options?: AILoggerOptions): AccumulatorState {\n const { enabled, options: captureOpts } = resolveToolInputs(options?.toolInputs)\n return {\n calls: 0,\n steps: 0,\n usage: {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n reasoningTokens: 0,\n },\n models: [],\n lastProvider: undefined,\n allToolCalls: [],\n allToolCallInputs: [],\n stepsUsage: [],\n lastFinishReason: undefined,\n lastMsToFirstChunk: undefined,\n lastMsToFinish: undefined,\n lastError: undefined,\n lastResponseId: undefined,\n toolInputs: enabled,\n toolInputsOptions: captureOpts,\n toolExecutions: [],\n generationStartTime: undefined,\n totalDurationMs: undefined,\n embedding: undefined,\n costMap: options?.cost,\n subscribers: new Set(),\n _flushed: freshWatermarks(),\n }\n}\n\nfunction computeEstimatedCost(state: AccumulatorState): number | undefined {\n if (!state.costMap) return undefined\n const lastModel = state.models[state.models.length - 1]\n if (!lastModel) return undefined\n const pricing = state.costMap[lastModel]\n if (!pricing) return undefined\n const inputCost = (state.usage.inputTokens / 1_000_000) * pricing.input\n const outputCost = (state.usage.outputTokens / 1_000_000) * pricing.output\n const total = inputCost + outputCost\n return total > 0 ? Math.round(total * 1_000_000) / 1_000_000 : undefined\n}\n\n/**\n * Build the `ai.*` payload from the accumulator. Pass `since` to emit\n * only the array entries appended after that watermark (used by\n * `flushState` to avoid quadratic growth on the wide event, since evlog's\n * `mergeInto` concatenates arrays). The default — fresh watermarks —\n * yields the full cumulative snapshot used by `getMetadata()` and\n * delivered to `onUpdate` subscribers.\n */\nfunction buildMetadata(state: AccumulatorState, since: Watermarks = freshWatermarks()): AIMetadata {\n const lastModel = state.models[state.models.length - 1]\n\n const data: AIMetadata = {\n calls: state.calls,\n inputTokens: state.usage.inputTokens,\n outputTokens: state.usage.outputTokens,\n totalTokens: state.usage.inputTokens + state.usage.outputTokens,\n }\n\n if (lastModel) data.model = lastModel\n if (state.lastProvider) data.provider = state.lastProvider\n if (state.usage.cacheReadTokens > 0) data.cacheReadTokens = state.usage.cacheReadTokens\n if (state.usage.cacheWriteTokens > 0) data.cacheWriteTokens = state.usage.cacheWriteTokens\n if (state.usage.reasoningTokens > 0) data.reasoningTokens = state.usage.reasoningTokens\n if (state.lastFinishReason) data.finishReason = state.lastFinishReason\n if (state.lastResponseId) data.responseId = state.lastResponseId\n if (state.lastMsToFirstChunk !== undefined) data.msToFirstChunk = state.lastMsToFirstChunk\n if (state.lastMsToFinish !== undefined) {\n data.msToFinish = state.lastMsToFinish\n if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) {\n data.tokensPerSecond = Math.round((state.usage.outputTokens / state.lastMsToFinish) * 1000)\n }\n }\n if (state.lastError) data.error = state.lastError\n if (state.totalDurationMs !== undefined) data.totalDurationMs = state.totalDurationMs\n if (state.embedding) data.embedding = { ...state.embedding }\n const cost = computeEstimatedCost(state)\n if (cost !== undefined) data.estimatedCost = cost\n\n if (state.toolInputs) {\n if (state.allToolCallInputs.length > since.toolCallInputs) {\n data.toolCalls = state.allToolCallInputs.slice(since.toolCallInputs).map(t => ({ ...t }))\n }\n } else if (state.allToolCalls.length > since.toolCalls) {\n data.toolCalls = state.allToolCalls.slice(since.toolCalls)\n }\n\n if (state.steps > 1 && state.stepsUsage.length > since.stepsUsage) {\n data.steps = state.steps\n data.stepsUsage = state.stepsUsage\n .slice(since.stepsUsage)\n .map(s => ({ ...s, ...(s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}) }))\n }\n\n if (state.toolExecutions.length > since.toolExecutions) {\n data.tools = state.toolExecutions.slice(since.toolExecutions).map(t => ({ ...t }))\n }\n\n // `models` is only emitted once a second distinct model appears, then\n // deduplicated across flushes so the merged wide event stays unique.\n const uniqueModels = new Set(state.models)\n if (uniqueModels.size > 1) {\n const newModels = [...uniqueModels].filter(m => !since.models.has(m))\n if (newModels.length > 0) data.models = newModels\n }\n\n return data\n}\n\nfunction notifySubscribers(state: AccumulatorState, metadata: AIMetadata): void {\n if (state.subscribers.size === 0) return\n for (const subscriber of state.subscribers) {\n try {\n subscriber(metadata)\n } catch {\n // Subscribers must not break the AI flow.\n }\n }\n}\n\n/**\n * Push the accumulator's latest changes onto the wide event using delta\n * semantics for arrays, then notify subscribers with the full snapshot.\n */\nfunction flushState(log: RequestLogger, state: AccumulatorState): void {\n const flushed = state._flushed\n const data = buildMetadata(state, flushed)\n\n flushed.toolCalls = state.allToolCalls.length\n flushed.toolCallInputs = state.allToolCallInputs.length\n flushed.toolExecutions = state.toolExecutions.length\n if (data.stepsUsage) flushed.stepsUsage = state.stepsUsage.length\n if (data.models) for (const m of data.models) flushed.models.add(m)\n\n log.set({ ai: data } as Record<string, unknown>)\n\n if (state.subscribers.size > 0) {\n notifySubscribers(state, buildMetadata(state))\n }\n}\n\nfunction recordModel(state: AccumulatorState, provider: string, modelId: string, responseModelId?: string): void {\n const resolved = resolveProviderAndModel(provider, responseModelId ?? modelId)\n state.models.push(resolved.model)\n state.lastProvider = resolved.provider\n}\n\nfunction safeParseJSON(input: string): unknown {\n try {\n return JSON.parse(input)\n } catch {\n return input\n }\n}\n\nfunction recordError(log: RequestLogger, state: AccumulatorState, model: { provider: string, modelId: string }, error: unknown): void {\n state.calls++\n state.steps++\n recordModel(state, model.provider, model.modelId)\n state.lastFinishReason = 'error'\n state.lastError = error instanceof Error ? error.message : String(error)\n\n const resolved = resolveProviderAndModel(model.provider, model.modelId)\n state.stepsUsage.push({\n model: resolved.model,\n inputTokens: 0,\n outputTokens: 0,\n })\n\n flushState(log, state)\n}\n\nfunction buildMiddleware(log: RequestLogger, options?: AILoggerOptions): LanguageModelV3Middleware {\n const state = createAccumulatorState(options)\n state._log = log\n return buildMiddlewareFromState(log, state)\n}\n\nfunction buildMiddlewareFromState(log: RequestLogger, state: AccumulatorState): LanguageModelV3Middleware {\n return {\n specificationVersion: 'v3',\n wrapGenerate: async ({ doGenerate, model }) => {\n try {\n const result = await doGenerate()\n\n state.calls++\n state.steps++\n addUsage(state.usage, result.usage)\n recordModel(state, model.provider, model.modelId, result.response?.modelId)\n state.lastFinishReason = result.finishReason.unified\n\n if (result.response?.id) {\n state.lastResponseId = result.response.id\n }\n\n const stepToolCalls: string[] = []\n for (const item of result.content) {\n if (item.type === 'tool-call') {\n state.allToolCalls.push(item.toolName)\n stepToolCalls.push(item.toolName)\n if (state.toolInputs) {\n const raw = typeof item.input === 'string' ? safeParseJSON(item.input) : item.input\n state.allToolCallInputs.push({\n name: item.toolName,\n input: processToolInput(raw, item.toolName, state.toolInputsOptions),\n })\n }\n }\n }\n\n const resolvedModel = resolveProviderAndModel(model.provider, result.response?.modelId ?? model.modelId)\n state.stepsUsage.push({\n model: resolvedModel.model,\n inputTokens: result.usage.inputTokens.total ?? 0,\n outputTokens: result.usage.outputTokens.total ?? 0,\n ...(stepToolCalls.length > 0 ? { toolCalls: stepToolCalls } : {}),\n })\n\n flushState(log, state)\n return result\n } catch (error) {\n recordError(log, state, model, error)\n throw error\n }\n },\n\n wrapStream: async ({ doStream, model }) => {\n const streamStart = Date.now()\n let firstChunkTime: number | undefined\n\n let streamUsage: UsageAccumulator | undefined\n let streamFinishReason: string | undefined\n let streamModelId: string | undefined\n let streamResponseId: string | undefined\n const streamToolCalls: string[] = []\n const streamToolInputBuffers = new Map<string, { name: string, chunks: string[] }>()\n let streamError: string | undefined\n\n let doStreamResult: Awaited<ReturnType<typeof doStream>>\n try {\n doStreamResult = await doStream()\n } catch (error) {\n recordError(log, state, model, error)\n throw error\n }\n\n const { stream, ...rest } = doStreamResult\n\n const transformStream = new TransformStream<\n LanguageModelV3StreamPart,\n LanguageModelV3StreamPart\n >({\n transform(chunk, controller) {\n if (!firstChunkTime && chunk.type === 'text-delta') {\n firstChunkTime = Date.now()\n }\n\n if (chunk.type === 'tool-input-start') {\n streamToolCalls.push(chunk.toolName)\n if (state.toolInputs) {\n streamToolInputBuffers.set(chunk.id, { name: chunk.toolName, chunks: [] })\n }\n }\n\n if (chunk.type === 'tool-input-delta' && state.toolInputs) {\n const buffer = streamToolInputBuffers.get(chunk.id)\n if (buffer) {\n buffer.chunks.push(chunk.delta)\n }\n }\n\n if (chunk.type === 'tool-input-end' && state.toolInputs) {\n const buffer = streamToolInputBuffers.get(chunk.id)\n if (buffer) {\n const raw = safeParseJSON(buffer.chunks.join(''))\n state.allToolCallInputs.push({\n name: buffer.name,\n input: processToolInput(raw, buffer.name, state.toolInputsOptions),\n })\n streamToolInputBuffers.delete(chunk.id)\n }\n }\n\n if (chunk.type === 'finish') {\n streamUsage = {\n inputTokens: chunk.usage.inputTokens.total ?? 0,\n outputTokens: chunk.usage.outputTokens.total ?? 0,\n cacheReadTokens: chunk.usage.inputTokens.cacheRead ?? 0,\n cacheWriteTokens: chunk.usage.inputTokens.cacheWrite ?? 0,\n reasoningTokens: chunk.usage.outputTokens.reasoning ?? 0,\n }\n streamFinishReason = chunk.finishReason.unified\n }\n\n if (chunk.type === 'response-metadata') {\n if (chunk.modelId) streamModelId = chunk.modelId\n if (chunk.id) streamResponseId = chunk.id\n }\n\n if (chunk.type === 'error') {\n streamError = chunk.error instanceof Error ? chunk.error.message : String(chunk.error)\n }\n\n controller.enqueue(chunk)\n },\n\n flush() {\n state.calls++\n state.steps++\n\n if (streamUsage) {\n state.usage.inputTokens += streamUsage.inputTokens\n state.usage.outputTokens += streamUsage.outputTokens\n state.usage.cacheReadTokens += streamUsage.cacheReadTokens\n state.usage.cacheWriteTokens += streamUsage.cacheWriteTokens\n state.usage.reasoningTokens += streamUsage.reasoningTokens\n }\n\n recordModel(state, model.provider, model.modelId, streamModelId)\n state.lastFinishReason = streamFinishReason\n\n state.allToolCalls.push(...streamToolCalls)\n\n if (streamResponseId) {\n state.lastResponseId = streamResponseId\n }\n\n if (firstChunkTime) {\n state.lastMsToFirstChunk = firstChunkTime - streamStart\n }\n state.lastMsToFinish = Date.now() - streamStart\n\n if (streamError) state.lastError = streamError\n\n const resolvedModel = resolveProviderAndModel(model.provider, streamModelId ?? model.modelId)\n state.stepsUsage.push({\n model: resolvedModel.model,\n inputTokens: streamUsage?.inputTokens ?? 0,\n outputTokens: streamUsage?.outputTokens ?? 0,\n ...(streamToolCalls.length > 0 ? { toolCalls: [...streamToolCalls] } : {}),\n })\n\n flushState(log, state)\n },\n })\n\n return {\n stream: stream.pipeThrough(transformStream),\n ...rest,\n }\n },\n }\n}\n\nfunction formatTelemetryError(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction recordToolExecution(\n state: AccumulatorState,\n input: { name: string, durationMs: number, success: boolean, error?: string },\n): void {\n const execution: AIToolExecution = {\n name: input.name,\n durationMs: input.durationMs,\n success: input.success,\n }\n if (input.error) execution.error = input.error\n state.toolExecutions.push(execution)\n}\n\nfunction adaptV6ToolEvent(event: V6ToolCallFinishEvent) {\n return {\n name: event.toolCall.toolName,\n durationMs: event.durationMs,\n success: event.success,\n error: !event.success && event.error !== undefined\n ? formatTelemetryError(event.error)\n : undefined,\n }\n}\n\nfunction adaptV7ToolEvent(event: V7ToolExecutionEndEvent) {\n const success = event.toolOutput.type === 'tool-result'\n return {\n name: event.toolCall.toolName,\n durationMs: event.toolExecutionMs,\n success,\n error: !success && event.toolOutput.type === 'tool-error'\n ? formatTelemetryError(event.toolOutput.error)\n : undefined,\n }\n}\n\nfunction recordGenerationEnd(state: AccumulatorState, log: RequestLogger): void {\n if (state.generationStartTime !== undefined) {\n state.totalDurationMs = Date.now() - state.generationStartTime\n }\n flushState(log, state)\n}\n\nfunction recordEmbedEnd(state: AccumulatorState, log: RequestLogger, event: V7EmbedEndEvent): void {\n state.calls++\n state.usage.inputTokens += event.usage.tokens\n const dimensions = event.embeddings[0]?.length\n state.embedding = {\n tokens: (state.embedding?.tokens ?? 0) + event.usage.tokens,\n model: event.modelId,\n ...(dimensions !== undefined\n ? { dimensions }\n : state.embedding?.dimensions\n ? { dimensions: state.embedding.dimensions }\n : {}),\n count: (state.embedding?.count ?? 0) + event.values.length,\n }\n flushState(log, state)\n}\n\nfunction finalizeIntegration(integration: EvlogTelemetryIntegration): EvlogTelemetry {\n if (typeof bindTelemetryIntegration === 'function') {\n return bindTelemetryIntegration(integration)\n }\n return integration\n}\n\n/**\n * Create an AI SDK telemetry integration that captures tool execution\n * timing, errors, and total generation wall time into the wide event.\n *\n * Compatible with AI SDK v6 (`TelemetryIntegration`) and v7 (`Telemetry`).\n * On v7, also captures embeddings via `onEmbedEnd`, aborts via `onAbort`,\n * and unrecoverable errors via `onError`.\n *\n * Complements the middleware-based `createAILogger`: the middleware captures\n * token usage and streaming metrics at the model level, while the integration\n * captures application-level lifecycle events (tool execution, total duration).\n *\n * When passed an `AILogger`, shares its accumulator so both paths write to\n * the same `ai.*` field. Can also be used standalone with a `RequestLogger`.\n *\n * @example Combined with middleware (recommended)\n * ```ts\n * import { createAILogger, createEvlogIntegration } from 'evlog/ai'\n *\n * const log = useLogger(event)\n * const ai = createAILogger(log)\n *\n * const result = await generateText({\n * model: ai.wrap('anthropic/claude-sonnet-4.6'),\n * tools: { getWeather },\n * telemetry: {\n * integrations: [createEvlogIntegration(ai)],\n * },\n * })\n * ```\n *\n * @example Standalone (no middleware wrapping)\n * ```ts\n * import { createEvlogIntegration } from 'evlog/ai'\n *\n * const integration = createEvlogIntegration(log)\n *\n * const result = await generateText({\n * model: openai('gpt-4o'),\n * telemetry: {\n * integrations: [integration],\n * },\n * })\n * ```\n */\nexport function createEvlogIntegration(\n logOrAi: RequestLogger | AILogger,\n options?: AILoggerOptions,\n): EvlogTelemetry {\n let log: RequestLogger\n let state: AccumulatorState\n\n if ('_state' in logOrAi && logOrAi._state) {\n state = logOrAi._state\n log = state._log!\n } else {\n log = logOrAi as RequestLogger\n state = createAccumulatorState(options)\n state._log = log\n }\n\n class EvlogIntegration implements EvlogTelemetryIntegration {\n\n onStart(_event: unknown) {\n state.generationStartTime = Date.now()\n }\n\n onToolCallFinish(event: unknown) {\n recordToolExecution(state, adaptV6ToolEvent(event as V6ToolCallFinishEvent))\n }\n\n onToolExecutionEnd(event: unknown) {\n recordToolExecution(state, adaptV7ToolEvent(event as V7ToolExecutionEndEvent))\n }\n\n onFinish(_event: unknown) {\n recordGenerationEnd(state, log)\n }\n\n onEnd(_event: unknown) {\n recordGenerationEnd(state, log)\n }\n\n onEmbedEnd(event: unknown) {\n recordEmbedEnd(state, log, event as V7EmbedEndEvent)\n }\n\n onAbort(event: unknown) {\n state.lastFinishReason = 'abort'\n const { reason } = event as { reason?: unknown }\n if (reason !== undefined) {\n state.lastError = formatTelemetryError(reason)\n }\n recordGenerationEnd(state, log)\n }\n\n onError(error: unknown) {\n state.lastError = formatTelemetryError(error)\n flushState(log, state)\n }\n\n }\n\n return finalizeIntegration(new EvlogIntegration())\n}\n"],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import "node:async_hooks";
2
+ //#region src/shared/asyncStorageScope.ts
3
+ /**
4
+ * Whether this runtime provides a working native `AsyncLocalStorage.enterWith()`.
5
+ *
6
+ * Cloudflare Workers expose `enterWith` on the prototype but throw when it is
7
+ * called, so a `typeof` check alone is not enough — we probe with a call.
8
+ */
9
+ function supportsAsyncLocalStorageEnterWith(storage) {
10
+ if (typeof storage.enterWith !== "function") return false;
11
+ try {
12
+ storage.enterWith(void 0);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+ /**
19
+ * Bind `value` to `storage` for the current async execution context.
20
+ * Uses native `enterWith()` when available; otherwise relies on
21
+ * {@link patchAsyncLocalStorageEnterWith}.
22
+ */
23
+ function bindAsyncLocalStorage(storage, value) {
24
+ storage.enterWith(value);
25
+ }
26
+ /** Clear a value previously bound with {@link bindAsyncLocalStorage}. */
27
+ function clearAsyncLocalStorage(storage) {
28
+ storage.enterWith(void 0);
29
+ }
30
+ /**
31
+ * Polyfill `enterWith()` on a single `AsyncLocalStorage` instance.
32
+ *
33
+ * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`).
34
+ * Unlike Express or Fastify, there is no single `next()` boundary to wrap in
35
+ * `storage.run()`, so the integration binds the logger with `enterWith()`.
36
+ *
37
+ * The polyfill stores the value on the ALS instance when no native `run()` frame
38
+ * is active. That matches single-request `wrangler dev` flows and async work
39
+ * spawned from a handler, but it does **not** replicate native per-async-context
40
+ * isolation when multiple requests interleave in the same isolate. Prefer `{ log }`
41
+ * from derive for concurrent Workers handlers.
42
+ */
43
+ function patchAsyncLocalStorageEnterWith(storage) {
44
+ if (supportsAsyncLocalStorageEnterWith(storage)) return;
45
+ let fallbackStore;
46
+ let runDepth = 0;
47
+ const originalGetStore = storage.getStore.bind(storage);
48
+ const originalRun = storage.run.bind(storage);
49
+ Object.defineProperty(storage, "enterWith", {
50
+ configurable: true,
51
+ writable: true,
52
+ value(store) {
53
+ fallbackStore = store;
54
+ }
55
+ });
56
+ Object.defineProperty(storage, "run", {
57
+ configurable: true,
58
+ writable: true,
59
+ value(store, callback, ...args) {
60
+ runDepth++;
61
+ try {
62
+ return originalRun(store, callback, ...args);
63
+ } finally {
64
+ runDepth--;
65
+ }
66
+ }
67
+ });
68
+ Object.defineProperty(storage, "getStore", {
69
+ configurable: true,
70
+ writable: true,
71
+ value() {
72
+ if (runDepth > 0) return originalGetStore();
73
+ const active = originalGetStore();
74
+ return active !== void 0 ? active : fallbackStore;
75
+ }
76
+ });
77
+ }
78
+ //#endregion
79
+ export { clearAsyncLocalStorage as n, patchAsyncLocalStorageEnterWith as r, bindAsyncLocalStorage as t };
80
+
81
+ //# sourceMappingURL=asyncStorageScope-DnF7Xk8l.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asyncStorageScope-DnF7Xk8l.mjs","names":[],"sources":["../src/shared/asyncStorageScope.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n\n/**\n * Whether this runtime provides a working native `AsyncLocalStorage.enterWith()`.\n *\n * Cloudflare Workers expose `enterWith` on the prototype but throw when it is\n * called, so a `typeof` check alone is not enough — we probe with a call.\n */\nexport function supportsAsyncLocalStorageEnterWith(\n storage: { enterWith?: unknown },\n): boolean {\n if (typeof storage.enterWith !== 'function') return false\n try {\n storage.enterWith(undefined)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Bind `value` to `storage` for the current async execution context.\n * Uses native `enterWith()` when available; otherwise relies on\n * {@link patchAsyncLocalStorageEnterWith}.\n */\nexport function bindAsyncLocalStorage<T>(\n storage: AsyncLocalStorage<T>,\n value: T,\n): void {\n storage.enterWith(value)\n}\n\n/** Clear a value previously bound with {@link bindAsyncLocalStorage}. */\nexport function clearAsyncLocalStorage<T>(storage: AsyncLocalStorage<T>): void {\n storage.enterWith(undefined as unknown as T)\n}\n\n/**\n * Polyfill `enterWith()` on a single `AsyncLocalStorage` instance.\n *\n * Elysia's lifecycle is split across hooks (`onRequest` → handler → `onAfterResponse`).\n * Unlike Express or Fastify, there is no single `next()` boundary to wrap in\n * `storage.run()`, so the integration binds the logger with `enterWith()`.\n *\n * The polyfill stores the value on the ALS instance when no native `run()` frame\n * is active. That matches single-request `wrangler dev` flows and async work\n * spawned from a handler, but it does **not** replicate native per-async-context\n * isolation when multiple requests interleave in the same isolate. Prefer `{ log }`\n * from derive for concurrent Workers handlers.\n */\nexport function patchAsyncLocalStorageEnterWith<T>(\n storage: AsyncLocalStorage<T>,\n): void {\n if (supportsAsyncLocalStorageEnterWith(storage)) return\n\n let fallbackStore: T | undefined\n let runDepth = 0\n const originalGetStore = storage.getStore.bind(storage)\n const originalRun = storage.run.bind(storage)\n\n Object.defineProperty(storage, 'enterWith', {\n configurable: true,\n writable: true,\n value(store: T): void {\n fallbackStore = store\n },\n })\n\n Object.defineProperty(storage, 'run', {\n configurable: true,\n writable: true,\n value<TReturn>(\n store: T,\n callback: (...args: unknown[]) => TReturn,\n ...args: unknown[]\n ): TReturn {\n runDepth++\n try {\n return originalRun(store, callback, ...args)\n } finally {\n runDepth--\n }\n },\n })\n\n Object.defineProperty(storage, 'getStore', {\n configurable: true,\n writable: true,\n value(): T | undefined {\n if (runDepth > 0) return originalGetStore()\n const active = originalGetStore()\n return active !== undefined ? active : fallbackStore\n },\n })\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,mCACd,SACS;AACT,KAAI,OAAO,QAAQ,cAAc,WAAY,QAAO;AACpD,KAAI;AACF,UAAQ,UAAU,KAAA,EAAU;AAC5B,SAAO;SACD;AACN,SAAO;;;;;;;;AASX,SAAgB,sBACd,SACA,OACM;AACN,SAAQ,UAAU,MAAM;;;AAI1B,SAAgB,uBAA0B,SAAqC;AAC7E,SAAQ,UAAU,KAAA,EAA0B;;;;;;;;;;;;;;;AAgB9C,SAAgB,gCACd,SACM;AACN,KAAI,mCAAmC,QAAQ,CAAE;CAEjD,IAAI;CACJ,IAAI,WAAW;CACf,MAAM,mBAAmB,QAAQ,SAAS,KAAK,QAAQ;CACvD,MAAM,cAAc,QAAQ,IAAI,KAAK,QAAQ;AAE7C,QAAO,eAAe,SAAS,aAAa;EAC1C,cAAc;EACd,UAAU;EACV,MAAM,OAAgB;AACpB,mBAAgB;;EAEnB,CAAC;AAEF,QAAO,eAAe,SAAS,OAAO;EACpC,cAAc;EACd,UAAU;EACV,MACE,OACA,UACA,GAAG,MACM;AACT;AACA,OAAI;AACF,WAAO,YAAY,OAAO,UAAU,GAAG,KAAK;aACpC;AACR;;;EAGL,CAAC;AAEF,QAAO,eAAe,SAAS,YAAY;EACzC,cAAc;EACd,UAAU;EACV,QAAuB;AACrB,OAAI,WAAW,EAAG,QAAO,kBAAkB;GAC3C,MAAM,SAAS,kBAAkB;AACjC,UAAO,WAAW,KAAA,IAAY,SAAS;;EAE1C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { colors, cssColors, detectEnvironment, escapeFormatString, formatDuration, getConsoleMethod, getCssLevelColor, getLevelColor, globToRegExp, isBrowser, isDev, isLevelEnabled, matchesPattern } from "./utils.mjs";
2
- import { n as buildErrorEntries, o as registerPrettyErrorSnippetReader, r as compactStackForStorage } from "./pretty-error-THg0U0w9.mjs";
2
+ import { n as buildErrorEntries, r as compactStackForStorage } from "./pretty-error-THg0U0w9.mjs";
3
3
  import { t as resolveDevTerminal } from "./dev-terminal-D4UaEm17.mjs";
4
4
  import { EvlogError } from "./error.mjs";
5
5
  import { r as getHeader$1 } from "./headers-VtmnWcfn.mjs";
@@ -525,6 +525,7 @@ let globalSilent = false;
525
525
  /** Minimum level for the global `log` API only (`ownsEvent === false`). Default: all levels. */
526
526
  let globalMinLevel = "debug";
527
527
  let _locked = false;
528
+ let _loggerInitialized = false;
528
529
  let globalPluginRunner = getEmptyPluginRunner();
529
530
  /**
530
531
  * Initialize the logger with configuration.
@@ -550,13 +551,9 @@ function initLogger(config = {}) {
550
551
  globalMinLevel = config.minLevel ?? "debug";
551
552
  globalPluginRunner = config.plugins?.length ? createPluginRunner(config.plugins) : getEmptyPluginRunner();
552
553
  if (globalPluginRunner.plugins.length > 0) globalPluginRunner.runSetup({ env: { ...globalEnv } });
553
- if (!isBrowser() && typeof process !== "undefined" && process.versions?.node) import("./pretty-error-snippet.node-itfCajBM.mjs").then((n) => n.t).then((mod) => {
554
- registerPrettyErrorSnippetReader(mod.readCodeSnippetFromDisk);
555
- }).catch(() => {
556
- registerPrettyErrorSnippetReader(null);
557
- });
558
554
  const hasAnyDrain = !!globalDrain || globalPluginRunner.hasDrain;
559
555
  if (globalSilent && !hasAnyDrain && !config._suppressDrainWarning) console.warn("[evlog] silent mode is enabled but no drain is configured. Events will be built and sampled but not output anywhere. Set a drain via initLogger({ drain }) or a framework hook (evlog:drain).");
556
+ _loggerInitialized = true;
560
557
  }
561
558
  /**
562
559
  * @internal Get the globally registered plugin runner.
@@ -587,6 +584,12 @@ function isLoggerLocked() {
587
584
  return _locked;
588
585
  }
589
586
  /**
587
+ * @internal Whether {@link initLogger} has completed at least once.
588
+ */
589
+ function isLoggerInitialized() {
590
+ return _loggerInitialized;
591
+ }
592
+ /**
590
593
  * @internal Get the globally configured drain callback.
591
594
  * Used by framework middleware to fall back to the global drain
592
595
  * when no middleware-level drain is provided.
@@ -912,8 +915,7 @@ function prettyPrintWideEvent(event) {
912
915
  styles.push(cssColors.dim, cssColors.reset, lc, cssColors.reset, cssColors.cyan, cssColors.reset);
913
916
  } else {
914
917
  const lc = getLevelColor(levelLabel);
915
- if (isDev()) parts.push(`${lc}${levelLabel.toUpperCase()}${colors.reset} ${colors.cyan}[${service}]${colors.reset}`);
916
- else parts.push(`${colors.dim}${ts}${colors.reset} ${lc}${levelLabel.toUpperCase()}${colors.reset} ${colors.cyan}[${service}]${colors.reset}`);
918
+ parts.push(`${colors.dim}${ts}${colors.reset} ${lc}${levelLabel.toUpperCase()}${colors.reset} ${colors.cyan}[${service}]${colors.reset}`);
917
919
  }
918
920
  if (rest.method && rest.path) {
919
921
  parts.push(browser ? ` ${escapeFormatString(String(rest.method))} ${escapeFormatString(String(rest.path))}` : ` ${rest.method} ${rest.path}`);
@@ -1159,8 +1161,9 @@ function createLogger(initialContext = {}, internalOptions) {
1159
1161
  const durationMs = Date.now() - startTime;
1160
1162
  const level = manualLevel ?? (hasError ? "error" : hasWarn ? "warn" : "info");
1161
1163
  let forceKeep = false;
1164
+ const auditForceKeep = consumeAuditForceKeep(context);
1162
1165
  if (overrides?._forceKeep) forceKeep = true;
1163
- else if (consumeAuditForceKeep(context)) forceKeep = true;
1166
+ else if (auditForceKeep) forceKeep = true;
1164
1167
  else if (globalSampling.keep?.length) forceKeep = shouldKeep({
1165
1168
  status: overrides?.status ?? context.status,
1166
1169
  duration: durationMs,
@@ -1542,75 +1545,6 @@ function auditDiff(before, after, options = {}) {
1542
1545
  return result;
1543
1546
  }
1544
1547
  /**
1545
- * Define a typed audit action with optional fixed target type and catalog metadata.
1546
- *
1547
- * Returns a curried helper that fills in the action name (and target shape
1548
- * if provided) so call sites stay terse and the action set is discoverable
1549
- * in one place. Metadata (`description`, `severity`, `requiresChanges`, …)
1550
- * is exposed on the factory for introspection, docs, and review tooling.
1551
- *
1552
- * @example
1553
- * ```ts
1554
- * const refund = defineAuditAction('invoice.refund', {
1555
- * target: 'invoice',
1556
- * severity: 'high',
1557
- * requiresChanges: true,
1558
- * redactPaths: ['cardNumber'],
1559
- * })
1560
- *
1561
- * log.audit(refund({
1562
- * actor: { type: 'user', id: user.id },
1563
- * target: { id: 'inv_889' }, // type inferred as 'invoice'
1564
- * outcome: 'success',
1565
- * }))
1566
- * ```
1567
- */
1568
- function defineAuditAction(action, options) {
1569
- const targetType = options?.target;
1570
- const factory = ((input) => {
1571
- const merged = {
1572
- ...input,
1573
- action
1574
- };
1575
- if (targetType && input.target && !input.target.type) merged.target = {
1576
- ...input.target,
1577
- type: targetType
1578
- };
1579
- return merged;
1580
- });
1581
- Object.defineProperties(factory, {
1582
- action: {
1583
- value: action,
1584
- enumerable: true
1585
- },
1586
- target: {
1587
- value: options?.target,
1588
- enumerable: true
1589
- },
1590
- description: {
1591
- value: options?.description,
1592
- enumerable: true
1593
- },
1594
- severity: {
1595
- value: options?.severity,
1596
- enumerable: true
1597
- },
1598
- requiresChanges: {
1599
- value: options?.requiresChanges,
1600
- enumerable: true
1601
- },
1602
- requiresReason: {
1603
- value: options?.requiresReason,
1604
- enumerable: true
1605
- },
1606
- redactPaths: {
1607
- value: options?.redactPaths,
1608
- enumerable: true
1609
- }
1610
- });
1611
- return factory;
1612
- }
1613
- /**
1614
1548
  * Test helper that captures every audit event emitted while it is active.
1615
1549
  *
1616
1550
  * Returns `{ events, restore, toIncludeAuditOf, assertAudit }`:
@@ -1688,14 +1622,21 @@ function consumeAuditForceKeep(context) {
1688
1622
  if (context.audit) return true;
1689
1623
  return false;
1690
1624
  }
1625
+ /** @internal True when `audit` has the minimum fields required before emit decoration. */
1626
+ function isCompleteAuditFields(a) {
1627
+ if (!a || typeof a !== "object") return false;
1628
+ const audit = a;
1629
+ return typeof audit.action === "string" && typeof audit.actor?.type === "string" && typeof audit.actor?.id === "string";
1630
+ }
1691
1631
  /**
1692
1632
  * @internal Decorate the audit field on an event right before drain — fills
1693
1633
  * in the deterministic idempotency key. Called by the logger pipeline so
1694
1634
  * it works for both `log.audit()` and direct `log.set({ audit })` paths.
1635
+ * Skips partial/legacy shapes that omit required actor fields.
1695
1636
  */
1696
1637
  function finalizeAudit(event) {
1697
1638
  const a = event.audit;
1698
- if (!a) return;
1639
+ if (!isCompleteAuditFields(a)) return;
1699
1640
  const decorated = decorateAudit(a, String(event.timestamp));
1700
1641
  event.audit = decorated;
1701
1642
  _testCollector?.(decorated, event);
@@ -1862,17 +1803,14 @@ function signed(drain, options) {
1862
1803
  }
1863
1804
  /**
1864
1805
  * @internal Resolve the Web Crypto SubtleCrypto interface. Available natively
1865
- * in browsers, Node 19+, Bun, Deno, and Cloudflare Workers. Falls back to
1866
- * Node's `webcrypto` for Node 18 (where `globalThis.crypto` is gated behind
1867
- * a flag). The dynamic import keeps `node:crypto` out of browser bundles.
1806
+ * in browsers, Node 19+, Bun, Deno, Cloudflare Workers, and Convex. Requires
1807
+ * `globalThis.crypto.subtle` no `node:crypto` fallback so non-Node bundlers
1808
+ * can import the main entrypoint without resolving Node built-ins (#387).
1868
1809
  */
1869
- async function getSubtle() {
1870
- const c = globalThis.crypto;
1871
- if (c?.subtle) return c.subtle;
1872
- return (await import(
1873
- /* @vite-ignore */
1874
- "node:crypto"
1875
- )).webcrypto.subtle;
1810
+ function getSubtle() {
1811
+ const subtle = globalThis.crypto?.subtle;
1812
+ if (subtle) return subtle;
1813
+ throw new Error("[evlog/audit] Web Crypto is not available. signed() requires globalThis.crypto.subtle (Node 19+, modern browsers, Cloudflare Workers, Convex, Bun, Deno).");
1876
1814
  }
1877
1815
  function normalizeAlgo(algorithm) {
1878
1816
  switch (algorithm.toLowerCase()) {
@@ -1893,10 +1831,10 @@ function bufToHex(buf) {
1893
1831
  return out;
1894
1832
  }
1895
1833
  async function digestHex(algorithm, data) {
1896
- return bufToHex(await (await getSubtle()).digest(normalizeAlgo(algorithm), new TextEncoder().encode(data)));
1834
+ return bufToHex(await getSubtle().digest(normalizeAlgo(algorithm), new TextEncoder().encode(data)));
1897
1835
  }
1898
1836
  async function hmacHex(algorithm, secret, data) {
1899
- const subtle = await getSubtle();
1837
+ const subtle = getSubtle();
1900
1838
  const hash = normalizeAlgo(algorithm);
1901
1839
  const key = await subtle.importKey("raw", new TextEncoder().encode(secret), {
1902
1840
  name: "HMAC",
@@ -1946,6 +1884,6 @@ const auditRedactPreset = { paths: [
1946
1884
  "set-cookie"
1947
1885
  ] };
1948
1886
  //#endregion
1949
- export { enricherPlugin as A, lockLogger as C, createPluginRunner as D, shouldKeep as E, resolveRedactConfig as F, isGloballyRedacted as M, normalizeRedactConfig as N, definePlugin as O, redactEvent as P, isLoggerLocked as S, mergeInto as T, getEnvironment as _, auditEnricher as a, initLogger as b, buildAuditFields as c, signed as d, withAudit as f, createRequestLogger as g, createLogger as h, auditDiff as i, getEmptyPluginRunner as j, drainPlugin as k, defineAuditAction as l, _log as m, AuditDeniedError as n, auditOnly as o, withAuditMethods as p, audit as r, auditRedactPreset as s, AUDIT_SCHEMA_VERSION as t, mockAudit as u, getGlobalDrain as v, markWideEventDrainStarted as w, isEnabled as x, getGlobalPluginRunner as y };
1887
+ export { definePlugin as A, isLoggerInitialized as C, mergeInto as D, markWideEventDrainStarted as E, normalizeRedactConfig as F, redactEvent as I, resolveRedactConfig as L, enricherPlugin as M, getEmptyPluginRunner as N, shouldKeep as O, isGloballyRedacted as P, isEnabled as S, lockLogger as T, createRequestLogger as _, auditEnricher as a, getGlobalPluginRunner as b, buildAuditFields as c, mockAudit as d, signed as f, createLogger as g, _log as h, auditDiff as i, drainPlugin as j, createPluginRunner as k, consumeAuditForceKeep as l, withAuditMethods as m, AuditDeniedError as n, auditOnly as o, withAudit as p, audit as r, auditRedactPreset as s, AUDIT_SCHEMA_VERSION as t, finalizeAudit as u, getEnvironment as v, isLoggerLocked as w, initLogger as x, getGlobalDrain as y };
1950
1888
 
1951
- //# sourceMappingURL=audit-BQt8yAHo.mjs.map
1889
+ //# sourceMappingURL=audit-BXCYMjcC.mjs.map