77 lines
3.2 KiB
TypeScript
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' });
|
|
}
|
|
},
|
|
);
|
|
}
|