Rene Fichtmueller b8543ec191
All checks were successful
changelog-draft / changelog-draft (push) Successful in 31s
feat: proxy subscription clients through gateway
2026-07-18 18:39:18 +02:00

77 lines
3.2 KiB
TypeScript

import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { z } from 'zod';
import { getPool } from '../db/client.js';
import { createRequestLogger } from '../modules/request-logger.js';
import { logger } from '../observability/logger.js';
import { requireGatewayApiKey } from '../security/tenant-auth.js';
import { logCompressionMetric } from '../utils/tokenvault-hooks.js';
const BridgeTrackingReportSchema = z.object({
request_id: z.string().min(8).max(220).optional(),
caller: z.string().min(1).max(120),
model: z.string().min(1).max(120),
source: z.string().min(1).max(80).default('subscription-bridge'),
tokens_in: z.number().int().nonnegative().max(100_000_000),
tokens_out: z.number().int().nonnegative().max(100_000_000),
latency_ms: z.number().int().nonnegative().max(3_600_000).default(0),
compression_tokens_before: z.number().int().nonnegative().max(100_000_000).optional(),
compression_tokens_after: z.number().int().nonnegative().max(100_000_000).optional(),
compression_mode: z.string().min(1).max(48).optional(),
});
function safeRequestId(value: string | undefined): string {
if (value && /^[A-Za-z0-9:._-]{8,220}$/.test(value)) return value;
return `bridge-report:${Date.now()}:${Math.random().toString(36).slice(2, 9)}`;
}
export async function trackingRoute(fastify: FastifyInstance): Promise<void> {
fastify.post(
'/api/tracking/report',
{ preHandler: requireGatewayApiKey },
async (request: FastifyRequest, reply: FastifyReply) => {
const parsed = BridgeTrackingReportSchema.safeParse(request.body);
if (!parsed.success) {
return reply.status(400).send({ success: false, error: parsed.error.errors[0]?.message ?? 'invalid report' });
}
const report = parsed.data;
const requestId = safeRequestId(report.request_id);
const before = report.compression_tokens_before ?? report.tokens_in;
const after = report.compression_tokens_after ?? report.tokens_in;
const saved = Math.max(0, before - after);
const db = getPool();
try {
const existing = await db.query('SELECT 1 FROM request_tracking WHERE request_id=$1 LIMIT 1', [requestId]);
if (existing.rowCount === 0) {
await createRequestLogger(db).logRequest(
requestId,
report.caller,
`subscription_bridge:${report.source}`.slice(0, 50),
report.model,
'approved',
report.tokens_in,
report.tokens_out,
0,
report.latency_ms,
0,
saved > 0,
);
await logCompressionMetric(db, {
filePath: requestId,
mode: report.compression_mode ?? (saved > 0 ? 'bridge:compressed' : 'bridge:none'),
tokensBefore: before,
tokensAfter: after,
savingsPct: before > 0 ? Math.round((saved / before) * 10_000) / 100 : 0,
toolUsed: 'gateway',
});
}
return reply.status(200).send({ success: true, requestId, duplicate: existing.rowCount !== 0 });
} catch (error) {
logger.error({ error, requestId }, 'Bridge tracking report failed');
return reply.status(500).send({ success: false, error: 'tracking report failed' });
}
},
);
}