@agentcash/telemetry 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/dist/builder.d.mts +58 -0
- package/dist/builder.d.ts +58 -0
- package/dist/builder.js +218 -0
- package/dist/builder.js.map +1 -0
- package/dist/builder.mjs +218 -0
- package/dist/builder.mjs.map +1 -0
- package/dist/chunk-FJ3YM6KO.js +55 -0
- package/dist/chunk-FJ3YM6KO.js.map +1 -0
- package/dist/chunk-GE7VBMQP.js +149 -0
- package/dist/chunk-GE7VBMQP.js.map +1 -0
- package/dist/chunk-JVLKV7CX.mjs +55 -0
- package/dist/chunk-JVLKV7CX.mjs.map +1 -0
- package/dist/chunk-O2PCP6KV.mjs +60 -0
- package/dist/chunk-O2PCP6KV.mjs.map +1 -0
- package/dist/chunk-P63MLKU3.js +60 -0
- package/dist/chunk-P63MLKU3.js.map +1 -0
- package/dist/chunk-VOY67KA4.mjs +149 -0
- package/dist/chunk-VOY67KA4.mjs.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14 -0
- package/dist/index.mjs.map +1 -0
- package/dist/router-plugin.d.mts +109 -0
- package/dist/router-plugin.d.ts +109 -0
- package/dist/router-plugin.js +99 -0
- package/dist/router-plugin.js.map +1 -0
- package/dist/router-plugin.mjs +99 -0
- package/dist/router-plugin.mjs.map +1 -0
- package/dist/siwx.d.mts +29 -0
- package/dist/siwx.d.ts +29 -0
- package/dist/siwx.js +99 -0
- package/dist/siwx.js.map +1 -0
- package/dist/siwx.mjs +99 -0
- package/dist/siwx.mjs.map +1 -0
- package/dist/types-Bl8IwXin.d.mts +57 -0
- package/dist/types-Bl8IwXin.d.ts +57 -0
- package/package.json +88 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/clickhouse.ts
|
|
2
|
+
var _client = require('@clickhouse/client');
|
|
3
|
+
var clickhouseClient = null;
|
|
4
|
+
var TABLE = "mcp_resource_invocations";
|
|
5
|
+
function initClickhouse(config) {
|
|
6
|
+
clickhouseClient = _client.createClient.call(void 0, {
|
|
7
|
+
url: config.url,
|
|
8
|
+
database: _nullishCoalesce(config.database, () => ( "default")),
|
|
9
|
+
username: _nullishCoalesce(config.username, () => ( "default")),
|
|
10
|
+
password: _nullishCoalesce(config.password, () => ( ""))
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function pingClickhouse() {
|
|
14
|
+
if (!clickhouseClient) {
|
|
15
|
+
console.error("[telemetry] Cannot verify: ClickHouse client not initialized.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
clickhouseClient.ping().then((result) => {
|
|
19
|
+
if (result.success) {
|
|
20
|
+
console.log("[telemetry] ClickHouse connected");
|
|
21
|
+
} else {
|
|
22
|
+
console.error("[telemetry] ClickHouse ping failed");
|
|
23
|
+
}
|
|
24
|
+
}).catch((error) => {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
console.error("[telemetry] ClickHouse ping failed:", message);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function insertInvocation(data) {
|
|
30
|
+
try {
|
|
31
|
+
if (!clickhouseClient) {
|
|
32
|
+
console.error("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
clickhouseClient.insert({
|
|
36
|
+
table: TABLE,
|
|
37
|
+
values: [data],
|
|
38
|
+
format: "JSONEachRow"
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
try {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
console.error("[telemetry] ClickHouse insert failed:", message);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
try {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
console.error("[telemetry] ClickHouse insert threw synchronously:", message);
|
|
50
|
+
} catch (e2) {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
exports.initClickhouse = initClickhouse; exports.pingClickhouse = pingClickhouse; exports.insertInvocation = insertInvocation;
|
|
60
|
+
//# sourceMappingURL=chunk-P63MLKU3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-P63MLKU3.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;AAAA,EAC/B,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CAAA,EAAuB;AACrC,EAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA,OAAA,CAAQ,KAAA,CAAM,+DAA+D,CAAA;AAC7E,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,KAAA,CAAM,oCAAoC,CAAA;AAAA,IACpD;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,KAAA,CAAM,qCAAA,EAAuC,OAAO,CAAA;AAAA,EAC9D,CAAC,CAAA;AACL;AAMO,SAAS,gBAAA,CAAiB,IAAA,EAAmC;AAClE,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAA,CAAQ,KAAA,CAAM,4EAA4E,CAAA;AAC1F,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,KAAA,CAAM,uCAAA,EAAyC,OAAO,CAAA;AAAA,MAChE,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,KAAA,CAAM,oDAAA,EAAsD,OAAO,CAAA;AAAA,IAC7E,EAAA,WAAQ;AAAA,IAER;AAAA,EACF;AACF;ADxBA;AACA;AACE;AACA;AACA;AACF,8HAAC","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-P63MLKU3.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 });\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"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initClickhouse,
|
|
3
|
+
insertInvocation,
|
|
4
|
+
pingClickhouse
|
|
5
|
+
} from "./chunk-O2PCP6KV.mjs";
|
|
6
|
+
|
|
7
|
+
// src/init.ts
|
|
8
|
+
var configuredOrigin;
|
|
9
|
+
function initTelemetry(config) {
|
|
10
|
+
initClickhouse(config.clickhouse);
|
|
11
|
+
if (config.origin) {
|
|
12
|
+
configuredOrigin = config.origin;
|
|
13
|
+
}
|
|
14
|
+
if (config.verify) {
|
|
15
|
+
pingClickhouse();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getOrigin() {
|
|
19
|
+
return configuredOrigin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/extract-wallet.ts
|
|
23
|
+
function extractVerifiedWallet(headers) {
|
|
24
|
+
try {
|
|
25
|
+
const payerAddress = headers.get("x-payer-address");
|
|
26
|
+
if (payerAddress) {
|
|
27
|
+
return payerAddress.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
const paymentHeader = headers.get("PAYMENT-SIGNATURE") ?? headers.get("payment-signature") ?? headers.get("X-PAYMENT") ?? headers.get("x-payment");
|
|
30
|
+
if (!paymentHeader) return null;
|
|
31
|
+
try {
|
|
32
|
+
const decoded = JSON.parse(Buffer.from(paymentHeader, "base64").toString());
|
|
33
|
+
const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;
|
|
34
|
+
return typeof from === "string" ? from.toLowerCase() : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/telemetry-core.ts
|
|
44
|
+
import { randomUUID } from "crypto";
|
|
45
|
+
function extractRequestMeta(request) {
|
|
46
|
+
const meta = {
|
|
47
|
+
requestId: randomUUID(),
|
|
48
|
+
startTime: Date.now(),
|
|
49
|
+
walletAddress: null,
|
|
50
|
+
clientId: null,
|
|
51
|
+
sessionId: null,
|
|
52
|
+
verifiedWallet: null,
|
|
53
|
+
route: "",
|
|
54
|
+
method: "",
|
|
55
|
+
origin: "",
|
|
56
|
+
referer: null,
|
|
57
|
+
requestContentType: null,
|
|
58
|
+
requestHeadersJson: null
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
meta.walletAddress = request.headers.get("X-Wallet-Address")?.toLowerCase() ?? null;
|
|
62
|
+
meta.clientId = request.headers.get("X-Client-ID") ?? null;
|
|
63
|
+
meta.sessionId = request.headers.get("X-Session-ID") ?? null;
|
|
64
|
+
meta.referer = request.headers.get("Referer") ?? null;
|
|
65
|
+
meta.requestContentType = request.headers.get("content-type") ?? null;
|
|
66
|
+
meta.route = request.nextUrl.pathname;
|
|
67
|
+
meta.method = request.method;
|
|
68
|
+
meta.origin = getOrigin() ?? request.nextUrl.origin;
|
|
69
|
+
meta.verifiedWallet = extractVerifiedWallet(request.headers);
|
|
70
|
+
meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
return meta;
|
|
74
|
+
}
|
|
75
|
+
function buildTelemetryContext(meta) {
|
|
76
|
+
const ctx = {
|
|
77
|
+
walletAddress: meta.walletAddress,
|
|
78
|
+
clientId: meta.clientId,
|
|
79
|
+
sessionId: meta.sessionId,
|
|
80
|
+
verifiedWallet: meta.verifiedWallet,
|
|
81
|
+
setVerifiedWallet: (address) => {
|
|
82
|
+
meta.verifiedWallet = address.toLowerCase();
|
|
83
|
+
ctx.verifiedWallet = meta.verifiedWallet;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return ctx;
|
|
87
|
+
}
|
|
88
|
+
function recordInvocation(meta, requestBody, response) {
|
|
89
|
+
try {
|
|
90
|
+
const invocation = {
|
|
91
|
+
id: meta.requestId,
|
|
92
|
+
x_wallet_address: meta.walletAddress,
|
|
93
|
+
x_client_id: meta.clientId,
|
|
94
|
+
session_id: meta.sessionId,
|
|
95
|
+
verified_wallet_address: meta.verifiedWallet,
|
|
96
|
+
method: meta.method,
|
|
97
|
+
route: meta.route,
|
|
98
|
+
origin: meta.origin,
|
|
99
|
+
referer: meta.referer,
|
|
100
|
+
request_content_type: meta.requestContentType,
|
|
101
|
+
request_headers: meta.requestHeadersJson,
|
|
102
|
+
request_body: requestBody,
|
|
103
|
+
status_code: response.status,
|
|
104
|
+
status_text: statusTextFromCode(response.status),
|
|
105
|
+
duration: Date.now() - meta.startTime,
|
|
106
|
+
response_content_type: response.contentType,
|
|
107
|
+
response_headers: response.headers,
|
|
108
|
+
response_body: response.body,
|
|
109
|
+
created_at: /* @__PURE__ */ new Date()
|
|
110
|
+
};
|
|
111
|
+
insertInvocation(invocation);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function statusTextFromCode(code) {
|
|
116
|
+
switch (code) {
|
|
117
|
+
case 200:
|
|
118
|
+
return "OK";
|
|
119
|
+
case 201:
|
|
120
|
+
return "Created";
|
|
121
|
+
case 204:
|
|
122
|
+
return "No Content";
|
|
123
|
+
case 400:
|
|
124
|
+
return "Bad Request";
|
|
125
|
+
case 401:
|
|
126
|
+
return "Unauthorized";
|
|
127
|
+
case 402:
|
|
128
|
+
return "Payment Required";
|
|
129
|
+
case 403:
|
|
130
|
+
return "Forbidden";
|
|
131
|
+
case 404:
|
|
132
|
+
return "Not Found";
|
|
133
|
+
case 500:
|
|
134
|
+
return "Internal Server Error";
|
|
135
|
+
case 504:
|
|
136
|
+
return "Gateway Timeout";
|
|
137
|
+
default:
|
|
138
|
+
return String(code);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export {
|
|
143
|
+
initTelemetry,
|
|
144
|
+
extractVerifiedWallet,
|
|
145
|
+
extractRequestMeta,
|
|
146
|
+
buildTelemetryContext,
|
|
147
|
+
recordInvocation
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=chunk-VOY67KA4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"sourcesContent":["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 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"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { T as TelemetryConfig, a as TelemetryContext } from './types-Bl8IwXin.mjs';
|
|
2
|
+
export { M as McpResourceInvocation } from './types-Bl8IwXin.mjs';
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the telemetry package. Call once at module level.
|
|
7
|
+
*
|
|
8
|
+
* This is synchronous — createClient() does not connect until first query.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope
|
|
11
|
+
* from route handlers. Call this in the same module that imports your route
|
|
12
|
+
* wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { initTelemetry, withTelemetry } from '@agentcash/telemetry';
|
|
16
|
+
*
|
|
17
|
+
* initTelemetry({
|
|
18
|
+
* clickhouse: {
|
|
19
|
+
* url: process.env.TELEM_CLICKHOUSE_URL!,
|
|
20
|
+
* database: process.env.TELEM_CLICKHOUSE_DATABASE,
|
|
21
|
+
* username: process.env.TELEM_CLICKHOUSE_USERNAME,
|
|
22
|
+
* password: process.env.TELEM_CLICKHOUSE_PASSWORD,
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function initTelemetry(config: TelemetryConfig): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Core telemetry wrapper for Next.js route handlers.
|
|
31
|
+
* Extracts identity headers, logs to ClickHouse, extracts verified wallet.
|
|
32
|
+
* This is a passive observer — it never influences the response.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
type TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Wrap a Next.js route handler with telemetry.
|
|
38
|
+
* Extracts identity headers, logs the invocation to ClickHouse,
|
|
39
|
+
* and auto-extracts verified wallet from x402 payment headers.
|
|
40
|
+
*
|
|
41
|
+
* The entire telemetry code path is wrapped in try/catch.
|
|
42
|
+
* Telemetry failures never affect the response.
|
|
43
|
+
*/
|
|
44
|
+
declare function withTelemetry(handler: TelemetryHandler): (request: NextRequest) => Promise<NextResponse>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract verified wallet address from x402 payment headers.
|
|
48
|
+
*
|
|
49
|
+
* Checks multiple sources in priority order:
|
|
50
|
+
* 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)
|
|
51
|
+
* 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly
|
|
52
|
+
*
|
|
53
|
+
* If the handler is executing, withX402 has already verified the payment signature.
|
|
54
|
+
* For manual x402 flows, the app verifies before calling business logic.
|
|
55
|
+
* Either way, the header content is trustworthy when this runs.
|
|
56
|
+
*/
|
|
57
|
+
declare function extractVerifiedWallet(headers: Headers): string | null;
|
|
58
|
+
|
|
59
|
+
export { TelemetryConfig, TelemetryContext, extractVerifiedWallet, initTelemetry, withTelemetry };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { T as TelemetryConfig, a as TelemetryContext } from './types-Bl8IwXin.js';
|
|
2
|
+
export { M as McpResourceInvocation } from './types-Bl8IwXin.js';
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the telemetry package. Call once at module level.
|
|
7
|
+
*
|
|
8
|
+
* This is synchronous — createClient() does not connect until first query.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope
|
|
11
|
+
* from route handlers. Call this in the same module that imports your route
|
|
12
|
+
* wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.
|
|
13
|
+
*
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { initTelemetry, withTelemetry } from '@agentcash/telemetry';
|
|
16
|
+
*
|
|
17
|
+
* initTelemetry({
|
|
18
|
+
* clickhouse: {
|
|
19
|
+
* url: process.env.TELEM_CLICKHOUSE_URL!,
|
|
20
|
+
* database: process.env.TELEM_CLICKHOUSE_DATABASE,
|
|
21
|
+
* username: process.env.TELEM_CLICKHOUSE_USERNAME,
|
|
22
|
+
* password: process.env.TELEM_CLICKHOUSE_PASSWORD,
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function initTelemetry(config: TelemetryConfig): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Core telemetry wrapper for Next.js route handlers.
|
|
31
|
+
* Extracts identity headers, logs to ClickHouse, extracts verified wallet.
|
|
32
|
+
* This is a passive observer — it never influences the response.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
type TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Wrap a Next.js route handler with telemetry.
|
|
38
|
+
* Extracts identity headers, logs the invocation to ClickHouse,
|
|
39
|
+
* and auto-extracts verified wallet from x402 payment headers.
|
|
40
|
+
*
|
|
41
|
+
* The entire telemetry code path is wrapped in try/catch.
|
|
42
|
+
* Telemetry failures never affect the response.
|
|
43
|
+
*/
|
|
44
|
+
declare function withTelemetry(handler: TelemetryHandler): (request: NextRequest) => Promise<NextResponse>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract verified wallet address from x402 payment headers.
|
|
48
|
+
*
|
|
49
|
+
* Checks multiple sources in priority order:
|
|
50
|
+
* 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)
|
|
51
|
+
* 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly
|
|
52
|
+
*
|
|
53
|
+
* If the handler is executing, withX402 has already verified the payment signature.
|
|
54
|
+
* For manual x402 flows, the app verifies before calling business logic.
|
|
55
|
+
* Either way, the header content is trustworthy when this runs.
|
|
56
|
+
*/
|
|
57
|
+
declare function extractVerifiedWallet(headers: Headers): string | null;
|
|
58
|
+
|
|
59
|
+
export { TelemetryConfig, TelemetryContext, extractVerifiedWallet, initTelemetry, withTelemetry };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
var _chunkFJ3YM6KOjs = require('./chunk-FJ3YM6KO.js');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
var _chunkGE7VBMQPjs = require('./chunk-GE7VBMQP.js');
|
|
8
|
+
require('./chunk-P63MLKU3.js');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
exports.extractVerifiedWallet = _chunkGE7VBMQPjs.extractVerifiedWallet; exports.initTelemetry = _chunkGE7VBMQPjs.initTelemetry; exports.withTelemetry = _chunkFJ3YM6KOjs.withTelemetry;
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/index.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B;AACE;AACA;AACA;AACF,uLAAC","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/index.js"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withTelemetry
|
|
3
|
+
} from "./chunk-JVLKV7CX.mjs";
|
|
4
|
+
import {
|
|
5
|
+
extractVerifiedWallet,
|
|
6
|
+
initTelemetry
|
|
7
|
+
} from "./chunk-VOY67KA4.mjs";
|
|
8
|
+
import "./chunk-O2PCP6KV.mjs";
|
|
9
|
+
export {
|
|
10
|
+
extractVerifiedWallet,
|
|
11
|
+
initTelemetry,
|
|
12
|
+
withTelemetry
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { T as TelemetryConfig } from './types-Bl8IwXin.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RouterPlugin adapter for @agentcash/router.
|
|
5
|
+
*
|
|
6
|
+
* Bridges the router's plugin hooks into ClickHouse telemetry.
|
|
7
|
+
* Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { createRouter } from '@agentcash/router';
|
|
11
|
+
* import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';
|
|
12
|
+
*
|
|
13
|
+
* const router = createRouter({
|
|
14
|
+
* payeeAddress: '...',
|
|
15
|
+
* plugin: createTelemetryPlugin({
|
|
16
|
+
* clickhouse: {
|
|
17
|
+
* url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',
|
|
18
|
+
* database: process.env.TELEM_CLICKHOUSE_DATABASE,
|
|
19
|
+
* username: process.env.TELEM_CLICKHOUSE_USERNAME,
|
|
20
|
+
* password: process.env.TELEM_CLICKHOUSE_PASSWORD,
|
|
21
|
+
* },
|
|
22
|
+
* }),
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface RequestMeta {
|
|
27
|
+
requestId: string;
|
|
28
|
+
method: string;
|
|
29
|
+
route: string;
|
|
30
|
+
origin: string;
|
|
31
|
+
referer: string | null;
|
|
32
|
+
walletAddress: string | null;
|
|
33
|
+
clientId: string | null;
|
|
34
|
+
sessionId: string | null;
|
|
35
|
+
contentType: string | null;
|
|
36
|
+
headers: Record<string, string>;
|
|
37
|
+
startTime: number;
|
|
38
|
+
}
|
|
39
|
+
interface PluginContext {
|
|
40
|
+
readonly requestId: string;
|
|
41
|
+
readonly route: string;
|
|
42
|
+
readonly walletAddress: string | null;
|
|
43
|
+
readonly clientId: string | null;
|
|
44
|
+
readonly sessionId: string | null;
|
|
45
|
+
verifiedWallet: string | null;
|
|
46
|
+
setVerifiedWallet(address: string): void;
|
|
47
|
+
}
|
|
48
|
+
interface PaymentEvent {
|
|
49
|
+
protocol: 'x402' | 'mpp';
|
|
50
|
+
payer: string;
|
|
51
|
+
amount: string;
|
|
52
|
+
network: string;
|
|
53
|
+
}
|
|
54
|
+
interface SettlementEvent {
|
|
55
|
+
protocol: 'x402' | 'mpp';
|
|
56
|
+
payer: string;
|
|
57
|
+
transaction: string;
|
|
58
|
+
network: string;
|
|
59
|
+
}
|
|
60
|
+
interface ResponseMeta {
|
|
61
|
+
statusCode: number;
|
|
62
|
+
statusText: string;
|
|
63
|
+
duration: number;
|
|
64
|
+
contentType: string | null;
|
|
65
|
+
headers: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
interface ErrorEvent {
|
|
68
|
+
status: number;
|
|
69
|
+
message: string;
|
|
70
|
+
settled: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface AlertEvent {
|
|
73
|
+
level: string;
|
|
74
|
+
message: string;
|
|
75
|
+
route: string;
|
|
76
|
+
meta?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
interface ProviderQuotaEvent {
|
|
79
|
+
provider: string;
|
|
80
|
+
route: string;
|
|
81
|
+
remaining: number | null;
|
|
82
|
+
limit: number | null;
|
|
83
|
+
level: string;
|
|
84
|
+
overage: string;
|
|
85
|
+
message: string;
|
|
86
|
+
}
|
|
87
|
+
/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */
|
|
88
|
+
interface RouterPlugin {
|
|
89
|
+
init?(config: {
|
|
90
|
+
origin?: string;
|
|
91
|
+
}): void | Promise<void>;
|
|
92
|
+
onRequest?(meta: RequestMeta): PluginContext;
|
|
93
|
+
onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;
|
|
94
|
+
onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;
|
|
95
|
+
onResponse?(ctx: PluginContext, response: ResponseMeta): void;
|
|
96
|
+
onError?(ctx: PluginContext, error: ErrorEvent): void;
|
|
97
|
+
onAlert?(ctx: PluginContext, alert: AlertEvent): void;
|
|
98
|
+
onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;
|
|
99
|
+
}
|
|
100
|
+
interface TelemetryPluginConfig {
|
|
101
|
+
clickhouse: TelemetryConfig['clickhouse'];
|
|
102
|
+
/** If true, pings ClickHouse on init. */
|
|
103
|
+
verify?: boolean;
|
|
104
|
+
/** Console logging for dev. Default: false. */
|
|
105
|
+
console?: boolean;
|
|
106
|
+
}
|
|
107
|
+
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
|
108
|
+
|
|
109
|
+
export { type TelemetryPluginConfig, createTelemetryPlugin };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { T as TelemetryConfig } from './types-Bl8IwXin.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RouterPlugin adapter for @agentcash/router.
|
|
5
|
+
*
|
|
6
|
+
* Bridges the router's plugin hooks into ClickHouse telemetry.
|
|
7
|
+
* Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { createRouter } from '@agentcash/router';
|
|
11
|
+
* import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';
|
|
12
|
+
*
|
|
13
|
+
* const router = createRouter({
|
|
14
|
+
* payeeAddress: '...',
|
|
15
|
+
* plugin: createTelemetryPlugin({
|
|
16
|
+
* clickhouse: {
|
|
17
|
+
* url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',
|
|
18
|
+
* database: process.env.TELEM_CLICKHOUSE_DATABASE,
|
|
19
|
+
* username: process.env.TELEM_CLICKHOUSE_USERNAME,
|
|
20
|
+
* password: process.env.TELEM_CLICKHOUSE_PASSWORD,
|
|
21
|
+
* },
|
|
22
|
+
* }),
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface RequestMeta {
|
|
27
|
+
requestId: string;
|
|
28
|
+
method: string;
|
|
29
|
+
route: string;
|
|
30
|
+
origin: string;
|
|
31
|
+
referer: string | null;
|
|
32
|
+
walletAddress: string | null;
|
|
33
|
+
clientId: string | null;
|
|
34
|
+
sessionId: string | null;
|
|
35
|
+
contentType: string | null;
|
|
36
|
+
headers: Record<string, string>;
|
|
37
|
+
startTime: number;
|
|
38
|
+
}
|
|
39
|
+
interface PluginContext {
|
|
40
|
+
readonly requestId: string;
|
|
41
|
+
readonly route: string;
|
|
42
|
+
readonly walletAddress: string | null;
|
|
43
|
+
readonly clientId: string | null;
|
|
44
|
+
readonly sessionId: string | null;
|
|
45
|
+
verifiedWallet: string | null;
|
|
46
|
+
setVerifiedWallet(address: string): void;
|
|
47
|
+
}
|
|
48
|
+
interface PaymentEvent {
|
|
49
|
+
protocol: 'x402' | 'mpp';
|
|
50
|
+
payer: string;
|
|
51
|
+
amount: string;
|
|
52
|
+
network: string;
|
|
53
|
+
}
|
|
54
|
+
interface SettlementEvent {
|
|
55
|
+
protocol: 'x402' | 'mpp';
|
|
56
|
+
payer: string;
|
|
57
|
+
transaction: string;
|
|
58
|
+
network: string;
|
|
59
|
+
}
|
|
60
|
+
interface ResponseMeta {
|
|
61
|
+
statusCode: number;
|
|
62
|
+
statusText: string;
|
|
63
|
+
duration: number;
|
|
64
|
+
contentType: string | null;
|
|
65
|
+
headers: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
interface ErrorEvent {
|
|
68
|
+
status: number;
|
|
69
|
+
message: string;
|
|
70
|
+
settled: boolean;
|
|
71
|
+
}
|
|
72
|
+
interface AlertEvent {
|
|
73
|
+
level: string;
|
|
74
|
+
message: string;
|
|
75
|
+
route: string;
|
|
76
|
+
meta?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
interface ProviderQuotaEvent {
|
|
79
|
+
provider: string;
|
|
80
|
+
route: string;
|
|
81
|
+
remaining: number | null;
|
|
82
|
+
limit: number | null;
|
|
83
|
+
level: string;
|
|
84
|
+
overage: string;
|
|
85
|
+
message: string;
|
|
86
|
+
}
|
|
87
|
+
/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */
|
|
88
|
+
interface RouterPlugin {
|
|
89
|
+
init?(config: {
|
|
90
|
+
origin?: string;
|
|
91
|
+
}): void | Promise<void>;
|
|
92
|
+
onRequest?(meta: RequestMeta): PluginContext;
|
|
93
|
+
onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;
|
|
94
|
+
onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;
|
|
95
|
+
onResponse?(ctx: PluginContext, response: ResponseMeta): void;
|
|
96
|
+
onError?(ctx: PluginContext, error: ErrorEvent): void;
|
|
97
|
+
onAlert?(ctx: PluginContext, alert: AlertEvent): void;
|
|
98
|
+
onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;
|
|
99
|
+
}
|
|
100
|
+
interface TelemetryPluginConfig {
|
|
101
|
+
clickhouse: TelemetryConfig['clickhouse'];
|
|
102
|
+
/** If true, pings ClickHouse on init. */
|
|
103
|
+
verify?: boolean;
|
|
104
|
+
/** Console logging for dev. Default: false. */
|
|
105
|
+
console?: boolean;
|
|
106
|
+
}
|
|
107
|
+
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
|
108
|
+
|
|
109
|
+
export { type TelemetryPluginConfig, createTelemetryPlugin };
|