@agentcash/telemetry 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/router-plugin.d.mts +8 -1
- package/dist/router-plugin.d.ts +8 -1
- package/dist/router-plugin.js +116 -52
- package/dist/router-plugin.js.map +1 -1
- package/dist/router-plugin.mjs +116 -52
- package/dist/router-plugin.mjs.map +1 -1
- package/package.json +4 -2
package/dist/router-plugin.d.mts
CHANGED
|
@@ -105,7 +105,14 @@ interface TelemetryPluginConfig {
|
|
|
105
105
|
clickhouse: TelemetryConfig['clickhouse'];
|
|
106
106
|
/** If true, pings ClickHouse on init. */
|
|
107
107
|
verify?: boolean;
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Emit a one-line `[telemetry]` console summary for every plugin hook
|
|
110
|
+
* (init, request, payment verify/settle, response, error, alert, quota).
|
|
111
|
+
* The full row still goes to ClickHouse regardless; these logs are for
|
|
112
|
+
* making Vercel logs debuggable without round-tripping to ClickHouse.
|
|
113
|
+
*
|
|
114
|
+
* Default: true. Pass `console: false` to silence.
|
|
115
|
+
*/
|
|
109
116
|
console?: boolean;
|
|
110
117
|
}
|
|
111
118
|
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
package/dist/router-plugin.d.ts
CHANGED
|
@@ -105,7 +105,14 @@ interface TelemetryPluginConfig {
|
|
|
105
105
|
clickhouse: TelemetryConfig['clickhouse'];
|
|
106
106
|
/** If true, pings ClickHouse on init. */
|
|
107
107
|
verify?: boolean;
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Emit a one-line `[telemetry]` console summary for every plugin hook
|
|
110
|
+
* (init, request, payment verify/settle, response, error, alert, quota).
|
|
111
|
+
* The full row still goes to ClickHouse regardless; these logs are for
|
|
112
|
+
* making Vercel logs debuggable without round-tripping to ClickHouse.
|
|
113
|
+
*
|
|
114
|
+
* Default: true. Pass `console: false` to silence.
|
|
115
|
+
*/
|
|
109
116
|
console?: boolean;
|
|
110
117
|
}
|
|
111
118
|
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
package/dist/router-plugin.js
CHANGED
|
@@ -5,13 +5,55 @@
|
|
|
5
5
|
var _chunkQEJ7ZGGHjs = require('./chunk-QEJ7ZGGH.js');
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
|
+
var _server = require('next/server');
|
|
9
|
+
function safeStringify(value) {
|
|
10
|
+
if (value === void 0) return null;
|
|
11
|
+
try {
|
|
12
|
+
return _nullishCoalesce(JSON.stringify(value), () => ( null));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function deferInsert(task) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof _server.after === "function") {
|
|
20
|
+
_server.after.call(void 0, task);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
} catch (e2) {
|
|
24
|
+
}
|
|
25
|
+
task();
|
|
26
|
+
}
|
|
27
|
+
function shortId(id) {
|
|
28
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
29
|
+
}
|
|
30
|
+
function shortAddr(addr) {
|
|
31
|
+
if (addr.length < 12) return addr;
|
|
32
|
+
return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
|
|
33
|
+
}
|
|
34
|
+
function formatNetwork(network) {
|
|
35
|
+
if (network === "eip155:8453") return "Base";
|
|
36
|
+
if (network.startsWith("solana:")) return "Solana";
|
|
37
|
+
if (network === "tempo:4217") return "Tempo";
|
|
38
|
+
return network;
|
|
39
|
+
}
|
|
40
|
+
function consoleForLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") return console.error;
|
|
42
|
+
if (level === "warn" || level === "warning") return console.warn;
|
|
43
|
+
return console.log;
|
|
44
|
+
}
|
|
8
45
|
function createTelemetryPlugin(config) {
|
|
9
46
|
_chunkQEJ7ZGGHjs.initClickhouse.call(void 0, config.clickhouse);
|
|
10
47
|
if (config.verify) {
|
|
11
48
|
_chunkQEJ7ZGGHjs.pingClickhouse.call(void 0, );
|
|
12
49
|
}
|
|
13
|
-
const log = _nullishCoalesce(config.console, () => (
|
|
50
|
+
const log = _nullishCoalesce(config.console, () => ( true));
|
|
14
51
|
return {
|
|
52
|
+
init({ origin } = {}) {
|
|
53
|
+
if (log) {
|
|
54
|
+
console.log(`[telemetry] init${origin ? ` origin=${origin}` : ""}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
15
57
|
onRequest(meta) {
|
|
16
58
|
const ctx = {
|
|
17
59
|
requestId: meta.requestId,
|
|
@@ -25,84 +67,106 @@ function createTelemetryPlugin(config) {
|
|
|
25
67
|
},
|
|
26
68
|
_meta: meta
|
|
27
69
|
};
|
|
70
|
+
if (log) {
|
|
71
|
+
const tags = [];
|
|
72
|
+
if (meta.clientId) tags.push(`client=${meta.clientId}`);
|
|
73
|
+
if (meta.sessionId) tags.push(`session=${meta.sessionId}`);
|
|
74
|
+
if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);
|
|
75
|
+
const suffix = tags.length ? ` ${tags.join(" ")}` : "";
|
|
76
|
+
console.log(
|
|
77
|
+
`[telemetry] ${shortId(meta.requestId)} \u2192 ${meta.method} ${meta.route}${suffix}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
28
80
|
return ctx;
|
|
29
81
|
},
|
|
30
82
|
onPaymentVerified(ctx, payment) {
|
|
31
83
|
ctx._payment = payment;
|
|
32
84
|
if (log) {
|
|
33
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
`[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`
|
|
87
|
+
);
|
|
34
88
|
}
|
|
35
89
|
},
|
|
36
90
|
onPaymentSettled(ctx, settlement) {
|
|
37
91
|
ctx._settlement = settlement;
|
|
38
92
|
if (log) {
|
|
39
|
-
console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
onResponse(ctx, response) {
|
|
43
|
-
const tCtx = ctx;
|
|
44
|
-
const meta = tCtx._meta;
|
|
45
|
-
if (log) {
|
|
46
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
47
93
|
console.log(
|
|
48
|
-
`[telemetry] ${
|
|
94
|
+
`[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`
|
|
49
95
|
);
|
|
50
96
|
}
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
},
|
|
98
|
+
onResponse(ctx, response) {
|
|
99
|
+
try {
|
|
100
|
+
const tCtx = ctx;
|
|
101
|
+
const meta = tCtx._meta;
|
|
102
|
+
if (log) {
|
|
103
|
+
const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : "";
|
|
104
|
+
console.log(
|
|
105
|
+
`[telemetry] ${shortId(meta.requestId)} \u2190 ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (response.statusCode === 402) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const row = {
|
|
112
|
+
id: meta.requestId,
|
|
113
|
+
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access', _ => _.walletAddress, 'optionalAccess', _2 => _2.toLowerCase, 'call', _3 => _3()]), () => ( null)),
|
|
114
|
+
x_client_id: meta.clientId,
|
|
115
|
+
session_id: meta.sessionId,
|
|
116
|
+
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access', _4 => _4.verifiedWallet, 'optionalAccess', _5 => _5.toLowerCase, 'call', _6 => _6()]), () => ( null)),
|
|
117
|
+
method: meta.method,
|
|
118
|
+
route: meta.route,
|
|
119
|
+
origin: meta.origin,
|
|
120
|
+
referer: meta.referer,
|
|
121
|
+
request_content_type: meta.contentType,
|
|
122
|
+
request_headers: safeStringify(meta.headers),
|
|
123
|
+
request_body: safeStringify(response.requestBody),
|
|
124
|
+
status_code: response.statusCode,
|
|
125
|
+
status_text: response.statusText,
|
|
126
|
+
duration: response.duration,
|
|
127
|
+
response_content_type: response.contentType,
|
|
128
|
+
response_headers: safeStringify(response.headers),
|
|
129
|
+
response_body: safeStringify(response.responseBody),
|
|
130
|
+
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _7 => _7._payment, 'optionalAccess', _8 => _8.protocol]), () => ( _optionalChain([tCtx, 'access', _9 => _9._settlement, 'optionalAccess', _10 => _10.protocol]))), () => ( null)),
|
|
131
|
+
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access', _11 => _11._payment, 'optionalAccess', _12 => _12.amount]), () => ( null)),
|
|
132
|
+
payment_network: (() => {
|
|
133
|
+
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _13 => _13._payment, 'optionalAccess', _14 => _14.network]), () => ( _optionalChain([tCtx, 'access', _15 => _15._settlement, 'optionalAccess', _16 => _16.network]))), () => ( null));
|
|
134
|
+
return n ? formatNetwork(n) : null;
|
|
135
|
+
})(),
|
|
136
|
+
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
|
|
137
|
+
created_at: /* @__PURE__ */ new Date()
|
|
138
|
+
};
|
|
139
|
+
deferInsert(() => _chunkQEJ7ZGGHjs.insertInvocation.call(void 0, row));
|
|
140
|
+
} catch (e3) {
|
|
53
141
|
}
|
|
54
|
-
const row = {
|
|
55
|
-
id: meta.requestId,
|
|
56
|
-
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access', _ => _.walletAddress, 'optionalAccess', _2 => _2.toLowerCase, 'call', _3 => _3()]), () => ( null)),
|
|
57
|
-
x_client_id: meta.clientId,
|
|
58
|
-
session_id: meta.sessionId,
|
|
59
|
-
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access', _4 => _4.verifiedWallet, 'optionalAccess', _5 => _5.toLowerCase, 'call', _6 => _6()]), () => ( null)),
|
|
60
|
-
method: meta.method,
|
|
61
|
-
route: meta.route,
|
|
62
|
-
origin: meta.origin,
|
|
63
|
-
referer: meta.referer,
|
|
64
|
-
request_content_type: meta.contentType,
|
|
65
|
-
request_headers: JSON.stringify(meta.headers),
|
|
66
|
-
request_body: response.requestBody !== void 0 ? JSON.stringify(response.requestBody) : null,
|
|
67
|
-
status_code: response.statusCode,
|
|
68
|
-
status_text: response.statusText,
|
|
69
|
-
duration: response.duration,
|
|
70
|
-
response_content_type: response.contentType,
|
|
71
|
-
response_headers: JSON.stringify(response.headers),
|
|
72
|
-
response_body: response.responseBody !== void 0 ? JSON.stringify(response.responseBody) : null,
|
|
73
|
-
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _7 => _7._payment, 'optionalAccess', _8 => _8.protocol]), () => ( _optionalChain([tCtx, 'access', _9 => _9._settlement, 'optionalAccess', _10 => _10.protocol]))), () => ( null)),
|
|
74
|
-
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access', _11 => _11._payment, 'optionalAccess', _12 => _12.amount]), () => ( null)),
|
|
75
|
-
payment_network: (() => {
|
|
76
|
-
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _13 => _13._payment, 'optionalAccess', _14 => _14.network]), () => ( _optionalChain([tCtx, 'access', _15 => _15._settlement, 'optionalAccess', _16 => _16.network]))), () => ( null));
|
|
77
|
-
if (!n) return null;
|
|
78
|
-
if (n === "eip155:8453") return "Base";
|
|
79
|
-
if (n.startsWith("solana:")) return "Solana";
|
|
80
|
-
if (n === "tempo:4217") return "Tempo";
|
|
81
|
-
return n;
|
|
82
|
-
})(),
|
|
83
|
-
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
|
|
84
|
-
created_at: /* @__PURE__ */ new Date()
|
|
85
|
-
};
|
|
86
|
-
_chunkQEJ7ZGGHjs.insertInvocation.call(void 0, row);
|
|
87
142
|
},
|
|
88
143
|
onError(ctx, error) {
|
|
89
144
|
if (log) {
|
|
90
|
-
|
|
145
|
+
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
+
console.error(
|
|
147
|
+
`[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`
|
|
148
|
+
);
|
|
91
149
|
}
|
|
92
150
|
},
|
|
93
151
|
onAlert(ctx, alert) {
|
|
94
152
|
if (log) {
|
|
95
|
-
const logFn = alert.level
|
|
153
|
+
const logFn = consoleForLevel(alert.level);
|
|
154
|
+
const id = _optionalChain([ctx, 'optionalAccess', _19 => _19.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
155
|
+
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
96
156
|
logFn(
|
|
97
|
-
`[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
98
|
-
|
|
157
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
158
|
+
...meta ? [meta] : []
|
|
99
159
|
);
|
|
100
160
|
}
|
|
101
161
|
},
|
|
102
162
|
onProviderQuota(ctx, event) {
|
|
103
163
|
if (log) {
|
|
104
|
-
const logFn = event.level
|
|
105
|
-
|
|
164
|
+
const logFn = consoleForLevel(event.level);
|
|
165
|
+
const id = _optionalChain([ctx, 'optionalAccess', _20 => _20.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
166
|
+
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
167
|
+
logFn(
|
|
168
|
+
`[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`
|
|
169
|
+
);
|
|
106
170
|
}
|
|
107
171
|
}
|
|
108
172
|
};
|
|
@@ -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;ACuIO,SAAS,qBAAA,CAAsB,MAAA,EAA6C;AAEjF,EAAA,6CAAA,MAAe,CAAO,UAAU,CAAA;AAChC,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,6CAAA,CAAe;AAAA,EACjB;AAEA,EAAA,MAAM,IAAA,mBAAM,MAAA,CAAO,OAAA,UAAW,OAAA;AAE9B,EAAA,OAAO;AAAA,IACL,SAAA,CAAU,IAAA,EAAkC;AAC1C,MAAA,MAAM,IAAA,EAA8B;AAAA,QAClC,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,QAChB,KAAA,EAAO,IAAA,CAAK,KAAA;AAAA,QACZ,aAAA,EAAe,IAAA,CAAK,aAAA;AAAA,QACpB,QAAA,EAAU,IAAA,CAAK,QAAA;AAAA,QACf,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,QAChB,cAAA,EAAgB,IAAA;AAAA,QAChB,iBAAA,CAAkB,OAAA,EAAiB;AACjC,UAAA,GAAA,CAAI,eAAA,EAAiB,OAAA;AAAA,QACvB,CAAA;AAAA,QACA,KAAA,EAAO;AAAA,MACT,CAAA;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IAEA,iBAAA,CAAkB,GAAA,EAAoB,OAAA,EAAuB;AAC3D,MAAC,GAAA,CAA+B,SAAA,EAAW,OAAA;AAC3C,MAAA,GAAA,CAAI,GAAA,EAAK;AACP,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,OAAA,CAAQ,QAAQ,CAAA,CAAA,EAAI,OAAA,CAAQ,KAAK,CAAA,CAAA,EAAI,OAAA,CAAQ,MAAM,CAAA,CAAA;AACzF,MAAA;AACF,IAAA;AAEkE,IAAA;AAClB,MAAA;AACrC,MAAA;AAC8E,QAAA;AACvF,MAAA;AACF,IAAA;AAEuD,IAAA;AACxC,MAAA;AACK,MAAA;AAET,MAAA;AAC+D,QAAA;AAC9D,QAAA;AACwE,UAAA;AAChF,QAAA;AACF,MAAA;AAGiC,MAAA;AAC/B,QAAA;AACF,MAAA;AAEmC,MAAA;AACxB,QAAA;AAC8C,QAAA;AACrC,QAAA;AACD,QAAA;AAC6C,QAAA;AAEjD,QAAA;AACD,QAAA;AACC,QAAA;AACC,QAAA;AACa,QAAA;AACiB,QAAA;AAEkC,QAAA;AAExD,QAAA;AACA,QAAA;AACH,QAAA;AACa,QAAA;AACiB,QAAA;AAE2B,QAAA;AAED,QAAA;AAClC,QAAA;AACjB,QAAA;AAC2C,UAAA;AAClD,UAAA;AACiB,UAAA;AACI,UAAA;AACL,UAAA;AACxB,UAAA;AACN,QAAA;AAC+C,QAAA;AAE7B,QAAA;AACvB,MAAA;AAEoB,MAAA;AACtB,IAAA;AAE+C,IAAA;AACpC,MAAA;AAC4D,QAAA;AACrE,MAAA;AACF,IAAA;AAE+C,IAAA;AACpC,MAAA;AAID,QAAA;AAGN,QAAA;AAC2E,UAAA;AAC3D,2BAAA;AAChB,QAAA;AACF,MAAA;AACF,IAAA;AAE+D,IAAA;AACpD,MAAA;AAKC,QAAA;AAEgF,QAAA;AAC1F,MAAA;AACF,IAAA;AACF,EAAA;AACF;ADnKgG;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 { 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 /** Console logging for dev. Default: false. */\n console?: boolean;\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 ?? false;\n\n return {\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 return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(`[telemetry] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : '';\n console.log(\n `[telemetry] ${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: JSON.stringify(meta.headers),\n request_body:\n response.requestBody !== undefined ? JSON.stringify(response.requestBody) : null,\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: JSON.stringify(response.headers),\n response_body:\n response.responseBody !== undefined ? JSON.stringify(response.responseBody) : null,\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 if (!n) return null;\n if (n === 'eip155:8453') return 'Base';\n if (n.startsWith('solana:')) return 'Solana';\n if (n === 'tempo:4217') return 'Tempo';\n return n;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n insertInvocation(row);\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n console.error(`[telemetry] ERROR ${error.status}: ${error.message}`);\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn =\n alert.level === 'critical' || alert.level === 'error'\n ? console.error\n : alert.level === 'warn'\n ? console.warn\n : console.log;\n logFn(\n `[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n alert.meta ?? '',\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn =\n event.level === 'critical'\n ? console.error\n : event.level === 'warn'\n ? console.warn\n : console.log;\n logFn(`[telemetry] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);\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. 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"]}
|
package/dist/router-plugin.mjs
CHANGED
|
@@ -5,13 +5,55 @@ import {
|
|
|
5
5
|
} from "./chunk-V553T6WE.mjs";
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
|
+
import { after } from "next/server";
|
|
9
|
+
function safeStringify(value) {
|
|
10
|
+
if (value === void 0) return null;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(value) ?? null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function deferInsert(task) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof after === "function") {
|
|
20
|
+
after(task);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
task();
|
|
26
|
+
}
|
|
27
|
+
function shortId(id) {
|
|
28
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
29
|
+
}
|
|
30
|
+
function shortAddr(addr) {
|
|
31
|
+
if (addr.length < 12) return addr;
|
|
32
|
+
return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
|
|
33
|
+
}
|
|
34
|
+
function formatNetwork(network) {
|
|
35
|
+
if (network === "eip155:8453") return "Base";
|
|
36
|
+
if (network.startsWith("solana:")) return "Solana";
|
|
37
|
+
if (network === "tempo:4217") return "Tempo";
|
|
38
|
+
return network;
|
|
39
|
+
}
|
|
40
|
+
function consoleForLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") return console.error;
|
|
42
|
+
if (level === "warn" || level === "warning") return console.warn;
|
|
43
|
+
return console.log;
|
|
44
|
+
}
|
|
8
45
|
function createTelemetryPlugin(config) {
|
|
9
46
|
initClickhouse(config.clickhouse);
|
|
10
47
|
if (config.verify) {
|
|
11
48
|
pingClickhouse();
|
|
12
49
|
}
|
|
13
|
-
const log = config.console ??
|
|
50
|
+
const log = config.console ?? true;
|
|
14
51
|
return {
|
|
52
|
+
init({ origin } = {}) {
|
|
53
|
+
if (log) {
|
|
54
|
+
console.log(`[telemetry] init${origin ? ` origin=${origin}` : ""}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
15
57
|
onRequest(meta) {
|
|
16
58
|
const ctx = {
|
|
17
59
|
requestId: meta.requestId,
|
|
@@ -25,84 +67,106 @@ function createTelemetryPlugin(config) {
|
|
|
25
67
|
},
|
|
26
68
|
_meta: meta
|
|
27
69
|
};
|
|
70
|
+
if (log) {
|
|
71
|
+
const tags = [];
|
|
72
|
+
if (meta.clientId) tags.push(`client=${meta.clientId}`);
|
|
73
|
+
if (meta.sessionId) tags.push(`session=${meta.sessionId}`);
|
|
74
|
+
if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);
|
|
75
|
+
const suffix = tags.length ? ` ${tags.join(" ")}` : "";
|
|
76
|
+
console.log(
|
|
77
|
+
`[telemetry] ${shortId(meta.requestId)} \u2192 ${meta.method} ${meta.route}${suffix}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
28
80
|
return ctx;
|
|
29
81
|
},
|
|
30
82
|
onPaymentVerified(ctx, payment) {
|
|
31
83
|
ctx._payment = payment;
|
|
32
84
|
if (log) {
|
|
33
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
`[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`
|
|
87
|
+
);
|
|
34
88
|
}
|
|
35
89
|
},
|
|
36
90
|
onPaymentSettled(ctx, settlement) {
|
|
37
91
|
ctx._settlement = settlement;
|
|
38
92
|
if (log) {
|
|
39
|
-
console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
onResponse(ctx, response) {
|
|
43
|
-
const tCtx = ctx;
|
|
44
|
-
const meta = tCtx._meta;
|
|
45
|
-
if (log) {
|
|
46
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
47
93
|
console.log(
|
|
48
|
-
`[telemetry] ${
|
|
94
|
+
`[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`
|
|
49
95
|
);
|
|
50
96
|
}
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
},
|
|
98
|
+
onResponse(ctx, response) {
|
|
99
|
+
try {
|
|
100
|
+
const tCtx = ctx;
|
|
101
|
+
const meta = tCtx._meta;
|
|
102
|
+
if (log) {
|
|
103
|
+
const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : "";
|
|
104
|
+
console.log(
|
|
105
|
+
`[telemetry] ${shortId(meta.requestId)} \u2190 ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (response.statusCode === 402) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const row = {
|
|
112
|
+
id: meta.requestId,
|
|
113
|
+
x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,
|
|
114
|
+
x_client_id: meta.clientId,
|
|
115
|
+
session_id: meta.sessionId,
|
|
116
|
+
verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,
|
|
117
|
+
method: meta.method,
|
|
118
|
+
route: meta.route,
|
|
119
|
+
origin: meta.origin,
|
|
120
|
+
referer: meta.referer,
|
|
121
|
+
request_content_type: meta.contentType,
|
|
122
|
+
request_headers: safeStringify(meta.headers),
|
|
123
|
+
request_body: safeStringify(response.requestBody),
|
|
124
|
+
status_code: response.statusCode,
|
|
125
|
+
status_text: response.statusText,
|
|
126
|
+
duration: response.duration,
|
|
127
|
+
response_content_type: response.contentType,
|
|
128
|
+
response_headers: safeStringify(response.headers),
|
|
129
|
+
response_body: safeStringify(response.responseBody),
|
|
130
|
+
payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,
|
|
131
|
+
payment_amount: tCtx._payment?.amount ?? null,
|
|
132
|
+
payment_network: (() => {
|
|
133
|
+
const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;
|
|
134
|
+
return n ? formatNetwork(n) : null;
|
|
135
|
+
})(),
|
|
136
|
+
payment_tx_hash: tCtx._settlement?.transaction ?? null,
|
|
137
|
+
created_at: /* @__PURE__ */ new Date()
|
|
138
|
+
};
|
|
139
|
+
deferInsert(() => insertInvocation(row));
|
|
140
|
+
} catch {
|
|
53
141
|
}
|
|
54
|
-
const row = {
|
|
55
|
-
id: meta.requestId,
|
|
56
|
-
x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,
|
|
57
|
-
x_client_id: meta.clientId,
|
|
58
|
-
session_id: meta.sessionId,
|
|
59
|
-
verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,
|
|
60
|
-
method: meta.method,
|
|
61
|
-
route: meta.route,
|
|
62
|
-
origin: meta.origin,
|
|
63
|
-
referer: meta.referer,
|
|
64
|
-
request_content_type: meta.contentType,
|
|
65
|
-
request_headers: JSON.stringify(meta.headers),
|
|
66
|
-
request_body: response.requestBody !== void 0 ? JSON.stringify(response.requestBody) : null,
|
|
67
|
-
status_code: response.statusCode,
|
|
68
|
-
status_text: response.statusText,
|
|
69
|
-
duration: response.duration,
|
|
70
|
-
response_content_type: response.contentType,
|
|
71
|
-
response_headers: JSON.stringify(response.headers),
|
|
72
|
-
response_body: response.responseBody !== void 0 ? JSON.stringify(response.responseBody) : null,
|
|
73
|
-
payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,
|
|
74
|
-
payment_amount: tCtx._payment?.amount ?? null,
|
|
75
|
-
payment_network: (() => {
|
|
76
|
-
const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;
|
|
77
|
-
if (!n) return null;
|
|
78
|
-
if (n === "eip155:8453") return "Base";
|
|
79
|
-
if (n.startsWith("solana:")) return "Solana";
|
|
80
|
-
if (n === "tempo:4217") return "Tempo";
|
|
81
|
-
return n;
|
|
82
|
-
})(),
|
|
83
|
-
payment_tx_hash: tCtx._settlement?.transaction ?? null,
|
|
84
|
-
created_at: /* @__PURE__ */ new Date()
|
|
85
|
-
};
|
|
86
|
-
insertInvocation(row);
|
|
87
142
|
},
|
|
88
143
|
onError(ctx, error) {
|
|
89
144
|
if (log) {
|
|
90
|
-
|
|
145
|
+
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
+
console.error(
|
|
147
|
+
`[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`
|
|
148
|
+
);
|
|
91
149
|
}
|
|
92
150
|
},
|
|
93
151
|
onAlert(ctx, alert) {
|
|
94
152
|
if (log) {
|
|
95
|
-
const logFn = alert.level
|
|
153
|
+
const logFn = consoleForLevel(alert.level);
|
|
154
|
+
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
155
|
+
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
96
156
|
logFn(
|
|
97
|
-
`[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
98
|
-
|
|
157
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
158
|
+
...meta ? [meta] : []
|
|
99
159
|
);
|
|
100
160
|
}
|
|
101
161
|
},
|
|
102
162
|
onProviderQuota(ctx, event) {
|
|
103
163
|
if (log) {
|
|
104
|
-
const logFn = event.level
|
|
105
|
-
|
|
164
|
+
const logFn = consoleForLevel(event.level);
|
|
165
|
+
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
166
|
+
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
167
|
+
logFn(
|
|
168
|
+
`[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`
|
|
169
|
+
);
|
|
106
170
|
}
|
|
107
171
|
}
|
|
108
172
|
};
|
|
@@ -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 { 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 /** Console logging for dev. Default: false. */\n console?: boolean;\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 ?? false;\n\n return {\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 return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(`[telemetry] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : '';\n console.log(\n `[telemetry] ${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: JSON.stringify(meta.headers),\n request_body:\n response.requestBody !== undefined ? JSON.stringify(response.requestBody) : null,\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: JSON.stringify(response.headers),\n response_body:\n response.responseBody !== undefined ? JSON.stringify(response.responseBody) : null,\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 if (!n) return null;\n if (n === 'eip155:8453') return 'Base';\n if (n.startsWith('solana:')) return 'Solana';\n if (n === 'tempo:4217') return 'Tempo';\n return n;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n insertInvocation(row);\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n console.error(`[telemetry] ERROR ${error.status}: ${error.message}`);\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn =\n alert.level === 'critical' || alert.level === 'error'\n ? console.error\n : alert.level === 'warn'\n ? console.warn\n : console.log;\n logFn(\n `[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n alert.meta ?? '',\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn =\n event.level === 'critical'\n ? console.error\n : event.level === 'warn'\n ? console.warn\n : console.log;\n logFn(`[telemetry] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);\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. 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":";;;;;;;AAuBA,SAAS,aAAa;AAoItB,SAAS,cAAc,OAA+B;AACpD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,SAAS,YAAY,MAAwB;AAC3C,MAAI;AACF,QAAI,OAAO,UAAU,YAAY;AAC/B,YAAM,IAAI;AACV;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,OAAK;AACP;AAOA,SAAS,QAAQ,IAAoB;AACnC,SAAO,GAAG,SAAS,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI;AAC1C;AAGA,SAAS,UAAU,MAAsB;AACvC,MAAI,KAAK,SAAS,GAAI,QAAO;AAC7B,SAAO,GAAG,KAAK,MAAM,GAAG,CAAC,CAAC,SAAI,KAAK,MAAM,EAAE,CAAC;AAC9C;AAGA,SAAS,cAAc,SAAyB;AAC9C,MAAI,YAAY,cAAe,QAAO;AACtC,MAAI,QAAQ,WAAW,SAAS,EAAG,QAAO;AAC1C,MAAI,YAAY,aAAc,QAAO;AACrC,SAAO;AACT;AAGA,SAAS,gBAAgB,OAA6C;AACpE,MAAI,UAAU,cAAc,UAAU,QAAS,QAAO,QAAQ;AAC9D,MAAI,UAAU,UAAU,UAAU,UAAW,QAAO,QAAQ;AAC5D,SAAO,QAAQ;AACjB;AAEO,SAAS,sBAAsB,QAA6C;AAEjF,iBAAe,OAAO,UAAU;AAChC,MAAI,OAAO,QAAQ;AACjB,mBAAe;AAAA,EACjB;AAEA,QAAM,MAAM,OAAO,WAAW;AAE9B,SAAO;AAAA,IACL,KAAK,EAAE,OAAO,IAAyB,CAAC,GAAG;AACzC,UAAI,KAAK;AACP,gBAAQ,IAAI,mBAAmB,SAAS,WAAW,MAAM,KAAK,EAAE,EAAE;AAAA,MACpE;AAAA,IACF;AAAA,IAEA,UAAU,MAAkC;AAC1C,YAAM,MAA8B;AAAA,QAClC,WAAW,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,eAAe,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,QAChB,kBAAkB,SAAiB;AACjC,cAAI,iBAAiB;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,MACT;AACA,UAAI,KAAK;AACP,cAAM,OAAiB,CAAC;AACxB,YAAI,KAAK,SAAU,MAAK,KAAK,UAAU,KAAK,QAAQ,EAAE;AACtD,YAAI,KAAK,UAAW,MAAK,KAAK,WAAW,KAAK,SAAS,EAAE;AACzD,YAAI,KAAK,cAAe,MAAK,KAAK,UAAU,UAAU,KAAK,aAAa,CAAC,EAAE;AAC3E,cAAM,SAAS,KAAK,SAAS,IAAI,KAAK,KAAK,GAAG,CAAC,KAAK;AACpD,gBAAQ;AAAA,UACN,eAAe,QAAQ,KAAK,SAAS,CAAC,WAAM,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,MAAM;AAAA,QAChF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,kBAAkB,KAAoB,SAAuB;AAC3D,MAAC,IAA+B,WAAW;AAC3C,UAAI,KAAK;AACP,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,aAAa,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS,UAAU,QAAQ,KAAK,CAAC,OAAO,cAAc,QAAQ,OAAO,CAAC;AAAA,QAC5J;AAAA,MACF;AAAA,IACF;AAAA,IAEA,iBAAiB,KAAoB,YAA6B;AAChE,MAAC,IAA+B,cAAc;AAC9C,UAAI,KAAK;AACP,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,YAAY,WAAW,QAAQ,OAAO,WAAW,WAAW,OAAO,cAAc,WAAW,OAAO,CAAC;AAAA,QAC3I;AAAA,MACF;AAAA,IACF;AAAA,IAEA,WAAW,KAAoB,UAAwB;AAIrD,UAAI;AACF,cAAM,OAAO;AACb,cAAM,OAAO,KAAK;AAElB,YAAI,KAAK;AACP,gBAAM,SAAS,IAAI,iBAAiB,WAAW,UAAU,IAAI,cAAc,CAAC,KAAK;AACjF,kBAAQ;AAAA,YACN,eAAe,QAAQ,KAAK,SAAS,CAAC,WAAM,KAAK,MAAM,IAAI,KAAK,KAAK,IAAI,SAAS,UAAU,KAAK,SAAS,QAAQ,MAAM,MAAM;AAAA,UAChI;AAAA,QACF;AAGA,YAAI,SAAS,eAAe,KAAK;AAC/B;AAAA,QACF;AAEA,cAAM,MAA6B;AAAA,UACjC,IAAI,KAAK;AAAA,UACT,kBAAkB,KAAK,eAAe,YAAY,KAAK;AAAA,UACvD,aAAa,KAAK;AAAA,UAClB,YAAY,KAAK;AAAA,UACjB,yBAAyB,IAAI,gBAAgB,YAAY,KAAK;AAAA,UAE9D,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,SAAS,KAAK;AAAA,UACd,sBAAsB,KAAK;AAAA,UAC3B,iBAAiB,cAAc,KAAK,OAAO;AAAA,UAC3C,cAAc,cAAc,SAAS,WAAW;AAAA,UAEhD,aAAa,SAAS;AAAA,UACtB,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,UACnB,uBAAuB,SAAS;AAAA,UAChC,kBAAkB,cAAc,SAAS,OAAO;AAAA,UAChD,eAAe,cAAc,SAAS,YAAY;AAAA,UAElD,kBAAkB,KAAK,UAAU,YAAY,KAAK,aAAa,YAAY;AAAA,UAC3E,gBAAgB,KAAK,UAAU,UAAU;AAAA,UACzC,kBAAkB,MAAM;AACtB,kBAAM,IAAI,KAAK,UAAU,WAAW,KAAK,aAAa,WAAW;AACjE,mBAAO,IAAI,cAAc,CAAC,IAAI;AAAA,UAChC,GAAG;AAAA,UACH,iBAAiB,KAAK,aAAa,eAAe;AAAA,UAElD,YAAY,oBAAI,KAAK;AAAA,QACvB;AAIA,oBAAY,MAAM,iBAAiB,GAAG,CAAC;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,QAAQ,KAAoB,OAAmB;AAC7C,UAAI,KAAK;AACP,cAAM,UAAU,MAAM,UAAU,uBAAuB;AACvD,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,UAAU,MAAM,MAAM,KAAK,MAAM,OAAO,GAAG,OAAO;AAAA,QACzF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ,KAAoB,OAAmB;AAC7C,UAAI,KAAK;AACP,cAAM,QAAQ,gBAAgB,MAAM,KAAK;AACzC,cAAM,KAAK,KAAK,YAAY,GAAG,QAAQ,IAAI,SAAS,CAAC,MAAM;AAC3D,cAAM,OAAO,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,IAAI,MAAM,OAAO;AAC7E;AAAA,UACE,eAAe,EAAE,SAAS,MAAM,MAAM,YAAY,CAAC,IAAI,MAAM,KAAK,KAAK,MAAM,OAAO;AAAA,UACpF,GAAI,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,gBAAgB,KAAoB,OAA2B;AAC7D,UAAI,KAAK;AACP,cAAM,QAAQ,gBAAgB,MAAM,KAAK;AACzC,cAAM,KAAK,KAAK,YAAY,GAAG,QAAQ,IAAI,SAAS,CAAC,MAAM;AAC3D,cAAM,QACJ,MAAM,aAAa,QAAQ,MAAM,SAAS,OACtC,KAAK,MAAM,SAAS,IAAI,MAAM,KAAK,MACnC;AACN;AAAA,UACE,eAAe,EAAE,SAAS,MAAM,MAAM,YAAY,CAAC,IAAI,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK,MAAM,OAAO,GAAG,KAAK;AAAA,QACnH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
+
"@changesets/cli": "^2.29.8",
|
|
68
69
|
"@clickhouse/client": "^1.16.0",
|
|
69
70
|
"@eslint/js": "^9.0.0",
|
|
70
71
|
"@x402/core": "^2.2.0",
|
|
@@ -87,6 +88,7 @@
|
|
|
87
88
|
"format:check": "prettier --check 'src/**/*.ts' '*.json' '*.mjs'",
|
|
88
89
|
"typecheck": "tsc --noEmit",
|
|
89
90
|
"test": "vitest run",
|
|
90
|
-
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm test"
|
|
91
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm test",
|
|
92
|
+
"changeset": "changeset"
|
|
91
93
|
}
|
|
92
94
|
}
|