evlog 2.13.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/datadog.d.mts +1 -1
- package/dist/adapters/fs.d.mts +1 -1
- package/dist/adapters/hyperdx.d.mts +1 -1
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/ai/index.d.mts +106 -5
- package/dist/ai/index.d.mts.map +1 -1
- package/dist/ai/index.mjs +28 -5
- package/dist/ai/index.mjs.map +1 -1
- package/dist/{logger-DnobymUQ.mjs → audit-d9esRZOK.mjs} +703 -4
- package/dist/audit-d9esRZOK.mjs.map +1 -0
- package/dist/{types-DbzDln7O.d.mts → audit-mUutdf6A.d.mts} +499 -3
- package/dist/audit-mUutdf6A.d.mts.map +1 -0
- package/dist/better-auth/index.d.mts +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.mjs +2 -2
- package/dist/enrichers.d.mts +1 -1
- package/dist/{error-B9CiGK_i.d.mts → error-D1FZI2Kd.d.mts} +2 -2
- package/dist/{error-B9CiGK_i.d.mts.map → error-D1FZI2Kd.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/{errors-Dr0r4OpR.d.mts → errors-NIXCyk6I.d.mts} +2 -2
- package/dist/{errors-Dr0r4OpR.d.mts.map → errors-NIXCyk6I.d.mts.map} +1 -1
- package/dist/express/index.d.mts +2 -2
- package/dist/express/index.mjs +2 -2
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.mjs +2 -2
- package/dist/{fork-Y4z8iHti.mjs → fork-CTJXnpl8.mjs} +3 -3
- package/dist/{fork-Y4z8iHti.mjs.map → fork-CTJXnpl8.mjs.map} +1 -1
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +1 -1
- package/dist/http.d.mts +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +2 -2
- package/dist/{logger-Dp6nYWjH.d.mts → logger-b3epPH0N.d.mts} +4 -4
- package/dist/logger-b3epPH0N.d.mts.map +1 -0
- package/dist/logger.d.mts +1 -1
- package/dist/logger.mjs +1 -1
- package/dist/{middleware-BtBuosFV.mjs → middleware-BWOJ7JI0.mjs} +2 -2
- package/dist/{middleware-BtBuosFV.mjs.map → middleware-BWOJ7JI0.mjs.map} +1 -1
- package/dist/{middleware-FgC1OdOD.d.mts → middleware-BYf26Lfu.d.mts} +2 -2
- package/dist/{middleware-FgC1OdOD.d.mts.map → middleware-BYf26Lfu.d.mts.map} +1 -1
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.mjs +2 -2
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +4 -4
- package/dist/next/index.mjs +2 -2
- package/dist/next/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +1 -1
- package/dist/nitro/v3/index.d.mts +2 -2
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/plugin.mjs +1 -1
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-CDHLfRdw.d.mts → nitro-DenB86W6.d.mts} +2 -2
- package/dist/{nitro-CDHLfRdw.d.mts.map → nitro-DenB86W6.d.mts.map} +1 -1
- package/dist/nuxt/module.d.mts +1 -1
- package/dist/nuxt/module.mjs +1 -1
- package/dist/{parseError-DM-lyezZ.d.mts → parseError-BR9pocvY.d.mts} +2 -2
- package/dist/parseError-BR9pocvY.d.mts.map +1 -0
- package/dist/react-router/index.d.mts +2 -2
- package/dist/react-router/index.mjs +2 -2
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.mjs +2 -2
- package/dist/toolkit.d.mts +3 -3
- package/dist/toolkit.mjs +2 -2
- package/dist/types.d.mts +2 -2
- package/dist/{useLogger-N5A-d5l9.d.mts → useLogger-C56tDPwf.d.mts} +2 -2
- package/dist/{useLogger-N5A-d5l9.d.mts.map → useLogger-C56tDPwf.d.mts.map} +1 -1
- package/dist/{utils-DnX6VMNi.d.mts → utils-DzGCLRFe.d.mts} +2 -2
- package/dist/{utils-DnX6VMNi.d.mts.map → utils-DzGCLRFe.d.mts.map} +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/workers.d.mts +1 -1
- package/dist/workers.mjs +1 -1
- package/package.json +1 -1
- package/dist/logger-DnobymUQ.mjs.map +0 -1
- package/dist/logger-Dp6nYWjH.d.mts.map +0 -1
- package/dist/parseError-DM-lyezZ.d.mts.map +0 -1
- package/dist/types-DbzDln7O.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -693,6 +693,62 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
693
693
|
})
|
|
694
694
|
```
|
|
695
695
|
|
|
696
|
+
## Audit Logs
|
|
697
|
+
|
|
698
|
+
Audit logs are not a parallel system: they are a typed `audit` field on the wide event plus a few helpers. Add 1 enricher + 1 drain wrapper + `log.audit()` and you get tamper-evident, redact-aware, force-kept audit events through the same pipeline.
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
// server/plugins/evlog.ts
|
|
702
|
+
import { auditEnricher, auditOnly, signed } from 'evlog'
|
|
703
|
+
import { createAxiomDrain } from 'evlog/axiom'
|
|
704
|
+
import { createFsDrain } from 'evlog/fs'
|
|
705
|
+
|
|
706
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
707
|
+
const enrich = [auditEnricher({ tenantId: ctx => ctx.headers?.['x-tenant-id'] })]
|
|
708
|
+
const audits = auditOnly(signed(createFsDrain({ path: '.audit/' }), { strategy: 'hash-chain' }), { await: true })
|
|
709
|
+
const main = createAxiomDrain()
|
|
710
|
+
|
|
711
|
+
nitroApp.hooks.hook('evlog:enrich', async ctx => { for (const e of enrich) await e(ctx) })
|
|
712
|
+
nitroApp.hooks.hook('evlog:drain', async ctx => { await Promise.all([main(ctx), audits(ctx)]) })
|
|
713
|
+
})
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// server/api/invoice/[id]/refund.post.ts
|
|
718
|
+
import { auditDiff } from 'evlog'
|
|
719
|
+
|
|
720
|
+
export default defineEventHandler(async (event) => {
|
|
721
|
+
const log = useLogger(event)
|
|
722
|
+
const before = await db.invoice.get(id)
|
|
723
|
+
const after = await db.invoice.refund(id)
|
|
724
|
+
|
|
725
|
+
log.audit?.({
|
|
726
|
+
action: 'invoice.refund',
|
|
727
|
+
actor: { type: 'user', id: user.id, email: user.email },
|
|
728
|
+
target: { type: 'invoice', id: after.id },
|
|
729
|
+
outcome: 'success',
|
|
730
|
+
changes: auditDiff(before, after),
|
|
731
|
+
})
|
|
732
|
+
})
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
| Symbol | Kind | Purpose |
|
|
736
|
+
|--------|------|---------|
|
|
737
|
+
| `log.audit(fields)` / `log.audit.deny(reason, fields)` | method | Sugar over `log.set({ audit })` + force-keep |
|
|
738
|
+
| `audit(fields)` | function | Standalone for jobs / scripts |
|
|
739
|
+
| `withAudit({ action, target })(fn)` | wrapper | Auto-emit success / failure / denied |
|
|
740
|
+
| `defineAuditAction(name, opts?)` | factory | Typed action registry |
|
|
741
|
+
| `auditDiff(before, after)` | helper | Redact-aware JSON Patch for `changes` |
|
|
742
|
+
| `mockAudit()` | test util | Capture and assert audits in tests |
|
|
743
|
+
| `auditEnricher({ tenantId? })` | enricher | Auto-fill `req`/`trace`/`ip`/`ua`/`tenantId` context |
|
|
744
|
+
| `auditOnly(drain, { await? })` | wrapper | Routes only events with `event.audit` |
|
|
745
|
+
| `signed(drain, { strategy: 'hmac' \| 'hash-chain', ... })` | wrapper | Tamper-evident integrity |
|
|
746
|
+
| `auditRedactPreset` | preset | Strict PII for audit events |
|
|
747
|
+
|
|
748
|
+
`AuditFields` is exported and merges with `BaseWideEvent` — augment it with `declare module` if you need extra typed fields. Audit events are always force-kept by tail sampling and get a deterministic `idempotencyKey` so retries are safe across drains.
|
|
749
|
+
|
|
750
|
+
See [the Audit Logs guide](https://evlog.dev/logging/audit) for compliance, GDPR, and recipe details.
|
|
751
|
+
|
|
696
752
|
## AI SDK Integration
|
|
697
753
|
|
|
698
754
|
Capture token usage, tool calls, model info, and streaming metrics from the [Vercel AI SDK](https://ai-sdk.dev) into wide events. Requires `ai >= 6.0.0`.
|
|
@@ -719,6 +775,23 @@ The middleware captures: `inputTokens`, `outputTokens`, `cacheReadTokens`, `reas
|
|
|
719
775
|
|
|
720
776
|
For embeddings: `ai.captureEmbed({ usage })`.
|
|
721
777
|
|
|
778
|
+
The same metadata is also exposed as a public API for custom analytics, billing, or user-facing dashboards:
|
|
779
|
+
|
|
780
|
+
```typescript
|
|
781
|
+
const ai = createAILogger(log, {
|
|
782
|
+
cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })
|
|
786
|
+
|
|
787
|
+
const metadata = ai.getMetadata() // structured snapshot (AIMetadata)
|
|
788
|
+
const cost = ai.getEstimatedCost() // dollars, or undefined
|
|
789
|
+
|
|
790
|
+
ai.onUpdate((metadata) => { // incremental updates per step
|
|
791
|
+
pushToClient({ tokens: metadata.totalTokens, cost: metadata.estimatedCost })
|
|
792
|
+
})
|
|
793
|
+
```
|
|
794
|
+
|
|
722
795
|
## Adapters
|
|
723
796
|
|
|
724
797
|
Send your logs to external observability platforms with built-in adapters.
|
package/dist/adapters/fs.d.mts
CHANGED
package/dist/adapters/otlp.d.mts
CHANGED
package/dist/ai/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Y as RequestLogger } from "../audit-mUutdf6A.mjs";
|
|
2
2
|
import { GatewayModelId, TelemetryIntegration } from "ai";
|
|
3
3
|
import { LanguageModelV3, LanguageModelV3Middleware } from "@ai-sdk/provider";
|
|
4
4
|
|
|
@@ -86,13 +86,18 @@ interface AIEmbeddingData {
|
|
|
86
86
|
count?: number;
|
|
87
87
|
}
|
|
88
88
|
/**
|
|
89
|
-
* Shape of the `ai` field written to the wide event
|
|
89
|
+
* Shape of the `ai` field written to the wide event, and the public
|
|
90
|
+
* snapshot returned by `AILogger.getMetadata()`.
|
|
91
|
+
*
|
|
92
|
+
* `model` and `provider` are populated after the first model call.
|
|
93
|
+
* They may be undefined when only `captureEmbed` has been called or
|
|
94
|
+
* before any AI activity has happened.
|
|
90
95
|
*/
|
|
91
96
|
interface AIEventData {
|
|
92
97
|
calls: number;
|
|
93
|
-
model
|
|
98
|
+
model?: string;
|
|
94
99
|
models?: string[];
|
|
95
|
-
provider
|
|
100
|
+
provider?: string;
|
|
96
101
|
inputTokens: number;
|
|
97
102
|
outputTokens: number;
|
|
98
103
|
totalTokens: number;
|
|
@@ -116,6 +121,18 @@ interface AIEventData {
|
|
|
116
121
|
embedding?: AIEmbeddingData;
|
|
117
122
|
estimatedCost?: number;
|
|
118
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Public alias for the metadata snapshot returned by `AILogger.getMetadata()`.
|
|
126
|
+
*
|
|
127
|
+
* Mirrors the shape of the `ai` field on the wide event, so the same object
|
|
128
|
+
* can be persisted, surfaced to end-users, or compared across runs.
|
|
129
|
+
*/
|
|
130
|
+
type AIMetadata = AIEventData;
|
|
131
|
+
/**
|
|
132
|
+
* Callback fired on every metadata update (per step, per embed, on error,
|
|
133
|
+
* and on integration completion). Receives a structured snapshot.
|
|
134
|
+
*/
|
|
135
|
+
type AIMetadataListener = (metadata: AIMetadata) => void;
|
|
119
136
|
interface AILogger {
|
|
120
137
|
/**
|
|
121
138
|
* Wrap a language model with evlog middleware.
|
|
@@ -163,6 +180,89 @@ interface AILogger {
|
|
|
163
180
|
dimensions?: number;
|
|
164
181
|
count?: number;
|
|
165
182
|
}) => void;
|
|
183
|
+
/**
|
|
184
|
+
* Get a snapshot of the current AI execution metadata.
|
|
185
|
+
*
|
|
186
|
+
* Returns the same structured object that is written to the `ai` field
|
|
187
|
+
* of the wide event. Safe to call at any time — including inside the
|
|
188
|
+
* AI SDK's `onFinish` callback, after `await generateText()`, or while a
|
|
189
|
+
* stream is in progress.
|
|
190
|
+
*
|
|
191
|
+
* The returned snapshot is a fresh copy: mutating it does not affect
|
|
192
|
+
* subsequent calls or the underlying state.
|
|
193
|
+
*
|
|
194
|
+
* @example Persist execution history after a run
|
|
195
|
+
* ```ts
|
|
196
|
+
* const ai = createAILogger(log, { cost: { ... } })
|
|
197
|
+
*
|
|
198
|
+
* await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })
|
|
199
|
+
*
|
|
200
|
+
* const metadata = ai.getMetadata()
|
|
201
|
+
* await db.insert('ai_runs', metadata)
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @example Surface usage to end-users in a streaming response
|
|
205
|
+
* ```ts
|
|
206
|
+
* const result = streamText({
|
|
207
|
+
* model: ai.wrap('anthropic/claude-sonnet-4.6'),
|
|
208
|
+
* messages,
|
|
209
|
+
* onFinish: () => {
|
|
210
|
+
* const { totalTokens, estimatedCost } = ai.getMetadata()
|
|
211
|
+
* trackUsage(userId, { totalTokens, estimatedCost })
|
|
212
|
+
* },
|
|
213
|
+
* })
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
getMetadata: () => AIMetadata;
|
|
217
|
+
/**
|
|
218
|
+
* Get the current estimated cost in dollars.
|
|
219
|
+
*
|
|
220
|
+
* Returns `undefined` if no `cost` map was provided to `createAILogger`,
|
|
221
|
+
* or if the model is not in the pricing map.
|
|
222
|
+
*
|
|
223
|
+
* Convenience for `getMetadata().estimatedCost`.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* const ai = createAILogger(log, {
|
|
228
|
+
* cost: { 'claude-sonnet-4.6': { input: 3, output: 15 } },
|
|
229
|
+
* })
|
|
230
|
+
*
|
|
231
|
+
* await generateText({ model: ai.wrap('anthropic/claude-sonnet-4.6'), prompt })
|
|
232
|
+
*
|
|
233
|
+
* console.log(`Cost: $${ai.getEstimatedCost()?.toFixed(4)}`)
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
getEstimatedCost: () => number | undefined;
|
|
237
|
+
/**
|
|
238
|
+
* Subscribe to metadata updates.
|
|
239
|
+
*
|
|
240
|
+
* The callback fires every time the underlying state flushes — once per
|
|
241
|
+
* step (in multi-step agent runs), once per `captureEmbed` call, on
|
|
242
|
+
* model errors, and once on `createEvlogIntegration`'s `onFinish`.
|
|
243
|
+
*
|
|
244
|
+
* Each invocation receives a fresh snapshot (same shape as `getMetadata`).
|
|
245
|
+
* Returns an unsubscribe function.
|
|
246
|
+
*
|
|
247
|
+
* @example Stream incremental usage updates to the client
|
|
248
|
+
* ```ts
|
|
249
|
+
* const ai = createAILogger(log)
|
|
250
|
+
*
|
|
251
|
+
* ai.onUpdate((metadata) => {
|
|
252
|
+
* pushToClient({ type: 'ai-progress', metadata })
|
|
253
|
+
* })
|
|
254
|
+
*
|
|
255
|
+
* const result = streamText({ model: ai.wrap('...'), messages })
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @example Cleanup
|
|
259
|
+
* ```ts
|
|
260
|
+
* const off = ai.onUpdate((metadata) => { ... })
|
|
261
|
+
* // later
|
|
262
|
+
* off()
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
onUpdate: (callback: AIMetadataListener) => () => void;
|
|
166
266
|
/**
|
|
167
267
|
* Internal accumulator state exposed for `createEvlogIntegration` to share.
|
|
168
268
|
* @internal
|
|
@@ -252,6 +352,7 @@ interface AccumulatorState {
|
|
|
252
352
|
totalDurationMs: number | undefined;
|
|
253
353
|
embedding: AIEmbeddingData | undefined;
|
|
254
354
|
costMap: Record<string, ModelCost> | undefined;
|
|
355
|
+
subscribers: Set<AIMetadataListener>;
|
|
255
356
|
/** @internal Logger reference for integration flush */
|
|
256
357
|
_log?: RequestLogger;
|
|
257
358
|
}
|
|
@@ -300,5 +401,5 @@ interface AccumulatorState {
|
|
|
300
401
|
*/
|
|
301
402
|
declare function createEvlogIntegration(logOrAi: RequestLogger | AILogger, options?: AILoggerOptions): TelemetryIntegration;
|
|
302
403
|
//#endregion
|
|
303
|
-
export { AIEmbeddingData, AIEventData, AILogger, AILoggerOptions, AIStepUsage, AIToolExecution, ModelCost, ToolInputsOptions, createAILogger, createAIMiddleware, createEvlogIntegration };
|
|
404
|
+
export { AIEmbeddingData, AIEventData, AILogger, AILoggerOptions, AIMetadata, AIMetadataListener, AIStepUsage, AIToolExecution, ModelCost, ToolInputsOptions, createAILogger, createAIMiddleware, createEvlogIntegration };
|
|
304
405
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/ai/index.d.mts.map
CHANGED
|
@@ -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;;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
|
|
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;;;;;;;;;UAWe,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;;;;;;;KASU,UAAA,GAAa,WAAA;;;;;KAMb,kBAAA,IAAsB,QAAA,EAAU,UAAA;AAAA,UAE3B,QAAA;EA0Cb;;;;;;;;;;;;;;AAgGH;;;;;;EArHC,IAAA,GAAO,KAAA,EAAO,eAAA,GAAkB,cAAA,KAAmB,eAAA;EA2HnD;;;;AA0DF;;;;;;;;;;;;;EAlKE,YAAA,GAAe,MAAA;IACb,KAAA;MAAS,MAAA;IAAA;IACT,KAAA;IACA,UAAA;IACA,KAAA;EAAA;EA4LmF;;;;;;;;;AAyCtF;;;;;;;;;;;;;;;;;;;;;;;;EAjMC,WAAA,QAAmB,UAAA;EA0MQ;;;;;;;;;;;;;;;;;;;EArL3B,gBAAA;EAkMwB;;;;;;;;AAqY1B;;;;;;;;;;;;;;;;;;;;EAziBE,QAAA,GAAW,QAAA,EAAU,kBAAA;;;;;EAMrB,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;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BnE,cAAA,CAAe,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,QAAA;AAAA,UA2CrE,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;EACxB,WAAA,EAAa,GAAA,CAAI,kBAAA;;EAEjB,IAAA,GAAO,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkYO,sBAAA,CACd,OAAA,EAAS,aAAA,GAAgB,QAAA,EACzB,OAAA,GAAU,eAAA,GACT,oBAAA"}
|
package/dist/ai/index.mjs
CHANGED
|
@@ -98,6 +98,14 @@ function createAILogger(log, options) {
|
|
|
98
98
|
};
|
|
99
99
|
flushState(log, state);
|
|
100
100
|
},
|
|
101
|
+
getMetadata: () => buildMetadata(state),
|
|
102
|
+
getEstimatedCost: () => computeEstimatedCost(state),
|
|
103
|
+
onUpdate: (callback) => {
|
|
104
|
+
state.subscribers.add(callback);
|
|
105
|
+
return () => {
|
|
106
|
+
state.subscribers.delete(callback);
|
|
107
|
+
};
|
|
108
|
+
},
|
|
101
109
|
_state: state
|
|
102
110
|
};
|
|
103
111
|
}
|
|
@@ -152,7 +160,8 @@ function createAccumulatorState(options) {
|
|
|
152
160
|
generationStartTime: void 0,
|
|
153
161
|
totalDurationMs: void 0,
|
|
154
162
|
embedding: void 0,
|
|
155
|
-
costMap: options?.cost
|
|
163
|
+
costMap: options?.cost,
|
|
164
|
+
subscribers: /* @__PURE__ */ new Set()
|
|
156
165
|
};
|
|
157
166
|
}
|
|
158
167
|
function computeEstimatedCost(state) {
|
|
@@ -164,7 +173,7 @@ function computeEstimatedCost(state) {
|
|
|
164
173
|
const total = state.usage.inputTokens / 1e6 * pricing.input + state.usage.outputTokens / 1e6 * pricing.output;
|
|
165
174
|
return total > 0 ? Math.round(total * 1e6) / 1e6 : void 0;
|
|
166
175
|
}
|
|
167
|
-
function
|
|
176
|
+
function buildMetadata(state) {
|
|
168
177
|
const uniqueModels = [...new Set(state.models)];
|
|
169
178
|
const lastModel = state.models[state.models.length - 1];
|
|
170
179
|
const data = {
|
|
@@ -180,12 +189,15 @@ function flushState(log, state) {
|
|
|
180
189
|
if (state.usage.cacheWriteTokens > 0) data.cacheWriteTokens = state.usage.cacheWriteTokens;
|
|
181
190
|
if (state.usage.reasoningTokens > 0) data.reasoningTokens = state.usage.reasoningTokens;
|
|
182
191
|
if (state.lastFinishReason) data.finishReason = state.lastFinishReason;
|
|
183
|
-
if (state.toolInputs && state.allToolCallInputs.length > 0) data.toolCalls =
|
|
192
|
+
if (state.toolInputs && state.allToolCallInputs.length > 0) data.toolCalls = state.allToolCallInputs.map((t) => ({ ...t }));
|
|
184
193
|
else if (state.allToolCalls.length > 0) data.toolCalls = [...state.allToolCalls];
|
|
185
194
|
if (state.lastResponseId) data.responseId = state.lastResponseId;
|
|
186
195
|
if (state.steps > 1) {
|
|
187
196
|
data.steps = state.steps;
|
|
188
|
-
data.stepsUsage =
|
|
197
|
+
data.stepsUsage = state.stepsUsage.map((s) => ({
|
|
198
|
+
...s,
|
|
199
|
+
...s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}
|
|
200
|
+
}));
|
|
189
201
|
}
|
|
190
202
|
if (state.lastMsToFirstChunk !== void 0) data.msToFirstChunk = state.lastMsToFirstChunk;
|
|
191
203
|
if (state.lastMsToFinish !== void 0) {
|
|
@@ -193,12 +205,23 @@ function flushState(log, state) {
|
|
|
193
205
|
if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) data.tokensPerSecond = Math.round(state.usage.outputTokens / state.lastMsToFinish * 1e3);
|
|
194
206
|
}
|
|
195
207
|
if (state.lastError) data.error = state.lastError;
|
|
196
|
-
if (state.toolExecutions.length > 0) data.tools =
|
|
208
|
+
if (state.toolExecutions.length > 0) data.tools = state.toolExecutions.map((t) => ({ ...t }));
|
|
197
209
|
if (state.totalDurationMs !== void 0) data.totalDurationMs = state.totalDurationMs;
|
|
198
210
|
if (state.embedding) data.embedding = { ...state.embedding };
|
|
199
211
|
const cost = computeEstimatedCost(state);
|
|
200
212
|
if (cost !== void 0) data.estimatedCost = cost;
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
function notifySubscribers(state, metadata) {
|
|
216
|
+
if (state.subscribers.size === 0) return;
|
|
217
|
+
for (const subscriber of state.subscribers) try {
|
|
218
|
+
subscriber(metadata);
|
|
219
|
+
} catch {}
|
|
220
|
+
}
|
|
221
|
+
function flushState(log, state) {
|
|
222
|
+
const data = buildMetadata(state);
|
|
201
223
|
log.set({ ai: data });
|
|
224
|
+
notifySubscribers(state, data);
|
|
202
225
|
}
|
|
203
226
|
function recordModel(state, provider, modelId, responseModelId) {
|
|
204
227
|
const resolved = resolveProviderAndModel(provider, responseModelId ?? modelId);
|
package/dist/ai/index.mjs.map
CHANGED
|
@@ -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.\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\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 * 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 _state: state,\n }\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 /** @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 }\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\nfunction flushState(log: RequestLogger, state: AccumulatorState): void {\n const uniqueModels = [...new Set(state.models)]\n const lastModel = state.models[state.models.length - 1]\n\n const data: Partial<AIEventData> & { calls: number, inputTokens: number, outputTokens: number, totalTokens: number } = {\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 (uniqueModels.length > 1) data.models = uniqueModels\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.toolInputs && state.allToolCallInputs.length > 0) {\n data.toolCalls = [...state.allToolCallInputs]\n } else if (state.allToolCalls.length > 0) {\n data.toolCalls = [...state.allToolCalls]\n }\n if (state.lastResponseId) data.responseId = state.lastResponseId\n if (state.steps > 1) {\n data.steps = state.steps\n data.stepsUsage = [...state.stepsUsage]\n }\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.toolExecutions.length > 0) data.tools = [...state.toolExecutions]\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 log.set({ ai: data } as Record<string, unknown>)\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 { 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\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 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 }\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\nfunction buildMetadata(state: AccumulatorState): AIMetadata {\n const uniqueModels = [...new Set(state.models)]\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 (uniqueModels.length > 1) data.models = uniqueModels\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.toolInputs && state.allToolCallInputs.length > 0) {\n data.toolCalls = state.allToolCallInputs.map(t => ({ ...t }))\n } else if (state.allToolCalls.length > 0) {\n data.toolCalls = [...state.allToolCalls]\n }\n if (state.lastResponseId) data.responseId = state.lastResponseId\n if (state.steps > 1) {\n data.steps = state.steps\n data.stepsUsage = state.stepsUsage.map(s => ({ ...s, ...(s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}) }))\n }\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.toolExecutions.length > 0) data.tools = state.toolExecutions.map(t => ({ ...t }))\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 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\nfunction flushState(log: RequestLogger, state: AccumulatorState): void {\n const data = buildMetadata(state)\n log.set({ ai: data } as Record<string, unknown>)\n notifySubscribers(state, data)\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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"}
|