evlog 2.8.0 → 2.10.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 +28 -0
- package/dist/{_drain-C9Nr-6Wc.mjs → _drain-CJDuM0ua.mjs} +7 -2
- package/dist/_drain-CJDuM0ua.mjs.map +1 -0
- package/dist/{_http-C2UoHWgm.mjs → _http-rRIz2Q0L.mjs} +22 -19
- package/dist/_http-rRIz2Q0L.mjs.map +1 -0
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/axiom.mjs +4 -4
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/better-stack.mjs +4 -4
- package/dist/adapters/better-stack.mjs.map +1 -1
- package/dist/adapters/fs.d.mts +1 -1
- package/dist/adapters/fs.mjs +1 -1
- package/dist/adapters/hyperdx.d.mts +65 -0
- package/dist/adapters/hyperdx.d.mts.map +1 -0
- package/dist/adapters/hyperdx.mjs +93 -0
- package/dist/adapters/hyperdx.mjs.map +1 -0
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/otlp.mjs +4 -4
- package/dist/adapters/otlp.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/posthog.mjs +6 -6
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/adapters/sentry.mjs +4 -4
- package/dist/adapters/sentry.mjs.map +1 -1
- package/dist/ai/index.d.mts +82 -5
- package/dist/ai/index.d.mts.map +1 -1
- package/dist/ai/index.mjs +237 -96
- package/dist/ai/index.mjs.map +1 -1
- package/dist/browser.d.mts +3 -1
- package/dist/browser.d.mts.map +1 -1
- package/dist/browser.mjs +2 -2
- package/dist/browser.mjs.map +1 -1
- package/dist/{dist-BFn8qsRC.mjs → dist-BsWcv7B8.mjs} +1 -1
- package/dist/{dist-BFn8qsRC.mjs.map → dist-BsWcv7B8.mjs.map} +1 -1
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.d.mts.map +1 -1
- package/dist/elysia/index.mjs +7 -7
- package/dist/elysia/index.mjs.map +1 -1
- package/dist/enrichers.d.mts +1 -1
- package/dist/{error-BheHTFFB.d.mts → error-BJ-I4sim.d.mts} +2 -2
- package/dist/{error-BheHTFFB.d.mts.map → error-BJ-I4sim.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/{errors-D8WVZclz.d.mts → errors-DBIBK0Bt.d.mts} +2 -2
- package/dist/{errors-D8WVZclz.d.mts.map → errors-DBIBK0Bt.d.mts.map} +1 -1
- package/dist/{errors-BQgyQ9xe.mjs → errors-gH4C9KSC.mjs} +1 -1
- package/dist/{errors-BQgyQ9xe.mjs.map → errors-gH4C9KSC.mjs.map} +1 -1
- package/dist/express/index.d.mts +2 -2
- package/dist/express/index.mjs +1 -1
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.mjs +1 -1
- package/dist/{headers-DJ_YZbxT.mjs → headers-DrdQ6uG5.mjs} +3 -3
- package/dist/{headers-DJ_YZbxT.mjs.map → headers-DrdQ6uG5.mjs.map} +1 -1
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/{logger-BkXYNnHP.d.mts → logger-DU3aQIUk.d.mts} +13 -3
- package/dist/logger-DU3aQIUk.d.mts.map +1 -0
- package/dist/logger.d.mts +2 -2
- package/dist/logger.mjs +16 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/{middleware-B-4hPOVG.d.mts → middleware-BjERCCEd.d.mts} +2 -2
- package/dist/{middleware-B-4hPOVG.d.mts.map → middleware-BjERCCEd.d.mts.map} +1 -1
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.mjs +1 -1
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +4 -4
- package/dist/next/index.d.mts.map +1 -1
- package/dist/next/index.mjs +3 -2
- package/dist/next/index.mjs.map +1 -1
- package/dist/next/instrumentation.d.mts +75 -0
- package/dist/next/instrumentation.d.mts.map +1 -0
- package/dist/next/instrumentation.mjs +108 -0
- package/dist/next/instrumentation.mjs.map +1 -0
- package/dist/nitro/errorHandler.mjs +2 -2
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +5 -4
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.mjs +3 -3
- package/dist/nitro/v3/middleware.d.mts +3 -1
- package/dist/nitro/v3/middleware.d.mts.map +1 -1
- package/dist/nitro/v3/middleware.mjs +2 -1
- package/dist/nitro/v3/middleware.mjs.map +1 -1
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/plugin.mjs +8 -7
- package/dist/nitro/v3/plugin.mjs.map +1 -1
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-DCNNxY_7.d.mts → nitro-BXmkH7BD.d.mts} +2 -2
- package/dist/{nitro-DCNNxY_7.d.mts.map → nitro-BXmkH7BD.d.mts.map} +1 -1
- package/dist/{nitro-CzyGROOC.mjs → nitro-Dpq5ZmcM.mjs} +2 -2
- package/dist/{nitro-CzyGROOC.mjs.map → nitro-Dpq5ZmcM.mjs.map} +1 -1
- package/dist/nuxt/module.d.mts +4 -3
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +4 -2
- package/dist/nuxt/module.mjs.map +1 -1
- package/dist/{parseError-B08FS7EQ.d.mts → parseError-CcvBYsbl.d.mts} +2 -2
- package/dist/parseError-CcvBYsbl.d.mts.map +1 -0
- package/dist/react-router/index.d.mts +47 -0
- package/dist/react-router/index.d.mts.map +1 -0
- package/dist/react-router/index.mjs +59 -0
- package/dist/react-router/index.mjs.map +1 -0
- package/dist/{routes-CGPmbzCZ.mjs → routes-CE3_c-iZ.mjs} +1 -1
- package/dist/{routes-CGPmbzCZ.mjs.map → routes-CE3_c-iZ.mjs.map} +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/client/log.d.mts.map +1 -1
- package/dist/runtime/client/log.mjs +3 -1
- package/dist/runtime/client/log.mjs.map +1 -1
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/runtime/utils/parseError.mjs +2 -1
- package/dist/runtime/utils/parseError.mjs.map +1 -1
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.mjs +3 -3
- package/dist/toolkit.d.mts +3 -3
- package/dist/toolkit.mjs +3 -3
- package/dist/{types-CBpJBj_7.d.mts → types-DbVDS9eu.d.mts} +8 -1
- package/dist/types-DbVDS9eu.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/dist/{useLogger-DBPGEDf_.d.mts → useLogger-DY9IByDJ.d.mts} +2 -2
- package/dist/{useLogger-DBPGEDf_.d.mts.map → useLogger-DY9IByDJ.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/package.json +50 -20
- package/dist/_drain-C9Nr-6Wc.mjs.map +0 -1
- package/dist/_http-C2UoHWgm.mjs.map +0 -1
- package/dist/logger-BkXYNnHP.d.mts.map +0 -1
- package/dist/parseError-B08FS7EQ.d.mts.map +0 -1
- package/dist/types-CBpJBj_7.d.mts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nexport interface PostHogEventsConfig extends PostHogConfig {\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n}\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst POSTHOG_EVENTS_FIELDS: ConfigField<PostHogEventsConfig>[] = [\n ...POSTHOG_FIELDS,\n { key: 'eventName' },\n { key: 'distinctId' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n const host = resolveHost(config)\n return {\n endpoint: `${host}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event format.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId ?? (typeof event.userId === 'string' ? event.userId : undefined) ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Logs (OTLP) — default\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog Logs via OTLP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_POSTHOG_API_KEY env var\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain({\n * apiKey: 'phc_...',\n * host: 'https://eu.i.posthog.com',\n * }))\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: () => {\n const config = resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: sendBatchToPostHog,\n })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendToPostHog(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendBatchToPostHog(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Events (custom events via /batch/)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog as custom events.\n *\n * Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead\n * which uses PostHog Logs (OTLP) and is significantly cheaper.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogEventsDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST\n *\n * @example\n * ```ts\n * nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({\n * eventName: 'server_request',\n * }))\n * ```\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {\n return defineDrain<PostHogEventsConfig>({\n name: 'posthog-events',\n resolve: () => {\n const config = resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')\n return null\n }\n return config as PostHogEventsConfig\n },\n send: sendBatchToPostHogEvents,\n })\n}\n\n/**\n * Send a single event to PostHog as a custom event.\n *\n * @example\n * ```ts\n * await sendToPostHogEvents(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog as custom events via the `/batch/` API.\n *\n * @example\n * ```ts\n * await sendBatchToPostHogEvents(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${resolveHost(config)}/batch/`\n const batch = events.map(event => toPostHogEvent(event, config))\n\n await httpPost({\n url,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: config.apiKey, batch }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;;AAkCA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,MAAM,wBAA4D;CAChE,GAAG;CACH,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACtB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AAEvD,QAAO;EACL,UAAU,GAFC,YAAY,OAAO,CAEZ;EAClB,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAA2C;CAC1F,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAE/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eAAe,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MAAc;EACnG;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AA4BH,SAAgB,mBAAmB,WAAoC;AACrE,QAAO,YAA2B;EAChC,MAAM;EACN,
|
|
1
|
+
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nexport interface PostHogEventsConfig extends PostHogConfig {\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n}\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst POSTHOG_EVENTS_FIELDS: ConfigField<PostHogEventsConfig>[] = [\n ...POSTHOG_FIELDS,\n { key: 'eventName' },\n { key: 'distinctId' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n const host = resolveHost(config)\n return {\n endpoint: `${host}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event format.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId ?? (typeof event.userId === 'string' ? event.userId : undefined) ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Logs (OTLP) — default\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog Logs via OTLP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_POSTHOG_API_KEY env var\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain({\n * apiKey: 'phc_...',\n * host: 'https://eu.i.posthog.com',\n * }))\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: sendBatchToPostHog,\n })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendToPostHog(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendBatchToPostHog(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Events (custom events via /batch/)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog as custom events.\n *\n * Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead\n * which uses PostHog Logs (OTLP) and is significantly cheaper.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogEventsDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST\n *\n * @example\n * ```ts\n * nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({\n * eventName: 'server_request',\n * }))\n * ```\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {\n return defineDrain<PostHogEventsConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')\n return null\n }\n return config as PostHogEventsConfig\n },\n send: sendBatchToPostHogEvents,\n })\n}\n\n/**\n * Send a single event to PostHog as a custom event.\n *\n * @example\n * ```ts\n * await sendToPostHogEvents(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog as custom events via the `/batch/` API.\n *\n * @example\n * ```ts\n * await sendBatchToPostHogEvents(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${resolveHost(config)}/batch/`\n const batch = events.map(event => toPostHogEvent(event, config))\n\n await httpPost({\n url,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: config.apiKey, batch }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;;AAkCA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,MAAM,wBAA4D;CAChE,GAAG;CACH,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACtB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AAEvD,QAAO;EACL,UAAU,GAFC,YAAY,OAAO,CAEZ;EAClB,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAA2C;CAC1F,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAE/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eAAe,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MAAc;EACnG;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AA4BH,SAAgB,mBAAmB,WAAoC;AACrE,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mHAAmH;AACjI,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;AA0BrD,SAAgB,yBAAyB,WAA0C;AACjF,QAAO,YAAiC;EACtC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAA0C,WAAW,uBAAuB,UAAU;AAC3G,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,gIAAgI;AAC9I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,oBAAoB,OAAkB,QAA4C;AACtG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAajD,eAAsB,yBAAyB,QAAqB,QAA4C;AAC9G,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,MAAM,GAAG,YAAY,OAAO,CAAC;CACnC,MAAM,QAAQ,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;AAEhE,OAAM,SAAS;EACb;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GAAE,SAAS,OAAO;GAAQ;GAAO,CAAC;EACvD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
|
package/dist/adapters/sentry.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as resolveAdapterConfig, t as httpPost } from "../_http-
|
|
2
|
-
import { t as defineDrain } from "../_drain-
|
|
1
|
+
import { n as resolveAdapterConfig, t as httpPost } from "../_http-rRIz2Q0L.mjs";
|
|
2
|
+
import { t as defineDrain } from "../_drain-CJDuM0ua.mjs";
|
|
3
3
|
import { t as OTEL_SEVERITY_NUMBER } from "../_severity-BLiOKoxh.mjs";
|
|
4
4
|
//#region src/adapters/sentry.ts
|
|
5
5
|
const SENTRY_FIELDS = [
|
|
@@ -167,8 +167,8 @@ function buildEnvelopeBody(logs, dsn) {
|
|
|
167
167
|
function createSentryDrain(overrides) {
|
|
168
168
|
return defineDrain({
|
|
169
169
|
name: "sentry",
|
|
170
|
-
resolve: () => {
|
|
171
|
-
const config = resolveAdapterConfig("sentry", SENTRY_FIELDS, overrides);
|
|
170
|
+
resolve: async () => {
|
|
171
|
+
const config = await resolveAdapterConfig("sentry", SENTRY_FIELDS, overrides);
|
|
172
172
|
if (!config.dsn) {
|
|
173
173
|
console.error("[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()");
|
|
174
174
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { OTEL_SEVERITY_NUMBER } from './_severity'\n\nexport interface SentryConfig {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineDrain<SentryConfig>({\n name: 'sentry',\n resolve: () => {\n const config = resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n send: sendBatchToSentry,\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { OTEL_SEVERITY_NUMBER } from './_severity'\n\nexport interface SentryConfig {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineDrain<SentryConfig>({\n name: 'sentry',\n resolve: async () => {\n const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n send: sendBatchToSentry,\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n })\n}\n"],"mappings":"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"}
|
package/dist/ai/index.d.mts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
|
-
import { g as RequestLogger } from "../types-
|
|
1
|
+
import { g as RequestLogger } from "../types-DbVDS9eu.mjs";
|
|
2
2
|
import { GatewayModelId } from "ai";
|
|
3
|
-
import { LanguageModelV3 } from "@ai-sdk/provider";
|
|
3
|
+
import { LanguageModelV3, LanguageModelV3Middleware } from "@ai-sdk/provider";
|
|
4
4
|
|
|
5
5
|
//#region src/ai/index.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Fine-grained control over tool call input capture.
|
|
8
|
+
*/
|
|
9
|
+
interface ToolInputsOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Max character length for the stringified input JSON.
|
|
12
|
+
* Inputs exceeding this limit are truncated with a `…` suffix.
|
|
13
|
+
*/
|
|
14
|
+
maxLength?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Custom transform applied to each captured input before storing.
|
|
17
|
+
* Receives the parsed input and tool name; return value is stored.
|
|
18
|
+
* Runs before `maxLength` truncation.
|
|
19
|
+
*/
|
|
20
|
+
transform?: (input: unknown, toolName: string) => unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for `createAILogger` and `createAIMiddleware`.
|
|
24
|
+
*/
|
|
25
|
+
interface AILoggerOptions {
|
|
26
|
+
/**
|
|
27
|
+
* When enabled, `toolCalls` contains `{ name, input }` objects instead of plain tool name strings.
|
|
28
|
+
* Opt-in because inputs can be large and may contain sensitive data.
|
|
29
|
+
*
|
|
30
|
+
* - `true` — capture all inputs as-is
|
|
31
|
+
* - `{ maxLength, transform }` — capture with truncation or custom transform
|
|
32
|
+
* @default false
|
|
33
|
+
*/
|
|
34
|
+
toolInputs?: boolean | ToolInputsOptions;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Per-step token usage breakdown for multi-step agent runs.
|
|
38
|
+
*/
|
|
39
|
+
interface AIStepUsage {
|
|
40
|
+
model: string;
|
|
41
|
+
inputTokens: number;
|
|
42
|
+
outputTokens: number;
|
|
43
|
+
toolCalls?: string[];
|
|
44
|
+
}
|
|
6
45
|
/**
|
|
7
46
|
* Shape of the `ai` field written to the wide event.
|
|
8
47
|
*/
|
|
@@ -18,8 +57,13 @@ interface AIEventData {
|
|
|
18
57
|
cacheWriteTokens?: number;
|
|
19
58
|
reasoningTokens?: number;
|
|
20
59
|
finishReason?: string;
|
|
21
|
-
toolCalls?: string[]
|
|
60
|
+
toolCalls?: string[] | Array<{
|
|
61
|
+
name: string;
|
|
62
|
+
input: unknown;
|
|
63
|
+
}>;
|
|
64
|
+
responseId?: string;
|
|
22
65
|
steps?: number;
|
|
66
|
+
stepsUsage?: AIStepUsage[];
|
|
23
67
|
msToFirstChunk?: number;
|
|
24
68
|
msToFinish?: number;
|
|
25
69
|
tokensPerSecond?: number;
|
|
@@ -34,6 +78,9 @@ interface AILogger {
|
|
|
34
78
|
* Accepts a `LanguageModelV3` object or a model string (e.g. `'anthropic/claude-sonnet-4.6'`).
|
|
35
79
|
* Strings are resolved via the AI SDK gateway.
|
|
36
80
|
*
|
|
81
|
+
* Also works with pre-wrapped models (e.g. from supermemory, guardrails):
|
|
82
|
+
* `ai.wrap(withSupermemory(base, orgId))` composes correctly.
|
|
83
|
+
*
|
|
37
84
|
* @example
|
|
38
85
|
* ```ts
|
|
39
86
|
* const ai = createAILogger(log)
|
|
@@ -61,6 +108,31 @@ interface AILogger {
|
|
|
61
108
|
};
|
|
62
109
|
}) => void;
|
|
63
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Create the evlog AI middleware that captures AI SDK data into a wide event.
|
|
113
|
+
*
|
|
114
|
+
* Use this when you need explicit middleware composition with other wrappers
|
|
115
|
+
* (e.g. supermemory, guardrails). For most cases, use `createAILogger` instead.
|
|
116
|
+
*
|
|
117
|
+
* Note: `captureEmbed` is not available with the raw middleware — use
|
|
118
|
+
* `createAILogger` if you need embedding capture.
|
|
119
|
+
*
|
|
120
|
+
* @example Nuxt API route with supermemory
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { createAIMiddleware } from 'evlog/ai'
|
|
123
|
+
* import { wrapLanguageModel } from 'ai'
|
|
124
|
+
*
|
|
125
|
+
* export default defineEventHandler(async (event) => {
|
|
126
|
+
* const log = useLogger(event)
|
|
127
|
+
*
|
|
128
|
+
* const model = wrapLanguageModel({
|
|
129
|
+
* model: withSupermemory(base, orgId),
|
|
130
|
+
* middleware: [createAIMiddleware(log, { toolInputs: true })],
|
|
131
|
+
* })
|
|
132
|
+
* })
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
declare function createAIMiddleware(log: RequestLogger, options?: AILoggerOptions): LanguageModelV3Middleware;
|
|
64
136
|
/**
|
|
65
137
|
* Create an AI logger that captures AI SDK data into the wide event.
|
|
66
138
|
*
|
|
@@ -81,8 +153,13 @@ interface AILogger {
|
|
|
81
153
|
* onFinish: ({ text }) => saveConversation(text),
|
|
82
154
|
* })
|
|
83
155
|
* ```
|
|
156
|
+
*
|
|
157
|
+
* @example Capture tool call inputs
|
|
158
|
+
* ```ts
|
|
159
|
+
* const ai = createAILogger(log, { toolInputs: true })
|
|
160
|
+
* ```
|
|
84
161
|
*/
|
|
85
|
-
declare function createAILogger(log: RequestLogger): AILogger;
|
|
162
|
+
declare function createAILogger(log: RequestLogger, options?: AILoggerOptions): AILogger;
|
|
86
163
|
//#endregion
|
|
87
|
-
export { AIEventData, AILogger, createAILogger };
|
|
164
|
+
export { AIEventData, AILogger, AILoggerOptions, AIStepUsage, ToolInputsOptions, createAILogger, createAIMiddleware };
|
|
88
165
|
//# 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,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;EACA,KAAA;EACA,cAAA;EACA,UAAA;EACA,eAAA;EACA,KAAA;AAAA;AAAA,UAGe,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;;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"}
|
package/dist/ai/index.mjs
CHANGED
|
@@ -24,6 +24,33 @@ function resolveProviderAndModel(provider, modelId) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Create the evlog AI middleware that captures AI SDK data into a wide event.
|
|
28
|
+
*
|
|
29
|
+
* Use this when you need explicit middleware composition with other wrappers
|
|
30
|
+
* (e.g. supermemory, guardrails). For most cases, use `createAILogger` instead.
|
|
31
|
+
*
|
|
32
|
+
* Note: `captureEmbed` is not available with the raw middleware — use
|
|
33
|
+
* `createAILogger` if you need embedding capture.
|
|
34
|
+
*
|
|
35
|
+
* @example Nuxt API route with supermemory
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { createAIMiddleware } from 'evlog/ai'
|
|
38
|
+
* import { wrapLanguageModel } from 'ai'
|
|
39
|
+
*
|
|
40
|
+
* export default defineEventHandler(async (event) => {
|
|
41
|
+
* const log = useLogger(event)
|
|
42
|
+
*
|
|
43
|
+
* const model = wrapLanguageModel({
|
|
44
|
+
* model: withSupermemory(base, orgId),
|
|
45
|
+
* middleware: [createAIMiddleware(log, { toolInputs: true })],
|
|
46
|
+
* })
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function createAIMiddleware(log, options) {
|
|
51
|
+
return buildMiddleware(log, options);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
27
54
|
* Create an AI logger that captures AI SDK data into the wide event.
|
|
28
55
|
*
|
|
29
56
|
* Uses model middleware (`wrapLanguageModel`) to transparently intercept
|
|
@@ -43,75 +70,173 @@ function resolveProviderAndModel(provider, modelId) {
|
|
|
43
70
|
* onFinish: ({ text }) => saveConversation(text),
|
|
44
71
|
* })
|
|
45
72
|
* ```
|
|
73
|
+
*
|
|
74
|
+
* @example Capture tool call inputs
|
|
75
|
+
* ```ts
|
|
76
|
+
* const ai = createAILogger(log, { toolInputs: true })
|
|
77
|
+
* ```
|
|
46
78
|
*/
|
|
47
|
-
function createAILogger(log) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let lastMsToFirstChunk;
|
|
62
|
-
let lastMsToFinish;
|
|
63
|
-
let lastError;
|
|
64
|
-
function flush() {
|
|
65
|
-
const uniqueModels = [...new Set(models)];
|
|
66
|
-
const lastModel = models[models.length - 1];
|
|
67
|
-
const lastProvider = providers[providers.length - 1];
|
|
68
|
-
const data = {
|
|
69
|
-
calls,
|
|
70
|
-
inputTokens: usage.inputTokens,
|
|
71
|
-
outputTokens: usage.outputTokens,
|
|
72
|
-
totalTokens: usage.inputTokens + usage.outputTokens
|
|
73
|
-
};
|
|
74
|
-
if (lastModel) data.model = lastModel;
|
|
75
|
-
if (lastProvider) data.provider = lastProvider;
|
|
76
|
-
if (uniqueModels.length > 1) data.models = uniqueModels;
|
|
77
|
-
if (usage.cacheReadTokens > 0) data.cacheReadTokens = usage.cacheReadTokens;
|
|
78
|
-
if (usage.cacheWriteTokens > 0) data.cacheWriteTokens = usage.cacheWriteTokens;
|
|
79
|
-
if (usage.reasoningTokens > 0) data.reasoningTokens = usage.reasoningTokens;
|
|
80
|
-
if (lastFinishReason) data.finishReason = lastFinishReason;
|
|
81
|
-
if (allToolCalls.length > 0) data.toolCalls = [...allToolCalls];
|
|
82
|
-
if (steps > 1) data.steps = steps;
|
|
83
|
-
if (lastMsToFirstChunk !== void 0) data.msToFirstChunk = lastMsToFirstChunk;
|
|
84
|
-
if (lastMsToFinish !== void 0) {
|
|
85
|
-
data.msToFinish = lastMsToFinish;
|
|
86
|
-
if (usage.outputTokens > 0 && lastMsToFinish > 0) data.tokensPerSecond = Math.round(usage.outputTokens / lastMsToFinish * 1e3);
|
|
79
|
+
function createAILogger(log, options) {
|
|
80
|
+
const state = createAccumulatorState(options);
|
|
81
|
+
const middleware = buildMiddlewareFromState(log, state);
|
|
82
|
+
return {
|
|
83
|
+
wrap: (model) => {
|
|
84
|
+
return wrapLanguageModel({
|
|
85
|
+
model: typeof model === "string" ? gateway(model) : model,
|
|
86
|
+
middleware
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
captureEmbed: (result) => {
|
|
90
|
+
state.calls++;
|
|
91
|
+
state.usage.inputTokens += result.usage.tokens;
|
|
92
|
+
flushState(log, state);
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function resolveToolInputs(raw) {
|
|
97
|
+
if (!raw) return {
|
|
98
|
+
enabled: false,
|
|
99
|
+
options: void 0
|
|
100
|
+
};
|
|
101
|
+
if (raw === true) return {
|
|
102
|
+
enabled: true,
|
|
103
|
+
options: void 0
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
enabled: true,
|
|
107
|
+
options: raw
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function processToolInput(input, toolName, options) {
|
|
111
|
+
let value = input;
|
|
112
|
+
if (options?.transform) value = options.transform(value, toolName);
|
|
113
|
+
if (options?.maxLength) {
|
|
114
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
115
|
+
if (str.length > options.maxLength) return `${str.slice(0, options.maxLength)}…`;
|
|
90
116
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
function createAccumulatorState(options) {
|
|
120
|
+
const { enabled, options: captureOpts } = resolveToolInputs(options?.toolInputs);
|
|
121
|
+
return {
|
|
122
|
+
calls: 0,
|
|
123
|
+
steps: 0,
|
|
124
|
+
usage: {
|
|
125
|
+
inputTokens: 0,
|
|
126
|
+
outputTokens: 0,
|
|
127
|
+
cacheReadTokens: 0,
|
|
128
|
+
cacheWriteTokens: 0,
|
|
129
|
+
reasoningTokens: 0
|
|
130
|
+
},
|
|
131
|
+
models: [],
|
|
132
|
+
lastProvider: void 0,
|
|
133
|
+
allToolCalls: [],
|
|
134
|
+
allToolCallInputs: [],
|
|
135
|
+
stepsUsage: [],
|
|
136
|
+
lastFinishReason: void 0,
|
|
137
|
+
lastMsToFirstChunk: void 0,
|
|
138
|
+
lastMsToFinish: void 0,
|
|
139
|
+
lastError: void 0,
|
|
140
|
+
lastResponseId: void 0,
|
|
141
|
+
toolInputs: enabled,
|
|
142
|
+
toolInputsOptions: captureOpts
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function flushState(log, state) {
|
|
146
|
+
const uniqueModels = [...new Set(state.models)];
|
|
147
|
+
const lastModel = state.models[state.models.length - 1];
|
|
148
|
+
const data = {
|
|
149
|
+
calls: state.calls,
|
|
150
|
+
inputTokens: state.usage.inputTokens,
|
|
151
|
+
outputTokens: state.usage.outputTokens,
|
|
152
|
+
totalTokens: state.usage.inputTokens + state.usage.outputTokens
|
|
153
|
+
};
|
|
154
|
+
if (lastModel) data.model = lastModel;
|
|
155
|
+
if (state.lastProvider) data.provider = state.lastProvider;
|
|
156
|
+
if (uniqueModels.length > 1) data.models = uniqueModels;
|
|
157
|
+
if (state.usage.cacheReadTokens > 0) data.cacheReadTokens = state.usage.cacheReadTokens;
|
|
158
|
+
if (state.usage.cacheWriteTokens > 0) data.cacheWriteTokens = state.usage.cacheWriteTokens;
|
|
159
|
+
if (state.usage.reasoningTokens > 0) data.reasoningTokens = state.usage.reasoningTokens;
|
|
160
|
+
if (state.lastFinishReason) data.finishReason = state.lastFinishReason;
|
|
161
|
+
if (state.toolInputs && state.allToolCallInputs.length > 0) data.toolCalls = [...state.allToolCallInputs];
|
|
162
|
+
else if (state.allToolCalls.length > 0) data.toolCalls = [...state.allToolCalls];
|
|
163
|
+
if (state.lastResponseId) data.responseId = state.lastResponseId;
|
|
164
|
+
if (state.steps > 1) {
|
|
165
|
+
data.steps = state.steps;
|
|
166
|
+
data.stepsUsage = [...state.stepsUsage];
|
|
167
|
+
}
|
|
168
|
+
if (state.lastMsToFirstChunk !== void 0) data.msToFirstChunk = state.lastMsToFirstChunk;
|
|
169
|
+
if (state.lastMsToFinish !== void 0) {
|
|
170
|
+
data.msToFinish = state.lastMsToFinish;
|
|
171
|
+
if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) data.tokensPerSecond = Math.round(state.usage.outputTokens / state.lastMsToFinish * 1e3);
|
|
172
|
+
}
|
|
173
|
+
if (state.lastError) data.error = state.lastError;
|
|
174
|
+
log.set({ ai: data });
|
|
175
|
+
}
|
|
176
|
+
function recordModel(state, provider, modelId, responseModelId) {
|
|
177
|
+
const resolved = resolveProviderAndModel(provider, responseModelId ?? modelId);
|
|
178
|
+
state.models.push(resolved.model);
|
|
179
|
+
state.lastProvider = resolved.provider;
|
|
180
|
+
}
|
|
181
|
+
function safeParseJSON(input) {
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(input);
|
|
184
|
+
} catch {
|
|
185
|
+
return input;
|
|
95
186
|
}
|
|
96
|
-
|
|
187
|
+
}
|
|
188
|
+
function recordError(log, state, model, error) {
|
|
189
|
+
state.calls++;
|
|
190
|
+
state.steps++;
|
|
191
|
+
recordModel(state, model.provider, model.modelId);
|
|
192
|
+
state.lastFinishReason = "error";
|
|
193
|
+
state.lastError = error instanceof Error ? error.message : String(error);
|
|
194
|
+
const resolved = resolveProviderAndModel(model.provider, model.modelId);
|
|
195
|
+
state.stepsUsage.push({
|
|
196
|
+
model: resolved.model,
|
|
197
|
+
inputTokens: 0,
|
|
198
|
+
outputTokens: 0
|
|
199
|
+
});
|
|
200
|
+
flushState(log, state);
|
|
201
|
+
}
|
|
202
|
+
function buildMiddleware(log, options) {
|
|
203
|
+
return buildMiddlewareFromState(log, createAccumulatorState(options));
|
|
204
|
+
}
|
|
205
|
+
function buildMiddlewareFromState(log, state) {
|
|
206
|
+
return {
|
|
207
|
+
specificationVersion: "v3",
|
|
97
208
|
wrapGenerate: async ({ doGenerate, model }) => {
|
|
98
209
|
try {
|
|
99
210
|
const result = await doGenerate();
|
|
100
|
-
calls++;
|
|
101
|
-
steps++;
|
|
102
|
-
addUsage(usage, result.usage);
|
|
103
|
-
recordModel(model.provider, model.modelId, result.response?.modelId);
|
|
104
|
-
lastFinishReason = result.finishReason.unified;
|
|
105
|
-
|
|
106
|
-
|
|
211
|
+
state.calls++;
|
|
212
|
+
state.steps++;
|
|
213
|
+
addUsage(state.usage, result.usage);
|
|
214
|
+
recordModel(state, model.provider, model.modelId, result.response?.modelId);
|
|
215
|
+
state.lastFinishReason = result.finishReason.unified;
|
|
216
|
+
if (result.response?.id) state.lastResponseId = result.response.id;
|
|
217
|
+
const stepToolCalls = [];
|
|
218
|
+
for (const item of result.content) if (item.type === "tool-call") {
|
|
219
|
+
state.allToolCalls.push(item.toolName);
|
|
220
|
+
stepToolCalls.push(item.toolName);
|
|
221
|
+
if (state.toolInputs) {
|
|
222
|
+
const raw = typeof item.input === "string" ? safeParseJSON(item.input) : item.input;
|
|
223
|
+
state.allToolCallInputs.push({
|
|
224
|
+
name: item.toolName,
|
|
225
|
+
input: processToolInput(raw, item.toolName, state.toolInputsOptions)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const resolvedModel = resolveProviderAndModel(model.provider, result.response?.modelId ?? model.modelId);
|
|
230
|
+
state.stepsUsage.push({
|
|
231
|
+
model: resolvedModel.model,
|
|
232
|
+
inputTokens: result.usage.inputTokens.total ?? 0,
|
|
233
|
+
outputTokens: result.usage.outputTokens.total ?? 0,
|
|
234
|
+
...stepToolCalls.length > 0 ? { toolCalls: stepToolCalls } : {}
|
|
235
|
+
});
|
|
236
|
+
flushState(log, state);
|
|
107
237
|
return result;
|
|
108
238
|
} catch (error) {
|
|
109
|
-
|
|
110
|
-
steps++;
|
|
111
|
-
recordModel(model.provider, model.modelId);
|
|
112
|
-
lastFinishReason = "error";
|
|
113
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
114
|
-
flush();
|
|
239
|
+
recordError(log, state, model, error);
|
|
115
240
|
throw error;
|
|
116
241
|
}
|
|
117
242
|
},
|
|
@@ -121,25 +246,43 @@ function createAILogger(log) {
|
|
|
121
246
|
let streamUsage;
|
|
122
247
|
let streamFinishReason;
|
|
123
248
|
let streamModelId;
|
|
249
|
+
let streamResponseId;
|
|
124
250
|
const streamToolCalls = [];
|
|
251
|
+
const streamToolInputBuffers = /* @__PURE__ */ new Map();
|
|
125
252
|
let streamError;
|
|
126
253
|
let doStreamResult;
|
|
127
254
|
try {
|
|
128
255
|
doStreamResult = await doStream();
|
|
129
256
|
} catch (error) {
|
|
130
|
-
|
|
131
|
-
steps++;
|
|
132
|
-
recordModel(model.provider, model.modelId);
|
|
133
|
-
lastFinishReason = "error";
|
|
134
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
135
|
-
flush();
|
|
257
|
+
recordError(log, state, model, error);
|
|
136
258
|
throw error;
|
|
137
259
|
}
|
|
138
260
|
const { stream, ...rest } = doStreamResult;
|
|
139
261
|
const transformStream = new TransformStream({
|
|
140
262
|
transform(chunk, controller) {
|
|
141
263
|
if (!firstChunkTime && chunk.type === "text-delta") firstChunkTime = Date.now();
|
|
142
|
-
if (chunk.type === "tool-input-start")
|
|
264
|
+
if (chunk.type === "tool-input-start") {
|
|
265
|
+
streamToolCalls.push(chunk.toolName);
|
|
266
|
+
if (state.toolInputs) streamToolInputBuffers.set(chunk.id, {
|
|
267
|
+
name: chunk.toolName,
|
|
268
|
+
chunks: []
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (chunk.type === "tool-input-delta" && state.toolInputs) {
|
|
272
|
+
const buffer = streamToolInputBuffers.get(chunk.id);
|
|
273
|
+
if (buffer) buffer.chunks.push(chunk.delta);
|
|
274
|
+
}
|
|
275
|
+
if (chunk.type === "tool-input-end" && state.toolInputs) {
|
|
276
|
+
const buffer = streamToolInputBuffers.get(chunk.id);
|
|
277
|
+
if (buffer) {
|
|
278
|
+
const raw = safeParseJSON(buffer.chunks.join(""));
|
|
279
|
+
state.allToolCallInputs.push({
|
|
280
|
+
name: buffer.name,
|
|
281
|
+
input: processToolInput(raw, buffer.name, state.toolInputsOptions)
|
|
282
|
+
});
|
|
283
|
+
streamToolInputBuffers.delete(chunk.id);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
143
286
|
if (chunk.type === "finish") {
|
|
144
287
|
streamUsage = {
|
|
145
288
|
inputTokens: chunk.usage.inputTokens.total ?? 0,
|
|
@@ -150,27 +293,38 @@ function createAILogger(log) {
|
|
|
150
293
|
};
|
|
151
294
|
streamFinishReason = chunk.finishReason.unified;
|
|
152
295
|
}
|
|
153
|
-
if (chunk.type === "response-metadata"
|
|
296
|
+
if (chunk.type === "response-metadata") {
|
|
297
|
+
if (chunk.modelId) streamModelId = chunk.modelId;
|
|
298
|
+
if (chunk.id) streamResponseId = chunk.id;
|
|
299
|
+
}
|
|
154
300
|
if (chunk.type === "error") streamError = chunk.error instanceof Error ? chunk.error.message : String(chunk.error);
|
|
155
301
|
controller.enqueue(chunk);
|
|
156
302
|
},
|
|
157
303
|
flush() {
|
|
158
|
-
calls++;
|
|
159
|
-
steps++;
|
|
304
|
+
state.calls++;
|
|
305
|
+
state.steps++;
|
|
160
306
|
if (streamUsage) {
|
|
161
|
-
usage.inputTokens += streamUsage.inputTokens;
|
|
162
|
-
usage.outputTokens += streamUsage.outputTokens;
|
|
163
|
-
usage.cacheReadTokens += streamUsage.cacheReadTokens;
|
|
164
|
-
usage.cacheWriteTokens += streamUsage.cacheWriteTokens;
|
|
165
|
-
usage.reasoningTokens += streamUsage.reasoningTokens;
|
|
307
|
+
state.usage.inputTokens += streamUsage.inputTokens;
|
|
308
|
+
state.usage.outputTokens += streamUsage.outputTokens;
|
|
309
|
+
state.usage.cacheReadTokens += streamUsage.cacheReadTokens;
|
|
310
|
+
state.usage.cacheWriteTokens += streamUsage.cacheWriteTokens;
|
|
311
|
+
state.usage.reasoningTokens += streamUsage.reasoningTokens;
|
|
166
312
|
}
|
|
167
|
-
recordModel(model.provider, model.modelId, streamModelId);
|
|
168
|
-
lastFinishReason = streamFinishReason;
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
313
|
+
recordModel(state, model.provider, model.modelId, streamModelId);
|
|
314
|
+
state.lastFinishReason = streamFinishReason;
|
|
315
|
+
state.allToolCalls.push(...streamToolCalls);
|
|
316
|
+
if (streamResponseId) state.lastResponseId = streamResponseId;
|
|
317
|
+
if (firstChunkTime) state.lastMsToFirstChunk = firstChunkTime - streamStart;
|
|
318
|
+
state.lastMsToFinish = Date.now() - streamStart;
|
|
319
|
+
if (streamError) state.lastError = streamError;
|
|
320
|
+
const resolvedModel = resolveProviderAndModel(model.provider, streamModelId ?? model.modelId);
|
|
321
|
+
state.stepsUsage.push({
|
|
322
|
+
model: resolvedModel.model,
|
|
323
|
+
inputTokens: streamUsage?.inputTokens ?? 0,
|
|
324
|
+
outputTokens: streamUsage?.outputTokens ?? 0,
|
|
325
|
+
...streamToolCalls.length > 0 ? { toolCalls: [...streamToolCalls] } : {}
|
|
326
|
+
});
|
|
327
|
+
flushState(log, state);
|
|
174
328
|
}
|
|
175
329
|
});
|
|
176
330
|
return {
|
|
@@ -179,21 +333,8 @@ function createAILogger(log) {
|
|
|
179
333
|
};
|
|
180
334
|
}
|
|
181
335
|
};
|
|
182
|
-
return {
|
|
183
|
-
wrap: (model) => {
|
|
184
|
-
return wrapLanguageModel({
|
|
185
|
-
model: typeof model === "string" ? gateway(model) : model,
|
|
186
|
-
middleware
|
|
187
|
-
});
|
|
188
|
-
},
|
|
189
|
-
captureEmbed: (result) => {
|
|
190
|
-
calls++;
|
|
191
|
-
usage.inputTokens += result.usage.tokens;
|
|
192
|
-
flush();
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
336
|
}
|
|
196
337
|
//#endregion
|
|
197
|
-
export { createAILogger };
|
|
338
|
+
export { createAILogger, createAIMiddleware };
|
|
198
339
|
|
|
199
340
|
//# sourceMappingURL=index.mjs.map
|