@agentcash/telemetry 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder.js.map +1 -1
- package/dist/chunk-47QZYT7Q.js.map +1 -1
- package/dist/chunk-MBH3LCBQ.js.map +1 -1
- package/dist/chunk-QEJ7ZGGH.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/router-plugin.d.mts +8 -1
- package/dist/router-plugin.d.ts +8 -1
- package/dist/router-plugin.js +116 -51
- package/dist/router-plugin.js.map +1 -1
- package/dist/router-plugin.mjs +116 -51
- package/dist/router-plugin.mjs.map +1 -1
- package/dist/siwx.js.map +1 -1
- package/package.json +14 -14
package/dist/builder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/builder.js","../src/route-builder.ts"],"names":[],"mappings":"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","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/builder.js","sourcesContent":[null,"/**\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"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/builder.js","../src/route-builder.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/builder.js","sourcesContent":[null,"/**\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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-47QZYT7Q.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-47QZYT7Q.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 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/chunk-MBH3LCBQ.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-MBH3LCBQ.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * });\n * ```\n */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fire-and-forget, fully wrapped in try/catch.\n */\nexport function recordInvocation(\n meta: RequestMeta,\n requestBody: string | null,\n response: {\n status: number;\n body: string | null;\n headers: string | null;\n contentType: string | null;\n },\n): void {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MBH3LCBQ.js","../src/init.ts","../src/extract-wallet.ts","../src/telemetry-core.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-MBH3LCBQ.js","sourcesContent":[null,"import type { TelemetryConfig } from './types';\nimport { initClickhouse, pingClickhouse } from './clickhouse';\n\nlet configuredOrigin: string | undefined;\n\n/**\n * Initialize the telemetry package. Call once at module level.\n *\n * This is synchronous — createClient() does not connect until first query.\n *\n * IMPORTANT: On Vercel, instrumentation.ts runs in a separate module scope\n * from route handlers. Call this in the same module that imports your route\n * wrappers (withTelemetry, createRouteBuilder, etc.), NOT in instrumentation.ts.\n *\n * ```typescript\n * import { initTelemetry, withTelemetry } from '@agentcash/telemetry';\n *\n * initTelemetry({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL!,\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * });\n * ```\n */\nexport function initTelemetry(config: TelemetryConfig): void {\n initClickhouse(config.clickhouse);\n if (config.origin) {\n configuredOrigin = config.origin;\n }\n if (config.verify) {\n pingClickhouse();\n }\n}\n\n/** Get the configured origin, or undefined if not set. */\nexport function getOrigin(): string | undefined {\n return configuredOrigin;\n}\n","/**\n * Extract verified wallet address from x402 payment headers.\n *\n * Checks multiple sources in priority order:\n * 1. x-payer-address — injected by @x402/next's withX402 after verification (highest confidence)\n * 2. PAYMENT-SIGNATURE / X-PAYMENT — decode the payment header directly\n *\n * If the handler is executing, withX402 has already verified the payment signature.\n * For manual x402 flows, the app verifies before calling business logic.\n * Either way, the header content is trustworthy when this runs.\n */\nexport function extractVerifiedWallet(headers: Headers): string | null {\n try {\n // 1. x-payer-address: injected by @x402/next's withX402 after verification\n const payerAddress = headers.get('x-payer-address');\n if (payerAddress) {\n return payerAddress.toLowerCase();\n }\n\n // 2. Decode from PAYMENT-SIGNATURE or X-PAYMENT header\n const paymentHeader =\n headers.get('PAYMENT-SIGNATURE') ??\n headers.get('payment-signature') ??\n headers.get('X-PAYMENT') ??\n headers.get('x-payment');\n\n if (!paymentHeader) return null;\n\n // Decode the base64 payment header to extract the payer address.\n // The header is a base64-encoded JSON object with the structure:\n // { payload: { authorization: { from: \"0x...\" }, signature: \"0x...\" } }\n try {\n const decoded = JSON.parse(Buffer.from(paymentHeader, 'base64').toString()) as {\n payload?: { authorization?: { from?: string }; from?: string };\n };\n const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from;\n return typeof from === 'string' ? from.toLowerCase() : null;\n } catch {\n return null;\n }\n } catch {\n return null;\n }\n}\n","/**\n * Shared telemetry primitives used by withTelemetry and the route builder.\n * Extracts request metadata, builds telemetry context, and records invocations.\n */\n\nimport { type NextRequest } from 'next/server';\nimport { randomUUID } from 'crypto';\nimport type { TelemetryContext, McpResourceInvocation, RequestMeta } from './types';\nimport { insertInvocation } from './clickhouse';\nimport { extractVerifiedWallet } from './extract-wallet';\nimport { getOrigin } from './init';\n\n/**\n * Extract identity headers, route info, and verified wallet from a request.\n * All wrapped in try/catch — returns safe defaults on failure.\n */\nexport function extractRequestMeta(request: NextRequest): RequestMeta {\n const meta: RequestMeta = {\n requestId: randomUUID(),\n startTime: Date.now(),\n walletAddress: null,\n clientId: null,\n sessionId: null,\n verifiedWallet: null,\n route: '',\n method: '',\n origin: '',\n referer: null,\n requestContentType: null,\n requestHeadersJson: null,\n };\n\n try {\n meta.walletAddress = request.headers.get('X-Wallet-Address')?.toLowerCase() ?? null;\n meta.clientId = request.headers.get('X-Client-ID') ?? null;\n meta.sessionId = request.headers.get('X-Session-ID') ?? null;\n meta.referer = request.headers.get('Referer') ?? null;\n meta.requestContentType = request.headers.get('content-type') ?? null;\n meta.route = request.nextUrl.pathname;\n meta.method = request.method;\n meta.origin = getOrigin() ?? request.nextUrl.origin;\n meta.verifiedWallet = extractVerifiedWallet(request.headers);\n meta.requestHeadersJson = JSON.stringify(Object.fromEntries(request.headers.entries()));\n } catch {\n // Header extraction failed — continue with defaults\n }\n\n return meta;\n}\n\n/**\n * Build a TelemetryContext from extracted request metadata.\n * setVerifiedWallet mutates meta.verifiedWallet so recordInvocation sees the update.\n */\nexport function buildTelemetryContext(meta: RequestMeta): TelemetryContext {\n const ctx: TelemetryContext = {\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: meta.verifiedWallet,\n setVerifiedWallet: (address: string) => {\n meta.verifiedWallet = address.toLowerCase();\n ctx.verifiedWallet = meta.verifiedWallet;\n },\n };\n return ctx;\n}\n\n/**\n * Record an invocation to ClickHouse. Fire-and-forget, fully wrapped in try/catch.\n */\nexport function recordInvocation(\n meta: RequestMeta,\n requestBody: string | null,\n response: {\n status: number;\n body: string | null;\n headers: string | null;\n contentType: string | null;\n },\n): void {\n try {\n const invocation: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: meta.verifiedWallet,\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.requestContentType,\n request_headers: meta.requestHeadersJson,\n request_body: requestBody,\n status_code: response.status,\n status_text: statusTextFromCode(response.status),\n duration: Date.now() - meta.startTime,\n response_content_type: response.contentType,\n response_headers: response.headers,\n response_body: response.body,\n payment_protocol: null,\n payment_amount: null,\n payment_network: null,\n payment_tx_hash: null,\n created_at: new Date(),\n };\n insertInvocation(invocation);\n } catch {\n // Never affects the response\n }\n}\n\nfunction statusTextFromCode(code: number): string {\n switch (code) {\n case 200:\n return 'OK';\n case 201:\n return 'Created';\n case 204:\n return 'No Content';\n case 400:\n return 'Bad Request';\n case 401:\n return 'Unauthorized';\n case 402:\n return 'Payment Required';\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not Found';\n case 500:\n return 'Internal Server Error';\n case 504:\n return 'Gateway Timeout';\n default:\n return String(code);\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QEJ7ZGGH.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,KAAA,CAAM,+DAA+D,CAAA;AAC7E,IAAA,MAAA;AAAA,EACF;AACA,EAAA,gBAAA,CACG,IAAA,CAAK,CAAA,CACL,IAAA,CAAK,CAAC,MAAA,EAAA,GAAW;AAChB,IAAA,GAAA,CAAI,MAAA,CAAO,OAAA,EAAS;AAClB,MAAA,OAAA,CAAQ,GAAA,CAAI,kCAAkC,CAAA;AAAA,IAChD,EAAA,KAAO;AACL,MAAA,OAAA,CAAQ,KAAA,CAAM,oCAAoC,CAAA;AAAA,IACpD;AAAA,EACF,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,IAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,IAAA,OAAA,CAAQ,KAAA,CAAM,qCAAA,EAAuC,OAAO,CAAA;AAAA,EAC9D,CAAC,CAAA;AACL;AAMO,SAAS,gBAAA,CAAiB,IAAA,EAAmC;AAClE,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,CAAC,gBAAA,EAAkB;AACrB,MAAA,OAAA,CAAQ,KAAA,CAAM,4EAA4E,CAAA;AAC1F,MAAA,MAAA;AAAA,IACF;AAGA,IAAA,gBAAA,CACG,MAAA,CAA8B;AAAA,MAC7B,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,CAAC,IAAI,CAAA;AAAA,MACb,MAAA,EAAQ;AAAA,IACV,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,EAAA,GAAmB;AACzB,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,QAAA,OAAA,CAAQ,KAAA,CAAM,uCAAA,EAAyC,OAAO,CAAA;AAAA,MAChE,EAAA,UAAQ;AAAA,MAER;AAAA,IACF,CAAC,CAAA;AAAA,EACL,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,EAAU,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,MAAA,CAAO,KAAK,CAAA;AACrE,MAAA,OAAA,CAAQ,KAAA,CAAM,oDAAA,EAAsD,OAAO,CAAA;AAAA,IAC7E,EAAA,WAAQ;AAAA,IAER;AAAA,EACF;AACF;ADxBA;AACA;AACE;AACA;AACA;AACF,8HAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/chunk-QEJ7ZGGH.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.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"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/index.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B;AACE;AACA;AACA;AACF,uLAAC","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/index.js"}
|
package/dist/router-plugin.d.mts
CHANGED
|
@@ -105,7 +105,14 @@ interface TelemetryPluginConfig {
|
|
|
105
105
|
clickhouse: TelemetryConfig['clickhouse'];
|
|
106
106
|
/** If true, pings ClickHouse on init. */
|
|
107
107
|
verify?: boolean;
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Emit a one-line `[telemetry]` console summary for every plugin hook
|
|
110
|
+
* (init, request, payment verify/settle, response, error, alert, quota).
|
|
111
|
+
* The full row still goes to ClickHouse regardless; these logs are for
|
|
112
|
+
* making Vercel logs debuggable without round-tripping to ClickHouse.
|
|
113
|
+
*
|
|
114
|
+
* Default: true. Pass `console: false` to silence.
|
|
115
|
+
*/
|
|
109
116
|
console?: boolean;
|
|
110
117
|
}
|
|
111
118
|
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
package/dist/router-plugin.d.ts
CHANGED
|
@@ -105,7 +105,14 @@ interface TelemetryPluginConfig {
|
|
|
105
105
|
clickhouse: TelemetryConfig['clickhouse'];
|
|
106
106
|
/** If true, pings ClickHouse on init. */
|
|
107
107
|
verify?: boolean;
|
|
108
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Emit a one-line `[telemetry]` console summary for every plugin hook
|
|
110
|
+
* (init, request, payment verify/settle, response, error, alert, quota).
|
|
111
|
+
* The full row still goes to ClickHouse regardless; these logs are for
|
|
112
|
+
* making Vercel logs debuggable without round-tripping to ClickHouse.
|
|
113
|
+
*
|
|
114
|
+
* Default: true. Pass `console: false` to silence.
|
|
115
|
+
*/
|
|
109
116
|
console?: boolean;
|
|
110
117
|
}
|
|
111
118
|
declare function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin;
|
package/dist/router-plugin.js
CHANGED
|
@@ -5,13 +5,55 @@
|
|
|
5
5
|
var _chunkQEJ7ZGGHjs = require('./chunk-QEJ7ZGGH.js');
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
|
+
var _server = require('next/server');
|
|
9
|
+
function safeStringify(value) {
|
|
10
|
+
if (value === void 0) return null;
|
|
11
|
+
try {
|
|
12
|
+
return _nullishCoalesce(JSON.stringify(value), () => ( null));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function deferInsert(task) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof _server.after === "function") {
|
|
20
|
+
_server.after.call(void 0, task);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
} catch (e2) {
|
|
24
|
+
}
|
|
25
|
+
task();
|
|
26
|
+
}
|
|
27
|
+
function shortId(id) {
|
|
28
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
29
|
+
}
|
|
30
|
+
function shortAddr(addr) {
|
|
31
|
+
if (addr.length < 12) return addr;
|
|
32
|
+
return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
|
|
33
|
+
}
|
|
34
|
+
function formatNetwork(network) {
|
|
35
|
+
if (network === "eip155:8453") return "Base";
|
|
36
|
+
if (network.startsWith("solana:")) return "Solana";
|
|
37
|
+
if (network === "tempo:4217") return "Tempo";
|
|
38
|
+
return network;
|
|
39
|
+
}
|
|
40
|
+
function consoleForLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") return console.error;
|
|
42
|
+
if (level === "warn" || level === "warning") return console.warn;
|
|
43
|
+
return console.log;
|
|
44
|
+
}
|
|
8
45
|
function createTelemetryPlugin(config) {
|
|
9
46
|
_chunkQEJ7ZGGHjs.initClickhouse.call(void 0, config.clickhouse);
|
|
10
47
|
if (config.verify) {
|
|
11
48
|
_chunkQEJ7ZGGHjs.pingClickhouse.call(void 0, );
|
|
12
49
|
}
|
|
13
|
-
const log = _nullishCoalesce(config.console, () => (
|
|
50
|
+
const log = _nullishCoalesce(config.console, () => ( true));
|
|
14
51
|
return {
|
|
52
|
+
init({ origin } = {}) {
|
|
53
|
+
if (log) {
|
|
54
|
+
console.log(`[telemetry] init${origin ? ` origin=${origin}` : ""}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
15
57
|
onRequest(meta) {
|
|
16
58
|
const ctx = {
|
|
17
59
|
requestId: meta.requestId,
|
|
@@ -25,83 +67,106 @@ function createTelemetryPlugin(config) {
|
|
|
25
67
|
},
|
|
26
68
|
_meta: meta
|
|
27
69
|
};
|
|
70
|
+
if (log) {
|
|
71
|
+
const tags = [];
|
|
72
|
+
if (meta.clientId) tags.push(`client=${meta.clientId}`);
|
|
73
|
+
if (meta.sessionId) tags.push(`session=${meta.sessionId}`);
|
|
74
|
+
if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);
|
|
75
|
+
const suffix = tags.length ? ` ${tags.join(" ")}` : "";
|
|
76
|
+
console.log(
|
|
77
|
+
`[telemetry] ${shortId(meta.requestId)} \u2192 ${meta.method} ${meta.route}${suffix}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
28
80
|
return ctx;
|
|
29
81
|
},
|
|
30
82
|
onPaymentVerified(ctx, payment) {
|
|
31
83
|
ctx._payment = payment;
|
|
32
84
|
if (log) {
|
|
33
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
`[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`
|
|
87
|
+
);
|
|
34
88
|
}
|
|
35
89
|
},
|
|
36
90
|
onPaymentSettled(ctx, settlement) {
|
|
37
91
|
ctx._settlement = settlement;
|
|
38
92
|
if (log) {
|
|
39
|
-
console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
onResponse(ctx, response) {
|
|
43
|
-
const tCtx = ctx;
|
|
44
|
-
const meta = tCtx._meta;
|
|
45
|
-
if (log) {
|
|
46
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
47
93
|
console.log(
|
|
48
|
-
`[telemetry] ${
|
|
94
|
+
`[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`
|
|
49
95
|
);
|
|
50
96
|
}
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
},
|
|
98
|
+
onResponse(ctx, response) {
|
|
99
|
+
try {
|
|
100
|
+
const tCtx = ctx;
|
|
101
|
+
const meta = tCtx._meta;
|
|
102
|
+
if (log) {
|
|
103
|
+
const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : "";
|
|
104
|
+
console.log(
|
|
105
|
+
`[telemetry] ${shortId(meta.requestId)} \u2190 ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (response.statusCode === 402) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const row = {
|
|
112
|
+
id: meta.requestId,
|
|
113
|
+
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access', _ => _.walletAddress, 'optionalAccess', _2 => _2.toLowerCase, 'call', _3 => _3()]), () => ( null)),
|
|
114
|
+
x_client_id: meta.clientId,
|
|
115
|
+
session_id: meta.sessionId,
|
|
116
|
+
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access', _4 => _4.verifiedWallet, 'optionalAccess', _5 => _5.toLowerCase, 'call', _6 => _6()]), () => ( null)),
|
|
117
|
+
method: meta.method,
|
|
118
|
+
route: meta.route,
|
|
119
|
+
origin: meta.origin,
|
|
120
|
+
referer: meta.referer,
|
|
121
|
+
request_content_type: meta.contentType,
|
|
122
|
+
request_headers: safeStringify(meta.headers),
|
|
123
|
+
request_body: safeStringify(response.requestBody),
|
|
124
|
+
status_code: response.statusCode,
|
|
125
|
+
status_text: response.statusText,
|
|
126
|
+
duration: response.duration,
|
|
127
|
+
response_content_type: response.contentType,
|
|
128
|
+
response_headers: safeStringify(response.headers),
|
|
129
|
+
response_body: safeStringify(response.responseBody),
|
|
130
|
+
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _7 => _7._payment, 'optionalAccess', _8 => _8.protocol]), () => ( _optionalChain([tCtx, 'access', _9 => _9._settlement, 'optionalAccess', _10 => _10.protocol]))), () => ( null)),
|
|
131
|
+
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access', _11 => _11._payment, 'optionalAccess', _12 => _12.amount]), () => ( null)),
|
|
132
|
+
payment_network: (() => {
|
|
133
|
+
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _13 => _13._payment, 'optionalAccess', _14 => _14.network]), () => ( _optionalChain([tCtx, 'access', _15 => _15._settlement, 'optionalAccess', _16 => _16.network]))), () => ( null));
|
|
134
|
+
return n ? formatNetwork(n) : null;
|
|
135
|
+
})(),
|
|
136
|
+
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
|
|
137
|
+
created_at: /* @__PURE__ */ new Date()
|
|
138
|
+
};
|
|
139
|
+
deferInsert(() => _chunkQEJ7ZGGHjs.insertInvocation.call(void 0, row));
|
|
140
|
+
} catch (e3) {
|
|
53
141
|
}
|
|
54
|
-
const row = {
|
|
55
|
-
id: meta.requestId,
|
|
56
|
-
x_wallet_address: _nullishCoalesce(_optionalChain([meta, 'access', _ => _.walletAddress, 'optionalAccess', _2 => _2.toLowerCase, 'call', _3 => _3()]), () => ( null)),
|
|
57
|
-
x_client_id: meta.clientId,
|
|
58
|
-
session_id: meta.sessionId,
|
|
59
|
-
verified_wallet_address: _nullishCoalesce(_optionalChain([ctx, 'access', _4 => _4.verifiedWallet, 'optionalAccess', _5 => _5.toLowerCase, 'call', _6 => _6()]), () => ( null)),
|
|
60
|
-
method: meta.method,
|
|
61
|
-
route: meta.route,
|
|
62
|
-
origin: meta.origin,
|
|
63
|
-
referer: meta.referer,
|
|
64
|
-
request_content_type: meta.contentType,
|
|
65
|
-
request_headers: JSON.stringify(meta.headers),
|
|
66
|
-
request_body: response.requestBody !== void 0 ? JSON.stringify(response.requestBody) : null,
|
|
67
|
-
status_code: response.statusCode,
|
|
68
|
-
status_text: response.statusText,
|
|
69
|
-
duration: response.duration,
|
|
70
|
-
response_content_type: response.contentType,
|
|
71
|
-
response_headers: JSON.stringify(response.headers),
|
|
72
|
-
response_body: response.responseBody !== void 0 ? JSON.stringify(response.responseBody) : null,
|
|
73
|
-
payment_protocol: _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _7 => _7._payment, 'optionalAccess', _8 => _8.protocol]), () => ( _optionalChain([tCtx, 'access', _9 => _9._settlement, 'optionalAccess', _10 => _10.protocol]))), () => ( null)),
|
|
74
|
-
payment_amount: _nullishCoalesce(_optionalChain([tCtx, 'access', _11 => _11._payment, 'optionalAccess', _12 => _12.amount]), () => ( null)),
|
|
75
|
-
payment_network: (() => {
|
|
76
|
-
const n = _nullishCoalesce(_nullishCoalesce(_optionalChain([tCtx, 'access', _13 => _13._payment, 'optionalAccess', _14 => _14.network]), () => ( _optionalChain([tCtx, 'access', _15 => _15._settlement, 'optionalAccess', _16 => _16.network]))), () => ( null));
|
|
77
|
-
if (!n) return null;
|
|
78
|
-
if (n === "eip155:8453") return "Base";
|
|
79
|
-
if (n.startsWith("solana:")) return "Solana";
|
|
80
|
-
return n;
|
|
81
|
-
})(),
|
|
82
|
-
payment_tx_hash: _nullishCoalesce(_optionalChain([tCtx, 'access', _17 => _17._settlement, 'optionalAccess', _18 => _18.transaction]), () => ( null)),
|
|
83
|
-
created_at: /* @__PURE__ */ new Date()
|
|
84
|
-
};
|
|
85
|
-
_chunkQEJ7ZGGHjs.insertInvocation.call(void 0, row);
|
|
86
142
|
},
|
|
87
143
|
onError(ctx, error) {
|
|
88
144
|
if (log) {
|
|
89
|
-
|
|
145
|
+
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
+
console.error(
|
|
147
|
+
`[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`
|
|
148
|
+
);
|
|
90
149
|
}
|
|
91
150
|
},
|
|
92
151
|
onAlert(ctx, alert) {
|
|
93
152
|
if (log) {
|
|
94
|
-
const logFn = alert.level
|
|
153
|
+
const logFn = consoleForLevel(alert.level);
|
|
154
|
+
const id = _optionalChain([ctx, 'optionalAccess', _19 => _19.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
155
|
+
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
95
156
|
logFn(
|
|
96
|
-
`[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
97
|
-
|
|
157
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
158
|
+
...meta ? [meta] : []
|
|
98
159
|
);
|
|
99
160
|
}
|
|
100
161
|
},
|
|
101
162
|
onProviderQuota(ctx, event) {
|
|
102
163
|
if (log) {
|
|
103
|
-
const logFn = event.level
|
|
104
|
-
|
|
164
|
+
const logFn = consoleForLevel(event.level);
|
|
165
|
+
const id = _optionalChain([ctx, 'optionalAccess', _20 => _20.requestId]) ? `${shortId(ctx.requestId)} ` : "";
|
|
166
|
+
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
167
|
+
logFn(
|
|
168
|
+
`[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`
|
|
169
|
+
);
|
|
105
170
|
}
|
|
106
171
|
}
|
|
107
172
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACA;ACuIO,SAAS,qBAAA,CAAsB,MAAA,EAA6C;AAEjF,EAAA,6CAAA,MAAe,CAAO,UAAU,CAAA;AAChC,EAAA,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ;AACjB,IAAA,6CAAA,CAAe;AAAA,EACjB;AAEA,EAAA,MAAM,IAAA,mBAAM,MAAA,CAAO,OAAA,UAAW,OAAA;AAE9B,EAAA,OAAO;AAAA,IACL,SAAA,CAAU,IAAA,EAAkC;AAC1C,MAAA,MAAM,IAAA,EAA8B;AAAA,QAClC,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,QAChB,KAAA,EAAO,IAAA,CAAK,KAAA;AAAA,QACZ,aAAA,EAAe,IAAA,CAAK,aAAA;AAAA,QACpB,QAAA,EAAU,IAAA,CAAK,QAAA;AAAA,QACf,SAAA,EAAW,IAAA,CAAK,SAAA;AAAA,QAChB,cAAA,EAAgB,IAAA;AAAA,QAChB,iBAAA,CAAkB,OAAA,EAAiB;AACjC,UAAA,GAAA,CAAI,eAAA,EAAiB,OAAA;AAAA,QACvB,CAAA;AAAA,QACA,KAAA,EAAO;AAAA,MACT,CAAA;AACA,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IAEA,iBAAA,CAAkB,GAAA,EAAoB,OAAA,EAAuB;AAC3D,MAAC,GAAA,CAA+B,SAAA,EAAW,OAAA;AAC3C,MAAA,GAAA,CAAI,GAAA,EAAK;AACP,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,OAAA,CAAQ,QAAQ,CAAA,CAAA,EAAI,OAAA,CAAQ,KAAK,CAAA,CAAA,EAAI,OAAA,CAAQ,MAAM,CAAA,CAAA;AACzF,MAAA;AACF,IAAA;AAEkE,IAAA;AAClB,MAAA;AACrC,MAAA;AAC8E,QAAA;AACvF,MAAA;AACF,IAAA;AAEuD,IAAA;AACxC,MAAA;AACK,MAAA;AAET,MAAA;AAC+D,QAAA;AAC9D,QAAA;AACwE,UAAA;AAChF,QAAA;AACF,MAAA;AAGiC,MAAA;AAC/B,QAAA;AACF,MAAA;AAEmC,MAAA;AACxB,QAAA;AAC8C,QAAA;AACrC,QAAA;AACD,QAAA;AAC6C,QAAA;AAEjD,QAAA;AACD,QAAA;AACC,QAAA;AACC,QAAA;AACa,QAAA;AACiB,QAAA;AAEkC,QAAA;AAExD,QAAA;AACA,QAAA;AACH,QAAA;AACa,QAAA;AACiB,QAAA;AAE2B,QAAA;AAED,QAAA;AAClC,QAAA;AACjB,QAAA;AAC2C,UAAA;AAClD,UAAA;AACiB,UAAA;AACI,UAAA;AAC7B,UAAA;AACN,QAAA;AAC+C,QAAA;AAE7B,QAAA;AACvB,MAAA;AAEoB,MAAA;AACtB,IAAA;AAE+C,IAAA;AACpC,MAAA;AAC4D,QAAA;AACrE,MAAA;AACF,IAAA;AAE+C,IAAA;AACpC,MAAA;AAID,QAAA;AAGN,QAAA;AAC2E,UAAA;AAC3D,2BAAA;AAChB,QAAA;AACF,MAAA;AACF,IAAA;AAE+D,IAAA;AACpD,MAAA;AAKC,QAAA;AAEgF,QAAA;AAC1F,MAAA;AACF,IAAA;AACF,EAAA;AACF;ADnKgG;AACA;AACA","file":"/Users/samragsdale/Documents/Code/merit-systems/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /** Console logging for dev. Default: false. */\n console?: boolean;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? false;\n\n return {\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(`[telemetry] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : '';\n console.log(\n `[telemetry] ${meta.route} → ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: JSON.stringify(meta.headers),\n request_body:\n response.requestBody !== undefined ? JSON.stringify(response.requestBody) : null,\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: JSON.stringify(response.headers),\n response_body:\n response.responseBody !== undefined ? JSON.stringify(response.responseBody) : null,\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n if (!n) return null;\n if (n === 'eip155:8453') return 'Base';\n if (n.startsWith('solana:')) return 'Solana';\n return n;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n insertInvocation(row);\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n console.error(`[telemetry] ERROR ${error.status}: ${error.message}`);\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn =\n alert.level === 'critical' || alert.level === 'error'\n ? console.error\n : alert.level === 'warn'\n ? console.warn\n : console.log;\n logFn(\n `[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n alert.meta ?? '',\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn =\n event.level === 'critical'\n ? console.error\n : event.level === 'warn'\n ? console.warn\n : console.log;\n logFn(`[telemetry] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);\n }\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","../src/router-plugin.ts"],"names":[],"mappings":"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","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/router-plugin.js","sourcesContent":[null,"/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Pick console.error / warn / log based on an alert/quota severity level. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') return console.error;\n if (level === 'warn' || level === 'warning') return console.warn;\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n console.error(\n `[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`,\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"]}
|
package/dist/router-plugin.mjs
CHANGED
|
@@ -5,13 +5,55 @@ import {
|
|
|
5
5
|
} from "./chunk-V553T6WE.mjs";
|
|
6
6
|
|
|
7
7
|
// src/router-plugin.ts
|
|
8
|
+
import { after } from "next/server";
|
|
9
|
+
function safeStringify(value) {
|
|
10
|
+
if (value === void 0) return null;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(value) ?? null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function deferInsert(task) {
|
|
18
|
+
try {
|
|
19
|
+
if (typeof after === "function") {
|
|
20
|
+
after(task);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
task();
|
|
26
|
+
}
|
|
27
|
+
function shortId(id) {
|
|
28
|
+
return id.length > 8 ? id.slice(0, 8) : id;
|
|
29
|
+
}
|
|
30
|
+
function shortAddr(addr) {
|
|
31
|
+
if (addr.length < 12) return addr;
|
|
32
|
+
return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
|
|
33
|
+
}
|
|
34
|
+
function formatNetwork(network) {
|
|
35
|
+
if (network === "eip155:8453") return "Base";
|
|
36
|
+
if (network.startsWith("solana:")) return "Solana";
|
|
37
|
+
if (network === "tempo:4217") return "Tempo";
|
|
38
|
+
return network;
|
|
39
|
+
}
|
|
40
|
+
function consoleForLevel(level) {
|
|
41
|
+
if (level === "critical" || level === "error") return console.error;
|
|
42
|
+
if (level === "warn" || level === "warning") return console.warn;
|
|
43
|
+
return console.log;
|
|
44
|
+
}
|
|
8
45
|
function createTelemetryPlugin(config) {
|
|
9
46
|
initClickhouse(config.clickhouse);
|
|
10
47
|
if (config.verify) {
|
|
11
48
|
pingClickhouse();
|
|
12
49
|
}
|
|
13
|
-
const log = config.console ??
|
|
50
|
+
const log = config.console ?? true;
|
|
14
51
|
return {
|
|
52
|
+
init({ origin } = {}) {
|
|
53
|
+
if (log) {
|
|
54
|
+
console.log(`[telemetry] init${origin ? ` origin=${origin}` : ""}`);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
15
57
|
onRequest(meta) {
|
|
16
58
|
const ctx = {
|
|
17
59
|
requestId: meta.requestId,
|
|
@@ -25,83 +67,106 @@ function createTelemetryPlugin(config) {
|
|
|
25
67
|
},
|
|
26
68
|
_meta: meta
|
|
27
69
|
};
|
|
70
|
+
if (log) {
|
|
71
|
+
const tags = [];
|
|
72
|
+
if (meta.clientId) tags.push(`client=${meta.clientId}`);
|
|
73
|
+
if (meta.sessionId) tags.push(`session=${meta.sessionId}`);
|
|
74
|
+
if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);
|
|
75
|
+
const suffix = tags.length ? ` ${tags.join(" ")}` : "";
|
|
76
|
+
console.log(
|
|
77
|
+
`[telemetry] ${shortId(meta.requestId)} \u2192 ${meta.method} ${meta.route}${suffix}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
28
80
|
return ctx;
|
|
29
81
|
},
|
|
30
82
|
onPaymentVerified(ctx, payment) {
|
|
31
83
|
ctx._payment = payment;
|
|
32
84
|
if (log) {
|
|
33
|
-
console.log(
|
|
85
|
+
console.log(
|
|
86
|
+
`[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`
|
|
87
|
+
);
|
|
34
88
|
}
|
|
35
89
|
},
|
|
36
90
|
onPaymentSettled(ctx, settlement) {
|
|
37
91
|
ctx._settlement = settlement;
|
|
38
92
|
if (log) {
|
|
39
|
-
console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
onResponse(ctx, response) {
|
|
43
|
-
const tCtx = ctx;
|
|
44
|
-
const meta = tCtx._meta;
|
|
45
|
-
if (log) {
|
|
46
|
-
const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : "";
|
|
47
93
|
console.log(
|
|
48
|
-
`[telemetry] ${
|
|
94
|
+
`[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`
|
|
49
95
|
);
|
|
50
96
|
}
|
|
51
|
-
|
|
52
|
-
|
|
97
|
+
},
|
|
98
|
+
onResponse(ctx, response) {
|
|
99
|
+
try {
|
|
100
|
+
const tCtx = ctx;
|
|
101
|
+
const meta = tCtx._meta;
|
|
102
|
+
if (log) {
|
|
103
|
+
const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : "";
|
|
104
|
+
console.log(
|
|
105
|
+
`[telemetry] ${shortId(meta.requestId)} \u2190 ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (response.statusCode === 402) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const row = {
|
|
112
|
+
id: meta.requestId,
|
|
113
|
+
x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,
|
|
114
|
+
x_client_id: meta.clientId,
|
|
115
|
+
session_id: meta.sessionId,
|
|
116
|
+
verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,
|
|
117
|
+
method: meta.method,
|
|
118
|
+
route: meta.route,
|
|
119
|
+
origin: meta.origin,
|
|
120
|
+
referer: meta.referer,
|
|
121
|
+
request_content_type: meta.contentType,
|
|
122
|
+
request_headers: safeStringify(meta.headers),
|
|
123
|
+
request_body: safeStringify(response.requestBody),
|
|
124
|
+
status_code: response.statusCode,
|
|
125
|
+
status_text: response.statusText,
|
|
126
|
+
duration: response.duration,
|
|
127
|
+
response_content_type: response.contentType,
|
|
128
|
+
response_headers: safeStringify(response.headers),
|
|
129
|
+
response_body: safeStringify(response.responseBody),
|
|
130
|
+
payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,
|
|
131
|
+
payment_amount: tCtx._payment?.amount ?? null,
|
|
132
|
+
payment_network: (() => {
|
|
133
|
+
const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;
|
|
134
|
+
return n ? formatNetwork(n) : null;
|
|
135
|
+
})(),
|
|
136
|
+
payment_tx_hash: tCtx._settlement?.transaction ?? null,
|
|
137
|
+
created_at: /* @__PURE__ */ new Date()
|
|
138
|
+
};
|
|
139
|
+
deferInsert(() => insertInvocation(row));
|
|
140
|
+
} catch {
|
|
53
141
|
}
|
|
54
|
-
const row = {
|
|
55
|
-
id: meta.requestId,
|
|
56
|
-
x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,
|
|
57
|
-
x_client_id: meta.clientId,
|
|
58
|
-
session_id: meta.sessionId,
|
|
59
|
-
verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,
|
|
60
|
-
method: meta.method,
|
|
61
|
-
route: meta.route,
|
|
62
|
-
origin: meta.origin,
|
|
63
|
-
referer: meta.referer,
|
|
64
|
-
request_content_type: meta.contentType,
|
|
65
|
-
request_headers: JSON.stringify(meta.headers),
|
|
66
|
-
request_body: response.requestBody !== void 0 ? JSON.stringify(response.requestBody) : null,
|
|
67
|
-
status_code: response.statusCode,
|
|
68
|
-
status_text: response.statusText,
|
|
69
|
-
duration: response.duration,
|
|
70
|
-
response_content_type: response.contentType,
|
|
71
|
-
response_headers: JSON.stringify(response.headers),
|
|
72
|
-
response_body: response.responseBody !== void 0 ? JSON.stringify(response.responseBody) : null,
|
|
73
|
-
payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,
|
|
74
|
-
payment_amount: tCtx._payment?.amount ?? null,
|
|
75
|
-
payment_network: (() => {
|
|
76
|
-
const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;
|
|
77
|
-
if (!n) return null;
|
|
78
|
-
if (n === "eip155:8453") return "Base";
|
|
79
|
-
if (n.startsWith("solana:")) return "Solana";
|
|
80
|
-
return n;
|
|
81
|
-
})(),
|
|
82
|
-
payment_tx_hash: tCtx._settlement?.transaction ?? null,
|
|
83
|
-
created_at: /* @__PURE__ */ new Date()
|
|
84
|
-
};
|
|
85
|
-
insertInvocation(row);
|
|
86
142
|
},
|
|
87
143
|
onError(ctx, error) {
|
|
88
144
|
if (log) {
|
|
89
|
-
|
|
145
|
+
const settled = error.settled ? " (payment settled)" : "";
|
|
146
|
+
console.error(
|
|
147
|
+
`[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`
|
|
148
|
+
);
|
|
90
149
|
}
|
|
91
150
|
},
|
|
92
151
|
onAlert(ctx, alert) {
|
|
93
152
|
if (log) {
|
|
94
|
-
const logFn = alert.level
|
|
153
|
+
const logFn = consoleForLevel(alert.level);
|
|
154
|
+
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
155
|
+
const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : void 0;
|
|
95
156
|
logFn(
|
|
96
|
-
`[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
97
|
-
|
|
157
|
+
`[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,
|
|
158
|
+
...meta ? [meta] : []
|
|
98
159
|
);
|
|
99
160
|
}
|
|
100
161
|
},
|
|
101
162
|
onProviderQuota(ctx, event) {
|
|
102
163
|
if (log) {
|
|
103
|
-
const logFn = event.level
|
|
104
|
-
|
|
164
|
+
const logFn = consoleForLevel(event.level);
|
|
165
|
+
const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : "";
|
|
166
|
+
const usage = event.remaining != null && event.limit != null ? ` (${event.remaining}/${event.limit})` : "";
|
|
167
|
+
logFn(
|
|
168
|
+
`[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`
|
|
169
|
+
);
|
|
105
170
|
}
|
|
106
171
|
}
|
|
107
172
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /** Console logging for dev. Default: false. */\n console?: boolean;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? false;\n\n return {\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(`[telemetry] VERIFIED ${payment.protocol} ${payment.payer} ${payment.amount}`);\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(`[telemetry] SETTLED ${settlement.protocol} tx=${settlement.transaction}`);\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${ctx.verifiedWallet}` : '';\n console.log(\n `[telemetry] ${meta.route} → ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: JSON.stringify(meta.headers),\n request_body:\n response.requestBody !== undefined ? JSON.stringify(response.requestBody) : null,\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: JSON.stringify(response.headers),\n response_body:\n response.responseBody !== undefined ? JSON.stringify(response.responseBody) : null,\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n if (!n) return null;\n if (n === 'eip155:8453') return 'Base';\n if (n.startsWith('solana:')) return 'Solana';\n return n;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n insertInvocation(row);\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n console.error(`[telemetry] ERROR ${error.status}: ${error.message}`);\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn =\n alert.level === 'critical' || alert.level === 'error'\n ? console.error\n : alert.level === 'warn'\n ? console.warn\n : console.log;\n logFn(\n `[telemetry] ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n alert.meta ?? '',\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn =\n event.level === 'critical'\n ? console.error\n : event.level === 'warn'\n ? console.warn\n : console.log;\n logFn(`[telemetry] QUOTA ${event.level.toUpperCase()} ${event.provider}: ${event.message}`);\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/router-plugin.ts"],"sourcesContent":["/**\n * RouterPlugin adapter for @agentcash/router.\n *\n * Bridges the router's plugin hooks into ClickHouse telemetry.\n * Uses the same mcp_resource_invocations table as the legacy withTelemetry wrapper.\n *\n * Usage:\n * import { createRouter } from '@agentcash/router';\n * import { createTelemetryPlugin } from '@agentcash/telemetry/plugin';\n *\n * const router = createRouter({\n * payeeAddress: '...',\n * plugin: createTelemetryPlugin({\n * clickhouse: {\n * url: process.env.TELEM_CLICKHOUSE_URL ?? 'http://localhost:8123',\n * database: process.env.TELEM_CLICKHOUSE_DATABASE,\n * username: process.env.TELEM_CLICKHOUSE_USERNAME,\n * password: process.env.TELEM_CLICKHOUSE_PASSWORD,\n * },\n * }),\n * });\n */\n\nimport { after } from 'next/server';\nimport { initClickhouse, pingClickhouse, insertInvocation } from './clickhouse';\nimport type { McpResourceInvocation, TelemetryConfig } from './types';\n\n// ---------------------------------------------------------------------------\n// Minimal RouterPlugin types (inlined to avoid depending on @agentcash/router\n// at runtime — the router passes these shapes, we just consume them)\n// ---------------------------------------------------------------------------\n\ninterface RequestMeta {\n requestId: string;\n method: string;\n route: string;\n origin: string;\n referer: string | null;\n walletAddress: string | null;\n clientId: string | null;\n sessionId: string | null;\n contentType: string | null;\n headers: Record<string, string>;\n startTime: number;\n}\n\ninterface PluginContext {\n readonly requestId: string;\n readonly route: string;\n readonly walletAddress: string | null;\n readonly clientId: string | null;\n readonly sessionId: string | null;\n verifiedWallet: string | null;\n setVerifiedWallet(address: string): void;\n}\n\ninterface PaymentEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n amount: string;\n network: string;\n}\n\ninterface SettlementEvent {\n protocol: 'x402' | 'mpp';\n payer: string;\n transaction: string;\n network: string;\n}\n\ninterface ResponseMeta {\n statusCode: number;\n statusText: string;\n duration: number;\n contentType: string | null;\n headers: Record<string, string>;\n /** Parsed request body (when .body() was used). undefined when no body was parsed. */\n requestBody?: unknown;\n /** Handler return value. undefined for raw Response returns (streams) or error paths. */\n responseBody?: unknown;\n}\n\ninterface ErrorEvent {\n status: number;\n message: string;\n settled: boolean;\n}\n\ninterface AlertEvent {\n level: string;\n message: string;\n route: string;\n meta?: Record<string, unknown>;\n}\n\ninterface ProviderQuotaEvent {\n provider: string;\n route: string;\n remaining: number | null;\n limit: number | null;\n level: string;\n overage: string;\n message: string;\n}\n\n/** RouterPlugin interface — must match @agentcash/router's RouterPlugin */\ninterface RouterPlugin {\n init?(config: { origin?: string }): void | Promise<void>;\n onRequest?(meta: RequestMeta): PluginContext;\n onPaymentVerified?(ctx: PluginContext, payment: PaymentEvent): void;\n onPaymentSettled?(ctx: PluginContext, settlement: SettlementEvent): void;\n onResponse?(ctx: PluginContext, response: ResponseMeta): void;\n onError?(ctx: PluginContext, error: ErrorEvent): void;\n onAlert?(ctx: PluginContext, alert: AlertEvent): void;\n onProviderQuota?(ctx: PluginContext, event: ProviderQuotaEvent): void;\n}\n\n// ---------------------------------------------------------------------------\n// Extended context — carries request metadata through the lifecycle\n// ---------------------------------------------------------------------------\n\ninterface TelemetryPluginContext extends PluginContext {\n /** Stored from onRequest for use in onResponse */\n _meta: RequestMeta;\n /** Payment info captured between verify and response */\n _payment?: PaymentEvent;\n /** Settlement info captured between settle and response */\n _settlement?: SettlementEvent;\n}\n\n// ---------------------------------------------------------------------------\n// Plugin factory\n// ---------------------------------------------------------------------------\n\nexport interface TelemetryPluginConfig {\n clickhouse: TelemetryConfig['clickhouse'];\n /** If true, pings ClickHouse on init. */\n verify?: boolean;\n /**\n * Emit a one-line `[telemetry]` console summary for every plugin hook\n * (init, request, payment verify/settle, response, error, alert, quota).\n * The full row still goes to ClickHouse regardless; these logs are for\n * making Vercel logs debuggable without round-tripping to ClickHouse.\n *\n * Default: true. Pass `console: false` to silence.\n */\n console?: boolean;\n}\n\n/**\n * JSON.stringify that never throws. Arbitrary request/response bodies can\n * contain circular references or BigInt values, which make JSON.stringify\n * throw. Returning null on failure keeps the rest of the row intact instead\n * of dropping the whole invocation.\n */\nfunction safeStringify(value: unknown): string | null {\n if (value === undefined) return null;\n try {\n return JSON.stringify(value) ?? null;\n } catch {\n return null;\n }\n}\n\n/**\n * Run the ClickHouse insert after the response is sent.\n *\n * The plugin's onResponse hook runs inside the route handler's request\n * scope, but the insert itself is async. Without after(), the insert promise\n * is left in-flight when the handler returns — on Vercel the function\n * instance freezes mid-insert, the socket dies, and the rejection surfaces\n * (\"socket hang up\" / \"Request timed out\") on a *later* thaw, attached in the\n * logs to an unrelated request. after() keeps the instance alive until the\n * insert settles, so any failure is logged against the request that caused\n * it. This matches what withTelemetry already does (see telemetry.ts).\n *\n * Falls back to running inline when after() is unavailable (Next < 15.1) or\n * called outside a request scope (tests, non-Next consumers).\n */\nfunction deferInsert(task: () => void): void {\n try {\n if (typeof after === 'function') {\n after(task);\n return;\n }\n } catch {\n // after() throws when called outside a request scope — fall through.\n }\n task();\n}\n\n// ---------------------------------------------------------------------------\n// Log formatting helpers\n// ---------------------------------------------------------------------------\n\n/** Short request id (8 chars) so log lines for the same request correlate visually. */\nfunction shortId(id: string): string {\n return id.length > 8 ? id.slice(0, 8) : id;\n}\n\n/** Truncate a wallet/tx address to `0x1234…abcd` for readable logs. */\nfunction shortAddr(addr: string): string {\n if (addr.length < 12) return addr;\n return `${addr.slice(0, 6)}…${addr.slice(-4)}`;\n}\n\n/** Map x402/MPP network identifiers to friendly names (Base, Solana, Tempo). */\nfunction formatNetwork(network: string): string {\n if (network === 'eip155:8453') return 'Base';\n if (network.startsWith('solana:')) return 'Solana';\n if (network === 'tempo:4217') return 'Tempo';\n return network;\n}\n\n/** Pick console.error / warn / log based on an alert/quota severity level. */\nfunction consoleForLevel(level: string): (...args: unknown[]) => void {\n if (level === 'critical' || level === 'error') return console.error;\n if (level === 'warn' || level === 'warning') return console.warn;\n return console.log;\n}\n\nexport function createTelemetryPlugin(config: TelemetryPluginConfig): RouterPlugin {\n // Initialize ClickHouse synchronously (connection happens on first query)\n initClickhouse(config.clickhouse);\n if (config.verify) {\n pingClickhouse();\n }\n\n const log = config.console ?? true;\n\n return {\n init({ origin }: { origin?: string } = {}) {\n if (log) {\n console.log(`[telemetry] init${origin ? ` origin=${origin}` : ''}`);\n }\n },\n\n onRequest(meta: RequestMeta): PluginContext {\n const ctx: TelemetryPluginContext = {\n requestId: meta.requestId,\n route: meta.route,\n walletAddress: meta.walletAddress,\n clientId: meta.clientId,\n sessionId: meta.sessionId,\n verifiedWallet: null,\n setVerifiedWallet(address: string) {\n ctx.verifiedWallet = address;\n },\n _meta: meta,\n };\n if (log) {\n const tags: string[] = [];\n if (meta.clientId) tags.push(`client=${meta.clientId}`);\n if (meta.sessionId) tags.push(`session=${meta.sessionId}`);\n if (meta.walletAddress) tags.push(`wallet=${shortAddr(meta.walletAddress)}`);\n const suffix = tags.length ? ` ${tags.join(' ')}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} → ${meta.method} ${meta.route}${suffix}`,\n );\n }\n return ctx as PluginContext;\n },\n\n onPaymentVerified(ctx: PluginContext, payment: PaymentEvent) {\n (ctx as TelemetryPluginContext)._payment = payment;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} verified ${payment.protocol} ${payment.amount} from ${shortAddr(payment.payer)} on ${formatNetwork(payment.network)}`,\n );\n }\n },\n\n onPaymentSettled(ctx: PluginContext, settlement: SettlementEvent) {\n (ctx as TelemetryPluginContext)._settlement = settlement;\n if (log) {\n console.log(\n `[telemetry] ${shortId(ctx.requestId)} settled ${settlement.protocol} tx=${settlement.transaction} on ${formatNetwork(settlement.network)}`,\n );\n }\n },\n\n onResponse(ctx: PluginContext, response: ResponseMeta) {\n // The plugin is a passive observer — wrap the whole hook so a\n // serialization failure or anything else can never escape into the\n // router (firePluginHook would otherwise log it as a router error).\n try {\n const tCtx = ctx as TelemetryPluginContext;\n const meta = tCtx._meta;\n\n if (log) {\n const wallet = ctx.verifiedWallet ? ` wallet=${shortAddr(ctx.verifiedWallet)}` : '';\n console.log(\n `[telemetry] ${shortId(meta.requestId)} ← ${meta.method} ${meta.route} ${response.statusCode} (${response.duration}ms)${wallet}`,\n );\n }\n\n // 402 is the x402/MPP payment challenge — not a real invocation, skip logging\n if (response.statusCode === 402) {\n return;\n }\n\n const row: McpResourceInvocation = {\n id: meta.requestId,\n x_wallet_address: meta.walletAddress?.toLowerCase() ?? null,\n x_client_id: meta.clientId,\n session_id: meta.sessionId,\n verified_wallet_address: ctx.verifiedWallet?.toLowerCase() ?? null,\n\n method: meta.method,\n route: meta.route,\n origin: meta.origin,\n referer: meta.referer,\n request_content_type: meta.contentType,\n request_headers: safeStringify(meta.headers),\n request_body: safeStringify(response.requestBody),\n\n status_code: response.statusCode,\n status_text: response.statusText,\n duration: response.duration,\n response_content_type: response.contentType,\n response_headers: safeStringify(response.headers),\n response_body: safeStringify(response.responseBody),\n\n payment_protocol: tCtx._payment?.protocol ?? tCtx._settlement?.protocol ?? null,\n payment_amount: tCtx._payment?.amount ?? null,\n payment_network: (() => {\n const n = tCtx._payment?.network ?? tCtx._settlement?.network ?? null;\n return n ? formatNetwork(n) : null;\n })(),\n payment_tx_hash: tCtx._settlement?.transaction ?? null,\n\n created_at: new Date(),\n };\n\n // Defer the insert so it completes within the request lifecycle\n // instead of leaving a frozen in-flight promise on Vercel.\n deferInsert(() => insertInvocation(row));\n } catch {\n // Telemetry must never affect the response.\n }\n },\n\n onError(ctx: PluginContext, error: ErrorEvent) {\n if (log) {\n const settled = error.settled ? ' (payment settled)' : '';\n console.error(\n `[telemetry] ${shortId(ctx.requestId)} error ${error.status}: ${error.message}${settled}`,\n );\n }\n },\n\n onAlert(ctx: PluginContext, alert: AlertEvent) {\n if (log) {\n const logFn = consoleForLevel(alert.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const meta = alert.meta && Object.keys(alert.meta).length > 0 ? alert.meta : undefined;\n logFn(\n `[telemetry] ${id}alert ${alert.level.toUpperCase()} ${alert.route}: ${alert.message}`,\n ...(meta ? [meta] : []),\n );\n }\n },\n\n onProviderQuota(ctx: PluginContext, event: ProviderQuotaEvent) {\n if (log) {\n const logFn = consoleForLevel(event.level);\n const id = ctx?.requestId ? `${shortId(ctx.requestId)} ` : '';\n const usage =\n event.remaining != null && event.limit != null\n ? ` (${event.remaining}/${event.limit})`\n : '';\n logFn(\n `[telemetry] ${id}quota ${event.level.toUpperCase()} ${event.provider} on ${event.route}: ${event.message}${usage}`,\n );\n }\n },\n };\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,aAAa;AAoItB,SAAS,cAAc,OAA+B;AACpD,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,UAAU,KAAK,KAAK;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,SAAS,YAAY,MAAwB;AAC3C,MAAI;AACF,QAAI,OAAO,UAAU,YAAY;AAC/B,YAAM,IAAI;AACV;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACA,OAAK;AACP;AAOA,SAAS,QAAQ,IAAoB;AACnC,SAAO,GAAG,SAAS,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI;AAC1C;AAGA,SAAS,UAAU,MAAsB;AACvC,MAAI,KAAK,SAAS,GAAI,QAAO;AAC7B,SAAO,GAAG,KAAK,MAAM,GAAG,CAAC,CAAC,SAAI,KAAK,MAAM,EAAE,CAAC;AAC9C;AAGA,SAAS,cAAc,SAAyB;AAC9C,MAAI,YAAY,cAAe,QAAO;AACtC,MAAI,QAAQ,WAAW,SAAS,EAAG,QAAO;AAC1C,MAAI,YAAY,aAAc,QAAO;AACrC,SAAO;AACT;AAGA,SAAS,gBAAgB,OAA6C;AACpE,MAAI,UAAU,cAAc,UAAU,QAAS,QAAO,QAAQ;AAC9D,MAAI,UAAU,UAAU,UAAU,UAAW,QAAO,QAAQ;AAC5D,SAAO,QAAQ;AACjB;AAEO,SAAS,sBAAsB,QAA6C;AAEjF,iBAAe,OAAO,UAAU;AAChC,MAAI,OAAO,QAAQ;AACjB,mBAAe;AAAA,EACjB;AAEA,QAAM,MAAM,OAAO,WAAW;AAE9B,SAAO;AAAA,IACL,KAAK,EAAE,OAAO,IAAyB,CAAC,GAAG;AACzC,UAAI,KAAK;AACP,gBAAQ,IAAI,mBAAmB,SAAS,WAAW,MAAM,KAAK,EAAE,EAAE;AAAA,MACpE;AAAA,IACF;AAAA,IAEA,UAAU,MAAkC;AAC1C,YAAM,MAA8B;AAAA,QAClC,WAAW,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,eAAe,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,WAAW,KAAK;AAAA,QAChB,gBAAgB;AAAA,QAChB,kBAAkB,SAAiB;AACjC,cAAI,iBAAiB;AAAA,QACvB;AAAA,QACA,OAAO;AAAA,MACT;AACA,UAAI,KAAK;AACP,cAAM,OAAiB,CAAC;AACxB,YAAI,KAAK,SAAU,MAAK,KAAK,UAAU,KAAK,QAAQ,EAAE;AACtD,YAAI,KAAK,UAAW,MAAK,KAAK,WAAW,KAAK,SAAS,EAAE;AACzD,YAAI,KAAK,cAAe,MAAK,KAAK,UAAU,UAAU,KAAK,aAAa,CAAC,EAAE;AAC3E,cAAM,SAAS,KAAK,SAAS,IAAI,KAAK,KAAK,GAAG,CAAC,KAAK;AACpD,gBAAQ;AAAA,UACN,eAAe,QAAQ,KAAK,SAAS,CAAC,WAAM,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,MAAM;AAAA,QAChF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IAEA,kBAAkB,KAAoB,SAAuB;AAC3D,MAAC,IAA+B,WAAW;AAC3C,UAAI,KAAK;AACP,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,aAAa,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS,UAAU,QAAQ,KAAK,CAAC,OAAO,cAAc,QAAQ,OAAO,CAAC;AAAA,QAC5J;AAAA,MACF;AAAA,IACF;AAAA,IAEA,iBAAiB,KAAoB,YAA6B;AAChE,MAAC,IAA+B,cAAc;AAC9C,UAAI,KAAK;AACP,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,YAAY,WAAW,QAAQ,OAAO,WAAW,WAAW,OAAO,cAAc,WAAW,OAAO,CAAC;AAAA,QAC3I;AAAA,MACF;AAAA,IACF;AAAA,IAEA,WAAW,KAAoB,UAAwB;AAIrD,UAAI;AACF,cAAM,OAAO;AACb,cAAM,OAAO,KAAK;AAElB,YAAI,KAAK;AACP,gBAAM,SAAS,IAAI,iBAAiB,WAAW,UAAU,IAAI,cAAc,CAAC,KAAK;AACjF,kBAAQ;AAAA,YACN,eAAe,QAAQ,KAAK,SAAS,CAAC,WAAM,KAAK,MAAM,IAAI,KAAK,KAAK,IAAI,SAAS,UAAU,KAAK,SAAS,QAAQ,MAAM,MAAM;AAAA,UAChI;AAAA,QACF;AAGA,YAAI,SAAS,eAAe,KAAK;AAC/B;AAAA,QACF;AAEA,cAAM,MAA6B;AAAA,UACjC,IAAI,KAAK;AAAA,UACT,kBAAkB,KAAK,eAAe,YAAY,KAAK;AAAA,UACvD,aAAa,KAAK;AAAA,UAClB,YAAY,KAAK;AAAA,UACjB,yBAAyB,IAAI,gBAAgB,YAAY,KAAK;AAAA,UAE9D,QAAQ,KAAK;AAAA,UACb,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,SAAS,KAAK;AAAA,UACd,sBAAsB,KAAK;AAAA,UAC3B,iBAAiB,cAAc,KAAK,OAAO;AAAA,UAC3C,cAAc,cAAc,SAAS,WAAW;AAAA,UAEhD,aAAa,SAAS;AAAA,UACtB,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,UACnB,uBAAuB,SAAS;AAAA,UAChC,kBAAkB,cAAc,SAAS,OAAO;AAAA,UAChD,eAAe,cAAc,SAAS,YAAY;AAAA,UAElD,kBAAkB,KAAK,UAAU,YAAY,KAAK,aAAa,YAAY;AAAA,UAC3E,gBAAgB,KAAK,UAAU,UAAU;AAAA,UACzC,kBAAkB,MAAM;AACtB,kBAAM,IAAI,KAAK,UAAU,WAAW,KAAK,aAAa,WAAW;AACjE,mBAAO,IAAI,cAAc,CAAC,IAAI;AAAA,UAChC,GAAG;AAAA,UACH,iBAAiB,KAAK,aAAa,eAAe;AAAA,UAElD,YAAY,oBAAI,KAAK;AAAA,QACvB;AAIA,oBAAY,MAAM,iBAAiB,GAAG,CAAC;AAAA,MACzC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IAEA,QAAQ,KAAoB,OAAmB;AAC7C,UAAI,KAAK;AACP,cAAM,UAAU,MAAM,UAAU,uBAAuB;AACvD,gBAAQ;AAAA,UACN,eAAe,QAAQ,IAAI,SAAS,CAAC,UAAU,MAAM,MAAM,KAAK,MAAM,OAAO,GAAG,OAAO;AAAA,QACzF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ,KAAoB,OAAmB;AAC7C,UAAI,KAAK;AACP,cAAM,QAAQ,gBAAgB,MAAM,KAAK;AACzC,cAAM,KAAK,KAAK,YAAY,GAAG,QAAQ,IAAI,SAAS,CAAC,MAAM;AAC3D,cAAM,OAAO,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,IAAI,MAAM,OAAO;AAC7E;AAAA,UACE,eAAe,EAAE,SAAS,MAAM,MAAM,YAAY,CAAC,IAAI,MAAM,KAAK,KAAK,MAAM,OAAO;AAAA,UACpF,GAAI,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,gBAAgB,KAAoB,OAA2B;AAC7D,UAAI,KAAK;AACP,cAAM,QAAQ,gBAAgB,MAAM,KAAK;AACzC,cAAM,KAAK,KAAK,YAAY,GAAG,QAAQ,IAAI,SAAS,CAAC,MAAM;AAC3D,cAAM,QACJ,MAAM,aAAa,QAAQ,MAAM,SAAS,OACtC,KAAK,MAAM,SAAS,IAAI,MAAM,KAAK,MACnC;AACN;AAAA,UACE,eAAe,EAAE,SAAS,MAAM,MAAM,YAAY,CAAC,IAAI,MAAM,QAAQ,OAAO,MAAM,KAAK,KAAK,MAAM,OAAO,GAAG,KAAK;AAAA,QACnH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/siwx.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/siwx.js","../src/siwx.ts"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B;AACA;ACKA,qCAA+C;AAC/C,gCAA4B;AAC5B;AACE;AACA;AACA;AACA;AAAA,8DACK;AACP,uCAA4C;AAoBrC,SAAS,iBAAA,CAAkB,OAAA,EAAsB;AACtD,EAAA,OAAO,4CAAA,MAAc,CAAO,OAAA,EAAsB,GAAA,EAAA,GAA0B;AAC1E,IAAA,MAAM,OAAA,mBAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA,UAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,gBAAgB,GAAA;AAE5F,IAAA,GAAA,CAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,0BAAA,CAA2B,OAAO,CAAA;AAAA,IAC3C;AAEA,IAAA,MAAM,QAAA,EAAU,0CAAA,MAAsB,CAAA;AAEtC,IAAA,MAAM,WAAA,EAAa,MAAM,8CAAA,OAAoB,EAAS,OAAA,CAAQ,GAAG,CAAA;AACjE,IAAA,GAAA,CAAI,CAAC,UAAA,CAAW,KAAA,EAAO;AACrB,MAAA,OAAO,oBAAA,CAAa,IAAA;AAAA,QAClB,EAAE,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,CAAA,wBAAA,EAA2B,UAAA,CAAW,KAAK,CAAA,EAAA;AACtD,QAAA;AAChB,MAAA;AACF,IAAA;AAEsD,IAAA;AACJ,IAAA;AAC5B,MAAA;AAC4C,QAAA;AAChD,QAAA;AAChB,MAAA;AACF,IAAA;AAEuD,IAAA;AACpB,IAAA;AAEX,IAAA;AACnB,MAAA;AACa,MAAA;AACO,IAAA;AAC1B,EAAA;AACH;AAKwE;AACvC,EAAA;AACa,EAAA;AACJ,EAAA;AAC0B,EAAA;AAE1C,EAAA;AACT,IAAA;AACN,IAAA;AACG,IAAA;AACK,MAAA;AACA,MAAA;AACH,MAAA;AACZ,IAAA;AACS,IAAA;AACP,MAAA;AACU,QAAA;AACC,QAAA;AACF,QAAA;AACC,QAAA;AACD,QAAA;AACY,QAAA;AACX,QAAA;AACV,MAAA;AACF,IAAA;AACY,IAAA;AACQ,MAAA;AACV,QAAA;AACQ,UAAA;AACC,UAAA;AACJ,UAAA;AACT,UAAA;AACA,UAAA;AACA,UAAA;AACW,UAAA;AACY,UAAA;AACzB,QAAA;AAC4D,QAAA;AACpC,QAAA;AAC1B,MAAA;AACF,IAAA;AACF,EAAA;AAE2D,EAAA;AAEF,EAAA;AAC/C,IAAA;AACC,IAAA;AACS,MAAA;AACI,MAAA;AACtB,IAAA;AACD,EAAA;AACH;ADnC6E;AACA;AACA","file":"/home/runner/work/agentcash-telemetry/agentcash-telemetry/dist/siwx.js","sourcesContent":[null,"/**\n * SIWX telemetry wrapper — composes SIWX verification with telemetry.\n * One-liner for routes that need wallet auth + telemetry.\n *\n * Delegates SIWX verification to @x402/extensions/sign-in-with-x.\n * This package does NOT implement SIWX itself.\n *\n * Import from '@agentcash/telemetry/siwx'.\n * Requires peer dep: @x402/extensions\n */\n\nimport { type NextRequest, NextResponse } from 'next/server';\nimport { randomBytes } from 'crypto';\nimport {\n parseSIWxHeader,\n validateSIWxMessage,\n verifySIWxSignature,\n buildSIWxSchema,\n} from '@x402/extensions/sign-in-with-x';\nimport { encodePaymentRequiredHeader } from '@x402/core/http';\nimport type { TelemetryContext } from './types';\nimport { withTelemetry } from './telemetry';\n\nexport type { SiwxTelemetryContext };\n\ninterface SiwxTelemetryContext extends Omit<TelemetryContext, 'verifiedWallet'> {\n /** Verified wallet address from SIWX authentication */\n verifiedWallet: string;\n}\n\ntype SiwxHandler = (request: NextRequest, ctx: SiwxTelemetryContext) => Promise<NextResponse>;\n\n/**\n * Wrap a Next.js route handler with SIWX verification + telemetry.\n *\n * Verifies the SIGN-IN-WITH-X header and sets the verified wallet automatically.\n * If no SIWX header is present, returns a 402 with SIWX challenge.\n * If verification fails, returns a 402 (matching x402 protocol convention).\n */\nexport function withSiwxTelemetry(handler: SiwxHandler) {\n return withTelemetry(async (request: NextRequest, ctx: TelemetryContext) => {\n const header = request.headers.get('SIGN-IN-WITH-X') ?? request.headers.get('sign-in-with-x');\n\n if (!header) {\n return buildSiwxChallengeResponse(request);\n }\n\n const payload = parseSIWxHeader(header);\n\n const validation = await validateSIWxMessage(payload, request.url);\n if (!validation.valid) {\n return NextResponse.json(\n { success: false, error: `SIWX validation failed: ${validation.error}` },\n { status: 402 },\n );\n }\n\n const verification = await verifySIWxSignature(payload);\n if (!verification.valid || !verification.address) {\n return NextResponse.json(\n { success: false, error: 'SIWX signature verification failed' },\n { status: 402 },\n );\n }\n\n const walletAddress = verification.address.toLowerCase();\n ctx.setVerifiedWallet(walletAddress);\n\n return handler(request, {\n ...ctx,\n verifiedWallet: walletAddress,\n } as SiwxTelemetryContext);\n });\n}\n\n/**\n * Build a 402 response with SIWX challenge.\n */\nfunction buildSiwxChallengeResponse(request: NextRequest): NextResponse {\n const url = new URL(request.url);\n const nonce = randomBytes(16).toString('hex');\n const issuedAt = new Date().toISOString();\n const expirationTime = new Date(Date.now() + 300_000).toISOString();\n\n const paymentRequired = {\n x402Version: 2,\n error: 'SIWX authentication required',\n resource: {\n url: request.url,\n description: 'SIWX-protected endpoint',\n mimeType: 'application/json',\n },\n accepts: [\n {\n scheme: 'exact' as const,\n network: 'eip155:8453' as const,\n asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n amount: '0',\n payTo: '0x0000000000000000000000000000000000000000',\n maxTimeoutSeconds: 300,\n extra: {},\n },\n ],\n extensions: {\n 'sign-in-with-x': {\n info: {\n domain: url.hostname,\n uri: request.url,\n version: '1',\n nonce,\n issuedAt,\n expirationTime,\n statement: 'Sign in to verify your wallet identity',\n resources: [request.url],\n },\n supportedChains: [{ chainId: 'eip155:8453', type: 'eip191' }],\n schema: buildSIWxSchema(),\n },\n },\n };\n\n const encoded = encodePaymentRequiredHeader(paymentRequired);\n\n return new NextResponse(JSON.stringify(paymentRequired), {\n status: 402,\n headers: {\n 'Content-Type': 'application/json',\n 'PAYMENT-REQUIRED': encoded,\n },\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentcash/telemetry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "ClickHouse telemetry plugin for @agentcash/router. Logs request lifecycle, payments, settlements, and provider quota to ClickHouse.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -30,17 +30,6 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "tsup",
|
|
35
|
-
"lint": "eslint src/",
|
|
36
|
-
"lint:fix": "eslint src/ --fix",
|
|
37
|
-
"format": "prettier --write 'src/**/*.ts' '*.json' '*.mjs'",
|
|
38
|
-
"format:check": "prettier --check 'src/**/*.ts' '*.json' '*.mjs'",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm test",
|
|
42
|
-
"prepublishOnly": "npm run check"
|
|
43
|
-
},
|
|
44
33
|
"keywords": [
|
|
45
34
|
"x402",
|
|
46
35
|
"agentcash",
|
|
@@ -76,6 +65,7 @@
|
|
|
76
65
|
}
|
|
77
66
|
},
|
|
78
67
|
"devDependencies": {
|
|
68
|
+
"@changesets/cli": "^2.29.8",
|
|
79
69
|
"@clickhouse/client": "^1.16.0",
|
|
80
70
|
"@eslint/js": "^9.0.0",
|
|
81
71
|
"@x402/core": "^2.2.0",
|
|
@@ -90,5 +80,15 @@
|
|
|
90
80
|
"vitest": "^4.0.18",
|
|
91
81
|
"zod": "^4.1.13"
|
|
92
82
|
},
|
|
93
|
-
"
|
|
94
|
-
|
|
83
|
+
"scripts": {
|
|
84
|
+
"build": "tsup",
|
|
85
|
+
"lint": "eslint src/",
|
|
86
|
+
"lint:fix": "eslint src/ --fix",
|
|
87
|
+
"format": "prettier --write 'src/**/*.ts' '*.json' '*.mjs'",
|
|
88
|
+
"format:check": "prettier --check 'src/**/*.ts' '*.json' '*.mjs'",
|
|
89
|
+
"typecheck": "tsc --noEmit",
|
|
90
|
+
"test": "vitest run",
|
|
91
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm run build && npm test",
|
|
92
|
+
"changeset": "changeset"
|
|
93
|
+
}
|
|
94
|
+
}
|