evlog 2.11.1 → 2.13.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 (173) hide show
  1. package/README.md +42 -2
  2. package/dist/{_drain-YH8ERc5l.mjs → _drain-CmCtsuF6.mjs} +1 -1
  3. package/dist/{_drain-YH8ERc5l.mjs.map → _drain-CmCtsuF6.mjs.map} +1 -1
  4. package/dist/{_http-C_2wbJw3.mjs → _http-CHSsrWDJ.mjs} +2 -2
  5. package/dist/{_http-C_2wbJw3.mjs.map → _http-CHSsrWDJ.mjs.map} +1 -1
  6. package/dist/{_severity-BZhz3f9e.mjs → _severity-CQijvfhU.mjs} +1 -1
  7. package/dist/{_severity-BZhz3f9e.mjs.map → _severity-CQijvfhU.mjs.map} +1 -1
  8. package/dist/adapters/axiom.d.mts +1 -1
  9. package/dist/adapters/axiom.mjs +2 -2
  10. package/dist/adapters/better-stack.d.mts +1 -1
  11. package/dist/adapters/better-stack.mjs +2 -2
  12. package/dist/adapters/datadog.d.mts +1 -1
  13. package/dist/adapters/datadog.mjs +2 -2
  14. package/dist/adapters/fs.d.mts +1 -1
  15. package/dist/adapters/fs.mjs +1 -1
  16. package/dist/adapters/hyperdx.d.mts +1 -1
  17. package/dist/adapters/hyperdx.mjs +2 -2
  18. package/dist/adapters/otlp.d.mts +1 -1
  19. package/dist/adapters/otlp.mjs +3 -3
  20. package/dist/adapters/posthog.d.mts +1 -1
  21. package/dist/adapters/posthog.mjs +2 -2
  22. package/dist/adapters/sentry.d.mts +1 -1
  23. package/dist/adapters/sentry.mjs +3 -3
  24. package/dist/ai/index.d.mts +144 -5
  25. package/dist/ai/index.d.mts.map +1 -1
  26. package/dist/ai/index.mjs +108 -5
  27. package/dist/ai/index.mjs.map +1 -1
  28. package/dist/better-auth/index.d.mts +220 -0
  29. package/dist/better-auth/index.d.mts.map +1 -0
  30. package/dist/better-auth/index.mjs +205 -0
  31. package/dist/better-auth/index.mjs.map +1 -0
  32. package/dist/browser.d.mts +13 -52
  33. package/dist/browser.d.mts.map +1 -1
  34. package/dist/browser.mjs +5 -81
  35. package/dist/browser.mjs.map +1 -1
  36. package/dist/client.d.mts +2 -2
  37. package/dist/client.mjs +2 -2
  38. package/dist/{dist-BFn8qsRC.mjs → dist-Do8P4zWd.mjs} +1 -1
  39. package/dist/{dist-BFn8qsRC.mjs.map → dist-Do8P4zWd.mjs.map} +1 -1
  40. package/dist/elysia/index.d.mts +2 -2
  41. package/dist/elysia/index.d.mts.map +1 -1
  42. package/dist/elysia/index.mjs +16 -4
  43. package/dist/elysia/index.mjs.map +1 -1
  44. package/dist/enrichers.d.mts +1 -1
  45. package/dist/{error-plrBYLQk.d.mts → error-B9CiGK_i.d.mts} +2 -2
  46. package/dist/{error-plrBYLQk.d.mts.map → error-B9CiGK_i.d.mts.map} +1 -1
  47. package/dist/error.d.mts +1 -1
  48. package/dist/{errors-gH4C9KSC.mjs → errors-BJRXUfMg.mjs} +1 -1
  49. package/dist/{errors-gH4C9KSC.mjs.map → errors-BJRXUfMg.mjs.map} +1 -1
  50. package/dist/{errors-bPoj9UZk.d.mts → errors-Dr0r4OpR.d.mts} +2 -2
  51. package/dist/{errors-bPoj9UZk.d.mts.map → errors-Dr0r4OpR.d.mts.map} +1 -1
  52. package/dist/express/index.d.mts +2 -2
  53. package/dist/express/index.d.mts.map +1 -1
  54. package/dist/express/index.mjs +8 -4
  55. package/dist/express/index.mjs.map +1 -1
  56. package/dist/fastify/index.d.mts +2 -2
  57. package/dist/fastify/index.d.mts.map +1 -1
  58. package/dist/fastify/index.mjs +8 -4
  59. package/dist/fastify/index.mjs.map +1 -1
  60. package/dist/fork-Y4z8iHti.mjs +72 -0
  61. package/dist/fork-Y4z8iHti.mjs.map +1 -0
  62. package/dist/headers-D74M0wsg.mjs +30 -0
  63. package/dist/headers-D74M0wsg.mjs.map +1 -0
  64. package/dist/hono/index.d.mts +2 -2
  65. package/dist/hono/index.mjs +2 -1
  66. package/dist/hono/index.mjs.map +1 -1
  67. package/dist/http.d.mts +65 -0
  68. package/dist/http.d.mts.map +1 -0
  69. package/dist/http.mjs +94 -0
  70. package/dist/http.mjs.map +1 -0
  71. package/dist/index.d.mts +7 -6
  72. package/dist/index.mjs +3 -2
  73. package/dist/logger-DnobymUQ.mjs +741 -0
  74. package/dist/logger-DnobymUQ.mjs.map +1 -0
  75. package/dist/{logger-CG1eop2_.d.mts → logger-Dp6nYWjH.d.mts} +6 -2
  76. package/dist/logger-Dp6nYWjH.d.mts.map +1 -0
  77. package/dist/logger.d.mts +1 -1
  78. package/dist/logger.mjs +1 -361
  79. package/dist/{headers-BSi3UHKL.mjs → middleware-BtBuosFV.mjs} +13 -32
  80. package/dist/middleware-BtBuosFV.mjs.map +1 -0
  81. package/dist/{middleware-DojmTj9Y.d.mts → middleware-FgC1OdOD.d.mts} +21 -3
  82. package/dist/middleware-FgC1OdOD.d.mts.map +1 -0
  83. package/dist/nestjs/index.d.mts +2 -2
  84. package/dist/nestjs/index.d.mts.map +1 -1
  85. package/dist/nestjs/index.mjs +8 -4
  86. package/dist/nestjs/index.mjs.map +1 -1
  87. package/dist/next/client.d.mts +9 -3
  88. package/dist/next/client.d.mts.map +1 -1
  89. package/dist/next/client.mjs +5 -3
  90. package/dist/next/client.mjs.map +1 -1
  91. package/dist/next/index.d.mts +9 -4
  92. package/dist/next/index.d.mts.map +1 -1
  93. package/dist/next/index.mjs +17 -2
  94. package/dist/next/index.mjs.map +1 -1
  95. package/dist/next/instrumentation.d.mts +3 -1
  96. package/dist/next/instrumentation.d.mts.map +1 -1
  97. package/dist/next/instrumentation.mjs +2 -1
  98. package/dist/next/instrumentation.mjs.map +1 -1
  99. package/dist/nitro/errorHandler.mjs +2 -2
  100. package/dist/nitro/module.d.mts +2 -2
  101. package/dist/nitro/plugin.mjs +7 -4
  102. package/dist/nitro/plugin.mjs.map +1 -1
  103. package/dist/nitro/v3/errorHandler.mjs +3 -3
  104. package/dist/nitro/v3/index.d.mts +2 -2
  105. package/dist/nitro/v3/module.d.mts +1 -1
  106. package/dist/nitro/v3/plugin.mjs +8 -5
  107. package/dist/nitro/v3/plugin.mjs.map +1 -1
  108. package/dist/nitro/v3/useLogger.d.mts +1 -1
  109. package/dist/{nitro-CfGx0wDJ.d.mts → nitro-CDHLfRdw.d.mts} +13 -2
  110. package/dist/nitro-CDHLfRdw.d.mts.map +1 -0
  111. package/dist/{nitro-Dpq5ZmcM.mjs → nitro-OmT_M4Pb.mjs} +2 -2
  112. package/dist/nitro-OmT_M4Pb.mjs.map +1 -0
  113. package/dist/{nitroConfigBridge-fidbf-Y_.mjs → nitroConfigBridge-C37lXaNm.mjs} +1 -1
  114. package/dist/{nitroConfigBridge-fidbf-Y_.mjs.map → nitroConfigBridge-C37lXaNm.mjs.map} +1 -1
  115. package/dist/nuxt/module.d.mts +26 -1
  116. package/dist/nuxt/module.d.mts.map +1 -1
  117. package/dist/nuxt/module.mjs +7 -2
  118. package/dist/nuxt/module.mjs.map +1 -1
  119. package/dist/{parseError-B_qXj8x4.d.mts → parseError-DM-lyezZ.d.mts} +2 -2
  120. package/dist/parseError-DM-lyezZ.d.mts.map +1 -0
  121. package/dist/react-router/index.d.mts +2 -2
  122. package/dist/react-router/index.d.mts.map +1 -1
  123. package/dist/react-router/index.mjs +8 -4
  124. package/dist/react-router/index.mjs.map +1 -1
  125. package/dist/{routes-CE3_c-iZ.mjs → routes-CGPmbzCZ.mjs} +1 -1
  126. package/dist/{routes-CE3_c-iZ.mjs.map → routes-CGPmbzCZ.mjs.map} +1 -1
  127. package/dist/runtime/client/log.d.mts +7 -2
  128. package/dist/runtime/client/log.d.mts.map +1 -1
  129. package/dist/runtime/client/log.mjs +24 -6
  130. package/dist/runtime/client/log.mjs.map +1 -1
  131. package/dist/runtime/client/plugin.mjs +1 -0
  132. package/dist/runtime/client/plugin.mjs.map +1 -1
  133. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  134. package/dist/runtime/server/useLogger.d.mts +1 -1
  135. package/dist/runtime/utils/parseError.d.mts +2 -2
  136. package/dist/runtime/utils/parseError.mjs +1 -1
  137. package/dist/{source-location-B1VVgXkh.mjs → source-location-DRvDDqfq.mjs} +1 -1
  138. package/dist/{source-location-B1VVgXkh.mjs.map → source-location-DRvDDqfq.mjs.map} +1 -1
  139. package/dist/{storage-B6NPh8rV.mjs → storage-CFGTn37X.mjs} +1 -1
  140. package/dist/{storage-B6NPh8rV.mjs.map → storage-CFGTn37X.mjs.map} +1 -1
  141. package/dist/sveltekit/index.d.mts +2 -2
  142. package/dist/sveltekit/index.d.mts.map +1 -1
  143. package/dist/sveltekit/index.mjs +10 -6
  144. package/dist/sveltekit/index.mjs.map +1 -1
  145. package/dist/toolkit.d.mts +41 -4
  146. package/dist/toolkit.d.mts.map +1 -1
  147. package/dist/toolkit.mjs +7 -5
  148. package/dist/{types-v_JkG_D7.d.mts → types-DbzDln7O.d.mts} +120 -4
  149. package/dist/types-DbzDln7O.d.mts.map +1 -0
  150. package/dist/types.d.mts +2 -2
  151. package/dist/{useLogger-TjKH37BO.d.mts → useLogger-N5A-d5l9.d.mts} +2 -2
  152. package/dist/{useLogger-TjKH37BO.d.mts.map → useLogger-N5A-d5l9.d.mts.map} +1 -1
  153. package/dist/utils-DnX6VMNi.d.mts +54 -0
  154. package/dist/utils-DnX6VMNi.d.mts.map +1 -0
  155. package/dist/utils.d.mts +2 -50
  156. package/dist/utils.mjs +13 -1
  157. package/dist/utils.mjs.map +1 -1
  158. package/dist/vite/index.d.mts +5 -1
  159. package/dist/vite/index.d.mts.map +1 -1
  160. package/dist/vite/index.mjs +3 -1
  161. package/dist/vite/index.mjs.map +1 -1
  162. package/dist/workers.d.mts +1 -1
  163. package/dist/workers.mjs +1 -1
  164. package/package.json +24 -3
  165. package/dist/headers-BSi3UHKL.mjs.map +0 -1
  166. package/dist/logger-CG1eop2_.d.mts.map +0 -1
  167. package/dist/logger.mjs.map +0 -1
  168. package/dist/middleware-DojmTj9Y.d.mts.map +0 -1
  169. package/dist/nitro-CfGx0wDJ.d.mts.map +0 -1
  170. package/dist/nitro-Dpq5ZmcM.mjs.map +0 -1
  171. package/dist/parseError-B_qXj8x4.d.mts.map +0 -1
  172. package/dist/types-v_JkG_D7.d.mts.map +0 -1
  173. package/dist/utils.d.mts.map +0 -1
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/HugoRCD/evlog/main/assets/evlog-banner.gif" width="100%" alt="evlog — Digging through logs is not observability. It's hope" />
3
+ </p>
4
+
1
5
  # evlog
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/evlog?color=black)](https://npmjs.com/package/evlog)
@@ -518,7 +522,7 @@ See the full [nestjs example](https://github.com/HugoRCD/evlog/tree/main/example
518
522
  Use the `log` API on the client side for structured browser logging:
519
523
 
520
524
  ```typescript
521
- import { log } from 'evlog/browser'
525
+ import { log } from 'evlog/client'
522
526
 
523
527
  log.info('checkout', 'User initiated checkout')
524
528
  log.error({ action: 'payment', error: 'validation_failed' })
@@ -556,6 +560,8 @@ When enabled:
556
560
  3. `evlog:drain` hook is called with `source: 'client'`
557
561
  4. External services receive the log
558
562
 
563
+ For a **framework-agnostic** batched HTTP drain (e.g. vanilla JS or custom endpoints), use `createHttpLogDrain` from [`evlog/http`](https://www.evlog.dev/adapters/http). The legacy import path `evlog/browser` is deprecated and will be removed in the next major release.
564
+
559
565
  ## Structured Errors
560
566
 
561
567
  Errors should tell you **what** happened, **why**, and **how to fix it**.
@@ -1060,6 +1066,40 @@ log.emit() // Emit final event
1060
1066
  log.getContext() // Get current context
1061
1067
  ```
1062
1068
 
1069
+ ### Wide event lifecycle and `log.fork()`
1070
+
1071
+ The framework emits **one wide event per HTTP request** when the response finishes (or on error). After `emit()` runs — including when head sampling drops the event (`emit()` returns `null`) — that logger instance is **sealed**: further `set`, `error`, `info`, and `warn` calls are ignored and emit a **`[evlog]` console warning** listing dropped keys. A second `emit()` is ignored with a warning. This avoids silent data loss when async work (unawaited promises, `setTimeout`, etc.) still resolves `useLogger()` to the same logger via `AsyncLocalStorage` after the response has already been logged.
1072
+
1073
+ **`log.fork(label, fn)`** runs work under a **child** request logger: inside `fn`, `useLogger()` returns the child. When `fn` settles, the child emits its **own** wide event with `operation` set to `label` and `_parentRequestId` set to the parent’s `requestId` (query and dashboard correlation). The parent event may be emitted **before** the child event; they are two separate events ordered by time.
1074
+
1075
+ `fork` is attached by integrations that use `AsyncLocalStorage` for `useLogger()`. Standalone `createLogger()` instances do not have `fork`.
1076
+
1077
+ | Integration | `log.fork()` |
1078
+ |-------------|----------------|
1079
+ | Express, Fastify, NestJS, SvelteKit, React Router, Elysia | Yes |
1080
+ | Next.js `withEvlog` | Yes |
1081
+ | Hono (`c.get('log')` only) | Not yet |
1082
+ | Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/logging/wide-events) |
1083
+
1084
+ ```typescript
1085
+ import { evlog, useLogger } from 'evlog/express'
1086
+
1087
+ app.post('/checkout', (req, res) => {
1088
+ const log = req.log
1089
+ log.set({ order_dispatched: true })
1090
+
1091
+ log.fork!('process_order', async () => {
1092
+ const childLog = useLogger()
1093
+ childLog.set({ inventory_checked: true })
1094
+ // child emits automatically when this async function completes
1095
+ })
1096
+
1097
+ res.json({ ok: true })
1098
+ })
1099
+ ```
1100
+
1101
+ Use optional chaining if `fork` might be absent: `log.fork?.('task', async () => { ... })`.
1102
+
1063
1103
  ### `initWorkersLogger(options?)`
1064
1104
 
1065
1105
  Initialize evlog for Cloudflare Workers (object logs + correct severity).
@@ -1154,7 +1194,7 @@ try {
1154
1194
  | **Hono** | `app.use(evlog())` with `import { evlog } from 'evlog/hono'` ([example](./examples/hono)) |
1155
1195
  | **Fastify** | `app.register(evlog)` with `import { evlog } from 'evlog/fastify'` ([example](./examples/fastify)) |
1156
1196
  | **Elysia** | `.use(evlog())` with `import { evlog } from 'evlog/elysia'` ([example](./examples/elysia)) |
1157
- | **Cloudflare Workers** | Manual setup with `import { initLogger, createRequestLogger } from 'evlog'` ([example](./examples/workers)) |
1197
+ | **Cloudflare Workers** | Manual setup with `import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'` ([example](./examples/workers)) |
1158
1198
  | **Custom** | Build your own with `import { createMiddlewareLogger } from 'evlog/toolkit'` ([guide](https://evlog.dev/frameworks/custom-integration)) |
1159
1199
  | **Analog** | Nitro v2 module setup |
1160
1200
  | **Vinxi** | Nitro v2 module setup |
@@ -20,4 +20,4 @@ function defineDrain(options) {
20
20
  //#endregion
21
21
  export { defineDrain as t };
22
22
 
23
- //# sourceMappingURL=_drain-YH8ERc5l.mjs.map
23
+ //# sourceMappingURL=_drain-CmCtsuF6.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_drain-YH8ERc5l.mjs","names":[],"sources":["../src/adapters/_drain.ts"],"sourcesContent":["import type { DrainContext, WideEvent } from '../types'\n\nexport interface DrainOptions<TConfig> {\n name: string\n resolve: () => TConfig | null | Promise<TConfig | null>\n send: (events: WideEvent[], config: TConfig) => Promise<void>\n}\n\n/**\n * Build a drain callback for `evlog:drain` (or `initLogger({ drain })`).\n * The returned function is async so `resolve` can load Nitro runtime config; hosts typically attach\n * the resulting promise to `waitUntil` so the HTTP response is not blocked (see Nitro plugin).\n */\nexport function defineDrain<TConfig>(options: DrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return async (ctx: DrainContext | DrainContext[]) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const config = await options.resolve()\n if (!config) return\n\n try {\n await options.send(contexts.map(c => c.event), config)\n } catch (error) {\n console.error(`[evlog/${options.name}] Failed to send events:`, error)\n }\n }\n}\n"],"mappings":";;;;;;AAaA,SAAgB,YAAqB,SAAuF;AAC1H,QAAO,OAAO,QAAuC;EACnD,MAAM,WAAW,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;AACjD,MAAI,SAAS,WAAW,EAAG;EAE3B,MAAM,SAAS,MAAM,QAAQ,SAAS;AACtC,MAAI,CAAC,OAAQ;AAEb,MAAI;AACF,SAAM,QAAQ,KAAK,SAAS,KAAI,MAAK,EAAE,MAAM,EAAE,OAAO;WAC/C,OAAO;AACd,WAAQ,MAAM,UAAU,QAAQ,KAAK,2BAA2B,MAAM"}
1
+ {"version":3,"file":"_drain-CmCtsuF6.mjs","names":[],"sources":["../src/adapters/_drain.ts"],"sourcesContent":["import type { DrainContext, WideEvent } from '../types'\n\nexport interface DrainOptions<TConfig> {\n name: string\n resolve: () => TConfig | null | Promise<TConfig | null>\n send: (events: WideEvent[], config: TConfig) => Promise<void>\n}\n\n/**\n * Build a drain callback for `evlog:drain` (or `initLogger({ drain })`).\n * The returned function is async so `resolve` can load Nitro runtime config; hosts typically attach\n * the resulting promise to `waitUntil` so the HTTP response is not blocked (see Nitro plugin).\n */\nexport function defineDrain<TConfig>(options: DrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return async (ctx: DrainContext | DrainContext[]) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const config = await options.resolve()\n if (!config) return\n\n try {\n await options.send(contexts.map(c => c.event), config)\n } catch (error) {\n console.error(`[evlog/${options.name}] Failed to send events:`, error)\n }\n }\n}\n"],"mappings":";;;;;;AAaA,SAAgB,YAAqB,SAAuF;AAC1H,QAAO,OAAO,QAAuC;EACnD,MAAM,WAAW,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;AACjD,MAAI,SAAS,WAAW,EAAG;EAE3B,MAAM,SAAS,MAAM,QAAQ,SAAS;AACtC,MAAI,CAAC,OAAQ;AAEb,MAAI;AACF,SAAM,QAAQ,KAAK,SAAS,KAAI,MAAK,EAAE,MAAM,EAAE,OAAO;WAC/C,OAAO;AACd,WAAQ,MAAM,UAAU,QAAQ,KAAK,2BAA2B,MAAM"}
@@ -1,4 +1,4 @@
1
- import { t as getNitroRuntimeConfigRecord } from "./nitroConfigBridge-fidbf-Y_.mjs";
1
+ import { t as getNitroRuntimeConfigRecord } from "./nitroConfigBridge-C37lXaNm.mjs";
2
2
  //#region src/adapters/_config.ts
3
3
  /**
4
4
  * Adapter runtime-config reads go through `getNitroRuntimeConfigRecord` in
@@ -68,4 +68,4 @@ async function httpPost({ url, headers, body, timeout, label, retries = 2 }) {
68
68
  //#endregion
69
69
  export { resolveAdapterConfig as n, httpPost as t };
70
70
 
71
- //# sourceMappingURL=_http-C_2wbJw3.mjs.map
71
+ //# sourceMappingURL=_http-CHSsrWDJ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_http-C_2wbJw3.mjs","names":[],"sources":["../src/adapters/_config.ts","../src/adapters/_http.ts"],"sourcesContent":["import { getNitroRuntimeConfigRecord } from '../shared/nitroConfigBridge'\n\n/**\n * Adapter runtime-config reads go through `getNitroRuntimeConfigRecord` in\n * `shared/nitroConfigBridge.ts` (documented there — Workers-safe dynamic imports).\n *\n * Drain handlers remain non-blocking when the host provides `waitUntil`.\n */\n\nexport function getRuntimeConfig(): Promise<Record<string, any> | undefined> {\n return getNitroRuntimeConfigRecord()\n}\n\nexport interface ConfigField<T> {\n key: keyof T & string\n env?: string[]\n}\n\nexport async function resolveAdapterConfig<T>(\n namespace: string,\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): Promise<Partial<T>> {\n const runtimeConfig = await getRuntimeConfig()\n const evlogNs = runtimeConfig?.evlog?.[namespace]\n const rootNs = runtimeConfig?.[namespace]\n\n const config: Record<string, unknown> = {}\n\n for (const { key, env } of fields) {\n config[key] =\n overrides?.[key]\n ?? evlogNs?.[key]\n ?? rootNs?.[key]\n ?? resolveEnv(env)\n }\n\n return config as Partial<T>\n}\n\nfunction resolveEnv(envKeys?: string[]): string | undefined {\n if (!envKeys) return undefined\n for (const key of envKeys) {\n const val = process.env[key]\n if (val) return val\n }\n return undefined\n}\n","export interface HttpPostOptions {\n url: string\n headers: Record<string, string>\n body: string\n timeout: number\n label: string\n retries?: number\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true\n if (error instanceof TypeError) return true\n if (error instanceof Error) {\n const match = error.message.match(/API error: (\\d+)/)\n if (match) return Number.parseInt(match[1]) >= 500\n }\n return false\n}\n\nexport async function httpPost({ url, headers, body, timeout, label, retries = 2 }: HttpPostOptions): Promise<void> {\n const normalizedRetries = Number.isFinite(retries) && retries >= 0 ? Math.floor(retries) : 2\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= normalizedRetries; attempt++) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n\n clearTimeout(timeoutId)\n return\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n lastError = new Error(`${label} request timed out after ${timeout}ms`)\n } else {\n lastError = error as Error\n }\n\n if (!isRetryable(error) || attempt === normalizedRetries) {\n throw lastError\n }\n\n await new Promise<void>(r => setTimeout(r, 200 * 2 ** attempt))\n }\n }\n\n throw lastError!\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,mBAA6D;AAC3E,QAAO,6BAA6B;;AAQtC,eAAsB,qBACpB,WACA,QACA,WACqB;CACrB,MAAM,gBAAgB,MAAM,kBAAkB;CAC9C,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,gBAAgB;CAE/B,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,EAAE,KAAK,SAAS,OACzB,QAAO,OACL,YAAY,QACT,UAAU,QACV,SAAS,QACT,WAAW,IAAI;AAGtB,QAAO;;AAGT,SAAS,WAAW,SAAwC;AAC1D,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO;;;;;ACnCpB,SAAS,YAAY,OAAyB;AAC5C,KAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,KAAI,iBAAiB,UAAW,QAAO;AACvC,KAAI,iBAAiB,OAAO;EAC1B,MAAM,QAAQ,MAAM,QAAQ,MAAM,mBAAmB;AACrD,MAAI,MAAO,QAAO,OAAO,SAAS,MAAM,GAAG,IAAI;;AAEjD,QAAO;;AAGT,eAAsB,SAAS,EAAE,KAAK,SAAS,MAAM,SAAS,OAAO,UAAU,KAAqC;CAClH,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG;CAE3F,IAAI;AAEJ,MAAK,IAAI,UAAU,GAAG,WAAW,mBAAmB,WAAW;EAC7D,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ;IACR;IACA;IACA,QAAQ,WAAW;IACpB,CAAC;AAEF,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB;IAC/D,MAAM,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB;AAC7E,UAAM,IAAI,MAAM,GAAG,MAAM,cAAc,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,WAAW;;AAGhG,gBAAa,UAAU;AACvB;WACO,OAAO;AACd,gBAAa,UAAU;AAEvB,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,6BAAY,IAAI,MAAM,GAAG,MAAM,2BAA2B,QAAQ,IAAI;OAEtE,aAAY;AAGd,OAAI,CAAC,YAAY,MAAM,IAAI,YAAY,kBACrC,OAAM;AAGR,SAAM,IAAI,SAAc,MAAK,WAAW,GAAG,MAAM,KAAK,QAAQ,CAAC;;;AAInE,OAAM"}
1
+ {"version":3,"file":"_http-CHSsrWDJ.mjs","names":[],"sources":["../src/adapters/_config.ts","../src/adapters/_http.ts"],"sourcesContent":["import { getNitroRuntimeConfigRecord } from '../shared/nitroConfigBridge'\n\n/**\n * Adapter runtime-config reads go through `getNitroRuntimeConfigRecord` in\n * `shared/nitroConfigBridge.ts` (documented there — Workers-safe dynamic imports).\n *\n * Drain handlers remain non-blocking when the host provides `waitUntil`.\n */\n\nexport function getRuntimeConfig(): Promise<Record<string, any> | undefined> {\n return getNitroRuntimeConfigRecord()\n}\n\nexport interface ConfigField<T> {\n key: keyof T & string\n env?: string[]\n}\n\nexport async function resolveAdapterConfig<T>(\n namespace: string,\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): Promise<Partial<T>> {\n const runtimeConfig = await getRuntimeConfig()\n const evlogNs = runtimeConfig?.evlog?.[namespace]\n const rootNs = runtimeConfig?.[namespace]\n\n const config: Record<string, unknown> = {}\n\n for (const { key, env } of fields) {\n config[key] =\n overrides?.[key]\n ?? evlogNs?.[key]\n ?? rootNs?.[key]\n ?? resolveEnv(env)\n }\n\n return config as Partial<T>\n}\n\nfunction resolveEnv(envKeys?: string[]): string | undefined {\n if (!envKeys) return undefined\n for (const key of envKeys) {\n const val = process.env[key]\n if (val) return val\n }\n return undefined\n}\n","export interface HttpPostOptions {\n url: string\n headers: Record<string, string>\n body: string\n timeout: number\n label: string\n retries?: number\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true\n if (error instanceof TypeError) return true\n if (error instanceof Error) {\n const match = error.message.match(/API error: (\\d+)/)\n if (match) return Number.parseInt(match[1]) >= 500\n }\n return false\n}\n\nexport async function httpPost({ url, headers, body, timeout, label, retries = 2 }: HttpPostOptions): Promise<void> {\n const normalizedRetries = Number.isFinite(retries) && retries >= 0 ? Math.floor(retries) : 2\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= normalizedRetries; attempt++) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n\n clearTimeout(timeoutId)\n return\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n lastError = new Error(`${label} request timed out after ${timeout}ms`)\n } else {\n lastError = error as Error\n }\n\n if (!isRetryable(error) || attempt === normalizedRetries) {\n throw lastError\n }\n\n await new Promise<void>(r => setTimeout(r, 200 * 2 ** attempt))\n }\n }\n\n throw lastError!\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,mBAA6D;AAC3E,QAAO,6BAA6B;;AAQtC,eAAsB,qBACpB,WACA,QACA,WACqB;CACrB,MAAM,gBAAgB,MAAM,kBAAkB;CAC9C,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,gBAAgB;CAE/B,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,EAAE,KAAK,SAAS,OACzB,QAAO,OACL,YAAY,QACT,UAAU,QACV,SAAS,QACT,WAAW,IAAI;AAGtB,QAAO;;AAGT,SAAS,WAAW,SAAwC;AAC1D,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO;;;;;ACnCpB,SAAS,YAAY,OAAyB;AAC5C,KAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAAc,QAAO;AACzE,KAAI,iBAAiB,UAAW,QAAO;AACvC,KAAI,iBAAiB,OAAO;EAC1B,MAAM,QAAQ,MAAM,QAAQ,MAAM,mBAAmB;AACrD,MAAI,MAAO,QAAO,OAAO,SAAS,MAAM,GAAG,IAAI;;AAEjD,QAAO;;AAGT,eAAsB,SAAS,EAAE,KAAK,SAAS,MAAM,SAAS,OAAO,UAAU,KAAqC;CAClH,MAAM,oBAAoB,OAAO,SAAS,QAAQ,IAAI,WAAW,IAAI,KAAK,MAAM,QAAQ,GAAG;CAE3F,IAAI;AAEJ,MAAK,IAAI,UAAU,GAAG,WAAW,mBAAmB,WAAW;EAC7D,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,QAAQ;IACR;IACA;IACA,QAAQ,WAAW;IACpB,CAAC;AAEF,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB;IAC/D,MAAM,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB;AAC7E,UAAM,IAAI,MAAM,GAAG,MAAM,cAAc,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,WAAW;;AAGhG,gBAAa,UAAU;AACvB;WACO,OAAO;AACd,gBAAa,UAAU;AAEvB,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,6BAAY,IAAI,MAAM,GAAG,MAAM,2BAA2B,QAAQ,IAAI;OAEtE,aAAY;AAGd,OAAI,CAAC,YAAY,MAAM,IAAI,YAAY,kBACrC,OAAM;AAGR,SAAM,IAAI,SAAc,MAAK,WAAW,GAAG,MAAM,KAAK,QAAQ,CAAC;;;AAInE,OAAM"}
@@ -14,4 +14,4 @@ const OTEL_SEVERITY_TEXT = {
14
14
  //#endregion
15
15
  export { OTEL_SEVERITY_TEXT as n, OTEL_SEVERITY_NUMBER as t };
16
16
 
17
- //# sourceMappingURL=_severity-BZhz3f9e.mjs.map
17
+ //# sourceMappingURL=_severity-CQijvfhU.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_severity-BZhz3f9e.mjs","names":[],"sources":["../src/adapters/_severity.ts"],"sourcesContent":["import type { LogLevel } from '../types'\n\nexport const OTEL_SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n}\n\nexport const OTEL_SEVERITY_TEXT: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n"],"mappings":";AAEA,MAAa,uBAAiD;CAC5D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAa,qBAA+C;CAC1D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR"}
1
+ {"version":3,"file":"_severity-CQijvfhU.mjs","names":[],"sources":["../src/adapters/_severity.ts"],"sourcesContent":["import type { LogLevel } from '../types'\n\nexport const OTEL_SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n}\n\nexport const OTEL_SEVERITY_TEXT: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n"],"mappings":";AAEA,MAAa,uBAAiD;CAC5D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAa,qBAA+C;CAC1D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR"}
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/axiom.d.ts
3
3
  interface BaseAxiomConfig {
4
4
  /** Axiom dataset name */
@@ -1,5 +1,5 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/axiom.ts
4
4
  const AXIOM_FIELDS = [
5
5
  {
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/better-stack.d.ts
3
3
  interface BetterStackConfig {
4
4
  /** Better Stack source token */
@@ -1,5 +1,5 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/better-stack.ts
4
4
  const BETTER_STACK_FIELDS = [
5
5
  {
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/datadog.d.ts
3
3
  interface DatadogConfig {
4
4
  /** Datadog API key with Logs intake permission */
@@ -1,5 +1,5 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  //#region src/adapters/datadog.ts
4
4
  const DATADOG_FIELDS = [
5
5
  {
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/fs.d.ts
3
3
  interface FsConfig {
4
4
  /** Directory for log files */
@@ -1,4 +1,4 @@
1
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
2
2
  import { join, sep } from "node:path";
3
3
  import { appendFile, mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
4
4
  //#region src/adapters/fs.ts
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  import { OTLPConfig } from "./otlp.mjs";
3
3
 
4
4
  //#region src/adapters/hyperdx.d.ts
@@ -1,5 +1,5 @@
1
- import { n as resolveAdapterConfig } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { n as resolveAdapterConfig } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { sendBatchToOTLP } from "./otlp.mjs";
4
4
  //#region src/adapters/hyperdx.ts
5
5
  /**
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/otlp.d.ts
3
3
  interface OTLPConfig {
4
4
  /** OTLP HTTP endpoint (e.g., http://localhost:4318) */
@@ -1,6 +1,6 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
3
- import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../_severity-BZhz3f9e.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
+ import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../_severity-CQijvfhU.mjs";
4
4
  //#region src/adapters/otlp.ts
5
5
  const OTLP_FIELDS = [
6
6
  {
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/posthog.d.ts
3
3
  interface PostHogConfig {
4
4
  /** PostHog project API key */
@@ -1,5 +1,5 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
3
  import { sendBatchToOTLP } from "./otlp.mjs";
4
4
  //#region src/adapters/posthog.ts
5
5
  const POSTHOG_FIELDS = [
@@ -1,4 +1,4 @@
1
- import { T as WideEvent, r as DrainContext } from "../types-v_JkG_D7.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/sentry.d.ts
3
3
  interface SentryConfig {
4
4
  /** Sentry DSN */
@@ -1,6 +1,6 @@
1
- import { n as resolveAdapterConfig, t as httpPost } from "../_http-C_2wbJw3.mjs";
2
- import { t as defineDrain } from "../_drain-YH8ERc5l.mjs";
3
- import { t as OTEL_SEVERITY_NUMBER } from "../_severity-BZhz3f9e.mjs";
1
+ import { n as resolveAdapterConfig, t as httpPost } from "../_http-CHSsrWDJ.mjs";
2
+ import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
3
+ import { t as OTEL_SEVERITY_NUMBER } from "../_severity-CQijvfhU.mjs";
4
4
  //#region src/adapters/sentry.ts
5
5
  const SENTRY_FIELDS = [
6
6
  {
@@ -1,5 +1,5 @@
1
- import { g as RequestLogger } from "../types-v_JkG_D7.mjs";
2
- import { GatewayModelId } from "ai";
1
+ import { _ as RequestLogger } from "../types-DbzDln7O.mjs";
2
+ import { GatewayModelId, TelemetryIntegration } from "ai";
3
3
  import { LanguageModelV3, LanguageModelV3Middleware } from "@ai-sdk/provider";
4
4
 
5
5
  //#region src/ai/index.d.ts
@@ -19,6 +19,13 @@ interface ToolInputsOptions {
19
19
  */
20
20
  transform?: (input: unknown, toolName: string) => unknown;
21
21
  }
22
+ /**
23
+ * Pricing entry for a single model: cost per 1 million tokens in dollars.
24
+ */
25
+ interface ModelCost {
26
+ input: number;
27
+ output: number;
28
+ }
22
29
  /**
23
30
  * Options for `createAILogger` and `createAIMiddleware`.
24
31
  */
@@ -32,6 +39,24 @@ interface AILoggerOptions {
32
39
  * @default false
33
40
  */
34
41
  toolInputs?: boolean | ToolInputsOptions;
42
+ /**
43
+ * Pricing map for estimating request cost.
44
+ * Keys are model IDs (e.g. `'claude-sonnet-4.6'`, `'gpt-4o'`), values are
45
+ * `{ input, output }` in dollars per 1M tokens.
46
+ *
47
+ * When provided, the wide event includes `ai.estimatedCost` (in dollars).
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * const ai = createAILogger(log, {
52
+ * cost: {
53
+ * 'claude-sonnet-4.6': { input: 3, output: 15 },
54
+ * 'gpt-4o': { input: 2.5, output: 10 },
55
+ * },
56
+ * })
57
+ * ```
58
+ */
59
+ cost?: Record<string, ModelCost>;
35
60
  }
36
61
  /**
37
62
  * Per-step token usage breakdown for multi-step agent runs.
@@ -42,6 +67,24 @@ interface AIStepUsage {
42
67
  outputTokens: number;
43
68
  toolCalls?: string[];
44
69
  }
70
+ /**
71
+ * Tool execution detail captured via `TelemetryIntegration`.
72
+ */
73
+ interface AIToolExecution {
74
+ name: string;
75
+ durationMs: number;
76
+ success: boolean;
77
+ error?: string;
78
+ }
79
+ /**
80
+ * Embedding metadata captured via `captureEmbed`.
81
+ */
82
+ interface AIEmbeddingData {
83
+ model?: string;
84
+ tokens: number;
85
+ dimensions?: number;
86
+ count?: number;
87
+ }
45
88
  /**
46
89
  * Shape of the `ai` field written to the wide event.
47
90
  */
@@ -68,12 +111,16 @@ interface AIEventData {
68
111
  msToFinish?: number;
69
112
  tokensPerSecond?: number;
70
113
  error?: string;
114
+ tools?: AIToolExecution[];
115
+ totalDurationMs?: number;
116
+ embedding?: AIEmbeddingData;
117
+ estimatedCost?: number;
71
118
  }
72
119
  interface AILogger {
73
120
  /**
74
121
  * Wrap a language model with evlog middleware.
75
- * All `generateText`, `streamText`, `generateObject`, and `streamObject` calls
76
- * using the wrapped model are captured automatically into the wide event.
122
+ * All `generateText` and `streamText` calls using the wrapped model
123
+ * are captured automatically into the wide event.
77
124
  *
78
125
  * Accepts a `LanguageModelV3` object or a model string (e.g. `'anthropic/claude-sonnet-4.6'`).
79
126
  * Strings are resolved via the AI SDK gateway.
@@ -100,13 +147,34 @@ interface AILogger {
100
147
  * ```ts
101
148
  * const { embedding, usage } = await embed({ model: embeddingModel, value: query })
102
149
  * ai.captureEmbed({ usage })
150
+ *
151
+ * // With model info (v2)
152
+ * ai.captureEmbed({ usage, model: 'text-embedding-3-small', dimensions: 1536 })
153
+ *
154
+ * // After embedMany
155
+ * ai.captureEmbed({ usage, count: texts.length })
103
156
  * ```
104
157
  */
105
158
  captureEmbed: (result: {
106
159
  usage: {
107
160
  tokens: number;
108
161
  };
162
+ model?: string;
163
+ dimensions?: number;
164
+ count?: number;
109
165
  }) => void;
166
+ /**
167
+ * Internal accumulator state exposed for `createEvlogIntegration` to share.
168
+ * @internal
169
+ */
170
+ _state: AccumulatorState;
171
+ }
172
+ interface UsageAccumulator {
173
+ inputTokens: number;
174
+ outputTokens: number;
175
+ cacheReadTokens: number;
176
+ cacheWriteTokens: number;
177
+ reasoningTokens: number;
110
178
  }
111
179
  /**
112
180
  * Create the evlog AI middleware that captures AI SDK data into a wide event.
@@ -160,6 +228,77 @@ declare function createAIMiddleware(log: RequestLogger, options?: AILoggerOption
160
228
  * ```
161
229
  */
162
230
  declare function createAILogger(log: RequestLogger, options?: AILoggerOptions): AILogger;
231
+ interface AccumulatorState {
232
+ calls: number;
233
+ steps: number;
234
+ usage: UsageAccumulator;
235
+ models: string[];
236
+ lastProvider: string | undefined;
237
+ allToolCalls: string[];
238
+ allToolCallInputs: Array<{
239
+ name: string;
240
+ input: unknown;
241
+ }>;
242
+ stepsUsage: AIStepUsage[];
243
+ lastFinishReason: string | undefined;
244
+ lastMsToFirstChunk: number | undefined;
245
+ lastMsToFinish: number | undefined;
246
+ lastError: string | undefined;
247
+ lastResponseId: string | undefined;
248
+ toolInputs: boolean;
249
+ toolInputsOptions: ToolInputsOptions | undefined;
250
+ toolExecutions: AIToolExecution[];
251
+ generationStartTime: number | undefined;
252
+ totalDurationMs: number | undefined;
253
+ embedding: AIEmbeddingData | undefined;
254
+ costMap: Record<string, ModelCost> | undefined;
255
+ /** @internal Logger reference for integration flush */
256
+ _log?: RequestLogger;
257
+ }
258
+ /**
259
+ * Create an AI SDK `TelemetryIntegration` that captures tool execution
260
+ * timing, errors, and total generation wall time into the wide event.
261
+ *
262
+ * Complements the middleware-based `createAILogger`: the middleware captures
263
+ * token usage and streaming metrics at the model level, while the integration
264
+ * captures application-level lifecycle events (tool execution, total duration).
265
+ *
266
+ * When passed an `AILogger`, shares its accumulator so both paths write to
267
+ * the same `ai.*` field. Can also be used standalone with a `RequestLogger`.
268
+ *
269
+ * @example Combined with middleware (recommended)
270
+ * ```ts
271
+ * import { createAILogger, createEvlogIntegration } from 'evlog/ai'
272
+ *
273
+ * const log = useLogger(event)
274
+ * const ai = createAILogger(log)
275
+ *
276
+ * const result = await generateText({
277
+ * model: ai.wrap('anthropic/claude-sonnet-4.6'),
278
+ * tools: { getWeather },
279
+ * experimental_telemetry: {
280
+ * isEnabled: true,
281
+ * integrations: [createEvlogIntegration(ai)],
282
+ * },
283
+ * })
284
+ * ```
285
+ *
286
+ * @example Standalone (no middleware wrapping)
287
+ * ```ts
288
+ * import { createEvlogIntegration } from 'evlog/ai'
289
+ *
290
+ * const integration = createEvlogIntegration(log)
291
+ *
292
+ * const result = await generateText({
293
+ * model: openai('gpt-4o'),
294
+ * experimental_telemetry: {
295
+ * isEnabled: true,
296
+ * integrations: [integration],
297
+ * },
298
+ * })
299
+ * ```
300
+ */
301
+ declare function createEvlogIntegration(logOrAi: RequestLogger | AILogger, options?: AILoggerOptions): TelemetryIntegration;
163
302
  //#endregion
164
- export { AIEventData, AILogger, AILoggerOptions, AIStepUsage, ToolInputsOptions, createAILogger, createAIMiddleware };
303
+ export { AIEmbeddingData, AIEventData, AILogger, AILoggerOptions, AIStepUsage, AIToolExecution, ModelCost, ToolInputsOptions, createAILogger, createAIMiddleware, createEvlogIntegration };
165
304
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ai/index.ts"],"mappings":";;;;;;;AAQA;UAAiB,iBAAA;;;;;EAKf,SAAA;EAM6B;;;AAM/B;;EANE,SAAA,IAAa,KAAA,WAAgB,QAAA;AAAA;;AAqB/B;;UAfiB,eAAA;EAeW;;;;;;;AAU5B;EAhBE,UAAA,aAAuB,iBAAA;AAAA;;;;UAMR,WAAA;EACf,KAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;AAAA;;;;UAMe,WAAA;EACf,KAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA,cAAuB,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EAC7C,UAAA;EACA,KAAA;EACA,UAAA,GAAa,WAAA;EACb,cAAA;EACA,UAAA;EACA,eAAA;EACA,KAAA;AAAA;AAAA,UAGe,QAAA;EAqBR;;;;;;;;AA8ET;;;;;;;;;;;;EA9EE,IAAA,GAAO,KAAA,EAAO,eAAA,GAAkB,cAAA,KAAmB,eAAA;EA8EuD;;AA8B5G;;;;;;;;;EA/FE,YAAA,GAAe,MAAA;IAAU,KAAA;MAAS,MAAA;IAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAiEpB,kBAAA,CAAmB,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BnE,cAAA,CAAe,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,QAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ai/index.ts"],"mappings":";;;;;;;AAQA;UAAiB,iBAAA;;;;;EAKf,SAAA;EAM6B;;;AAM/B;;EANE,SAAA,IAAa,KAAA,WAAgB,QAAA;AAAA;;AAc/B;;UARiB,SAAA;EACf,KAAA;EACA,MAAA;AAAA;;;;UAMe,eAAA;EA2Bf;;;;;AAMF;;;EAxBE,UAAA,aAAuB,iBAAA;EAyBvB;;;;;;AASF;;;;;;;;;;AAUA;EA1BE,IAAA,GAAO,MAAA,SAAe,SAAA;AAAA;;;;UAMP,WAAA;EACf,KAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;AAAA;;;;UAMe,eAAA;EACf,IAAA;EACA,UAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;UAMe,eAAA;EACf,KAAA;EACA,MAAA;EACA,UAAA;EACA,KAAA;AAAA;;;;UAMe,WAAA;EACf,KAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA,cAAuB,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EAC7C,UAAA;EACA,KAAA;EACA,UAAA,GAAa,WAAA;EACb,cAAA;EACA,UAAA;EACA,eAAA;EACA,KAAA;EACA,KAAA,GAAQ,eAAA;EACR,eAAA;EACA,SAAA,GAAY,eAAA;EACZ,aAAA;AAAA;AAAA,UAGe,QAAA;EAqBR;;;;;;;;;;;;;AA+BR;;;;;;;EA/BC,IAAA,GAAO,KAAA,EAAO,eAAA,GAAkB,cAAA,KAAmB,eAAA;EAsCnD;;;AAyDF;;;;;;;;;;;;;;EA5EE,YAAA,GAAe,MAAA;IACb,KAAA;MAAS,MAAA;IAAA;IACT,KAAA;IACA,UAAA;IACA,KAAA;EAAA;EAsGmF;;;;EA/FrF,MAAA,EAAQ,gBAAA;AAAA;AAAA,UAGA,gBAAA;EACR,WAAA;EACA,YAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDc,kBAAA,CAAmB,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,yBAAA;;;;;;;;;;;;;;;;;;;AAocnF;;;;;;;;iBAtagB,cAAA,CAAe,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,QAAA;AAAA,UAgCrE,gBAAA;EACR,KAAA;EACA,KAAA;EACA,KAAA,EAAO,gBAAA;EACP,MAAA;EACA,YAAA;EACA,YAAA;EACA,iBAAA,EAAmB,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EACzC,UAAA,EAAY,WAAA;EACZ,gBAAA;EACA,kBAAA;EACA,cAAA;EACA,SAAA;EACA,cAAA;EACA,UAAA;EACA,iBAAA,EAAmB,iBAAA;EACnB,cAAA,EAAgB,eAAA;EAChB,mBAAA;EACA,eAAA;EACA,SAAA,EAAW,eAAA;EACX,OAAA,EAAS,MAAA,SAAe,SAAA;;EAExB,IAAA,GAAO,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgXO,sBAAA,CACd,OAAA,EAAS,aAAA,GAAgB,QAAA,EACzB,OAAA,GAAU,eAAA,GACT,oBAAA"}
package/dist/ai/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { gateway, wrapLanguageModel } from "ai";
1
+ import { bindTelemetryIntegration, gateway, wrapLanguageModel } from "ai";
2
2
  //#region src/ai/index.ts
3
3
  function addUsage(acc, usage) {
4
4
  acc.inputTokens += usage.inputTokens.total ?? 0;
@@ -78,6 +78,7 @@ function createAIMiddleware(log, options) {
78
78
  */
79
79
  function createAILogger(log, options) {
80
80
  const state = createAccumulatorState(options);
81
+ state._log = log;
81
82
  const middleware = buildMiddlewareFromState(log, state);
82
83
  return {
83
84
  wrap: (model) => {
@@ -89,8 +90,15 @@ function createAILogger(log, options) {
89
90
  captureEmbed: (result) => {
90
91
  state.calls++;
91
92
  state.usage.inputTokens += result.usage.tokens;
93
+ state.embedding = {
94
+ tokens: (state.embedding?.tokens ?? 0) + result.usage.tokens,
95
+ ...result.model ? { model: result.model } : state.embedding?.model ? { model: state.embedding.model } : {},
96
+ ...result.dimensions ? { dimensions: result.dimensions } : state.embedding?.dimensions ? { dimensions: state.embedding.dimensions } : {},
97
+ ...result.count ? { count: (state.embedding?.count ?? 0) + result.count } : state.embedding?.count ? { count: state.embedding.count } : {}
98
+ };
92
99
  flushState(log, state);
93
- }
100
+ },
101
+ _state: state
94
102
  };
95
103
  }
96
104
  function resolveToolInputs(raw) {
@@ -139,9 +147,23 @@ function createAccumulatorState(options) {
139
147
  lastError: void 0,
140
148
  lastResponseId: void 0,
141
149
  toolInputs: enabled,
142
- toolInputsOptions: captureOpts
150
+ toolInputsOptions: captureOpts,
151
+ toolExecutions: [],
152
+ generationStartTime: void 0,
153
+ totalDurationMs: void 0,
154
+ embedding: void 0,
155
+ costMap: options?.cost
143
156
  };
144
157
  }
158
+ function computeEstimatedCost(state) {
159
+ if (!state.costMap) return void 0;
160
+ const lastModel = state.models[state.models.length - 1];
161
+ if (!lastModel) return void 0;
162
+ const pricing = state.costMap[lastModel];
163
+ if (!pricing) return void 0;
164
+ const total = state.usage.inputTokens / 1e6 * pricing.input + state.usage.outputTokens / 1e6 * pricing.output;
165
+ return total > 0 ? Math.round(total * 1e6) / 1e6 : void 0;
166
+ }
145
167
  function flushState(log, state) {
146
168
  const uniqueModels = [...new Set(state.models)];
147
169
  const lastModel = state.models[state.models.length - 1];
@@ -171,6 +193,11 @@ function flushState(log, state) {
171
193
  if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) data.tokensPerSecond = Math.round(state.usage.outputTokens / state.lastMsToFinish * 1e3);
172
194
  }
173
195
  if (state.lastError) data.error = state.lastError;
196
+ if (state.toolExecutions.length > 0) data.tools = [...state.toolExecutions];
197
+ if (state.totalDurationMs !== void 0) data.totalDurationMs = state.totalDurationMs;
198
+ if (state.embedding) data.embedding = { ...state.embedding };
199
+ const cost = computeEstimatedCost(state);
200
+ if (cost !== void 0) data.estimatedCost = cost;
174
201
  log.set({ ai: data });
175
202
  }
176
203
  function recordModel(state, provider, modelId, responseModelId) {
@@ -200,7 +227,9 @@ function recordError(log, state, model, error) {
200
227
  flushState(log, state);
201
228
  }
202
229
  function buildMiddleware(log, options) {
203
- return buildMiddlewareFromState(log, createAccumulatorState(options));
230
+ const state = createAccumulatorState(options);
231
+ state._log = log;
232
+ return buildMiddlewareFromState(log, state);
204
233
  }
205
234
  function buildMiddlewareFromState(log, state) {
206
235
  return {
@@ -334,7 +363,81 @@ function buildMiddlewareFromState(log, state) {
334
363
  }
335
364
  };
336
365
  }
366
+ /**
367
+ * Create an AI SDK `TelemetryIntegration` that captures tool execution
368
+ * timing, errors, and total generation wall time into the wide event.
369
+ *
370
+ * Complements the middleware-based `createAILogger`: the middleware captures
371
+ * token usage and streaming metrics at the model level, while the integration
372
+ * captures application-level lifecycle events (tool execution, total duration).
373
+ *
374
+ * When passed an `AILogger`, shares its accumulator so both paths write to
375
+ * the same `ai.*` field. Can also be used standalone with a `RequestLogger`.
376
+ *
377
+ * @example Combined with middleware (recommended)
378
+ * ```ts
379
+ * import { createAILogger, createEvlogIntegration } from 'evlog/ai'
380
+ *
381
+ * const log = useLogger(event)
382
+ * const ai = createAILogger(log)
383
+ *
384
+ * const result = await generateText({
385
+ * model: ai.wrap('anthropic/claude-sonnet-4.6'),
386
+ * tools: { getWeather },
387
+ * experimental_telemetry: {
388
+ * isEnabled: true,
389
+ * integrations: [createEvlogIntegration(ai)],
390
+ * },
391
+ * })
392
+ * ```
393
+ *
394
+ * @example Standalone (no middleware wrapping)
395
+ * ```ts
396
+ * import { createEvlogIntegration } from 'evlog/ai'
397
+ *
398
+ * const integration = createEvlogIntegration(log)
399
+ *
400
+ * const result = await generateText({
401
+ * model: openai('gpt-4o'),
402
+ * experimental_telemetry: {
403
+ * isEnabled: true,
404
+ * integrations: [integration],
405
+ * },
406
+ * })
407
+ * ```
408
+ */
409
+ function createEvlogIntegration(logOrAi, options) {
410
+ let log;
411
+ let state;
412
+ if ("_state" in logOrAi && logOrAi._state) {
413
+ state = logOrAi._state;
414
+ log = state._log;
415
+ } else {
416
+ log = logOrAi;
417
+ state = createAccumulatorState(options);
418
+ state._log = log;
419
+ }
420
+ class EvlogIntegration {
421
+ onStart(_event) {
422
+ state.generationStartTime = Date.now();
423
+ }
424
+ onToolCallFinish(event) {
425
+ const execution = {
426
+ name: event.toolCall.toolName,
427
+ durationMs: event.durationMs,
428
+ success: event.success
429
+ };
430
+ if (!event.success && event.error) execution.error = event.error instanceof Error ? event.error.message : String(event.error);
431
+ state.toolExecutions.push(execution);
432
+ }
433
+ onFinish(_event) {
434
+ if (state.generationStartTime) state.totalDurationMs = Date.now() - state.generationStartTime;
435
+ flushState(log, state);
436
+ }
437
+ }
438
+ return bindTelemetryIntegration(new EvlogIntegration());
439
+ }
337
440
  //#endregion
338
- export { createAILogger, createAIMiddleware };
441
+ export { createAILogger, createAIMiddleware, createEvlogIntegration };
339
442
 
340
443
  //# sourceMappingURL=index.mjs.map