@agentcash/telemetry 0.5.1 → 0.5.2
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/builder.js +5 -5
- package/dist/builder.mjs +2 -2
- package/dist/{chunk-5JJYROO4.mjs → chunk-IA66E7KQ.mjs} +2 -2
- package/dist/{chunk-MBH3LCBQ.js → chunk-MGILRTLU.js} +5 -5
- package/dist/{chunk-MBH3LCBQ.js.map → chunk-MGILRTLU.js.map} +1 -1
- package/dist/{chunk-47QZYT7Q.js → chunk-QQAIZOLL.js} +5 -5
- package/dist/{chunk-47QZYT7Q.js.map → chunk-QQAIZOLL.js.map} +1 -1
- package/dist/{chunk-MJIU4ZVL.mjs → chunk-SSFH754L.mjs} +2 -2
- package/dist/{chunk-QEJ7ZGGH.js → chunk-TGF67ZBD.js} +7 -7
- package/dist/{chunk-QEJ7ZGGH.js.map → chunk-TGF67ZBD.js.map} +1 -1
- package/dist/{chunk-V553T6WE.mjs → chunk-YWUHTPJC.mjs} +7 -7
- package/dist/chunk-YWUHTPJC.mjs.map +1 -0
- package/dist/index.js +4 -4
- package/dist/index.mjs +3 -3
- package/dist/router-plugin.d.mts +12 -1
- package/dist/router-plugin.d.ts +12 -1
- package/dist/router-plugin.js +23 -8
- package/dist/router-plugin.js.map +1 -1
- package/dist/router-plugin.mjs +20 -5
- package/dist/router-plugin.mjs.map +1 -1
- package/dist/siwx.js +4 -4
- package/dist/siwx.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-V553T6WE.mjs.map +0 -1
- /package/dist/{chunk-5JJYROO4.mjs.map → chunk-IA66E7KQ.mjs.map} +0 -0
- /package/dist/{chunk-MJIU4ZVL.mjs.map → chunk-SSFH754L.mjs.map} +0 -0
package/dist/builder.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
6
|
-
require('./chunk-
|
|
5
|
+
var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
|
|
6
|
+
require('./chunk-TGF67ZBD.js');
|
|
7
7
|
|
|
8
8
|
// src/route-builder.ts
|
|
9
9
|
var _server = require('next/server');
|
|
@@ -84,10 +84,10 @@ var RouteBuilder = class _RouteBuilder {
|
|
|
84
84
|
handler(fn) {
|
|
85
85
|
const { accepts, bodySchema, querySchema, outputSchema, outputExample, description } = this.config;
|
|
86
86
|
const coreHandler = async (request) => {
|
|
87
|
-
const meta =
|
|
88
|
-
const ctx =
|
|
87
|
+
const meta = _chunkMGILRTLUjs.extractRequestMeta.call(void 0, request);
|
|
88
|
+
const ctx = _chunkMGILRTLUjs.buildTelemetryContext.call(void 0, meta);
|
|
89
89
|
const log = (status, responseBody, resp) => {
|
|
90
|
-
|
|
90
|
+
_chunkMGILRTLUjs.recordInvocation.call(void 0, meta, requestBodyString, {
|
|
91
91
|
status,
|
|
92
92
|
body: responseBody,
|
|
93
93
|
headers: JSON.stringify(Object.fromEntries(resp.headers.entries())),
|
package/dist/builder.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
buildTelemetryContext,
|
|
3
3
|
extractRequestMeta,
|
|
4
4
|
recordInvocation
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-SSFH754L.mjs";
|
|
6
|
+
import "./chunk-YWUHTPJC.mjs";
|
|
7
7
|
|
|
8
8
|
// src/route-builder.ts
|
|
9
9
|
import { NextResponse } from "next/server";
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
buildTelemetryContext,
|
|
3
3
|
extractRequestMeta,
|
|
4
4
|
recordInvocation
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SSFH754L.mjs";
|
|
6
6
|
|
|
7
7
|
// src/telemetry.ts
|
|
8
8
|
import { NextResponse } from "next/server";
|
|
@@ -63,4 +63,4 @@ function withTelemetry(handler) {
|
|
|
63
63
|
export {
|
|
64
64
|
withTelemetry
|
|
65
65
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-IA66E7KQ.mjs.map
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkTGF67ZBDjs = require('./chunk-TGF67ZBD.js');
|
|
6
6
|
|
|
7
7
|
// src/init.ts
|
|
8
8
|
var configuredOrigin;
|
|
9
9
|
function initTelemetry(config) {
|
|
10
|
-
|
|
10
|
+
_chunkTGF67ZBDjs.initClickhouse.call(void 0, config.clickhouse);
|
|
11
11
|
if (config.origin) {
|
|
12
12
|
configuredOrigin = config.origin;
|
|
13
13
|
}
|
|
14
14
|
if (config.verify) {
|
|
15
|
-
|
|
15
|
+
_chunkTGF67ZBDjs.pingClickhouse.call(void 0, );
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
function getOrigin() {
|
|
@@ -112,7 +112,7 @@ function recordInvocation(meta, requestBody, response) {
|
|
|
112
112
|
payment_tx_hash: null,
|
|
113
113
|
created_at: /* @__PURE__ */ new Date()
|
|
114
114
|
};
|
|
115
|
-
|
|
115
|
+
_chunkTGF67ZBDjs.insertInvocation.call(void 0, invocation);
|
|
116
116
|
} catch (e4) {
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -150,4 +150,4 @@ function statusTextFromCode(code) {
|
|
|
150
150
|
|
|
151
151
|
|
|
152
152
|
exports.initTelemetry = initTelemetry; exports.extractVerifiedWallet = extractVerifiedWallet; exports.extractRequestMeta = extractRequestMeta; exports.buildTelemetryContext = buildTelemetryContext; exports.recordInvocation = recordInvocation;
|
|
153
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-MGILRTLU.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MBH3LCBQ.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MBH3LCBQ.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\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 */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fire-and-forget, fully wrapped in try/catch.\n */\nexport function recordInvocation(\n meta: RequestMeta,\n requestBody: string | null,\n response: {\n status: number;\n body: string | null;\n headers: string | null;\n contentType: string | null;\n },\n): void {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MGILRTLU.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MGILRTLU.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\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 */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fire-and-forget, fully wrapped in try/catch.\n */\nexport function recordInvocation(\n meta: RequestMeta,\n requestBody: string | null,\n response: {\n status: number;\n body: string | null;\n headers: string | null;\n contentType: string | null;\n },\n): void {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"]}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
|
|
6
6
|
|
|
7
7
|
// src/telemetry.ts
|
|
8
8
|
var _server = require('next/server');
|
|
9
9
|
|
|
10
10
|
function withTelemetry(handler) {
|
|
11
11
|
return async (request) => {
|
|
12
|
-
const meta =
|
|
13
|
-
const ctx =
|
|
12
|
+
const meta = _chunkMGILRTLUjs.extractRequestMeta.call(void 0, request);
|
|
13
|
+
const ctx = _chunkMGILRTLUjs.buildTelemetryContext.call(void 0, meta);
|
|
14
14
|
let requestBodyString = null;
|
|
15
15
|
if (meta.method === "POST" || meta.method === "PUT" || meta.method === "PATCH") {
|
|
16
16
|
try {
|
|
@@ -43,7 +43,7 @@ function withTelemetry(handler) {
|
|
|
43
43
|
}
|
|
44
44
|
_server.after.call(void 0, () => {
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
_chunkMGILRTLUjs.recordInvocation.call(void 0, meta, requestBodyString, {
|
|
47
47
|
status,
|
|
48
48
|
body: responseBodyString,
|
|
49
49
|
headers: responseHeaders,
|
|
@@ -63,4 +63,4 @@ function withTelemetry(handler) {
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
exports.withTelemetry = withTelemetry;
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
66
|
+
//# sourceMappingURL=chunk-QQAIZOLL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QQAIZOLL.js","../src/telemetry.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACAA,qCAA+C;AAC/C;AAkBO,SAAS,aAAA,CAAc,OAAA,EAA2B;AACvD,EAAA,OAAO,MAAA,CAAO,OAAA,EAAA,GAAgD;AAC5D,IAAA,MAAM,KAAA,EAAO,iDAAA,OAA0B,CAAA;AACvC,IAAA,MAAM,IAAA,EAAM,oDAAA,IAA0B,CAAA;AAGtC,IAAA,IAAI,kBAAA,EAAmC,IAAA;AACvC,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,IAAW,OAAA,GAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,OAAA,IAAW,OAAA,EAAS;AAC9E,MAAA,IAAI;AACF,QAAA,MAAM,KAAA,EAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AACxC,QAAA,GAAA,CAAI,IAAA,EAAM,kBAAA,EAAoB,IAAA;AAAA,MAChC,EAAA,UAAQ;AAAA,MAER;AAAA,IACF;AAGA,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,aAAA,EAAwB,IAAA;AAE5B,IAAA,IAAI;AACF,MAAA,SAAA,EAAW,MAAM,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAAA,IACvC,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,MAAA,aAAA,EAAe,KAAA;AACf,MAAA,GAAA,CAAI,MAAA,WAAiB,oBAAA,EAAc;AACjC,QAAA,SAAA,EAAW,KAAA;AAAA,MACb,EAAA,KAAO;AACL,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,uBAAA;AACzD,QAAA,SAAA,EAAW,oBAAA,CAAa,IAAA,CAAK,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,QAAQ,CAAA,EAAG,EAAE,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,MAClF;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,QAAA,CAAS,OAAA,IAAW,GAAA,EAAK;AAG3B,MAAA,MAAM,OAAA,EAAS,QAAA,CAAS,MAAA;AACxB,MAAA,MAAM,gBAAA,EAAkB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AACrF,MAAA,MAAM,YAAA,mBAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AAC5D,MAAA,IAAI,mBAAA,EAAoC,IAAA;AACxC,MAAA,IAAI;AACF,QAAA,mBAAA,EAAqB,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AAAA,MACnD,EAAA,WAAQ;AAAA,MAER;AAIA,MAAA,2BAAA,CAAM,EAAA,GAAM;AACV,QAAA,IAAI;AACF,UAAA,+CAAA,IAAiB,EAAM,iBAAA,EAAmB;AAAA,YACxC,MAAA;AAAA,YACA,IAAA,EAAM,kBAAA;AAAA,YACN,OAAA,EAAS,eAAA;AAAA,YACT;AAAA,UACF,CAAC,CAAA;AAAA,QACH,EAAA,WAAQ;AAAA,QAER;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAGA,IAAA,GAAA,CAAI,aAAA,GAAgB,CAAA,CAAE,aAAA,WAAwB,oBAAA,CAAA,EAAe;AAC3D,MAAA,MAAM,YAAA;AAAA,IACR;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;ADjCA;AACA;AACE;AACF,sCAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QQAIZOLL.js","sourcesContent":[null,"/**\n * Core telemetry wrapper for Next.js route handlers.\n * Extracts identity headers, logs to ClickHouse, extracts verified wallet.\n * This is a passive observer — it never influences the response.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { after } from 'next/server';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\ntype TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with telemetry.\n * Extracts identity headers, logs the invocation to ClickHouse,\n * and auto-extracts verified wallet from x402 payment headers.\n *\n * Uses Next.js after() to defer the ClickHouse insert until after\n * the response is sent. On Vercel this keeps the Lambda alive until\n * the insert completes, avoiding frozen in-flight promises.\n *\n * The entire telemetry code path is wrapped in try/catch.\n * Telemetry failures never affect the response.\n */\nexport function withTelemetry(handler: TelemetryHandler) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n // Capture request body for logging (only for methods with bodies)\n let requestBodyString: string | null = null;\n if (meta.method === 'POST' || meta.method === 'PUT' || meta.method === 'PATCH') {\n try {\n const body = await request.clone().text();\n if (body) requestBodyString = body;\n } catch {\n // Body read failed — that's fine\n }\n }\n\n // Execute the actual handler\n let response: NextResponse;\n let handlerError: unknown = null;\n\n try {\n response = await handler(request, ctx);\n } catch (error: unknown) {\n handlerError = error;\n if (error instanceof NextResponse) {\n response = error;\n } else {\n const message = error instanceof Error ? error.message : 'Internal server error';\n response = NextResponse.json({ success: false, error: message }, { status: 500 });\n }\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.status !== 402) {\n // Capture all response data before returning — response.clone() must happen\n // before Next.js consumes the body to send it to the client.\n const status = response.status;\n const responseHeaders = JSON.stringify(Object.fromEntries(response.headers.entries()));\n const contentType = response.headers.get('content-type') ?? null;\n let responseBodyString: string | null = null;\n try {\n responseBodyString = await response.clone().text();\n } catch {\n // Response body read failed — that's fine\n }\n\n // Defer the ClickHouse insert until after the response is sent.\n // On Vercel, after() keeps the Lambda alive until the insert completes.\n after(() => {\n try {\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBodyString,\n headers: responseHeaders,\n contentType,\n });\n } catch {\n // Telemetry never affects the response\n }\n });\n }\n\n // Re-throw the original error if it wasn't a NextResponse\n if (handlerError && !(handlerError instanceof NextResponse)) {\n throw handlerError;\n }\n\n return response;\n };\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
initClickhouse,
|
|
3
3
|
insertInvocation,
|
|
4
4
|
pingClickhouse
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YWUHTPJC.mjs";
|
|
6
6
|
|
|
7
7
|
// src/init.ts
|
|
8
8
|
var configuredOrigin;
|
|
@@ -150,4 +150,4 @@ export {
|
|
|
150
150
|
buildTelemetryContext,
|
|
151
151
|
recordInvocation
|
|
152
152
|
};
|
|
153
|
-
//# sourceMappingURL=chunk-
|
|
153
|
+
//# sourceMappingURL=chunk-SSFH754L.mjs.map
|
|
@@ -17,24 +17,24 @@ function initClickhouse(config) {
|
|
|
17
17
|
}
|
|
18
18
|
function pingClickhouse() {
|
|
19
19
|
if (!clickhouseClient) {
|
|
20
|
-
console.
|
|
20
|
+
console.warn("[telemetry] Cannot verify: ClickHouse client not initialized.");
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
clickhouseClient.ping().then((result) => {
|
|
24
24
|
if (result.success) {
|
|
25
25
|
console.log("[telemetry] ClickHouse connected");
|
|
26
26
|
} else {
|
|
27
|
-
console.
|
|
27
|
+
console.warn("[telemetry] ClickHouse ping failed");
|
|
28
28
|
}
|
|
29
29
|
}).catch((error) => {
|
|
30
30
|
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
-
console.
|
|
31
|
+
console.warn("[telemetry] ClickHouse ping failed:", message);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
function insertInvocation(data) {
|
|
35
35
|
try {
|
|
36
36
|
if (!clickhouseClient) {
|
|
37
|
-
console.
|
|
37
|
+
console.warn("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
clickhouseClient.insert({
|
|
@@ -44,14 +44,14 @@ function insertInvocation(data) {
|
|
|
44
44
|
}).catch((error) => {
|
|
45
45
|
try {
|
|
46
46
|
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
-
console.
|
|
47
|
+
console.warn("[telemetry] ClickHouse insert failed:", message);
|
|
48
48
|
} catch (e) {
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
} catch (error) {
|
|
52
52
|
try {
|
|
53
53
|
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
-
console.
|
|
54
|
+
console.warn("[telemetry] ClickHouse insert threw synchronously:", message);
|
|
55
55
|
} catch (e2) {
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -62,4 +62,4 @@ function insertInvocation(data) {
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
exports.initClickhouse = initClickhouse; exports.pingClickhouse = pingClickhouse; exports.insertInvocation = insertInvocation;
|
|
65
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
//# sourceMappingURL=chunk-TGF67ZBD.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-TGF67ZBD.js","../src/clickhouse.ts"],"names":[],"mappings":"AAAA;ACAA,4CAA6B;AAG7B,IAAI,iBAAA,EAA2D,IAAA;AAE/D,IAAM,MAAA,EAAQ,0BAAA;AAMP,SAAS,cAAA,CAAe,MAAA,EAA6C;AAC1E,EAAA,iBAAA,EAAmB,kCAAA;AAAa,IAC9B,GAAA,EAAK,MAAA,CAAO,GAAA;AAAA,IACZ,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,IAAA;AAAA;AAAA;AAAA;AAAA,IAI7B,UAAA,EAAY,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IAC7B,eAAA,EAAiB;AAAA,EACnB,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CAAA,EAAuB;AACrC,EAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA,OAAA,CAAQ,IAAA,CAAK,+DAA+D,CAAA;AAC5E,IAAA,MAAA;AAAA,EACF;AACA,EAAA,gBAAA,CACG,IAAA,CAAK,CAAA,CACL,IAAA,CAAK,CAAC,MAAA,EAAA,GAAW;AAChB,IAAA,GAAA,CAAI,MAAA,CAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,kCAAkC,CAAA;AAAA,IAChD,EAAA,KAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,oCAAoC,CAAA;AAAA,IACnD;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,IAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,IAAA,OAAA,CAAQ,IAAA,CAAK,qCAAA,EAAuC,OAAO,CAAA;AAAA,EAC7D,CAAC,CAAA;AACL;AAMO,SAAS,gBAAA,CAAiB,IAAA,EAAmC;AAClE,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAA,CAAQ,IAAA,CAAK,4EAA4E,CAAA;AACzF,MAAA,MAAA;AAAA,IACF;AAGA,IAAA,gBAAA,CACG,MAAA,CAA8B;AAAA,MAC7B,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,CAAC,IAAI,CAAA;AAAA,MACb,MAAA,EAAQ;AAAA,IACV,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,QAAA,OAAA,CAAQ,IAAA,CAAK,uCAAA,EAAyC,OAAO,CAAA;AAAA,MAC/D,EAAA,UAAQ;AAAA,MAER;AAAA,IACF,CAAC,CAAA;AAAA,EACL,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,MAAA,OAAA,CAAQ,IAAA,CAAK,oDAAA,EAAsD,OAAO,CAAA;AAAA,IAC5E,EAAA,WAAQ;AAAA,IAER;AAAA,EACF;AACF;ADxBA;AACA;AACE;AACA;AACA;AACF,8HAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-TGF67ZBD.js","sourcesContent":[null,"import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts are fire-and-forget (a fast failure is fine).\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"]}
|
|
@@ -17,24 +17,24 @@ function initClickhouse(config) {
|
|
|
17
17
|
}
|
|
18
18
|
function pingClickhouse() {
|
|
19
19
|
if (!clickhouseClient) {
|
|
20
|
-
console.
|
|
20
|
+
console.warn("[telemetry] Cannot verify: ClickHouse client not initialized.");
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
clickhouseClient.ping().then((result) => {
|
|
24
24
|
if (result.success) {
|
|
25
25
|
console.log("[telemetry] ClickHouse connected");
|
|
26
26
|
} else {
|
|
27
|
-
console.
|
|
27
|
+
console.warn("[telemetry] ClickHouse ping failed");
|
|
28
28
|
}
|
|
29
29
|
}).catch((error) => {
|
|
30
30
|
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
-
console.
|
|
31
|
+
console.warn("[telemetry] ClickHouse ping failed:", message);
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
function insertInvocation(data) {
|
|
35
35
|
try {
|
|
36
36
|
if (!clickhouseClient) {
|
|
37
|
-
console.
|
|
37
|
+
console.warn("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
clickhouseClient.insert({
|
|
@@ -44,14 +44,14 @@ function insertInvocation(data) {
|
|
|
44
44
|
}).catch((error) => {
|
|
45
45
|
try {
|
|
46
46
|
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
-
console.
|
|
47
|
+
console.warn("[telemetry] ClickHouse insert failed:", message);
|
|
48
48
|
} catch {
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
} catch (error) {
|
|
52
52
|
try {
|
|
53
53
|
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
-
console.
|
|
54
|
+
console.warn("[telemetry] ClickHouse insert threw synchronously:", message);
|
|
55
55
|
} catch {
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -62,4 +62,4 @@ export {
|
|
|
62
62
|
pingClickhouse,
|
|
63
63
|
insertInvocation
|
|
64
64
|
};
|
|
65
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
//# sourceMappingURL=chunk-YWUHTPJC.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/clickhouse.ts"],"sourcesContent":["import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts are fire-and-forget (a fast failure is fine).\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAG7B,IAAI,mBAA2D;AAE/D,IAAM,QAAQ;AAMP,SAAS,eAAe,QAA6C;AAC1E,qBAAmB,aAAa;AAAA,IAC9B,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA;AAAA;AAAA;AAAA,IAI7B,YAAY,EAAE,SAAS,MAAM;AAAA,IAC7B,iBAAiB;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,iBAAuB;AACrC,MAAI,CAAC,kBAAkB;AACrB,YAAQ,KAAK,+DAA+D;AAC5E;AAAA,EACF;AACA,mBACG,KAAK,EACL,KAAK,CAAC,WAAW;AAChB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kCAAkC;AAAA,IAChD,OAAO;AACL,cAAQ,KAAK,oCAAoC;AAAA,IACnD;AAAA,EACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,KAAK,uCAAuC,OAAO;AAAA,EAC7D,CAAC;AACL;AAMO,SAAS,iBAAiB,MAAmC;AAClE,MAAI;AACF,QAAI,CAAC,kBAAkB;AACrB,cAAQ,KAAK,4EAA4E;AACzF;AAAA,IACF;AAGA,qBACG,OAA8B;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ,CAAC,IAAI;AAAA,MACb,QAAQ;AAAA,IACV,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI;AACF,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,gBAAQ,KAAK,yCAAyC,OAAO;AAAA,MAC/D,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACL,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,KAAK,sDAAsD,OAAO;AAAA,IAC5E,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkQQAIZOLLjs = require('./chunk-QQAIZOLL.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
8
|
-
require('./chunk-
|
|
7
|
+
var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
|
|
8
|
+
require('./chunk-TGF67ZBD.js');
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
exports.extractVerifiedWallet =
|
|
13
|
+
exports.extractVerifiedWallet = _chunkMGILRTLUjs.extractVerifiedWallet; exports.initTelemetry = _chunkMGILRTLUjs.initTelemetry; exports.withTelemetry = _chunkQQAIZOLLjs.withTelemetry;
|
|
14
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withTelemetry
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-IA66E7KQ.mjs";
|
|
4
4
|
import {
|
|
5
5
|
extractVerifiedWallet,
|
|
6
6
|
initTelemetry
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-SSFH754L.mjs";
|
|
8
|
+
import "./chunk-YWUHTPJC.mjs";
|
|
9
9
|
export {
|
|
10
10
|
extractVerifiedWallet,
|
|
11
11
|
initTelemetry,
|
package/dist/router-plugin.d.mts
CHANGED
|
@@ -65,13 +65,24 @@ interface ResponseMeta {
|
|
|
65
65
|
headers: Record<string, string>;
|
|
66
66
|
/** Parsed request body (when .body() was used). undefined when no body was parsed. */
|
|
67
67
|
requestBody?: unknown;
|
|
68
|
-
/** Handler return value
|
|
68
|
+
/** Handler return value or structured router-generated error body. */
|
|
69
69
|
responseBody?: unknown;
|
|
70
70
|
}
|
|
71
71
|
interface ErrorEvent {
|
|
72
72
|
status: number;
|
|
73
73
|
message: string;
|
|
74
74
|
settled: boolean;
|
|
75
|
+
requestId?: string;
|
|
76
|
+
route?: string;
|
|
77
|
+
method?: string;
|
|
78
|
+
duration?: number;
|
|
79
|
+
walletAddress?: string | null;
|
|
80
|
+
verifiedWallet?: string | null;
|
|
81
|
+
clientId?: string | null;
|
|
82
|
+
sessionId?: string | null;
|
|
83
|
+
errorName?: string;
|
|
84
|
+
stack?: string;
|
|
85
|
+
cause?: unknown;
|
|
75
86
|
}
|
|
76
87
|
interface AlertEvent {
|
|
77
88
|
level: string;
|
package/dist/router-plugin.d.ts
CHANGED
|
@@ -65,13 +65,24 @@ interface ResponseMeta {
|
|
|
65
65
|
headers: Record<string, string>;
|
|
66
66
|
/** Parsed request body (when .body() was used). undefined when no body was parsed. */
|
|
67
67
|
requestBody?: unknown;
|
|
68
|
-
/** Handler return value
|
|
68
|
+
/** Handler return value or structured router-generated error body. */
|
|
69
69
|
responseBody?: unknown;
|
|
70
70
|
}
|
|
71
71
|
interface ErrorEvent {
|
|
72
72
|
status: number;
|
|
73
73
|
message: string;
|
|
74
74
|
settled: boolean;
|
|
75
|
+
requestId?: string;
|
|
76
|
+
route?: string;
|
|
77
|
+
method?: string;
|
|
78
|
+
duration?: number;
|
|
79
|
+
walletAddress?: string | null;
|
|
80
|
+
verifiedWallet?: string | null;
|
|
81
|
+
clientId?: string | null;
|
|
82
|
+
sessionId?: string | null;
|
|
83
|
+
errorName?: string;
|
|
84
|
+
stack?: string;
|
|
85
|
+
cause?: unknown;
|
|
75
86
|
}
|
|
76
87
|
interface AlertEvent {
|
|
77
88
|
level: string;
|
package/dist/router-plugin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkTGF67ZBDjs = require('./chunk-TGF67ZBD.js');
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
8
|
var _server = require('next/server');
|
|
@@ -38,14 +38,15 @@ function formatNetwork(network) {
|
|
|
38
38
|
return network;
|
|
39
39
|
}
|
|
40
40
|
function consoleForLevel(level) {
|
|
41
|
-
if (level === "critical" || level === "error")
|
|
42
|
-
|
|
41
|
+
if (level === "critical" || level === "error" || level === "warn" || level === "warning") {
|
|
42
|
+
return console.warn;
|
|
43
|
+
}
|
|
43
44
|
return console.log;
|
|
44
45
|
}
|
|
45
46
|
function createTelemetryPlugin(config) {
|
|
46
|
-
|
|
47
|
+
_chunkTGF67ZBDjs.initClickhouse.call(void 0, config.clickhouse);
|
|
47
48
|
if (config.verify) {
|
|
48
|
-
|
|
49
|
+
_chunkTGF67ZBDjs.pingClickhouse.call(void 0, );
|
|
49
50
|
}
|
|
50
51
|
const log = _nullishCoalesce(config.console, () => ( true));
|
|
51
52
|
return {
|
|
@@ -136,15 +137,29 @@ function createTelemetryPlugin(config) {
|
|
|
136
137
|
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
|
|
137
138
|
created_at: /* @__PURE__ */ new Date()
|
|
138
139
|
};
|
|
139
|
-
deferInsert(() =>
|
|
140
|
+
deferInsert(() => _chunkTGF67ZBDjs.insertInvocation.call(void 0, row));
|
|
140
141
|
} catch (e3) {
|
|
141
142
|
}
|
|
142
143
|
},
|
|
143
144
|
onError(ctx, error) {
|
|
144
145
|
if (log) {
|
|
145
146
|
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
const route = _nullishCoalesce(error.route, () => ( ctx.route));
|
|
148
|
+
const requestId = _nullishCoalesce(error.requestId, () => ( ctx.requestId));
|
|
149
|
+
const logFn = error.status >= 500 ? console.error : console.warn;
|
|
150
|
+
logFn(
|
|
151
|
+
`[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,
|
|
152
|
+
{
|
|
153
|
+
requestId,
|
|
154
|
+
method: error.method,
|
|
155
|
+
duration: error.duration,
|
|
156
|
+
walletAddress: _nullishCoalesce(error.walletAddress, () => ( ctx.walletAddress)),
|
|
157
|
+
verifiedWallet: _nullishCoalesce(error.verifiedWallet, () => ( ctx.verifiedWallet)),
|
|
158
|
+
clientId: _nullishCoalesce(error.clientId, () => ( ctx.clientId)),
|
|
159
|
+
sessionId: _nullishCoalesce(error.sessionId, () => ( ctx.sessionId)),
|
|
160
|
+
errorName: error.errorName,
|
|
161
|
+
stack: error.stack
|
|
162
|
+
}
|
|
148
163
|
);
|
|
149
164
|
}
|
|
150
165
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACiBA,qCAAsB;AAoItB,SAAS,aAAA,CAAc,KAAA,EAA+B;AACpD,EAAA,GAAA,CAAI,MAAA,IAAU,KAAA,CAAA,EAAW,OAAO,IAAA;AAChC,EAAA,IAAI;AACF,IAAA,wBAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,UAAK,MAAA;AAAA,EAClC,EAAA,UAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAiBA,SAAS,WAAA,CAAY,IAAA,EAAwB;AAC3C,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,OAAO,cAAA,IAAU,UAAA,EAAY;AAC/B,MAAA,2BAAA,IAAU,CAAA;AACV,MAAA,MAAA;AAAA,IACF;AAAA,EACF,EAAA,WAAQ;AAAA,EAER;AACA,EAAA,IAAA,CAAK,CAAA;AACP;AAOA,SAAS,OAAA,CAAQ,EAAA,EAAoB;AACnC,EAAA,OAAO,EAAA,CAAG,OAAA,EAAS,EAAA,EAAI,EAAA,CAAG,KAAA,CAAM,CAAA,EAAG,CAAC,EAAA,EAAI,EAAA;AAC1C;AAGA,SAAS,SAAA,CAAU,IAAA,EAAsB;AACvC,EAAA,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,EAAA,EAAI,OAAO,IAAA;AAC7B,EAAA,OAAO,CAAA,EAAA;AACT;AAGS;AACH,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACT;AAGS;AACH,EAAA;AACA,EAAA;AACG,EAAA;AACT;AAEgB;AAEd,EAAA;AACI,EAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AAEC,EAAA;AACA,IAAA;AACC,MAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACF,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEA,IAAA;AACkC,MAAA;AAC5B,MAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACkC,MAAA;AAC5B,MAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AAIM,MAAA;AACF,QAAA;AACA,QAAA;AAEA,QAAA;AACE,UAAA;AACA,UAAA;AAAQ,YAAA;AAER,UAAA;AACF,QAAA;AAGA,QAAA;AACE,UAAA;AACF,QAAA;AAEA,QAAA;AACE,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AAEA,UAAA;AACA,UAAA;AACA,UAAA;AACE,YAAA;AACA,YAAA;AACF,UAAA;AACA,UAAA;AAEA,UAAA;AACF,QAAA;AAIA,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AAEA,IAAA;AACM,MAAA;AACF,QAAA;AACA,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACM,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACM,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AAIA,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACF;AD5MU;AACA;AACA","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. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\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): 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/** Pick console.error / warn / log based on an alert/quota severity level. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') return console.error;\n if (level === 'warn' || level === 'warning') return console.warn;\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 console.error(\n `[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`,\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): 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"]}
|
package/dist/router-plugin.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
initClickhouse,
|
|
3
3
|
insertInvocation,
|
|
4
4
|
pingClickhouse
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YWUHTPJC.mjs";
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
8
|
import { after } from "next/server";
|
|
@@ -38,8 +38,9 @@ function formatNetwork(network) {
|
|
|
38
38
|
return network;
|
|
39
39
|
}
|
|
40
40
|
function consoleForLevel(level) {
|
|
41
|
-
if (level === "critical" || level === "error")
|
|
42
|
-
|
|
41
|
+
if (level === "critical" || level === "error" || level === "warn" || level === "warning") {
|
|
42
|
+
return console.warn;
|
|
43
|
+
}
|
|
43
44
|
return console.log;
|
|
44
45
|
}
|
|
45
46
|
function createTelemetryPlugin(config) {
|
|
@@ -143,8 +144,22 @@ function createTelemetryPlugin(config) {
|
|
|
143
144
|
onError(ctx, error) {
|
|
144
145
|
if (log) {
|
|
145
146
|
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
const route = error.route ?? ctx.route;
|
|
148
|
+
const requestId = error.requestId ?? ctx.requestId;
|
|
149
|
+
const logFn = error.status >= 500 ? console.error : console.warn;
|
|
150
|
+
logFn(
|
|
151
|
+
`[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,
|
|
152
|
+
{
|
|
153
|
+
requestId,
|
|
154
|
+
method: error.method,
|
|
155
|
+
duration: error.duration,
|
|
156
|
+
walletAddress: error.walletAddress ?? ctx.walletAddress,
|
|
157
|
+
verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,
|
|
158
|
+
clientId: error.clientId ?? ctx.clientId,
|
|
159
|
+
sessionId: error.sessionId ?? ctx.sessionId,
|
|
160
|
+
errorName: error.errorName,
|
|
161
|
+
stack: error.stack
|
|
162
|
+
}
|
|
148
163
|
);
|
|
149
164
|
}
|
|
150
165
|
},
|
|
@@ -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. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\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): 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/** Pick console.error / warn / log based on an alert/quota severity level. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') return console.error;\n if (level === 'warn' || level === 'warning') return console.warn;\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 console.error(\n `[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`,\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): 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":[]}
|
package/dist/siwx.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
require('./chunk-
|
|
5
|
-
require('./chunk-
|
|
3
|
+
var _chunkQQAIZOLLjs = require('./chunk-QQAIZOLL.js');
|
|
4
|
+
require('./chunk-MGILRTLU.js');
|
|
5
|
+
require('./chunk-TGF67ZBD.js');
|
|
6
6
|
|
|
7
7
|
// src/siwx.ts
|
|
8
8
|
var _server = require('next/server');
|
|
@@ -15,7 +15,7 @@ var _crypto = require('crypto');
|
|
|
15
15
|
var _signinwithx = require('@x402/extensions/sign-in-with-x');
|
|
16
16
|
var _http = require('@x402/core/http');
|
|
17
17
|
function withSiwxTelemetry(handler) {
|
|
18
|
-
return
|
|
18
|
+
return _chunkQQAIZOLLjs.withTelemetry.call(void 0, async (request, ctx) => {
|
|
19
19
|
const header = _nullishCoalesce(request.headers.get("SIGN-IN-WITH-X"), () => ( request.headers.get("sign-in-with-x")));
|
|
20
20
|
if (!header) {
|
|
21
21
|
return buildSiwxChallengeResponse(request);
|
package/dist/siwx.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withTelemetry
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-IA66E7KQ.mjs";
|
|
4
|
+
import "./chunk-SSFH754L.mjs";
|
|
5
|
+
import "./chunk-YWUHTPJC.mjs";
|
|
6
6
|
|
|
7
7
|
// src/siwx.ts
|
|
8
8
|
import { NextResponse } from "next/server";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/telemetry",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/clickhouse.ts"],"sourcesContent":["import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts are fire-and-forget (a fast failure is fine).\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.error('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.error('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.error('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAG7B,IAAI,mBAA2D;AAE/D,IAAM,QAAQ;AAMP,SAAS,eAAe,QAA6C;AAC1E,qBAAmB,aAAa;AAAA,IAC9B,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA;AAAA;AAAA;AAAA,IAI7B,YAAY,EAAE,SAAS,MAAM;AAAA,IAC7B,iBAAiB;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,iBAAuB;AACrC,MAAI,CAAC,kBAAkB;AACrB,YAAQ,MAAM,+DAA+D;AAC7E;AAAA,EACF;AACA,mBACG,KAAK,EACL,KAAK,CAAC,WAAW;AAChB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kCAAkC;AAAA,IAChD,OAAO;AACL,cAAQ,MAAM,oCAAoC;AAAA,IACpD;AAAA,EACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,uCAAuC,OAAO;AAAA,EAC9D,CAAC;AACL;AAMO,SAAS,iBAAiB,MAAmC;AAClE,MAAI;AACF,QAAI,CAAC,kBAAkB;AACrB,cAAQ,MAAM,4EAA4E;AAC1F;AAAA,IACF;AAGA,qBACG,OAA8B;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ,CAAC,IAAI;AAAA,MACb,QAAQ;AAAA,IACV,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI;AACF,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,gBAAQ,MAAM,yCAAyC,OAAO;AAAA,MAChE,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACL,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,MAAM,sDAAsD,OAAO;AAAA,IAC7E,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}
|
|
File without changes
|
|
File without changes
|