@agentcash/telemetry 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/builder.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
6
- require('./chunk-TGF67ZBD.js');
5
+ var _chunkHNYH5ZV6js = require('./chunk-HNYH5ZV6.js');
6
+ require('./chunk-SYJ7254V.js');
7
7
 
8
8
  // src/route-builder.ts
9
9
  var _server = require('next/server');
@@ -84,10 +84,10 @@ var RouteBuilder = class _RouteBuilder {
84
84
  handler(fn) {
85
85
  const { accepts, bodySchema, querySchema, outputSchema, outputExample, description } = this.config;
86
86
  const coreHandler = async (request) => {
87
- const meta = _chunkMGILRTLUjs.extractRequestMeta.call(void 0, request);
88
- const ctx = _chunkMGILRTLUjs.buildTelemetryContext.call(void 0, meta);
87
+ const meta = _chunkHNYH5ZV6js.extractRequestMeta.call(void 0, request);
88
+ const ctx = _chunkHNYH5ZV6js.buildTelemetryContext.call(void 0, meta);
89
89
  const log = (status, responseBody, resp) => {
90
- _chunkMGILRTLUjs.recordInvocation.call(void 0, meta, requestBodyString, {
90
+ _chunkHNYH5ZV6js.recordInvocation.call(void 0, meta, requestBodyString, {
91
91
  status,
92
92
  body: responseBody,
93
93
  headers: JSON.stringify(Object.fromEntries(resp.headers.entries())),
package/dist/builder.mjs CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  buildTelemetryContext,
3
3
  extractRequestMeta,
4
4
  recordInvocation
5
- } from "./chunk-SSFH754L.mjs";
6
- import "./chunk-YWUHTPJC.mjs";
5
+ } from "./chunk-WRW4I6WU.mjs";
6
+ import "./chunk-KHVIJ7JU.mjs";
7
7
 
8
8
  // src/route-builder.ts
9
9
  import { NextResponse } from "next/server";
@@ -2,7 +2,7 @@ import {
2
2
  buildTelemetryContext,
3
3
  extractRequestMeta,
4
4
  recordInvocation
5
- } from "./chunk-SSFH754L.mjs";
5
+ } from "./chunk-WRW4I6WU.mjs";
6
6
 
7
7
  // src/telemetry.ts
8
8
  import { NextResponse } from "next/server";
@@ -41,17 +41,14 @@ function withTelemetry(handler) {
41
41
  responseBodyString = await response.clone().text();
42
42
  } catch {
43
43
  }
44
- after(() => {
45
- try {
46
- recordInvocation(meta, requestBodyString, {
47
- status,
48
- body: responseBodyString,
49
- headers: responseHeaders,
50
- contentType
51
- });
52
- } catch {
53
- }
54
- });
44
+ after(
45
+ () => recordInvocation(meta, requestBodyString, {
46
+ status,
47
+ body: responseBodyString,
48
+ headers: responseHeaders,
49
+ contentType
50
+ })
51
+ );
55
52
  }
56
53
  if (handlerError && !(handlerError instanceof NextResponse)) {
57
54
  throw handlerError;
@@ -63,4 +60,4 @@ function withTelemetry(handler) {
63
60
  export {
64
61
  withTelemetry
65
62
  };
66
- //# sourceMappingURL=chunk-IA66E7KQ.mjs.map
63
+ //# sourceMappingURL=chunk-BC4VERQO.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * Core telemetry wrapper for Next.js route handlers.\n * Extracts identity headers, logs to ClickHouse, extracts verified wallet.\n * This is a passive observer — it never influences the response.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { after } from 'next/server';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\ntype TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with telemetry.\n * Extracts identity headers, logs the invocation to ClickHouse,\n * and auto-extracts verified wallet from x402 payment headers.\n *\n * Uses Next.js after() to defer the ClickHouse insert until after\n * the response is sent. On Vercel this keeps the Lambda alive until\n * the insert completes, avoiding frozen in-flight promises.\n *\n * The entire telemetry code path is wrapped in try/catch.\n * Telemetry failures never affect the response.\n */\nexport function withTelemetry(handler: TelemetryHandler) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n // Capture request body for logging (only for methods with bodies)\n let requestBodyString: string | null = null;\n if (meta.method === 'POST' || meta.method === 'PUT' || meta.method === 'PATCH') {\n try {\n const body = await request.clone().text();\n if (body) requestBodyString = body;\n } catch {\n // Body read failed — that's fine\n }\n }\n\n // Execute the actual handler\n let response: NextResponse;\n let handlerError: unknown = null;\n\n try {\n response = await handler(request, ctx);\n } catch (error: unknown) {\n handlerError = error;\n if (error instanceof NextResponse) {\n response = error;\n } else {\n const message = error instanceof Error ? error.message : 'Internal server error';\n response = NextResponse.json({ success: false, error: message }, { status: 500 });\n }\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.status !== 402) {\n // Capture all response data before returning — response.clone() must happen\n // before Next.js consumes the body to send it to the client.\n const status = response.status;\n const responseHeaders = JSON.stringify(Object.fromEntries(response.headers.entries()));\n const contentType = response.headers.get('content-type') ?? null;\n let responseBodyString: string | null = null;\n try {\n responseBodyString = await response.clone().text();\n } catch {\n // Response body read failed — that's fine\n }\n\n // Defer the ClickHouse insert until after the response is sent.\n // On Vercel, after() keeps the Lambda alive until the insert completes.\n after(() => {\n try {\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBodyString,\n headers: responseHeaders,\n contentType,\n });\n } catch {\n // Telemetry never affects the response\n }\n });\n }\n\n // Re-throw the original error if it wasn't a NextResponse\n if (handlerError && !(handlerError instanceof NextResponse)) {\n throw handlerError;\n }\n\n return response;\n };\n}\n"],"mappings":";;;;;;;AAMA,SAA2B,oBAAoB;AAC/C,SAAS,aAAa;AAkBf,SAAS,cAAc,SAA2B;AACvD,SAAO,OAAO,YAAgD;AAC5D,UAAM,OAAO,mBAAmB,OAAO;AACvC,UAAM,MAAM,sBAAsB,IAAI;AAGtC,QAAI,oBAAmC;AACvC,QAAI,KAAK,WAAW,UAAU,KAAK,WAAW,SAAS,KAAK,WAAW,SAAS;AAC9E,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,KAAK;AACxC,YAAI,KAAM,qBAAoB;AAAA,MAChC,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,eAAwB;AAE5B,QAAI;AACF,iBAAW,MAAM,QAAQ,SAAS,GAAG;AAAA,IACvC,SAAS,OAAgB;AACvB,qBAAe;AACf,UAAI,iBAAiB,cAAc;AACjC,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,mBAAW,aAAa,KAAK,EAAE,SAAS,OAAO,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClF;AAAA,IACF;AAGA,QAAI,SAAS,WAAW,KAAK;AAG3B,YAAM,SAAS,SAAS;AACxB,YAAM,kBAAkB,KAAK,UAAU,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC,CAAC;AACrF,YAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,UAAI,qBAAoC;AACxC,UAAI;AACF,6BAAqB,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,MACnD,QAAQ;AAAA,MAER;AAIA,YAAM,MAAM;AACV,YAAI;AACF,2BAAiB,MAAM,mBAAmB;AAAA,YACxC;AAAA,YACA,MAAM;AAAA,YACN,SAAS;AAAA,YACT;AAAA,UACF,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAAA,IACH;AAGA,QAAI,gBAAgB,EAAE,wBAAwB,eAAe;AAC3D,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/telemetry.ts"],"sourcesContent":["/**\n * Core telemetry wrapper for Next.js route handlers.\n * Extracts identity headers, logs to ClickHouse, extracts verified wallet.\n * This is a passive observer — it never influences the response.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { after } from 'next/server';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\ntype TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with telemetry.\n * Extracts identity headers, logs the invocation to ClickHouse,\n * and auto-extracts verified wallet from x402 payment headers.\n *\n * Uses Next.js after() to defer the ClickHouse insert until after\n * the response is sent. On Vercel this keeps the Lambda alive until\n * the insert completes, avoiding frozen in-flight promises.\n *\n * The entire telemetry code path is wrapped in try/catch.\n * Telemetry failures never affect the response.\n */\nexport function withTelemetry(handler: TelemetryHandler) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n // Capture request body for logging (only for methods with bodies)\n let requestBodyString: string | null = null;\n if (meta.method === 'POST' || meta.method === 'PUT' || meta.method === 'PATCH') {\n try {\n const body = await request.clone().text();\n if (body) requestBodyString = body;\n } catch {\n // Body read failed — that's fine\n }\n }\n\n // Execute the actual handler\n let response: NextResponse;\n let handlerError: unknown = null;\n\n try {\n response = await handler(request, ctx);\n } catch (error: unknown) {\n handlerError = error;\n if (error instanceof NextResponse) {\n response = error;\n } else {\n const message = error instanceof Error ? error.message : 'Internal server error';\n response = NextResponse.json({ success: false, error: message }, { status: 500 });\n }\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.status !== 402) {\n // Capture all response data before returning — response.clone() must happen\n // before Next.js consumes the body to send it to the client.\n const status = response.status;\n const responseHeaders = JSON.stringify(Object.fromEntries(response.headers.entries()));\n const contentType = response.headers.get('content-type') ?? null;\n let responseBodyString: string | null = null;\n try {\n responseBodyString = await response.clone().text();\n } catch {\n // Response body read failed — that's fine\n }\n\n // Defer the ClickHouse insert until after the response is sent.\n // On Vercel, after() keeps the Lambda alive until the insert completes.\n after(() =>\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBodyString,\n headers: responseHeaders,\n contentType,\n }),\n );\n }\n\n // Re-throw the original error if it wasn't a NextResponse\n if (handlerError && !(handlerError instanceof NextResponse)) {\n throw handlerError;\n }\n\n return response;\n };\n}\n"],"mappings":";;;;;;;AAMA,SAA2B,oBAAoB;AAC/C,SAAS,aAAa;AAkBf,SAAS,cAAc,SAA2B;AACvD,SAAO,OAAO,YAAgD;AAC5D,UAAM,OAAO,mBAAmB,OAAO;AACvC,UAAM,MAAM,sBAAsB,IAAI;AAGtC,QAAI,oBAAmC;AACvC,QAAI,KAAK,WAAW,UAAU,KAAK,WAAW,SAAS,KAAK,WAAW,SAAS;AAC9E,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,MAAM,EAAE,KAAK;AACxC,YAAI,KAAM,qBAAoB;AAAA,MAChC,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,eAAwB;AAE5B,QAAI;AACF,iBAAW,MAAM,QAAQ,SAAS,GAAG;AAAA,IACvC,SAAS,OAAgB;AACvB,qBAAe;AACf,UAAI,iBAAiB,cAAc;AACjC,mBAAW;AAAA,MACb,OAAO;AACL,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,mBAAW,aAAa,KAAK,EAAE,SAAS,OAAO,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClF;AAAA,IACF;AAGA,QAAI,SAAS,WAAW,KAAK;AAG3B,YAAM,SAAS,SAAS;AACxB,YAAM,kBAAkB,KAAK,UAAU,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC,CAAC;AACrF,YAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;AAC5D,UAAI,qBAAoC;AACxC,UAAI;AACF,6BAAqB,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,MACnD,QAAQ;AAAA,MAER;AAIA;AAAA,QAAM,MACJ,iBAAiB,MAAM,mBAAmB;AAAA,UACxC;AAAA,UACA,MAAM;AAAA,UACN,SAAS;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,gBAAgB,EAAE,wBAAwB,eAAe;AAC3D,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -2,15 +2,15 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
5
+ var _chunkHNYH5ZV6js = require('./chunk-HNYH5ZV6.js');
6
6
 
7
7
  // src/telemetry.ts
8
8
  var _server = require('next/server');
9
9
 
10
10
  function withTelemetry(handler) {
11
11
  return async (request) => {
12
- const meta = _chunkMGILRTLUjs.extractRequestMeta.call(void 0, request);
13
- const ctx = _chunkMGILRTLUjs.buildTelemetryContext.call(void 0, meta);
12
+ const meta = _chunkHNYH5ZV6js.extractRequestMeta.call(void 0, request);
13
+ const ctx = _chunkHNYH5ZV6js.buildTelemetryContext.call(void 0, meta);
14
14
  let requestBodyString = null;
15
15
  if (meta.method === "POST" || meta.method === "PUT" || meta.method === "PATCH") {
16
16
  try {
@@ -41,17 +41,14 @@ function withTelemetry(handler) {
41
41
  responseBodyString = await response.clone().text();
42
42
  } catch (e2) {
43
43
  }
44
- _server.after.call(void 0, () => {
45
- try {
46
- _chunkMGILRTLUjs.recordInvocation.call(void 0, meta, requestBodyString, {
47
- status,
48
- body: responseBodyString,
49
- headers: responseHeaders,
50
- contentType
51
- });
52
- } catch (e3) {
53
- }
54
- });
44
+ _server.after.call(void 0,
45
+ () => _chunkHNYH5ZV6js.recordInvocation.call(void 0, meta, requestBodyString, {
46
+ status,
47
+ body: responseBodyString,
48
+ headers: responseHeaders,
49
+ contentType
50
+ })
51
+ );
55
52
  }
56
53
  if (handlerError && !(handlerError instanceof _server.NextResponse)) {
57
54
  throw handlerError;
@@ -63,4 +60,4 @@ function withTelemetry(handler) {
63
60
 
64
61
 
65
62
  exports.withTelemetry = withTelemetry;
66
- //# sourceMappingURL=chunk-QQAIZOLL.js.map
63
+ //# sourceMappingURL=chunk-FRDVVXVB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-FRDVVXVB.js","../src/telemetry.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACAA,qCAA+C;AAC/C;AAkBO,SAAS,aAAA,CAAc,OAAA,EAA2B;AACvD,EAAA,OAAO,MAAA,CAAO,OAAA,EAAA,GAAgD;AAC5D,IAAA,MAAM,KAAA,EAAO,iDAAA,OAA0B,CAAA;AACvC,IAAA,MAAM,IAAA,EAAM,oDAAA,IAA0B,CAAA;AAGtC,IAAA,IAAI,kBAAA,EAAmC,IAAA;AACvC,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,IAAW,OAAA,GAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,OAAA,IAAW,OAAA,EAAS;AAC9E,MAAA,IAAI;AACF,QAAA,MAAM,KAAA,EAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AACxC,QAAA,GAAA,CAAI,IAAA,EAAM,kBAAA,EAAoB,IAAA;AAAA,MAChC,EAAA,UAAQ;AAAA,MAER;AAAA,IACF;AAGA,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,aAAA,EAAwB,IAAA;AAE5B,IAAA,IAAI;AACF,MAAA,SAAA,EAAW,MAAM,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAAA,IACvC,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,MAAA,aAAA,EAAe,KAAA;AACf,MAAA,GAAA,CAAI,MAAA,WAAiB,oBAAA,EAAc;AACjC,QAAA,SAAA,EAAW,KAAA;AAAA,MACb,EAAA,KAAO;AACL,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,uBAAA;AACzD,QAAA,SAAA,EAAW,oBAAA,CAAa,IAAA,CAAK,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,QAAQ,CAAA,EAAG,EAAE,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,MAClF;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,QAAA,CAAS,OAAA,IAAW,GAAA,EAAK;AAG3B,MAAA,MAAM,OAAA,EAAS,QAAA,CAAS,MAAA;AACxB,MAAA,MAAM,gBAAA,EAAkB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AACrF,MAAA,MAAM,YAAA,mBAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AAC5D,MAAA,IAAI,mBAAA,EAAoC,IAAA;AACxC,MAAA,IAAI;AACF,QAAA,mBAAA,EAAqB,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AAAA,MACnD,EAAA,WAAQ;AAAA,MAER;AAIA,MAAA,2BAAA;AAAA,QAAM,CAAA,EAAA,GACJ,+CAAA,IAAiB,EAAM,iBAAA,EAAmB;AAAA,UACxC,MAAA;AAAA,UACA,IAAA,EAAM,kBAAA;AAAA,UACN,OAAA,EAAS,eAAA;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACH,CAAA;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,aAAA,GAAgB,CAAA,CAAE,aAAA,WAAwB,oBAAA,CAAA,EAAe;AAC3D,MAAA,MAAM,YAAA;AAAA,IACR;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;ADhCA;AACA;AACE;AACF,sCAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-FRDVVXVB.js","sourcesContent":[null,"/**\n * Core telemetry wrapper for Next.js route handlers.\n * Extracts identity headers, logs to ClickHouse, extracts verified wallet.\n * This is a passive observer — it never influences the response.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { after } from 'next/server';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\ntype TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with telemetry.\n * Extracts identity headers, logs the invocation to ClickHouse,\n * and auto-extracts verified wallet from x402 payment headers.\n *\n * Uses Next.js after() to defer the ClickHouse insert until after\n * the response is sent. On Vercel this keeps the Lambda alive until\n * the insert completes, avoiding frozen in-flight promises.\n *\n * The entire telemetry code path is wrapped in try/catch.\n * Telemetry failures never affect the response.\n */\nexport function withTelemetry(handler: TelemetryHandler) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n // Capture request body for logging (only for methods with bodies)\n let requestBodyString: string | null = null;\n if (meta.method === 'POST' || meta.method === 'PUT' || meta.method === 'PATCH') {\n try {\n const body = await request.clone().text();\n if (body) requestBodyString = body;\n } catch {\n // Body read failed — that's fine\n }\n }\n\n // Execute the actual handler\n let response: NextResponse;\n let handlerError: unknown = null;\n\n try {\n response = await handler(request, ctx);\n } catch (error: unknown) {\n handlerError = error;\n if (error instanceof NextResponse) {\n response = error;\n } else {\n const message = error instanceof Error ? error.message : 'Internal server error';\n response = NextResponse.json({ success: false, error: message }, { status: 500 });\n }\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.status !== 402) {\n // Capture all response data before returning — response.clone() must happen\n // before Next.js consumes the body to send it to the client.\n const status = response.status;\n const responseHeaders = JSON.stringify(Object.fromEntries(response.headers.entries()));\n const contentType = response.headers.get('content-type') ?? null;\n let responseBodyString: string | null = null;\n try {\n responseBodyString = await response.clone().text();\n } catch {\n // Response body read failed — that's fine\n }\n\n // Defer the ClickHouse insert until after the response is sent.\n // On Vercel, after() keeps the Lambda alive until the insert completes.\n after(() =>\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBodyString,\n headers: responseHeaders,\n contentType,\n }),\n );\n }\n\n // Re-throw the original error if it wasn't a NextResponse\n if (handlerError && !(handlerError instanceof NextResponse)) {\n throw handlerError;\n }\n\n return response;\n };\n}\n"]}
@@ -2,17 +2,17 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkTGF67ZBDjs = require('./chunk-TGF67ZBD.js');
5
+ var _chunkSYJ7254Vjs = require('./chunk-SYJ7254V.js');
6
6
 
7
7
  // src/init.ts
8
8
  var configuredOrigin;
9
9
  function initTelemetry(config) {
10
- _chunkTGF67ZBDjs.initClickhouse.call(void 0, config.clickhouse);
10
+ _chunkSYJ7254Vjs.initClickhouse.call(void 0, config.clickhouse);
11
11
  if (config.origin) {
12
12
  configuredOrigin = config.origin;
13
13
  }
14
14
  if (config.verify) {
15
- _chunkTGF67ZBDjs.pingClickhouse.call(void 0, );
15
+ _chunkSYJ7254Vjs.pingClickhouse.call(void 0, );
16
16
  }
17
17
  }
18
18
  function getOrigin() {
@@ -85,7 +85,7 @@ function buildTelemetryContext(meta) {
85
85
  };
86
86
  return ctx;
87
87
  }
88
- function recordInvocation(meta, requestBody, response) {
88
+ async function recordInvocation(meta, requestBody, response) {
89
89
  try {
90
90
  const invocation = {
91
91
  id: meta.requestId,
@@ -112,7 +112,7 @@ function recordInvocation(meta, requestBody, response) {
112
112
  payment_tx_hash: null,
113
113
  created_at: /* @__PURE__ */ new Date()
114
114
  };
115
- _chunkTGF67ZBDjs.insertInvocation.call(void 0, invocation);
115
+ await _chunkSYJ7254Vjs.insertInvocation.call(void 0, invocation);
116
116
  } catch (e4) {
117
117
  }
118
118
  }
@@ -150,4 +150,4 @@ function statusTextFromCode(code) {
150
150
 
151
151
 
152
152
  exports.initTelemetry = initTelemetry; exports.extractVerifiedWallet = extractVerifiedWallet; exports.extractRequestMeta = extractRequestMeta; exports.buildTelemetryContext = buildTelemetryContext; exports.recordInvocation = recordInvocation;
153
- //# sourceMappingURL=chunk-MGILRTLU.js.map
153
+ //# sourceMappingURL=chunk-HNYH5ZV6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-HNYH5ZV6.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACHA,IAAI,gBAAA;AAwBG,SAAS,aAAA,CAAc,MAAA,EAA+B;AAC3D,EAAA,6CAAA,MAAe,CAAO,UAAU,CAAA;AAChC,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,iBAAA,EAAmB,MAAA,CAAO,MAAA;AAAA,EAC5B;AACA,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,6CAAA,CAAe;AAAA,EACjB;AACF;AAGO,SAAS,SAAA,CAAA,EAAgC;AAC9C,EAAA,OAAO,gBAAA;AACT;ADpBA;AACA;AEVO,SAAS,qBAAA,CAAsB,OAAA,EAAiC;AACrE,EAAA,IAAI;AAEF,IAAA,MAAM,aAAA,EAAe,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA;AAClD,IAAA,GAAA,CAAI,YAAA,EAAc;AAChB,MAAA,OAAO,YAAA,CAAa,WAAA,CAAY,CAAA;AAAA,IAClC;AAGA,IAAA,MAAM,cAAA,qDACJ,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,UAC/B,OAAA,CAAQ,GAAA,CAAI,mBAAmB,GAAA,UAC/B,OAAA,CAAQ,GAAA,CAAI,WAAW,GAAA,UACvB,OAAA,CAAQ,GAAA,CAAI,WAAW,GAAA;AAEzB,IAAA,GAAA,CAAI,CAAC,aAAA,EAAe,OAAO,IAAA;AAK3B,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,aAAA,EAAe,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA;AAG1E,MAAA,MAAM,KAAA,mCAAO,OAAA,2BAAS,OAAA,6BAAS,aAAA,6BAAe,MAAA,0BAAQ,OAAA,6BAAS,OAAA,6BAAS,QAAA;AACxE,MAAA,OAAO,OAAO,KAAA,IAAS,SAAA,EAAW,IAAA,CAAK,WAAA,CAAY,EAAA,EAAI,IAAA;AAAA,IACzD,EAAA,UAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,EAAA,WAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AFFA;AACA;AGpCA,gCAA2B;AAUpB,SAAS,kBAAA,CAAmB,OAAA,EAAmC;AACpE,EAAA,MAAM,KAAA,EAAoB;AAAA,IACxB,SAAA,EAAW,gCAAA,CAAW;AAAA,IACtB,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,CAAA;AAAA,IACpB,aAAA,EAAe,IAAA;AAAA,IACf,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,cAAA,EAAgB,IAAA;AAAA,IAChB,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,EAAQ,EAAA;AAAA,IACR,MAAA,EAAQ,EAAA;AAAA,IACR,OAAA,EAAS,IAAA;AAAA,IACT,kBAAA,EAAoB,IAAA;AAAA,IACpB,kBAAA,EAAoB;AAAA,EACtB,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,cAAA,mCAAgB,OAAA,qBAAQ,OAAA,qBAAQ,GAAA,mBAAI,kBAAkB,CAAA,6BAAG,WAAA,qBAAY,GAAA,UAAK,MAAA;AAC/E,IAAA,IAAA,CAAK,SAAA,mBAAW,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA,UAAK,MAAA;AACtD,IAAA,IAAA,CAAK,UAAA,mBAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AACxD,IAAA,IAAA,CAAK,QAAA,mBAAU,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,UAAK,MAAA;AACjD,IAAA,IAAA,CAAK,mBAAA,mBAAqB,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AACjE,IAAA,IAAA,CAAK,MAAA,EAAQ,OAAA,CAAQ,OAAA,CAAQ,QAAA;AAC7B,IAAA,IAAA,CAAK,OAAA,EAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,OAAA,mBAAS,SAAA,CAAU,CAAA,UAAK,OAAA,CAAQ,OAAA,CAAQ,QAAA;AAC7C,IAAA,IAAA,CAAK,eAAA,EAAiB,qBAAA,CAAsB,OAAA,CAAQ,OAAO,CAAA;AAC3D,IAAA,IAAA,CAAK,mBAAA,EAAqB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,EACxF,EAAA,WAAQ;AAAA,EAER;AAEA,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,qBAAA,CAAsB,IAAA,EAAqC;AACzE,EAAA,MAAM,IAAA,EAAwB;AAAA,IAC5B,aAAA,EAAe,IAAA,CAAK,aAAA;AAAA,IACpB,QAAA,EAAU,IAAA,CAAK,QAAA;AAAA,IACf,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,IAChB,cAAA,EAAgB,IAAA,CAAK,cAAA;AAAA,IACrB,iBAAA,EAAmB,CAAC,OAAA,EAAA,GAAoB;AACtC,MAAA,IAAA,CAAK,eAAA,EAAiB,OAAA,CAAQ,WAAA,CAAY,CAAA;AAC1C,MAAA,GAAA,CAAI,eAAA,EAAiB,IAAA,CAAK,cAAA;AAAA,IAC5B;AAAA,EACF,CAAA;AACA,EAAA,OAAO,GAAA;AACT;AAKA,MAAA,SAAsB,gBAAA,CACpB,IAAA,EACA,WAAA,EACA,QAAA,EAMe;AACf,EAAA,IAAI;AACF,IAAA,MAAM,WAAA,EAAoC;AAAA,MACxC,EAAA,EAAI,IAAA,CAAK,SAAA;AAAA,MACT,gBAAA,EAAkB,IAAA,CAAK,aAAA;AAAA,MACvB,WAAA,EAAa,IAAA,CAAK,QAAA;AAAA,MAClB,UAAA,EAAY,IAAA,CAAK,SAAA;AAAA,MACjB,uBAAA,EAAyB,IAAA,CAAK,cAAA;AAAA,MAC9B,MAAA,EAAQ,IAAA,CAAK,MAAA;AAAA,MACb,KAAA,EAAO,IAAA,CAAK,KAAA;AAAA,MACZ,MAAA,EAAQ,IAAA,CAAK,MAAA;AAAA,MACb,OAAA,EAAS,IAAA,CAAK,OAAA;AAAA,MACd,oBAAA,EAAsB,IAAA,CAAK,kBAAA;AAAA,MAC3B,eAAA,EAAiB,IAAA,CAAK,kBAAA;AAAA,MACtB,YAAA,EAAc,WAAA;AAAA,MACd,WAAA,EAAa,QAAA,CAAS,MAAA;AAAA,MACtB,WAAA,EAAa,kBAAA,CAAmB,QAAA,CAAS,MAAM,CAAA;AAAA,MAC/C,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,SAAA;AAAA,MAC5B,qBAAA,EAAuB,QAAA,CAAS,WAAA;AAAA,MAChC,gBAAA,EAAkB,QAAA,CAAS,OAAA;AAAA,MAC3B,aAAA,EAAe,QAAA,CAAS,IAAA;AAAA,MACxB,gBAAA,EAAkB,IAAA;AAAA,MAClB,cAAA,EAAgB,IAAA;AAAA,MAChB,eAAA,EAAiB,IAAA;AAAA,MACjB,eAAA,EAAiB,IAAA;AAAA,MACjB,UAAA,kBAAY,IAAI,IAAA,CAAK;AAAA,IACvB,CAAA;AACA,IAAA,MAAM,+CAAA,UAA2B,CAAA;AAAA,EACnC,EAAA,WAAQ;AAAA,EAER;AACF;AAEA,SAAS,kBAAA,CAAmB,IAAA,EAAsB;AAChD,EAAA,OAAA,CAAQ,IAAA,EAAM;AAAA,IACZ,KAAK,GAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,YAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,aAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,cAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,uBAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,iBAAA;AAAA,IACT,OAAA;AACE,MAAA,OAAO,MAAA,CAAO,IAAI,CAAA;AAAA,EACtB;AACF;AHMA;AACA;AACE;AACA;AACA;AACA;AACA;AACF,kPAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-HNYH5ZV6.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * });\n * ```\n */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fully wrapped in try/catch.\n */\nexport async 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): Promise<void> {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n await 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"]}
@@ -10,7 +10,7 @@ function initClickhouse(config) {
10
10
  password: config.password ?? "",
11
11
  // Serverless-safe defaults: disable keep-alive to prevent stale pooled
12
12
  // sockets after Lambda freeze/thaw, and shorten the request timeout since
13
- // inserts are fire-and-forget (a fast failure is fine).
13
+ // inserts run after the response has been sent.
14
14
  keep_alive: { enabled: false },
15
15
  request_timeout: 5e3
16
16
  });
@@ -31,27 +31,21 @@ function pingClickhouse() {
31
31
  console.warn("[telemetry] ClickHouse ping failed:", message);
32
32
  });
33
33
  }
34
- function insertInvocation(data) {
34
+ async function insertInvocation(data) {
35
35
  try {
36
36
  if (!clickhouseClient) {
37
37
  console.warn("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
38
38
  return;
39
39
  }
40
- clickhouseClient.insert({
40
+ await clickhouseClient.insert({
41
41
  table: TABLE,
42
42
  values: [data],
43
43
  format: "JSONEachRow"
44
- }).catch((error) => {
45
- try {
46
- const message = error instanceof Error ? error.message : String(error);
47
- console.warn("[telemetry] ClickHouse insert failed:", message);
48
- } catch {
49
- }
50
44
  });
51
45
  } catch (error) {
52
46
  try {
53
47
  const message = error instanceof Error ? error.message : String(error);
54
- console.warn("[telemetry] ClickHouse insert threw synchronously:", message);
48
+ console.warn("[telemetry] ClickHouse insert failed:", message);
55
49
  } catch {
56
50
  }
57
51
  }
@@ -62,4 +56,4 @@ export {
62
56
  pingClickhouse,
63
57
  insertInvocation
64
58
  };
65
- //# sourceMappingURL=chunk-YWUHTPJC.mjs.map
59
+ //# sourceMappingURL=chunk-KHVIJ7JU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clickhouse.ts"],"sourcesContent":["import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts run after the response has been sent.\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws. Returns a settled promise so Next's\n * after() can keep the function alive until the insert completes.\n */\nexport async function insertInvocation(data: McpResourceInvocation): Promise<void> {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n await clickhouseClient.insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAG7B,IAAI,mBAA2D;AAE/D,IAAM,QAAQ;AAMP,SAAS,eAAe,QAA6C;AAC1E,qBAAmB,aAAa;AAAA,IAC9B,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA;AAAA;AAAA;AAAA,IAI7B,YAAY,EAAE,SAAS,MAAM;AAAA,IAC7B,iBAAiB;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,iBAAuB;AACrC,MAAI,CAAC,kBAAkB;AACrB,YAAQ,KAAK,+DAA+D;AAC5E;AAAA,EACF;AACA,mBACG,KAAK,EACL,KAAK,CAAC,WAAW;AAChB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kCAAkC;AAAA,IAChD,OAAO;AACL,cAAQ,KAAK,oCAAoC;AAAA,IACnD;AAAA,EACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,KAAK,uCAAuC,OAAO;AAAA,EAC7D,CAAC;AACL;AAOA,eAAsB,iBAAiB,MAA4C;AACjF,MAAI;AACF,QAAI,CAAC,kBAAkB;AACrB,cAAQ,KAAK,4EAA4E;AACzF;AAAA,IACF;AAEA,UAAM,iBAAiB,OAA8B;AAAA,MACnD,OAAO;AAAA,MACP,QAAQ,CAAC,IAAI;AAAA,MACb,QAAQ;AAAA,IACV,CAAC;AAAA,EACH,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,KAAK,yCAAyC,OAAO;AAAA,IAC/D,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}
@@ -10,7 +10,7 @@ function initClickhouse(config) {
10
10
  password: _nullishCoalesce(config.password, () => ( "")),
11
11
  // Serverless-safe defaults: disable keep-alive to prevent stale pooled
12
12
  // sockets after Lambda freeze/thaw, and shorten the request timeout since
13
- // inserts are fire-and-forget (a fast failure is fine).
13
+ // inserts run after the response has been sent.
14
14
  keep_alive: { enabled: false },
15
15
  request_timeout: 5e3
16
16
  });
@@ -31,28 +31,22 @@ function pingClickhouse() {
31
31
  console.warn("[telemetry] ClickHouse ping failed:", message);
32
32
  });
33
33
  }
34
- function insertInvocation(data) {
34
+ async function insertInvocation(data) {
35
35
  try {
36
36
  if (!clickhouseClient) {
37
37
  console.warn("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
38
38
  return;
39
39
  }
40
- clickhouseClient.insert({
40
+ await clickhouseClient.insert({
41
41
  table: TABLE,
42
42
  values: [data],
43
43
  format: "JSONEachRow"
44
- }).catch((error) => {
45
- try {
46
- const message = error instanceof Error ? error.message : String(error);
47
- console.warn("[telemetry] ClickHouse insert failed:", message);
48
- } catch (e) {
49
- }
50
44
  });
51
45
  } catch (error) {
52
46
  try {
53
47
  const message = error instanceof Error ? error.message : String(error);
54
- console.warn("[telemetry] ClickHouse insert threw synchronously:", message);
55
- } catch (e2) {
48
+ console.warn("[telemetry] ClickHouse insert failed:", message);
49
+ } catch (e) {
56
50
  }
57
51
  }
58
52
  }
@@ -62,4 +56,4 @@ function insertInvocation(data) {
62
56
 
63
57
 
64
58
  exports.initClickhouse = initClickhouse; exports.pingClickhouse = pingClickhouse; exports.insertInvocation = insertInvocation;
65
- //# sourceMappingURL=chunk-TGF67ZBD.js.map
59
+ //# sourceMappingURL=chunk-SYJ7254V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-SYJ7254V.js","../src/clickhouse.ts"],"names":[],"mappings":"AAAA;ACAA,4CAA6B;AAG7B,IAAI,iBAAA,EAA2D,IAAA;AAE/D,IAAM,MAAA,EAAQ,0BAAA;AAMP,SAAS,cAAA,CAAe,MAAA,EAA6C;AAC1E,EAAA,iBAAA,EAAmB,kCAAA;AAAa,IAC9B,GAAA,EAAK,MAAA,CAAO,GAAA;AAAA,IACZ,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,IAAA;AAAA;AAAA;AAAA;AAAA,IAI7B,UAAA,EAAY,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IAC7B,eAAA,EAAiB;AAAA,EACnB,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CAAA,EAAuB;AACrC,EAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA,OAAA,CAAQ,IAAA,CAAK,+DAA+D,CAAA;AAC5E,IAAA,MAAA;AAAA,EACF;AACA,EAAA,gBAAA,CACG,IAAA,CAAK,CAAA,CACL,IAAA,CAAK,CAAC,MAAA,EAAA,GAAW;AAChB,IAAA,GAAA,CAAI,MAAA,CAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,kCAAkC,CAAA;AAAA,IAChD,EAAA,KAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,oCAAoC,CAAA;AAAA,IACnD;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,IAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,IAAA,OAAA,CAAQ,IAAA,CAAK,qCAAA,EAAuC,OAAO,CAAA;AAAA,EAC7D,CAAC,CAAA;AACL;AAOA,MAAA,SAAsB,gBAAA,CAAiB,IAAA,EAA4C;AACjF,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAA,CAAQ,IAAA,CAAK,4EAA4E,CAAA;AACzF,MAAA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,gBAAA,CAAiB,MAAA,CAA8B;AAAA,MACnD,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,CAAC,IAAI,CAAA;AAAA,MACb,MAAA,EAAQ;AAAA,IACV,CAAC,CAAA;AAAA,EACH,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,MAAA,OAAA,CAAQ,IAAA,CAAK,uCAAA,EAAyC,OAAO,CAAA;AAAA,IAC/D,EAAA,UAAQ;AAAA,IAER;AAAA,EACF;AACF;ADrBA;AACA;AACE;AACA;AACA;AACF,8HAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-SYJ7254V.js","sourcesContent":[null,"import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts run after the response has been sent.\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws. Returns a settled promise so Next's\n * after() can keep the function alive until the insert completes.\n */\nexport async function insertInvocation(data: McpResourceInvocation): Promise<void> {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n await clickhouseClient.insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"]}
@@ -2,7 +2,7 @@ import {
2
2
  initClickhouse,
3
3
  insertInvocation,
4
4
  pingClickhouse
5
- } from "./chunk-YWUHTPJC.mjs";
5
+ } from "./chunk-KHVIJ7JU.mjs";
6
6
 
7
7
  // src/init.ts
8
8
  var configuredOrigin;
@@ -85,7 +85,7 @@ function buildTelemetryContext(meta) {
85
85
  };
86
86
  return ctx;
87
87
  }
88
- function recordInvocation(meta, requestBody, response) {
88
+ async function recordInvocation(meta, requestBody, response) {
89
89
  try {
90
90
  const invocation = {
91
91
  id: meta.requestId,
@@ -112,7 +112,7 @@ function recordInvocation(meta, requestBody, response) {
112
112
  payment_tx_hash: null,
113
113
  created_at: /* @__PURE__ */ new Date()
114
114
  };
115
- insertInvocation(invocation);
115
+ await insertInvocation(invocation);
116
116
  } catch {
117
117
  }
118
118
  }
@@ -150,4 +150,4 @@ export {
150
150
  buildTelemetryContext,
151
151
  recordInvocation
152
152
  };
153
- //# sourceMappingURL=chunk-SSFH754L.mjs.map
153
+ //# sourceMappingURL=chunk-WRW4I6WU.mjs.map
@@ -1 +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 payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"],"mappings":"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","names":[]}
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. Fully wrapped in try/catch.\n */\nexport async 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): Promise<void> {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n await 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.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkQQAIZOLLjs = require('./chunk-QQAIZOLL.js');
3
+ var _chunkFRDVVXVBjs = require('./chunk-FRDVVXVB.js');
4
4
 
5
5
 
6
6
 
7
- var _chunkMGILRTLUjs = require('./chunk-MGILRTLU.js');
8
- require('./chunk-TGF67ZBD.js');
7
+ var _chunkHNYH5ZV6js = require('./chunk-HNYH5ZV6.js');
8
+ require('./chunk-SYJ7254V.js');
9
9
 
10
10
 
11
11
 
12
12
 
13
- exports.extractVerifiedWallet = _chunkMGILRTLUjs.extractVerifiedWallet; exports.initTelemetry = _chunkMGILRTLUjs.initTelemetry; exports.withTelemetry = _chunkQQAIZOLLjs.withTelemetry;
13
+ exports.extractVerifiedWallet = _chunkHNYH5ZV6js.extractVerifiedWallet; exports.initTelemetry = _chunkHNYH5ZV6js.initTelemetry; exports.withTelemetry = _chunkFRDVVXVBjs.withTelemetry;
14
14
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  withTelemetry
3
- } from "./chunk-IA66E7KQ.mjs";
3
+ } from "./chunk-BC4VERQO.mjs";
4
4
  import {
5
5
  extractVerifiedWallet,
6
6
  initTelemetry
7
- } from "./chunk-SSFH754L.mjs";
8
- import "./chunk-YWUHTPJC.mjs";
7
+ } from "./chunk-WRW4I6WU.mjs";
8
+ import "./chunk-KHVIJ7JU.mjs";
9
9
  export {
10
10
  extractVerifiedWallet,
11
11
  initTelemetry,
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkTGF67ZBDjs = require('./chunk-TGF67ZBD.js');
5
+ var _chunkSYJ7254Vjs = require('./chunk-SYJ7254V.js');
6
6
 
7
7
  // src/router-plugin.ts
8
8
  var _server = require('next/server');
@@ -44,9 +44,9 @@ function consoleForLevel(level) {
44
44
  return console.log;
45
45
  }
46
46
  function createTelemetryPlugin(config) {
47
- _chunkTGF67ZBDjs.initClickhouse.call(void 0, config.clickhouse);
47
+ _chunkSYJ7254Vjs.initClickhouse.call(void 0, config.clickhouse);
48
48
  if (config.verify) {
49
- _chunkTGF67ZBDjs.pingClickhouse.call(void 0, );
49
+ _chunkSYJ7254Vjs.pingClickhouse.call(void 0, );
50
50
  }
51
51
  const log = _nullishCoalesce(config.console, () => ( true));
52
52
  return {
@@ -137,7 +137,7 @@ function createTelemetryPlugin(config) {
137
137
  payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
138
138
  created_at: /* @__PURE__ */ new Date()
139
139
  };
140
- deferInsert(() => _chunkTGF67ZBDjs.insertInvocation.call(void 0, row));
140
+ deferInsert(() => _chunkSYJ7254Vjs.insertInvocation.call(void 0, row));
141
141
  } catch (e3) {
142
142
  }
143
143
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"]}
@@ -2,7 +2,7 @@ import {
2
2
  initClickhouse,
3
3
  insertInvocation,
4
4
  pingClickhouse
5
- } from "./chunk-YWUHTPJC.mjs";
5
+ } from "./chunk-KHVIJ7JU.mjs";
6
6
 
7
7
  // src/router-plugin.ts
8
8
  import { after } from "next/server";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value or structured router-generated error body. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n requestId?: string;\n route?: string;\n method?: string;\n duration?: number;\n walletAddress?: string | null;\n verifiedWallet?: string | null;\n clientId?: string | null;\n sessionId?: string | null;\n errorName?: string;\n stack?: string;\n cause?: unknown;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void | Promise<void>): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Alert/quota hooks are diagnostic context; HTTP 5xx is the only error-severity path. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error' || level === 'warn' || level === 'warning') {\n return console.warn;\n }\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n const route = error.route ?? ctx.route;\n const requestId = error.requestId ?? ctx.requestId;\n const logFn = error.status >= 500 ? console.error : console.warn;\n logFn(\n `[telemetry] ${shortId(requestId)} error ${route} ${error.status}: ${error.message}${settled}`,\n {\n requestId,\n method: error.method,\n duration: error.duration,\n walletAddress: error.walletAddress ?? ctx.walletAddress,\n verifiedWallet: error.verifiedWallet ?? ctx.verifiedWallet,\n clientId: error.clientId ?? ctx.clientId,\n sessionId: error.sessionId ?? ctx.sessionId,\n errorName: error.errorName,\n stack: error.stack,\n },\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
package/dist/siwx.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
2
 
3
- var _chunkQQAIZOLLjs = require('./chunk-QQAIZOLL.js');
4
- require('./chunk-MGILRTLU.js');
5
- require('./chunk-TGF67ZBD.js');
3
+ var _chunkFRDVVXVBjs = require('./chunk-FRDVVXVB.js');
4
+ require('./chunk-HNYH5ZV6.js');
5
+ require('./chunk-SYJ7254V.js');
6
6
 
7
7
  // src/siwx.ts
8
8
  var _server = require('next/server');
@@ -15,7 +15,7 @@ var _crypto = require('crypto');
15
15
  var _signinwithx = require('@x402/extensions/sign-in-with-x');
16
16
  var _http = require('@x402/core/http');
17
17
  function withSiwxTelemetry(handler) {
18
- return _chunkQQAIZOLLjs.withTelemetry.call(void 0, async (request, ctx) => {
18
+ return _chunkFRDVVXVBjs.withTelemetry.call(void 0, async (request, ctx) => {
19
19
  const header = _nullishCoalesce(request.headers.get("SIGN-IN-WITH-X"), () => ( request.headers.get("sign-in-with-x")));
20
20
  if (!header) {
21
21
  return buildSiwxChallengeResponse(request);
package/dist/siwx.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  withTelemetry
3
- } from "./chunk-IA66E7KQ.mjs";
4
- import "./chunk-SSFH754L.mjs";
5
- import "./chunk-YWUHTPJC.mjs";
3
+ } from "./chunk-BC4VERQO.mjs";
4
+ import "./chunk-WRW4I6WU.mjs";
5
+ import "./chunk-KHVIJ7JU.mjs";
6
6
 
7
7
  // src/siwx.ts
8
8
  import { NextResponse } from "next/server";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/telemetry",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "ClickHouse telemetry plugin for @agentcash/router. Logs request lifecycle, payments, settlements, and provider quota to ClickHouse.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MGILRTLU.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACHA,IAAI,gBAAA;AAwBG,SAAS,aAAA,CAAc,MAAA,EAA+B;AAC3D,EAAA,6CAAA,MAAe,CAAO,UAAU,CAAA;AAChC,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,iBAAA,EAAmB,MAAA,CAAO,MAAA;AAAA,EAC5B;AACA,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,6CAAA,CAAe;AAAA,EACjB;AACF;AAGO,SAAS,SAAA,CAAA,EAAgC;AAC9C,EAAA,OAAO,gBAAA;AACT;ADpBA;AACA;AEVO,SAAS,qBAAA,CAAsB,OAAA,EAAiC;AACrE,EAAA,IAAI;AAEF,IAAA,MAAM,aAAA,EAAe,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA;AAClD,IAAA,GAAA,CAAI,YAAA,EAAc;AAChB,MAAA,OAAO,YAAA,CAAa,WAAA,CAAY,CAAA;AAAA,IAClC;AAGA,IAAA,MAAM,cAAA,qDACJ,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,UAC/B,OAAA,CAAQ,GAAA,CAAI,mBAAmB,GAAA,UAC/B,OAAA,CAAQ,GAAA,CAAI,WAAW,GAAA,UACvB,OAAA,CAAQ,GAAA,CAAI,WAAW,GAAA;AAEzB,IAAA,GAAA,CAAI,CAAC,aAAA,EAAe,OAAO,IAAA;AAK3B,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,aAAA,EAAe,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAC,CAAA;AAG1E,MAAA,MAAM,KAAA,mCAAO,OAAA,2BAAS,OAAA,6BAAS,aAAA,6BAAe,MAAA,0BAAQ,OAAA,6BAAS,OAAA,6BAAS,QAAA;AACxE,MAAA,OAAO,OAAO,KAAA,IAAS,SAAA,EAAW,IAAA,CAAK,WAAA,CAAY,EAAA,EAAI,IAAA;AAAA,IACzD,EAAA,UAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF,EAAA,WAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AFFA;AACA;AGpCA,gCAA2B;AAUpB,SAAS,kBAAA,CAAmB,OAAA,EAAmC;AACpE,EAAA,MAAM,KAAA,EAAoB;AAAA,IACxB,SAAA,EAAW,gCAAA,CAAW;AAAA,IACtB,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,CAAA;AAAA,IACpB,aAAA,EAAe,IAAA;AAAA,IACf,QAAA,EAAU,IAAA;AAAA,IACV,SAAA,EAAW,IAAA;AAAA,IACX,cAAA,EAAgB,IAAA;AAAA,IAChB,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,EAAQ,EAAA;AAAA,IACR,MAAA,EAAQ,EAAA;AAAA,IACR,OAAA,EAAS,IAAA;AAAA,IACT,kBAAA,EAAoB,IAAA;AAAA,IACpB,kBAAA,EAAoB;AAAA,EACtB,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,cAAA,mCAAgB,OAAA,qBAAQ,OAAA,qBAAQ,GAAA,mBAAI,kBAAkB,CAAA,6BAAG,WAAA,qBAAY,GAAA,UAAK,MAAA;AAC/E,IAAA,IAAA,CAAK,SAAA,mBAAW,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,aAAa,CAAA,UAAK,MAAA;AACtD,IAAA,IAAA,CAAK,UAAA,mBAAY,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AACxD,IAAA,IAAA,CAAK,QAAA,mBAAU,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,UAAK,MAAA;AACjD,IAAA,IAAA,CAAK,mBAAA,mBAAqB,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AACjE,IAAA,IAAA,CAAK,MAAA,EAAQ,OAAA,CAAQ,OAAA,CAAQ,QAAA;AAC7B,IAAA,IAAA,CAAK,OAAA,EAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,OAAA,mBAAS,SAAA,CAAU,CAAA,UAAK,OAAA,CAAQ,OAAA,CAAQ,QAAA;AAC7C,IAAA,IAAA,CAAK,eAAA,EAAiB,qBAAA,CAAsB,OAAA,CAAQ,OAAO,CAAA;AAC3D,IAAA,IAAA,CAAK,mBAAA,EAAqB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,EACxF,EAAA,WAAQ;AAAA,EAER;AAEA,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,qBAAA,CAAsB,IAAA,EAAqC;AACzE,EAAA,MAAM,IAAA,EAAwB;AAAA,IAC5B,aAAA,EAAe,IAAA,CAAK,aAAA;AAAA,IACpB,QAAA,EAAU,IAAA,CAAK,QAAA;AAAA,IACf,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,IAChB,cAAA,EAAgB,IAAA,CAAK,cAAA;AAAA,IACrB,iBAAA,EAAmB,CAAC,OAAA,EAAA,GAAoB;AACtC,MAAA,IAAA,CAAK,eAAA,EAAiB,OAAA,CAAQ,WAAA,CAAY,CAAA;AAC1C,MAAA,GAAA,CAAI,eAAA,EAAiB,IAAA,CAAK,cAAA;AAAA,IAC5B;AAAA,EACF,CAAA;AACA,EAAA,OAAO,GAAA;AACT;AAKO,SAAS,gBAAA,CACd,IAAA,EACA,WAAA,EACA,QAAA,EAMM;AACN,EAAA,IAAI;AACF,IAAA,MAAM,WAAA,EAAoC;AAAA,MACxC,EAAA,EAAI,IAAA,CAAK,SAAA;AAAA,MACT,gBAAA,EAAkB,IAAA,CAAK,aAAA;AAAA,MACvB,WAAA,EAAa,IAAA,CAAK,QAAA;AAAA,MAClB,UAAA,EAAY,IAAA,CAAK,SAAA;AAAA,MACjB,uBAAA,EAAyB,IAAA,CAAK,cAAA;AAAA,MAC9B,MAAA,EAAQ,IAAA,CAAK,MAAA;AAAA,MACb,KAAA,EAAO,IAAA,CAAK,KAAA;AAAA,MACZ,MAAA,EAAQ,IAAA,CAAK,MAAA;AAAA,MACb,OAAA,EAAS,IAAA,CAAK,OAAA;AAAA,MACd,oBAAA,EAAsB,IAAA,CAAK,kBAAA;AAAA,MAC3B,eAAA,EAAiB,IAAA,CAAK,kBAAA;AAAA,MACtB,YAAA,EAAc,WAAA;AAAA,MACd,WAAA,EAAa,QAAA,CAAS,MAAA;AAAA,MACtB,WAAA,EAAa,kBAAA,CAAmB,QAAA,CAAS,MAAM,CAAA;AAAA,MAC/C,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,SAAA;AAAA,MAC5B,qBAAA,EAAuB,QAAA,CAAS,WAAA;AAAA,MAChC,gBAAA,EAAkB,QAAA,CAAS,OAAA;AAAA,MAC3B,aAAA,EAAe,QAAA,CAAS,IAAA;AAAA,MACxB,gBAAA,EAAkB,IAAA;AAAA,MAClB,cAAA,EAAgB,IAAA;AAAA,MAChB,eAAA,EAAiB,IAAA;AAAA,MACjB,eAAA,EAAiB,IAAA;AAAA,MACjB,UAAA,kBAAY,IAAI,IAAA,CAAK;AAAA,IACvB,CAAA;AACA,IAAA,+CAAA,UAA2B,CAAA;AAAA,EAC7B,EAAA,WAAQ;AAAA,EAER;AACF;AAEA,SAAS,kBAAA,CAAmB,IAAA,EAAsB;AAChD,EAAA,OAAA,CAAQ,IAAA,EAAM;AAAA,IACZ,KAAK,GAAA;AACH,MAAA,OAAO,IAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,SAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,YAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,aAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,cAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,kBAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,WAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,uBAAA;AAAA,IACT,KAAK,GAAA;AACH,MAAA,OAAO,iBAAA;AAAA,IACT,OAAA;AACE,MAAA,OAAO,MAAA,CAAO,IAAI,CAAA;AAAA,EACtB;AACF;AHMA;AACA;AACE;AACA;AACA;AACA;AACA;AACF,kPAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MGILRTLU.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * });\n * ```\n */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fire-and-forget, fully wrapped in try/catch.\n */\nexport function recordInvocation(\n meta: RequestMeta,\n requestBody: string | null,\n response: {\n status: number;\n body: string | null;\n headers: string | null;\n contentType: string | null;\n },\n): void {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QQAIZOLL.js","../src/telemetry.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACAA,qCAA+C;AAC/C;AAkBO,SAAS,aAAA,CAAc,OAAA,EAA2B;AACvD,EAAA,OAAO,MAAA,CAAO,OAAA,EAAA,GAAgD;AAC5D,IAAA,MAAM,KAAA,EAAO,iDAAA,OAA0B,CAAA;AACvC,IAAA,MAAM,IAAA,EAAM,oDAAA,IAA0B,CAAA;AAGtC,IAAA,IAAI,kBAAA,EAAmC,IAAA;AACvC,IAAA,GAAA,CAAI,IAAA,CAAK,OAAA,IAAW,OAAA,GAAU,IAAA,CAAK,OAAA,IAAW,MAAA,GAAS,IAAA,CAAK,OAAA,IAAW,OAAA,EAAS;AAC9E,MAAA,IAAI;AACF,QAAA,MAAM,KAAA,EAAO,MAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AACxC,QAAA,GAAA,CAAI,IAAA,EAAM,kBAAA,EAAoB,IAAA;AAAA,MAChC,EAAA,UAAQ;AAAA,MAER;AAAA,IACF;AAGA,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI,aAAA,EAAwB,IAAA;AAE5B,IAAA,IAAI;AACF,MAAA,SAAA,EAAW,MAAM,OAAA,CAAQ,OAAA,EAAS,GAAG,CAAA;AAAA,IACvC,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,MAAA,aAAA,EAAe,KAAA;AACf,MAAA,GAAA,CAAI,MAAA,WAAiB,oBAAA,EAAc;AACjC,QAAA,SAAA,EAAW,KAAA;AAAA,MACb,EAAA,KAAO;AACL,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,uBAAA;AACzD,QAAA,SAAA,EAAW,oBAAA,CAAa,IAAA,CAAK,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,QAAQ,CAAA,EAAG,EAAE,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,MAClF;AAAA,IACF;AAGA,IAAA,GAAA,CAAI,QAAA,CAAS,OAAA,IAAW,GAAA,EAAK;AAG3B,MAAA,MAAM,OAAA,EAAS,QAAA,CAAS,MAAA;AACxB,MAAA,MAAM,gBAAA,EAAkB,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AACrF,MAAA,MAAM,YAAA,mBAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK,MAAA;AAC5D,MAAA,IAAI,mBAAA,EAAoC,IAAA;AACxC,MAAA,IAAI;AACF,QAAA,mBAAA,EAAqB,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AAAA,MACnD,EAAA,WAAQ;AAAA,MAER;AAIA,MAAA,2BAAA,CAAM,EAAA,GAAM;AACV,QAAA,IAAI;AACF,UAAA,+CAAA,IAAiB,EAAM,iBAAA,EAAmB;AAAA,YACxC,MAAA;AAAA,YACA,IAAA,EAAM,kBAAA;AAAA,YACN,OAAA,EAAS,eAAA;AAAA,YACT;AAAA,UACF,CAAC,CAAA;AAAA,QACH,EAAA,WAAQ;AAAA,QAER;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAGA,IAAA,GAAA,CAAI,aAAA,GAAgB,CAAA,CAAE,aAAA,WAAwB,oBAAA,CAAA,EAAe;AAC3D,MAAA,MAAM,YAAA;AAAA,IACR;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AACF;ADjCA;AACA;AACE;AACF,sCAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QQAIZOLL.js","sourcesContent":[null,"/**\n * Core telemetry wrapper for Next.js route handlers.\n * Extracts identity headers, logs to ClickHouse, extracts verified wallet.\n * This is a passive observer — it never influences the response.\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { after } from 'next/server';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\ntype TelemetryHandler = (request: NextRequest, ctx: TelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with telemetry.\n * Extracts identity headers, logs the invocation to ClickHouse,\n * and auto-extracts verified wallet from x402 payment headers.\n *\n * Uses Next.js after() to defer the ClickHouse insert until after\n * the response is sent. On Vercel this keeps the Lambda alive until\n * the insert completes, avoiding frozen in-flight promises.\n *\n * The entire telemetry code path is wrapped in try/catch.\n * Telemetry failures never affect the response.\n */\nexport function withTelemetry(handler: TelemetryHandler) {\n return async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n // Capture request body for logging (only for methods with bodies)\n let requestBodyString: string | null = null;\n if (meta.method === 'POST' || meta.method === 'PUT' || meta.method === 'PATCH') {\n try {\n const body = await request.clone().text();\n if (body) requestBodyString = body;\n } catch {\n // Body read failed — that's fine\n }\n }\n\n // Execute the actual handler\n let response: NextResponse;\n let handlerError: unknown = null;\n\n try {\n response = await handler(request, ctx);\n } catch (error: unknown) {\n handlerError = error;\n if (error instanceof NextResponse) {\n response = error;\n } else {\n const message = error instanceof Error ? error.message : 'Internal server error';\n response = NextResponse.json({ success: false, error: message }, { status: 500 });\n }\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.status !== 402) {\n // Capture all response data before returning — response.clone() must happen\n // before Next.js consumes the body to send it to the client.\n const status = response.status;\n const responseHeaders = JSON.stringify(Object.fromEntries(response.headers.entries()));\n const contentType = response.headers.get('content-type') ?? null;\n let responseBodyString: string | null = null;\n try {\n responseBodyString = await response.clone().text();\n } catch {\n // Response body read failed — that's fine\n }\n\n // Defer the ClickHouse insert until after the response is sent.\n // On Vercel, after() keeps the Lambda alive until the insert completes.\n after(() => {\n try {\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBodyString,\n headers: responseHeaders,\n contentType,\n });\n } catch {\n // Telemetry never affects the response\n }\n });\n }\n\n // Re-throw the original error if it wasn't a NextResponse\n if (handlerError && !(handlerError instanceof NextResponse)) {\n throw handlerError;\n }\n\n return response;\n };\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-TGF67ZBD.js","../src/clickhouse.ts"],"names":[],"mappings":"AAAA;ACAA,4CAA6B;AAG7B,IAAI,iBAAA,EAA2D,IAAA;AAE/D,IAAM,MAAA,EAAQ,0BAAA;AAMP,SAAS,cAAA,CAAe,MAAA,EAA6C;AAC1E,EAAA,iBAAA,EAAmB,kCAAA;AAAa,IAC9B,GAAA,EAAK,MAAA,CAAO,GAAA;AAAA,IACZ,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,WAAA;AAAA,IAC7B,QAAA,mBAAU,MAAA,CAAO,QAAA,UAAY,IAAA;AAAA;AAAA;AAAA;AAAA,IAI7B,UAAA,EAAY,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,IAC7B,eAAA,EAAiB;AAAA,EACnB,CAAC,CAAA;AACH;AAKO,SAAS,cAAA,CAAA,EAAuB;AACrC,EAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,IAAA,OAAA,CAAQ,IAAA,CAAK,+DAA+D,CAAA;AAC5E,IAAA,MAAA;AAAA,EACF;AACA,EAAA,gBAAA,CACG,IAAA,CAAK,CAAA,CACL,IAAA,CAAK,CAAC,MAAA,EAAA,GAAW;AAChB,IAAA,GAAA,CAAI,MAAA,CAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,kCAAkC,CAAA;AAAA,IAChD,EAAA,KAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,oCAAoC,CAAA;AAAA,IACnD;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,IAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,IAAA,OAAA,CAAQ,IAAA,CAAK,qCAAA,EAAuC,OAAO,CAAA;AAAA,EAC7D,CAAC,CAAA;AACL;AAMO,SAAS,gBAAA,CAAiB,IAAA,EAAmC;AAClE,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAA,CAAQ,IAAA,CAAK,4EAA4E,CAAA;AACzF,MAAA,MAAA;AAAA,IACF;AAGA,IAAA,gBAAA,CACG,MAAA,CAA8B;AAAA,MAC7B,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,CAAC,IAAI,CAAA;AAAA,MACb,MAAA,EAAQ;AAAA,IACV,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,QAAA,OAAA,CAAQ,IAAA,CAAK,uCAAA,EAAyC,OAAO,CAAA;AAAA,MAC/D,EAAA,UAAQ;AAAA,MAER;AAAA,IACF,CAAC,CAAA;AAAA,EACL,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,MAAA,OAAA,CAAQ,IAAA,CAAK,oDAAA,EAAsD,OAAO,CAAA;AAAA,IAC5E,EAAA,WAAQ;AAAA,IAER;AAAA,EACF;AACF;ADxBA;AACA;AACE;AACA;AACA;AACF,8HAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-TGF67ZBD.js","sourcesContent":[null,"import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts are fire-and-forget (a fast failure is fine).\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/clickhouse.ts"],"sourcesContent":["import { createClient } from '@clickhouse/client';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\nlet clickhouseClient: ReturnType<typeof createClient> | null = null;\n\nconst TABLE = 'mcp_resource_invocations';\n\n/**\n * Initialize the ClickHouse client singleton.\n * createClient() is synchronous — no async needed.\n */\nexport function initClickhouse(config: TelemetryConfig['clickhouse']): void {\n clickhouseClient = createClient({\n url: config.url,\n database: config.database ?? 'default',\n username: config.username ?? 'default',\n password: config.password ?? '',\n // Serverless-safe defaults: disable keep-alive to prevent stale pooled\n // sockets after Lambda freeze/thaw, and shorten the request timeout since\n // inserts are fire-and-forget (a fast failure is fine).\n keep_alive: { enabled: false },\n request_timeout: 5_000,\n });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.warn('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.warn('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.warn('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.warn('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAG7B,IAAI,mBAA2D;AAE/D,IAAM,QAAQ;AAMP,SAAS,eAAe,QAA6C;AAC1E,qBAAmB,aAAa;AAAA,IAC9B,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA;AAAA;AAAA;AAAA,IAI7B,YAAY,EAAE,SAAS,MAAM;AAAA,IAC7B,iBAAiB;AAAA,EACnB,CAAC;AACH;AAKO,SAAS,iBAAuB;AACrC,MAAI,CAAC,kBAAkB;AACrB,YAAQ,KAAK,+DAA+D;AAC5E;AAAA,EACF;AACA,mBACG,KAAK,EACL,KAAK,CAAC,WAAW;AAChB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kCAAkC;AAAA,IAChD,OAAO;AACL,cAAQ,KAAK,oCAAoC;AAAA,IACnD;AAAA,EACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,KAAK,uCAAuC,OAAO;AAAA,EAC7D,CAAC;AACL;AAMO,SAAS,iBAAiB,MAAmC;AAClE,MAAI;AACF,QAAI,CAAC,kBAAkB;AACrB,cAAQ,KAAK,4EAA4E;AACzF;AAAA,IACF;AAGA,qBACG,OAA8B;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ,CAAC,IAAI;AAAA,MACb,QAAQ;AAAA,IACV,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI;AACF,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,gBAAQ,KAAK,yCAAyC,OAAO;AAAA,MAC/D,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACL,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,KAAK,sDAAsD,OAAO;AAAA,IAC5E,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}