@agentcash/telemetry 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -0
- package/dist/builder.d.mts +58 -0
- package/dist/builder.d.ts +58 -0
- package/dist/builder.js +218 -0
- package/dist/builder.js.map +1 -0
- package/dist/builder.mjs +218 -0
- package/dist/builder.mjs.map +1 -0
- package/dist/chunk-FJ3YM6KO.js +55 -0
- package/dist/chunk-FJ3YM6KO.js.map +1 -0
- package/dist/chunk-GE7VBMQP.js +149 -0
- package/dist/chunk-GE7VBMQP.js.map +1 -0
- package/dist/chunk-JVLKV7CX.mjs +55 -0
- package/dist/chunk-JVLKV7CX.mjs.map +1 -0
- package/dist/chunk-O2PCP6KV.mjs +60 -0
- package/dist/chunk-O2PCP6KV.mjs.map +1 -0
- package/dist/chunk-P63MLKU3.js +60 -0
- package/dist/chunk-P63MLKU3.js.map +1 -0
- package/dist/chunk-VOY67KA4.mjs +149 -0
- package/dist/chunk-VOY67KA4.mjs.map +1 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14 -0
- package/dist/index.mjs.map +1 -0
- package/dist/router-plugin.d.mts +109 -0
- package/dist/router-plugin.d.ts +109 -0
- package/dist/router-plugin.js +99 -0
- package/dist/router-plugin.js.map +1 -0
- package/dist/router-plugin.mjs +99 -0
- package/dist/router-plugin.mjs.map +1 -0
- package/dist/siwx.d.mts +29 -0
- package/dist/siwx.d.ts +29 -0
- package/dist/siwx.js +99 -0
- package/dist/siwx.js.map +1 -0
- package/dist/siwx.mjs +99 -0
- package/dist/siwx.mjs.map +1 -0
- package/dist/types-Bl8IwXin.d.mts +57 -0
- package/dist/types-Bl8IwXin.d.ts +57 -0
- package/package.json +88 -0
package/dist/builder.mjs
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTelemetryContext,
|
|
3
|
+
extractRequestMeta,
|
|
4
|
+
recordInvocation
|
|
5
|
+
} from "./chunk-VOY67KA4.mjs";
|
|
6
|
+
import "./chunk-O2PCP6KV.mjs";
|
|
7
|
+
|
|
8
|
+
// src/route-builder.ts
|
|
9
|
+
import { NextResponse } from "next/server";
|
|
10
|
+
import { withX402 } from "@x402/next";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
|
|
13
|
+
var HttpError = class extends Error {
|
|
14
|
+
constructor(message, status) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.name = "HttpError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function formatValidationError(error) {
|
|
21
|
+
const issues = error.issues;
|
|
22
|
+
if (issues.length === 0) return "Validation failed";
|
|
23
|
+
if (issues.length === 1) {
|
|
24
|
+
const issue = issues[0];
|
|
25
|
+
const path = issue.path.join(".");
|
|
26
|
+
const field = path || "request";
|
|
27
|
+
if (issue.message) return `${field}: ${issue.message}`;
|
|
28
|
+
return `${field}: Invalid value`;
|
|
29
|
+
}
|
|
30
|
+
const errors = issues.map((issue) => {
|
|
31
|
+
const path = issue.path.join(".");
|
|
32
|
+
const field = path || "request";
|
|
33
|
+
return issue.message ? `${field}: ${issue.message}` : `${field}: Invalid`;
|
|
34
|
+
});
|
|
35
|
+
return `Validation failed: ${errors.join("; ")}`;
|
|
36
|
+
}
|
|
37
|
+
var RouteBuilder = class _RouteBuilder {
|
|
38
|
+
config;
|
|
39
|
+
options;
|
|
40
|
+
constructor(config, options) {
|
|
41
|
+
this.config = { accepts: [], ...config };
|
|
42
|
+
this.options = options ?? {};
|
|
43
|
+
}
|
|
44
|
+
price(amount, network, asset) {
|
|
45
|
+
return new _RouteBuilder(
|
|
46
|
+
{ ...this.config, accepts: [{ amount, network, asset }] },
|
|
47
|
+
this.options
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
accepts(options) {
|
|
51
|
+
return new _RouteBuilder(
|
|
52
|
+
{ ...this.config, accepts: options },
|
|
53
|
+
this.options
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
body(schema) {
|
|
57
|
+
return new _RouteBuilder(
|
|
58
|
+
{ ...this.config, bodySchema: schema },
|
|
59
|
+
this.options
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
query(schema) {
|
|
63
|
+
return new _RouteBuilder(
|
|
64
|
+
{ ...this.config, querySchema: schema },
|
|
65
|
+
this.options
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
output(schema, example) {
|
|
69
|
+
return new _RouteBuilder(
|
|
70
|
+
{
|
|
71
|
+
...this.config,
|
|
72
|
+
outputSchema: schema,
|
|
73
|
+
outputExample: example
|
|
74
|
+
},
|
|
75
|
+
this.options
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
description(text) {
|
|
79
|
+
return new _RouteBuilder(
|
|
80
|
+
{ ...this.config, description: text },
|
|
81
|
+
this.options
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
handler(fn) {
|
|
85
|
+
const { accepts, bodySchema, querySchema, outputSchema, outputExample, description } = this.config;
|
|
86
|
+
const coreHandler = async (request) => {
|
|
87
|
+
const meta = extractRequestMeta(request);
|
|
88
|
+
const ctx = buildTelemetryContext(meta);
|
|
89
|
+
const log = (status, responseBody, resp) => {
|
|
90
|
+
recordInvocation(meta, requestBodyString, {
|
|
91
|
+
status,
|
|
92
|
+
body: responseBody,
|
|
93
|
+
headers: JSON.stringify(Object.fromEntries(resp.headers.entries())),
|
|
94
|
+
contentType: resp.headers.get("content-type") ?? null
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
let body = void 0;
|
|
98
|
+
let query = void 0;
|
|
99
|
+
let requestBodyString = null;
|
|
100
|
+
if (bodySchema) {
|
|
101
|
+
let rawBody;
|
|
102
|
+
try {
|
|
103
|
+
rawBody = await request.json();
|
|
104
|
+
requestBodyString = JSON.stringify(rawBody);
|
|
105
|
+
} catch {
|
|
106
|
+
const errorResp = NextResponse.json(
|
|
107
|
+
{ success: false, error: "Invalid JSON body" },
|
|
108
|
+
{ status: 400 }
|
|
109
|
+
);
|
|
110
|
+
log(400, JSON.stringify({ success: false, error: "Invalid JSON body" }), errorResp);
|
|
111
|
+
return errorResp;
|
|
112
|
+
}
|
|
113
|
+
const parsed = bodySchema.safeParse(rawBody);
|
|
114
|
+
if (!parsed.success) {
|
|
115
|
+
const message = formatValidationError(parsed.error);
|
|
116
|
+
const errorBody = {
|
|
117
|
+
success: false,
|
|
118
|
+
error: "Validation failed",
|
|
119
|
+
message,
|
|
120
|
+
details: parsed.error.flatten()
|
|
121
|
+
};
|
|
122
|
+
const errorResp = NextResponse.json(errorBody, { status: 400 });
|
|
123
|
+
log(400, JSON.stringify(errorBody), errorResp);
|
|
124
|
+
return errorResp;
|
|
125
|
+
}
|
|
126
|
+
body = parsed.data;
|
|
127
|
+
}
|
|
128
|
+
if (querySchema) {
|
|
129
|
+
const searchParams = Object.fromEntries(request.nextUrl.searchParams);
|
|
130
|
+
const parsed = querySchema.safeParse(searchParams);
|
|
131
|
+
if (!parsed.success) {
|
|
132
|
+
const message = formatValidationError(parsed.error);
|
|
133
|
+
const errorBody = {
|
|
134
|
+
success: false,
|
|
135
|
+
error: "Query validation failed",
|
|
136
|
+
message,
|
|
137
|
+
details: parsed.error.flatten()
|
|
138
|
+
};
|
|
139
|
+
const errorResp = NextResponse.json(errorBody, { status: 400 });
|
|
140
|
+
log(400, JSON.stringify(errorBody), errorResp);
|
|
141
|
+
return errorResp;
|
|
142
|
+
}
|
|
143
|
+
query = parsed.data;
|
|
144
|
+
}
|
|
145
|
+
let response;
|
|
146
|
+
try {
|
|
147
|
+
response = await fn({ body, query, request, telemetry: ctx });
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
150
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
151
|
+
const errorBody = { success: false, error: message };
|
|
152
|
+
const errorResp = NextResponse.json(errorBody, { status });
|
|
153
|
+
log(status, JSON.stringify(errorBody), errorResp);
|
|
154
|
+
return errorResp;
|
|
155
|
+
}
|
|
156
|
+
if (response && typeof response === "object" && "success" in response && !response.success) {
|
|
157
|
+
const errorResp = NextResponse.json(response, { status: 500 });
|
|
158
|
+
log(500, JSON.stringify(response), errorResp);
|
|
159
|
+
return errorResp;
|
|
160
|
+
}
|
|
161
|
+
const successResp = NextResponse.json(response);
|
|
162
|
+
let responseBodyString = null;
|
|
163
|
+
try {
|
|
164
|
+
responseBodyString = JSON.stringify(response);
|
|
165
|
+
} catch {
|
|
166
|
+
}
|
|
167
|
+
log(200, responseBodyString, successResp);
|
|
168
|
+
return successResp;
|
|
169
|
+
};
|
|
170
|
+
if (accepts.length === 0) {
|
|
171
|
+
return coreHandler;
|
|
172
|
+
}
|
|
173
|
+
const X402_BYPASS = process.env.X402_BYPASS === "true";
|
|
174
|
+
if (X402_BYPASS) {
|
|
175
|
+
return coreHandler;
|
|
176
|
+
}
|
|
177
|
+
const X402_PAYEE_ADDRESS = process.env.X402_PAYEE_ADDRESS;
|
|
178
|
+
if (!X402_PAYEE_ADDRESS) {
|
|
179
|
+
throw new Error("X402_PAYEE_ADDRESS environment variable is required when using .price()");
|
|
180
|
+
}
|
|
181
|
+
const routeConfig = {
|
|
182
|
+
description,
|
|
183
|
+
accepts: accepts.map(({ amount, network, asset }) => ({
|
|
184
|
+
scheme: "exact",
|
|
185
|
+
network,
|
|
186
|
+
price: amount,
|
|
187
|
+
payTo: X402_PAYEE_ADDRESS,
|
|
188
|
+
...asset && { extra: { asset } }
|
|
189
|
+
})),
|
|
190
|
+
extensions: buildDiscoveryExtensions(bodySchema, querySchema, outputSchema, outputExample)
|
|
191
|
+
};
|
|
192
|
+
if (!this.options.x402Server) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
"x402Server is required when using .price(). Pass it to createRouteBuilder({ x402Server })."
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return withX402(coreHandler, routeConfig, this.options.x402Server);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
function buildDiscoveryExtensions(bodySchema, querySchema, outputSchema, outputExample) {
|
|
201
|
+
const inputJsonSchema = bodySchema ? z.toJSONSchema(bodySchema, { target: "draft-2020-12" }) : querySchema ? z.toJSONSchema(querySchema, { target: "draft-2020-12" }) : void 0;
|
|
202
|
+
const outputJsonSchema = outputSchema ? z.toJSONSchema(outputSchema, { target: "draft-2020-12" }) : void 0;
|
|
203
|
+
if (!inputJsonSchema) return void 0;
|
|
204
|
+
const config = {
|
|
205
|
+
bodyType: bodySchema ? "json" : void 0,
|
|
206
|
+
inputSchema: inputJsonSchema,
|
|
207
|
+
output: outputJsonSchema ? { schema: outputJsonSchema, example: outputExample ?? {} } : void 0
|
|
208
|
+
};
|
|
209
|
+
return { ...declareDiscoveryExtension(config) };
|
|
210
|
+
}
|
|
211
|
+
function createRouteBuilder(options) {
|
|
212
|
+
return new RouteBuilder(void 0, options);
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
HttpError,
|
|
216
|
+
createRouteBuilder
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=builder.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/route-builder.ts"],"sourcesContent":["/**\n * Convenience route builder that composes telemetry + validation + x402 wrapping.\n * This is optional — servers can use withTelemetry directly.\n *\n * Import from '@agentcash/telemetry/builder'.\n * Requires peer deps: @x402/next, zod (^4), @x402/extensions\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { withX402 } from '@x402/next';\nimport { z, type ZodType } from 'zod';\nimport { declareDiscoveryExtension } from '@x402/extensions/bazaar';\nimport type { TelemetryContext } from './types';\nimport { extractRequestMeta, buildTelemetryContext, recordInvocation } from './telemetry-core';\n\nexport class HttpError extends Error {\n constructor(\n message: string,\n public status: number,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\ntype AcceptsOption = {\n amount: string;\n network: string;\n asset?: string;\n};\n\ntype BuilderConfig<TBody = unknown, TQuery = unknown, TOutput = unknown> = {\n accepts: AcceptsOption[];\n bodySchema?: ZodType<TBody>;\n querySchema?: ZodType<TQuery>;\n outputSchema?: ZodType<TOutput>;\n outputExample?: TOutput;\n description?: string;\n};\n\ntype HandlerContext<TBody, TQuery> = {\n body: TBody;\n query: TQuery;\n request: NextRequest;\n telemetry: TelemetryContext;\n};\n\ntype HandlerFn<TBody, TQuery, TResponse> = (\n ctx: HandlerContext<TBody, TQuery>,\n) => Promise<TResponse>;\n\nfunction formatValidationError(error: import('zod').ZodError): string {\n const issues = error.issues;\n if (issues.length === 0) return 'Validation failed';\n\n if (issues.length === 1) {\n const issue = issues[0];\n const path = issue.path.join('.');\n const field = path || 'request';\n if (issue.message) return `${field}: ${issue.message}`;\n return `${field}: Invalid value`;\n }\n\n const errors = issues.map((issue) => {\n const path = issue.path.join('.');\n const field = path || 'request';\n return issue.message ? `${field}: ${issue.message}` : `${field}: Invalid`;\n });\n\n return `Validation failed: ${errors.join('; ')}`;\n}\n\nexport interface RouteBuilderOptions {\n /** The x402 resource server instance (from @x402/core/server). Required when using .price(). */\n x402Server?: unknown;\n}\n\nclass RouteBuilder<TBody = unknown, TQuery = unknown, TOutput = unknown> {\n private config: BuilderConfig<TBody, TQuery, TOutput>;\n private options: RouteBuilderOptions;\n\n constructor(\n config?: Partial<BuilderConfig<TBody, TQuery, TOutput>>,\n options?: RouteBuilderOptions,\n ) {\n this.config = { accepts: [], ...config };\n this.options = options ?? {};\n }\n\n price(amount: string, network: string, asset?: string) {\n return new RouteBuilder<TBody, TQuery, TOutput>(\n { ...this.config, accepts: [{ amount, network, asset }] },\n this.options,\n );\n }\n\n accepts(options: AcceptsOption[]) {\n return new RouteBuilder<TBody, TQuery, TOutput>(\n { ...this.config, accepts: options },\n this.options,\n );\n }\n\n body<T>(schema: ZodType<T>) {\n return new RouteBuilder<T, TQuery, TOutput>(\n { ...this.config, bodySchema: schema as ZodType<unknown> } as BuilderConfig<\n T,\n TQuery,\n TOutput\n >,\n this.options,\n );\n }\n\n query<T>(schema: ZodType<T>) {\n return new RouteBuilder<TBody, T, TOutput>(\n { ...this.config, querySchema: schema as ZodType<unknown> } as BuilderConfig<\n TBody,\n T,\n TOutput\n >,\n this.options,\n );\n }\n\n output<T>(schema: ZodType<T>, example?: T) {\n return new RouteBuilder<TBody, TQuery, T>(\n {\n ...this.config,\n outputSchema: schema as ZodType<unknown>,\n outputExample: example,\n } as BuilderConfig<TBody, TQuery, T>,\n this.options,\n );\n }\n\n description(text: string) {\n return new RouteBuilder<TBody, TQuery, TOutput>(\n { ...this.config, description: text },\n this.options,\n );\n }\n\n handler<TResponse>(fn: HandlerFn<TBody, TQuery, TResponse>) {\n const { accepts, bodySchema, querySchema, outputSchema, outputExample, description } =\n this.config;\n\n const coreHandler = async (request: NextRequest): Promise<NextResponse> => {\n const meta = extractRequestMeta(request);\n const ctx = buildTelemetryContext(meta);\n\n const log = (status: number, responseBody: string | null, resp: NextResponse) => {\n recordInvocation(meta, requestBodyString, {\n status,\n body: responseBody,\n headers: JSON.stringify(Object.fromEntries(resp.headers.entries())),\n contentType: resp.headers.get('content-type') ?? null,\n });\n };\n\n // Parse and validate body\n let body: TBody = undefined as TBody;\n let query: TQuery = undefined as TQuery;\n let requestBodyString: string | null = null;\n\n if (bodySchema) {\n let rawBody: unknown;\n try {\n rawBody = await request.json();\n requestBodyString = JSON.stringify(rawBody);\n } catch {\n const errorResp = NextResponse.json(\n { success: false, error: 'Invalid JSON body' },\n { status: 400 },\n );\n log(400, JSON.stringify({ success: false, error: 'Invalid JSON body' }), errorResp);\n return errorResp;\n }\n\n const parsed = bodySchema.safeParse(rawBody);\n if (!parsed.success) {\n const message = formatValidationError(parsed.error);\n const errorBody = {\n success: false,\n error: 'Validation failed',\n message,\n details: parsed.error.flatten(),\n };\n const errorResp = NextResponse.json(errorBody, { status: 400 });\n log(400, JSON.stringify(errorBody), errorResp);\n return errorResp;\n }\n body = parsed.data;\n }\n\n // Parse and validate query\n if (querySchema) {\n const searchParams = Object.fromEntries(request.nextUrl.searchParams);\n const parsed = querySchema.safeParse(searchParams);\n if (!parsed.success) {\n const message = formatValidationError(parsed.error);\n const errorBody = {\n success: false,\n error: 'Query validation failed',\n message,\n details: parsed.error.flatten(),\n };\n const errorResp = NextResponse.json(errorBody, { status: 400 });\n log(400, JSON.stringify(errorBody), errorResp);\n return errorResp;\n }\n query = parsed.data;\n }\n\n // Execute user handler\n let response: TResponse;\n try {\n response = await fn({ body, query, request, telemetry: ctx });\n } catch (error: unknown) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n const status = error instanceof HttpError ? error.status : 500;\n const errorBody = { success: false, error: message };\n const errorResp = NextResponse.json(errorBody, { status });\n log(status, JSON.stringify(errorBody), errorResp);\n return errorResp;\n }\n\n // Detect { success: false } to prevent x402 settlement\n if (\n response &&\n typeof response === 'object' &&\n 'success' in response &&\n !(response as { success: boolean }).success\n ) {\n const errorResp = NextResponse.json(response, { status: 500 });\n log(500, JSON.stringify(response), errorResp);\n return errorResp;\n }\n\n // Success\n const successResp = NextResponse.json(response);\n let responseBodyString: string | null = null;\n try {\n responseBodyString = JSON.stringify(response);\n } catch {\n // Not serializable — that's fine\n }\n log(200, responseBodyString, successResp);\n return successResp;\n };\n\n // If no pricing, return the core handler directly (no x402 wrapping)\n if (accepts.length === 0) {\n return coreHandler;\n }\n\n // Wrap with x402\n const X402_BYPASS = process.env.X402_BYPASS === 'true';\n if (X402_BYPASS) {\n return coreHandler;\n }\n\n const X402_PAYEE_ADDRESS = process.env.X402_PAYEE_ADDRESS;\n if (!X402_PAYEE_ADDRESS) {\n throw new Error('X402_PAYEE_ADDRESS environment variable is required when using .price()');\n }\n\n const routeConfig = {\n description,\n accepts: accepts.map(({ amount, network, asset }) => ({\n scheme: 'exact' as const,\n network: network as `${string}:${string}`,\n price: amount,\n payTo: X402_PAYEE_ADDRESS,\n ...(asset && { extra: { asset } }),\n })),\n extensions: buildDiscoveryExtensions(bodySchema, querySchema, outputSchema, outputExample),\n };\n\n if (!this.options.x402Server) {\n throw new Error(\n 'x402Server is required when using .price(). Pass it to createRouteBuilder({ x402Server }).',\n );\n }\n\n return withX402(coreHandler, routeConfig, this.options.x402Server as never);\n }\n}\n\nfunction buildDiscoveryExtensions(\n bodySchema?: ZodType<unknown>,\n querySchema?: ZodType<unknown>,\n outputSchema?: ZodType<unknown>,\n outputExample?: unknown,\n): Record<string, unknown> | undefined {\n const inputJsonSchema = bodySchema\n ? z.toJSONSchema(bodySchema, { target: 'draft-2020-12' })\n : querySchema\n ? z.toJSONSchema(querySchema, { target: 'draft-2020-12' })\n : undefined;\n\n const outputJsonSchema = outputSchema\n ? z.toJSONSchema(outputSchema, { target: 'draft-2020-12' })\n : undefined;\n\n if (!inputJsonSchema) return undefined;\n\n const config = {\n bodyType: bodySchema ? 'json' : undefined,\n inputSchema: inputJsonSchema,\n output: outputJsonSchema\n ? { schema: outputJsonSchema, example: outputExample ?? {} }\n : undefined,\n };\n\n return { ...declareDiscoveryExtension(config as never) };\n}\n\n/**\n * Create a new route builder instance.\n */\nexport function createRouteBuilder(options?: RouteBuilderOptions) {\n return new RouteBuilder(undefined, options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _chunkGE7VBMQPjs = require('./chunk-GE7VBMQP.js');
|
|
6
|
+
|
|
7
|
+
// src/telemetry.ts
|
|
8
|
+
var _server = require('next/server');
|
|
9
|
+
function withTelemetry(handler) {
|
|
10
|
+
return async (request) => {
|
|
11
|
+
const meta = _chunkGE7VBMQPjs.extractRequestMeta.call(void 0, request);
|
|
12
|
+
const ctx = _chunkGE7VBMQPjs.buildTelemetryContext.call(void 0, meta);
|
|
13
|
+
let requestBodyString = null;
|
|
14
|
+
if (meta.method === "POST" || meta.method === "PUT" || meta.method === "PATCH") {
|
|
15
|
+
try {
|
|
16
|
+
const body = await request.clone().text();
|
|
17
|
+
if (body) requestBodyString = body;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let response;
|
|
22
|
+
let handlerError = null;
|
|
23
|
+
try {
|
|
24
|
+
response = await handler(request, ctx);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
handlerError = error;
|
|
27
|
+
if (error instanceof _server.NextResponse) {
|
|
28
|
+
response = error;
|
|
29
|
+
} else {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
31
|
+
response = _server.NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let responseBodyString = null;
|
|
35
|
+
try {
|
|
36
|
+
responseBodyString = await response.clone().text();
|
|
37
|
+
} catch (e2) {
|
|
38
|
+
}
|
|
39
|
+
_chunkGE7VBMQPjs.recordInvocation.call(void 0, meta, requestBodyString, {
|
|
40
|
+
status: response.status,
|
|
41
|
+
body: responseBodyString,
|
|
42
|
+
headers: JSON.stringify(Object.fromEntries(response.headers.entries())),
|
|
43
|
+
contentType: _nullishCoalesce(response.headers.get("content-type"), () => ( null))
|
|
44
|
+
});
|
|
45
|
+
if (handlerError && !(handlerError instanceof _server.NextResponse)) {
|
|
46
|
+
throw handlerError;
|
|
47
|
+
}
|
|
48
|
+
return response;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
exports.withTelemetry = withTelemetry;
|
|
55
|
+
//# sourceMappingURL=chunk-FJ3YM6KO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-FJ3YM6KO.js","../src/telemetry.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACAA,qCAA+C;AAcxC,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,IAAI,mBAAA,EAAoC,IAAA;AACxC,IAAA,IAAI;AACF,MAAA,mBAAA,EAAqB,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,CAAE,IAAA,CAAK,CAAA;AAAA,IACnD,EAAA,WAAQ;AAAA,IAER;AAEA,IAAA,+CAAA,IAAiB,EAAM,iBAAA,EAAmB;AAAA,MACxC,MAAA,EAAQ,QAAA,CAAS,MAAA;AAAA,MACjB,IAAA,EAAM,kBAAA;AAAA,MACN,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,WAAA,CAAY,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,MACtE,WAAA,mBAAa,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA,UAAK;AAAA,IACvD,CAAC,CAAA;AAGD,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;ADxBA;AACA;AACE;AACF,sCAAC","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-FJ3YM6KO.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 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 * 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 // Log to ClickHouse (fire-and-forget)\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 recordInvocation(meta, requestBodyString, {\n status: response.status,\n body: responseBodyString,\n headers: JSON.stringify(Object.fromEntries(response.headers.entries())),\n contentType: response.headers.get('content-type') ?? null,\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"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _chunkP63MLKU3js = require('./chunk-P63MLKU3.js');
|
|
6
|
+
|
|
7
|
+
// src/init.ts
|
|
8
|
+
var configuredOrigin;
|
|
9
|
+
function initTelemetry(config) {
|
|
10
|
+
_chunkP63MLKU3js.initClickhouse.call(void 0, config.clickhouse);
|
|
11
|
+
if (config.origin) {
|
|
12
|
+
configuredOrigin = config.origin;
|
|
13
|
+
}
|
|
14
|
+
if (config.verify) {
|
|
15
|
+
_chunkP63MLKU3js.pingClickhouse.call(void 0, );
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getOrigin() {
|
|
19
|
+
return configuredOrigin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/extract-wallet.ts
|
|
23
|
+
function extractVerifiedWallet(headers) {
|
|
24
|
+
try {
|
|
25
|
+
const payerAddress = headers.get("x-payer-address");
|
|
26
|
+
if (payerAddress) {
|
|
27
|
+
return payerAddress.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
const paymentHeader = _nullishCoalesce(_nullishCoalesce(_nullishCoalesce(headers.get("PAYMENT-SIGNATURE"), () => ( headers.get("payment-signature"))), () => ( headers.get("X-PAYMENT"))), () => ( headers.get("x-payment")));
|
|
30
|
+
if (!paymentHeader) return null;
|
|
31
|
+
try {
|
|
32
|
+
const decoded = JSON.parse(Buffer.from(paymentHeader, "base64").toString());
|
|
33
|
+
const from = _nullishCoalesce(_optionalChain([decoded, 'optionalAccess', _ => _.payload, 'optionalAccess', _2 => _2.authorization, 'optionalAccess', _3 => _3.from]), () => ( _optionalChain([decoded, 'optionalAccess', _4 => _4.payload, 'optionalAccess', _5 => _5.from])));
|
|
34
|
+
return typeof from === "string" ? from.toLowerCase() : null;
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
} catch (e2) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/telemetry-core.ts
|
|
44
|
+
var _crypto = require('crypto');
|
|
45
|
+
function extractRequestMeta(request) {
|
|
46
|
+
const meta = {
|
|
47
|
+
requestId: _crypto.randomUUID.call(void 0, ),
|
|
48
|
+
startTime: Date.now(),
|
|
49
|
+
walletAddress: null,
|
|
50
|
+
clientId: null,
|
|
51
|
+
sessionId: null,
|
|
52
|
+
verifiedWallet: null,
|
|
53
|
+
route: "",
|
|
54
|
+
method: "",
|
|
55
|
+
origin: "",
|
|
56
|
+
referer: null,
|
|
57
|
+
requestContentType: null,
|
|
58
|
+
requestHeadersJson: null
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
meta.walletAddress = _nullishCoalesce(_optionalChain([request, 'access', _6 => _6.headers, 'access', _7 => _7.get, 'call', _8 => _8("X-Wallet-Address"), 'optionalAccess', _9 => _9.toLowerCase, 'call', _10 => _10()]), () => ( null));
|
|
62
|
+
meta.clientId = _nullishCoalesce(request.headers.get("X-Client-ID"), () => ( null));
|
|
63
|
+
meta.sessionId = _nullishCoalesce(request.headers.get("X-Session-ID"), () => ( null));
|
|
64
|
+
meta.referer = _nullishCoalesce(request.headers.get("Referer"), () => ( null));
|
|
65
|
+
meta.requestContentType = _nullishCoalesce(request.headers.get("content-type"), () => ( null));
|
|
66
|
+
meta.route = request.nextUrl.pathname;
|
|
67
|
+
meta.method = request.method;
|
|
68
|
+
meta.origin = _nullishCoalesce(getOrigin(), () => ( request.nextUrl.origin));
|
|
69
|
+
meta.verifiedWallet = extractVerifiedWallet(request.headers);
|
|
70
|
+
meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));
|
|
71
|
+
} catch (e3) {
|
|
72
|
+
}
|
|
73
|
+
return meta;
|
|
74
|
+
}
|
|
75
|
+
function buildTelemetryContext(meta) {
|
|
76
|
+
const ctx = {
|
|
77
|
+
walletAddress: meta.walletAddress,
|
|
78
|
+
clientId: meta.clientId,
|
|
79
|
+
sessionId: meta.sessionId,
|
|
80
|
+
verifiedWallet: meta.verifiedWallet,
|
|
81
|
+
setVerifiedWallet: (address) => {
|
|
82
|
+
meta.verifiedWallet = address.toLowerCase();
|
|
83
|
+
ctx.verifiedWallet = meta.verifiedWallet;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return ctx;
|
|
87
|
+
}
|
|
88
|
+
function recordInvocation(meta, requestBody, response) {
|
|
89
|
+
try {
|
|
90
|
+
const invocation = {
|
|
91
|
+
id: meta.requestId,
|
|
92
|
+
x_wallet_address: meta.walletAddress,
|
|
93
|
+
x_client_id: meta.clientId,
|
|
94
|
+
session_id: meta.sessionId,
|
|
95
|
+
verified_wallet_address: meta.verifiedWallet,
|
|
96
|
+
method: meta.method,
|
|
97
|
+
route: meta.route,
|
|
98
|
+
origin: meta.origin,
|
|
99
|
+
referer: meta.referer,
|
|
100
|
+
request_content_type: meta.requestContentType,
|
|
101
|
+
request_headers: meta.requestHeadersJson,
|
|
102
|
+
request_body: requestBody,
|
|
103
|
+
status_code: response.status,
|
|
104
|
+
status_text: statusTextFromCode(response.status),
|
|
105
|
+
duration: Date.now() - meta.startTime,
|
|
106
|
+
response_content_type: response.contentType,
|
|
107
|
+
response_headers: response.headers,
|
|
108
|
+
response_body: response.body,
|
|
109
|
+
created_at: /* @__PURE__ */ new Date()
|
|
110
|
+
};
|
|
111
|
+
_chunkP63MLKU3js.insertInvocation.call(void 0, invocation);
|
|
112
|
+
} catch (e4) {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function statusTextFromCode(code) {
|
|
116
|
+
switch (code) {
|
|
117
|
+
case 200:
|
|
118
|
+
return "OK";
|
|
119
|
+
case 201:
|
|
120
|
+
return "Created";
|
|
121
|
+
case 204:
|
|
122
|
+
return "No Content";
|
|
123
|
+
case 400:
|
|
124
|
+
return "Bad Request";
|
|
125
|
+
case 401:
|
|
126
|
+
return "Unauthorized";
|
|
127
|
+
case 402:
|
|
128
|
+
return "Payment Required";
|
|
129
|
+
case 403:
|
|
130
|
+
return "Forbidden";
|
|
131
|
+
case 404:
|
|
132
|
+
return "Not Found";
|
|
133
|
+
case 500:
|
|
134
|
+
return "Internal Server Error";
|
|
135
|
+
case 504:
|
|
136
|
+
return "Gateway Timeout";
|
|
137
|
+
default:
|
|
138
|
+
return String(code);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
exports.initTelemetry = initTelemetry; exports.extractVerifiedWallet = extractVerifiedWallet; exports.extractRequestMeta = extractRequestMeta; exports.buildTelemetryContext = buildTelemetryContext; exports.recordInvocation = recordInvocation;
|
|
149
|
+
//# sourceMappingURL=chunk-GE7VBMQP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-GE7VBMQP.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"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","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-GE7VBMQP.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 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"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildTelemetryContext,
|
|
3
|
+
extractRequestMeta,
|
|
4
|
+
recordInvocation
|
|
5
|
+
} from "./chunk-VOY67KA4.mjs";
|
|
6
|
+
|
|
7
|
+
// src/telemetry.ts
|
|
8
|
+
import { NextResponse } from "next/server";
|
|
9
|
+
function withTelemetry(handler) {
|
|
10
|
+
return async (request) => {
|
|
11
|
+
const meta = extractRequestMeta(request);
|
|
12
|
+
const ctx = buildTelemetryContext(meta);
|
|
13
|
+
let requestBodyString = null;
|
|
14
|
+
if (meta.method === "POST" || meta.method === "PUT" || meta.method === "PATCH") {
|
|
15
|
+
try {
|
|
16
|
+
const body = await request.clone().text();
|
|
17
|
+
if (body) requestBodyString = body;
|
|
18
|
+
} catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let response;
|
|
22
|
+
let handlerError = null;
|
|
23
|
+
try {
|
|
24
|
+
response = await handler(request, ctx);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
handlerError = error;
|
|
27
|
+
if (error instanceof NextResponse) {
|
|
28
|
+
response = error;
|
|
29
|
+
} else {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Internal server error";
|
|
31
|
+
response = NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let responseBodyString = null;
|
|
35
|
+
try {
|
|
36
|
+
responseBodyString = await response.clone().text();
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
recordInvocation(meta, requestBodyString, {
|
|
40
|
+
status: response.status,
|
|
41
|
+
body: responseBodyString,
|
|
42
|
+
headers: JSON.stringify(Object.fromEntries(response.headers.entries())),
|
|
43
|
+
contentType: response.headers.get("content-type") ?? null
|
|
44
|
+
});
|
|
45
|
+
if (handlerError && !(handlerError instanceof NextResponse)) {
|
|
46
|
+
throw handlerError;
|
|
47
|
+
}
|
|
48
|
+
return response;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
withTelemetry
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=chunk-JVLKV7CX.mjs.map
|
|
@@ -0,0 +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 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 * 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 // Log to ClickHouse (fire-and-forget)\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 recordInvocation(meta, requestBodyString, {\n status: response.status,\n body: responseBodyString,\n headers: JSON.stringify(Object.fromEntries(response.headers.entries())),\n contentType: response.headers.get('content-type') ?? null,\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;AAcxC,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,qBAAoC;AACxC,QAAI;AACF,2BAAqB,MAAM,SAAS,MAAM,EAAE,KAAK;AAAA,IACnD,QAAQ;AAAA,IAER;AAEA,qBAAiB,MAAM,mBAAmB;AAAA,MACxC,QAAQ,SAAS;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,KAAK,UAAU,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC,CAAC;AAAA,MACtE,aAAa,SAAS,QAAQ,IAAI,cAAc,KAAK;AAAA,IACvD,CAAC;AAGD,QAAI,gBAAgB,EAAE,wBAAwB,eAAe;AAC3D,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/clickhouse.ts
|
|
2
|
+
import { createClient } from "@clickhouse/client";
|
|
3
|
+
var clickhouseClient = null;
|
|
4
|
+
var TABLE = "mcp_resource_invocations";
|
|
5
|
+
function initClickhouse(config) {
|
|
6
|
+
clickhouseClient = createClient({
|
|
7
|
+
url: config.url,
|
|
8
|
+
database: config.database ?? "default",
|
|
9
|
+
username: config.username ?? "default",
|
|
10
|
+
password: config.password ?? ""
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function pingClickhouse() {
|
|
14
|
+
if (!clickhouseClient) {
|
|
15
|
+
console.error("[telemetry] Cannot verify: ClickHouse client not initialized.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
clickhouseClient.ping().then((result) => {
|
|
19
|
+
if (result.success) {
|
|
20
|
+
console.log("[telemetry] ClickHouse connected");
|
|
21
|
+
} else {
|
|
22
|
+
console.error("[telemetry] ClickHouse ping failed");
|
|
23
|
+
}
|
|
24
|
+
}).catch((error) => {
|
|
25
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
+
console.error("[telemetry] ClickHouse ping failed:", message);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function insertInvocation(data) {
|
|
30
|
+
try {
|
|
31
|
+
if (!clickhouseClient) {
|
|
32
|
+
console.error("[telemetry] ClickHouse client not initialized. Call initTelemetry() first.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
clickhouseClient.insert({
|
|
36
|
+
table: TABLE,
|
|
37
|
+
values: [data],
|
|
38
|
+
format: "JSONEachRow"
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
try {
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
console.error("[telemetry] ClickHouse insert failed:", message);
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
try {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
console.error("[telemetry] ClickHouse insert threw synchronously:", message);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export {
|
|
56
|
+
initClickhouse,
|
|
57
|
+
pingClickhouse,
|
|
58
|
+
insertInvocation
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-O2PCP6KV.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 });\n}\n\n/**\n * Ping ClickHouse to verify the connection. Fire-and-forget, logs result.\n */\nexport function pingClickhouse(): void {\n if (!clickhouseClient) {\n console.error('[telemetry] Cannot verify: ClickHouse client not initialized.');\n return;\n }\n clickhouseClient\n .ping()\n .then((result) => {\n if (result.success) {\n console.log('[telemetry] ClickHouse connected');\n } else {\n console.error('[telemetry] ClickHouse ping failed');\n }\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse ping failed:', message);\n });\n}\n\n/**\n * Fire-and-forget insert into mcp_resource_invocations.\n * Wrapped in try/catch — never throws, never blocks.\n */\nexport function insertInvocation(data: McpResourceInvocation): void {\n try {\n if (!clickhouseClient) {\n console.error('[telemetry] ClickHouse client not initialized. Call initTelemetry() first.');\n return;\n }\n\n // Fire and forget — do NOT await\n clickhouseClient\n .insert<McpResourceInvocation>({\n table: TABLE,\n values: [data],\n format: 'JSONEachRow',\n })\n .catch((error: unknown) => {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse insert failed:', message);\n } catch {\n // Absolutely nothing escapes\n }\n });\n } catch (error: unknown) {\n try {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[telemetry] ClickHouse insert threw synchronously:', message);\n } catch {\n // Absolutely nothing escapes\n }\n }\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAG7B,IAAI,mBAA2D;AAE/D,IAAM,QAAQ;AAMP,SAAS,eAAe,QAA6C;AAC1E,qBAAmB,aAAa;AAAA,IAC9B,KAAK,OAAO;AAAA,IACZ,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,IAC7B,UAAU,OAAO,YAAY;AAAA,EAC/B,CAAC;AACH;AAKO,SAAS,iBAAuB;AACrC,MAAI,CAAC,kBAAkB;AACrB,YAAQ,MAAM,+DAA+D;AAC7E;AAAA,EACF;AACA,mBACG,KAAK,EACL,KAAK,CAAC,WAAW;AAChB,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAI,kCAAkC;AAAA,IAChD,OAAO;AACL,cAAQ,MAAM,oCAAoC;AAAA,IACpD;AAAA,EACF,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,uCAAuC,OAAO;AAAA,EAC9D,CAAC;AACL;AAMO,SAAS,iBAAiB,MAAmC;AAClE,MAAI;AACF,QAAI,CAAC,kBAAkB;AACrB,cAAQ,MAAM,4EAA4E;AAC1F;AAAA,IACF;AAGA,qBACG,OAA8B;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ,CAAC,IAAI;AAAA,MACb,QAAQ;AAAA,IACV,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI;AACF,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,gBAAQ,MAAM,yCAAyC,OAAO;AAAA,MAChE,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACL,SAAS,OAAgB;AACvB,QAAI;AACF,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,cAAQ,MAAM,sDAAsD,OAAO;AAAA,IAC7E,QAAQ;AAAA,IAER;AAAA,EACF;AACF;","names":[]}
|