@agentcash/telemetry 0.5.3 → 0.5.4
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/dist/router-plugin.js +40 -13
- package/dist/router-plugin.js.map +1 -1
- package/dist/router-plugin.mjs +32 -5
- package/dist/router-plugin.mjs.map +1 -1
- package/package.json +1 -1
package/dist/router-plugin.js
CHANGED
|
@@ -37,12 +37,34 @@ function formatNetwork(network) {
|
|
|
37
37
|
if (network === "tempo:4217") return "Tempo";
|
|
38
38
|
return network;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function consoleForAlertLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") {
|
|
42
|
+
return console.error;
|
|
43
|
+
}
|
|
44
|
+
if (level === "warn" || level === "warning") {
|
|
45
|
+
return console.warn;
|
|
46
|
+
}
|
|
47
|
+
return console.log;
|
|
48
|
+
}
|
|
49
|
+
function consoleForQuotaLevel(level) {
|
|
41
50
|
if (level === "critical" || level === "error" || level === "warn" || level === "warning") {
|
|
42
51
|
return console.warn;
|
|
43
52
|
}
|
|
44
53
|
return console.log;
|
|
45
54
|
}
|
|
55
|
+
function requestLogContext(ctx) {
|
|
56
|
+
const meta = ctx._meta;
|
|
57
|
+
const headers = _nullishCoalesce(_optionalChain([meta, 'optionalAccess', _ => _.headers]), () => ( {}));
|
|
58
|
+
return {
|
|
59
|
+
requestId: ctx.requestId,
|
|
60
|
+
vercelRequestId: _nullishCoalesce(headers["x-vercel-id"], () => ( null)),
|
|
61
|
+
traceparent: _nullishCoalesce(headers.traceparent, () => ( null)),
|
|
62
|
+
walletAddress: ctx.walletAddress,
|
|
63
|
+
verifiedWallet: ctx.verifiedWallet,
|
|
64
|
+
clientId: ctx.clientId,
|
|
65
|
+
sessionId: ctx.sessionId
|
|
66
|
+
};
|
|
67
|
+
}
|
|
46
68
|
function createTelemetryPlugin(config) {
|
|
47
69
|
_chunkSYJ7254Vjs.initClickhouse.call(void 0, config.clickhouse);
|
|
48
70
|
if (config.verify) {
|
|
@@ -111,10 +133,10 @@ function createTelemetryPlugin(config) {
|
|
|
111
133
|
}
|
|
112
134
|
const row = {
|
|
113
135
|
id: meta.requestId,
|
|
114
|
-
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access',
|
|
136
|
+
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access', _2 => _2.walletAddress, 'optionalAccess', _3 => _3.toLowerCase, 'call', _4 => _4()]), () => ( null)),
|
|
115
137
|
x_client_id: meta.clientId,
|
|
116
138
|
session_id: meta.sessionId,
|
|
117
|
-
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access',
|
|
139
|
+
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access', _5 => _5.verifiedWallet, 'optionalAccess', _6 => _6.toLowerCase, 'call', _7 => _7()]), () => ( null)),
|
|
118
140
|
method: meta.method,
|
|
119
141
|
route: meta.route,
|
|
120
142
|
origin: meta.origin,
|
|
@@ -128,13 +150,13 @@ function createTelemetryPlugin(config) {
|
|
|
128
150
|
response_content_type: response.contentType,
|
|
129
151
|
response_headers: safeStringify(response.headers),
|
|
130
152
|
response_body: safeStringify(response.responseBody),
|
|
131
|
-
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access',
|
|
132
|
-
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access',
|
|
153
|
+
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _8 => _8._payment, 'optionalAccess', _9 => _9.protocol]), () => ( _optionalChain([tCtx, 'access', _10 => _10._settlement, 'optionalAccess', _11 => _11.protocol]))), () => ( null)),
|
|
154
|
+
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access', _12 => _12._payment, 'optionalAccess', _13 => _13.amount]), () => ( null)),
|
|
133
155
|
payment_network: (() => {
|
|
134
|
-
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access',
|
|
156
|
+
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _14 => _14._payment, 'optionalAccess', _15 => _15.network]), () => ( _optionalChain([tCtx, 'access', _16 => _16._settlement, 'optionalAccess', _17 => _17.network]))), () => ( null));
|
|
135
157
|
return n ? formatNetwork(n) : null;
|
|
136
158
|
})(),
|
|
137
|
-
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access',
|
|
159
|
+
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _18 => _18._settlement, 'optionalAccess', _19 => _19.transaction]), () => ( null)),
|
|
138
160
|
created_at: /* @__PURE__ */ new Date()
|
|
139
161
|
};
|
|
140
162
|
deferInsert(() => _chunkSYJ7254Vjs.insertInvocation.call(void 0, row));
|
|
@@ -150,6 +172,7 @@ function createTelemetryPlugin(config) {
|
|
|
150
172
|
logFn(
|
|
151
173
|
`[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,
|
|
152
174
|
{
|
|
175
|
+
...requestLogContext(ctx),
|
|
153
176
|
requestId,
|
|
154
177
|
method: error.method,
|
|
155
178
|
duration: error.duration,
|
|
@@ -165,19 +188,23 @@ function createTelemetryPlugin(config) {
|
|
|
165
188
|
},
|
|
166
189
|
onAlert(ctx, alert) {
|
|
167
190
|
if (log) {
|
|
168
|
-
const logFn =
|
|
169
|
-
const id = _optionalChain([ctx, 'optionalAccess',
|
|
191
|
+
const logFn = consoleForAlertLevel(alert.level);
|
|
192
|
+
const id = _optionalChain([ctx, 'optionalAccess', _20 => _20.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
170
193
|
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
194
|
+
const requestMeta = {
|
|
195
|
+
...requestLogContext(ctx),
|
|
196
|
+
...meta
|
|
197
|
+
};
|
|
171
198
|
logFn(
|
|
172
|
-
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
173
|
-
|
|
199
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message} requestId=${ctx.requestId}`,
|
|
200
|
+
requestMeta
|
|
174
201
|
);
|
|
175
202
|
}
|
|
176
203
|
},
|
|
177
204
|
onProviderQuota(ctx, event) {
|
|
178
205
|
if (log) {
|
|
179
|
-
const logFn =
|
|
180
|
-
const id = _optionalChain([ctx, 'optionalAccess',
|
|
206
|
+
const logFn = consoleForQuotaLevel(event.level);
|
|
207
|
+
const id = _optionalChain([ctx, 'optionalAccess', _21 => _21.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
181
208
|
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
182
209
|
logFn(
|
|
183
210
|
`[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Router alerts report internal failures; error/critical alerts should page like errors. */\nfunction consoleForAlertLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') {\n return console.error;\n }\n if (level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\n/** Quota hooks are diagnostic context, not necessarily request failures. */\nfunction consoleForQuotaLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nfunction requestLogContext(ctx: PluginContext) {\n const meta = (ctx as Partial<TelemetryPluginContext>)._meta;\n const headers = meta?.headers ?? {};\n\n return {\n requestId: ctx.requestId,\n vercelRequestId: headers['x-vercel-id'] ?? null,\n traceparent: headers.traceparent ?? null,\n walletAddress: ctx.walletAddress,\n verifiedWallet: ctx.verifiedWallet,\n clientId: ctx.clientId,\n sessionId: ctx.sessionId,\n };\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n ...requestLogContext(ctx),\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForAlertLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n const requestMeta = {\n ...requestLogContext(ctx),\n ...meta,\n };\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message} requestId=${ctx.requestId}`,\n requestMeta,\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForQuotaLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"]}
|
package/dist/router-plugin.mjs
CHANGED
|
@@ -37,12 +37,34 @@ function formatNetwork(network) {
|
|
|
37
37
|
if (network === "tempo:4217") return "Tempo";
|
|
38
38
|
return network;
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function consoleForAlertLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") {
|
|
42
|
+
return console.error;
|
|
43
|
+
}
|
|
44
|
+
if (level === "warn" || level === "warning") {
|
|
45
|
+
return console.warn;
|
|
46
|
+
}
|
|
47
|
+
return console.log;
|
|
48
|
+
}
|
|
49
|
+
function consoleForQuotaLevel(level) {
|
|
41
50
|
if (level === "critical" || level === "error" || level === "warn" || level === "warning") {
|
|
42
51
|
return console.warn;
|
|
43
52
|
}
|
|
44
53
|
return console.log;
|
|
45
54
|
}
|
|
55
|
+
function requestLogContext(ctx) {
|
|
56
|
+
const meta = ctx._meta;
|
|
57
|
+
const headers = meta?.headers ?? {};
|
|
58
|
+
return {
|
|
59
|
+
requestId: ctx.requestId,
|
|
60
|
+
vercelRequestId: headers["x-vercel-id"] ?? null,
|
|
61
|
+
traceparent: headers.traceparent ?? null,
|
|
62
|
+
walletAddress: ctx.walletAddress,
|
|
63
|
+
verifiedWallet: ctx.verifiedWallet,
|
|
64
|
+
clientId: ctx.clientId,
|
|
65
|
+
sessionId: ctx.sessionId
|
|
66
|
+
};
|
|
67
|
+
}
|
|
46
68
|
function createTelemetryPlugin(config) {
|
|
47
69
|
initClickhouse(config.clickhouse);
|
|
48
70
|
if (config.verify) {
|
|
@@ -150,6 +172,7 @@ function createTelemetryPlugin(config) {
|
|
|
150
172
|
logFn(
|
|
151
173
|
`[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,
|
|
152
174
|
{
|
|
175
|
+
...requestLogContext(ctx),
|
|
153
176
|
requestId,
|
|
154
177
|
method: error.method,
|
|
155
178
|
duration: error.duration,
|
|
@@ -165,18 +188,22 @@ function createTelemetryPlugin(config) {
|
|
|
165
188
|
},
|
|
166
189
|
onAlert(ctx, alert) {
|
|
167
190
|
if (log) {
|
|
168
|
-
const logFn =
|
|
191
|
+
const logFn = consoleForAlertLevel(alert.level);
|
|
169
192
|
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
170
193
|
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
194
|
+
const requestMeta = {
|
|
195
|
+
...requestLogContext(ctx),
|
|
196
|
+
...meta
|
|
197
|
+
};
|
|
171
198
|
logFn(
|
|
172
|
-
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
173
|
-
|
|
199
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message} requestId=${ctx.requestId}`,
|
|
200
|
+
requestMeta
|
|
174
201
|
);
|
|
175
202
|
}
|
|
176
203
|
},
|
|
177
204
|
onProviderQuota(ctx, event) {
|
|
178
205
|
if (log) {
|
|
179
|
-
const logFn =
|
|
206
|
+
const logFn = consoleForQuotaLevel(event.level);
|
|
180
207
|
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
181
208
|
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
182
209
|
logFn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Router alerts report internal failures; error/critical alerts should page like errors. */\nfunction consoleForAlertLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') {\n return console.error;\n }\n if (level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\n/** Quota hooks are diagnostic context, not necessarily request failures. */\nfunction consoleForQuotaLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nfunction requestLogContext(ctx: PluginContext) {\n const meta = (ctx as Partial<TelemetryPluginContext>)._meta;\n const headers = meta?.headers ?? {};\n\n return {\n requestId: ctx.requestId,\n vercelRequestId: headers['x-vercel-id'] ?? null,\n traceparent: headers.traceparent ?? null,\n walletAddress: ctx.walletAddress,\n verifiedWallet: ctx.verifiedWallet,\n clientId: ctx.clientId,\n sessionId: ctx.sessionId,\n };\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n ...requestLogContext(ctx),\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForAlertLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n const requestMeta = {\n ...requestLogContext(ctx),\n ...meta,\n };\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message} requestId=${ctx.requestId}`,\n requestMeta,\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForQuotaLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/telemetry",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "ClickHouse telemetry plugin for @agentcash/router. Logs request lifecycle, payments, settlements, and provider quota to ClickHouse.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|