govyn 0.0.1 → 0.2.5

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.
Files changed (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -1
  3. package/configs/multi-provider.yaml +68 -0
  4. package/configs/openai-only.yaml +45 -0
  5. package/configs/team-setup.yaml +88 -0
  6. package/dist/action-logger.d.ts +128 -0
  7. package/dist/action-logger.js +356 -0
  8. package/dist/action-logger.js.map +1 -0
  9. package/dist/admin-cli.d.ts +2 -0
  10. package/dist/admin-cli.js +36 -0
  11. package/dist/admin-cli.js.map +1 -0
  12. package/dist/agents.d.ts +23 -0
  13. package/dist/agents.js +59 -0
  14. package/dist/agents.js.map +1 -0
  15. package/dist/alert-api.d.ts +14 -0
  16. package/dist/alert-api.js +355 -0
  17. package/dist/alert-api.js.map +1 -0
  18. package/dist/alert-manager.d.ts +77 -0
  19. package/dist/alert-manager.js +267 -0
  20. package/dist/alert-manager.js.map +1 -0
  21. package/dist/approval-api.d.ts +19 -0
  22. package/dist/approval-api.js +82 -0
  23. package/dist/approval-api.js.map +1 -0
  24. package/dist/approval-timeout.d.ts +29 -0
  25. package/dist/approval-timeout.js +45 -0
  26. package/dist/approval-timeout.js.map +1 -0
  27. package/dist/approval.d.ts +78 -0
  28. package/dist/approval.js +101 -0
  29. package/dist/approval.js.map +1 -0
  30. package/dist/auth.d.ts +47 -0
  31. package/dist/auth.js +335 -0
  32. package/dist/auth.js.map +1 -0
  33. package/dist/budget-api.d.ts +20 -0
  34. package/dist/budget-api.js +85 -0
  35. package/dist/budget-api.js.map +1 -0
  36. package/dist/budget-enforcer.d.ts +102 -0
  37. package/dist/budget-enforcer.js +294 -0
  38. package/dist/budget-enforcer.js.map +1 -0
  39. package/dist/cli.d.ts +15 -0
  40. package/dist/cli.js +200 -0
  41. package/dist/cli.js.map +1 -0
  42. package/dist/config.d.ts +15 -0
  43. package/dist/config.js +267 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/cost-aggregator.d.ts +69 -0
  46. package/dist/cost-aggregator.js +305 -0
  47. package/dist/cost-aggregator.js.map +1 -0
  48. package/dist/cost-api.d.ts +29 -0
  49. package/dist/cost-api.js +128 -0
  50. package/dist/cost-api.js.map +1 -0
  51. package/dist/database-url.d.ts +6 -0
  52. package/dist/database-url.js +47 -0
  53. package/dist/database-url.js.map +1 -0
  54. package/dist/db-retention.d.ts +53 -0
  55. package/dist/db-retention.js +82 -0
  56. package/dist/db-retention.js.map +1 -0
  57. package/dist/db-schema.d.ts +17 -0
  58. package/dist/db-schema.js +167 -0
  59. package/dist/db-schema.js.map +1 -0
  60. package/dist/db-writer.d.ts +55 -0
  61. package/dist/db-writer.js +115 -0
  62. package/dist/db-writer.js.map +1 -0
  63. package/dist/db.d.ts +33 -0
  64. package/dist/db.js +78 -0
  65. package/dist/db.js.map +1 -0
  66. package/dist/events.d.ts +77 -0
  67. package/dist/events.js +12 -0
  68. package/dist/events.js.map +1 -0
  69. package/dist/health.d.ts +14 -0
  70. package/dist/health.js +49 -0
  71. package/dist/health.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/index.js +14 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/init-wizard.d.ts +12 -0
  76. package/dist/init-wizard.js +206 -0
  77. package/dist/init-wizard.js.map +1 -0
  78. package/dist/log-api.d.ts +20 -0
  79. package/dist/log-api.js +371 -0
  80. package/dist/log-api.js.map +1 -0
  81. package/dist/log-rotator.d.ts +55 -0
  82. package/dist/log-rotator.js +157 -0
  83. package/dist/log-rotator.js.map +1 -0
  84. package/dist/loop-detector.d.ts +71 -0
  85. package/dist/loop-detector.js +122 -0
  86. package/dist/loop-detector.js.map +1 -0
  87. package/dist/persistence-types.d.ts +165 -0
  88. package/dist/persistence-types.js +2 -0
  89. package/dist/persistence-types.js.map +1 -0
  90. package/dist/persistence.d.ts +185 -0
  91. package/dist/persistence.js +785 -0
  92. package/dist/persistence.js.map +1 -0
  93. package/dist/policy-api.d.ts +25 -0
  94. package/dist/policy-api.js +347 -0
  95. package/dist/policy-api.js.map +1 -0
  96. package/dist/policy-engine.d.ts +76 -0
  97. package/dist/policy-engine.js +835 -0
  98. package/dist/policy-engine.js.map +1 -0
  99. package/dist/policy-file.d.ts +10 -0
  100. package/dist/policy-file.js +52 -0
  101. package/dist/policy-file.js.map +1 -0
  102. package/dist/policy-parser.d.ts +21 -0
  103. package/dist/policy-parser.js +560 -0
  104. package/dist/policy-parser.js.map +1 -0
  105. package/dist/policy-types.d.ts +216 -0
  106. package/dist/policy-types.js +8 -0
  107. package/dist/policy-types.js.map +1 -0
  108. package/dist/policy-watcher.d.ts +54 -0
  109. package/dist/policy-watcher.js +116 -0
  110. package/dist/policy-watcher.js.map +1 -0
  111. package/dist/pricing.d.ts +69 -0
  112. package/dist/pricing.js +93 -0
  113. package/dist/pricing.js.map +1 -0
  114. package/dist/prompt.d.ts +6 -0
  115. package/dist/prompt.js +47 -0
  116. package/dist/prompt.js.map +1 -0
  117. package/dist/providers/anthropic.d.ts +18 -0
  118. package/dist/providers/anthropic.js +61 -0
  119. package/dist/providers/anthropic.js.map +1 -0
  120. package/dist/providers/custom.d.ts +19 -0
  121. package/dist/providers/custom.js +54 -0
  122. package/dist/providers/custom.js.map +1 -0
  123. package/dist/providers/openai.d.ts +17 -0
  124. package/dist/providers/openai.js +48 -0
  125. package/dist/providers/openai.js.map +1 -0
  126. package/dist/proxy.d.ts +57 -0
  127. package/dist/proxy.js +477 -0
  128. package/dist/proxy.js.map +1 -0
  129. package/dist/router.d.ts +23 -0
  130. package/dist/router.js +89 -0
  131. package/dist/router.js.map +1 -0
  132. package/dist/runtime.d.ts +1 -0
  133. package/dist/runtime.js +139 -0
  134. package/dist/runtime.js.map +1 -0
  135. package/dist/security.d.ts +64 -0
  136. package/dist/security.js +422 -0
  137. package/dist/security.js.map +1 -0
  138. package/dist/server.d.ts +33 -0
  139. package/dist/server.js +1147 -0
  140. package/dist/server.js.map +1 -0
  141. package/dist/sqlite-schema.d.ts +6 -0
  142. package/dist/sqlite-schema.js +134 -0
  143. package/dist/sqlite-schema.js.map +1 -0
  144. package/dist/streaming.d.ts +24 -0
  145. package/dist/streaming.js +63 -0
  146. package/dist/streaming.js.map +1 -0
  147. package/dist/tokens.d.ts +45 -0
  148. package/dist/tokens.js +237 -0
  149. package/dist/tokens.js.map +1 -0
  150. package/dist/types.d.ts +344 -0
  151. package/dist/types.js +5 -0
  152. package/dist/types.js.map +1 -0
  153. package/package.json +66 -2
package/dist/proxy.js ADDED
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Request forwarding logic for the Govyn proxy server.
3
+ *
4
+ * Uses Node.js built-in http/https module for zero-dependency,
5
+ * low-latency request forwarding (per ADR-013).
6
+ *
7
+ * - Forwards ALL upstream response headers verbatim, including rate-limit headers
8
+ * (Retry-After, x-ratelimit-*) — per ADR-016: 429s are passed through, not retried
9
+ * - Delegates SSE responses to handleStreamingResponse for chunk-by-chunk piping
10
+ * - Returns 502 only for proxy-own errors (upstream unreachable, connection timeout)
11
+ * - After each response completes, extracts tokens and records cost (non-blocking)
12
+ */
13
+ import * as http from 'node:http';
14
+ import * as https from 'node:https';
15
+ import * as crypto from 'node:crypto';
16
+ import { mapOpenAIHeaders } from './providers/openai.js';
17
+ import { mapAnthropicHeaders } from './providers/anthropic.js';
18
+ import { mapCustomHeaders } from './providers/custom.js';
19
+ import { handleStreamingResponse } from './streaming.js';
20
+ import { extractTokenUsage, extractTokenUsageFromSSE } from './tokens.js';
21
+ import { calculateCost } from './pricing.js';
22
+ import { govynEvents } from './events.js';
23
+ const HOP_BY_HOP_RESPONSE_HEADERS = new Set([
24
+ 'connection',
25
+ 'keep-alive',
26
+ 'proxy-authenticate',
27
+ 'proxy-authorization',
28
+ 'te',
29
+ 'trailer',
30
+ 'transfer-encoding',
31
+ 'upgrade',
32
+ ]);
33
+ /**
34
+ * Select the appropriate header mapping function based on provider type.
35
+ */
36
+ function mapHeaders(incomingHeaders, routeMatch) {
37
+ const { provider, providerType } = routeMatch;
38
+ switch (providerType) {
39
+ case 'openai':
40
+ return mapOpenAIHeaders(incomingHeaders, provider.apiKeyEnv);
41
+ case 'anthropic':
42
+ return mapAnthropicHeaders(incomingHeaders, provider.apiKeyEnv);
43
+ case 'custom':
44
+ return mapCustomHeaders(incomingHeaders, provider.apiKeyEnv);
45
+ default: {
46
+ // Exhaustive check
47
+ const _exhaustive = providerType;
48
+ throw new Error(`Unknown provider type: ${String(_exhaustive)}`);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Send a JSON error response to the client.
54
+ * Only used for proxy-own errors (upstream unreachable, timeout, etc.).
55
+ * Upstream error responses (4xx, 5xx) are forwarded verbatim.
56
+ */
57
+ function sendErrorResponse(res, statusCode, message, code) {
58
+ const body = JSON.stringify({ error: { message, code } });
59
+ if (!res.headersSent) {
60
+ res.writeHead(statusCode, {
61
+ 'content-type': 'application/json',
62
+ 'content-length': Buffer.byteLength(body).toString(),
63
+ });
64
+ }
65
+ res.end(body);
66
+ }
67
+ /**
68
+ * Send a loop_detected 429 response in Govyn-native format.
69
+ *
70
+ * @param res - The outgoing client response
71
+ * @param agentId - The agent that triggered loop detection
72
+ * @param cooldownSeconds - How long the agent will be blocked
73
+ */
74
+ function sendLoopDetectedError(res, agentId, cooldownSeconds) {
75
+ const cooldownExpiresAt = new Date(Date.now() + cooldownSeconds * 1000).toISOString();
76
+ const body = JSON.stringify({
77
+ error: {
78
+ type: 'loop_error',
79
+ code: 'loop_detected',
80
+ message: 'Agent blocked: repeated identical requests detected',
81
+ details: {
82
+ agent_id: agentId,
83
+ cooldown_seconds: cooldownSeconds,
84
+ cooldown_expires_at: cooldownExpiresAt,
85
+ },
86
+ },
87
+ });
88
+ res.writeHead(429, {
89
+ 'content-type': 'application/json',
90
+ 'content-length': Buffer.byteLength(body).toString(),
91
+ 'retry-after': cooldownSeconds.toString(),
92
+ });
93
+ res.end(body);
94
+ }
95
+ /**
96
+ * Forward an incoming HTTP request to the upstream provider API.
97
+ *
98
+ * - Uses Node.js http/https module (NOT node-fetch, NOT axios)
99
+ * - Reads request body from incoming stream
100
+ * - Maps headers for the target provider
101
+ * - Forwards ALL upstream response headers verbatim (including rate-limit headers)
102
+ * - For SSE responses: delegates to handleStreamingResponse for chunk-by-chunk piping
103
+ * - For non-SSE responses: pipes upstream response body to client directly
104
+ * - For upstream errors (4xx, 5xx): forwards status code + headers + body verbatim
105
+ * - For proxy errors (upstream unreachable, timeout): returns 502 with Govyn error format
106
+ * - Logs time from request start to first upstream byte
107
+ * - After response completes, extracts token usage and records cost (non-blocking)
108
+ * - If loopDetector provided: checks for repeated identical requests before forwarding
109
+ *
110
+ * @param req - The incoming client request
111
+ * @param res - The outgoing client response
112
+ * @param routeMatch - The matched route (provider, path, type)
113
+ * @param agentId - The resolved agent identifier for this request
114
+ * @param pricingTable - Pricing table for cost calculation
115
+ * @param aggregator - Cost aggregator to record results
116
+ * @param budgetWarning - Optional budget warning info to add as response header
117
+ * @param loopDetector - Optional loop detector for detecting repeated identical requests
118
+ * @param budgetEnforcer - Optional budget enforcer for triggering loop block on detection
119
+ * @param actionLogger - Optional action logger for structured request logging
120
+ */
121
+ export async function forwardRequest(req, res, routeMatch, agentId, pricingTable, aggregator, budgetWarning, loopDetector, budgetEnforcer, actionLogger, bufferedBody, requestedModel, policyResult, dbWriter) {
122
+ const requestStart = Date.now();
123
+ const { provider, upstreamPath } = routeMatch;
124
+ // Parse the upstream base URL
125
+ let upstreamUrl;
126
+ try {
127
+ upstreamUrl = new URL(provider.baseUrl);
128
+ }
129
+ catch {
130
+ sendErrorResponse(res, 502, 'Invalid upstream base URL configured', 'invalid_config');
131
+ return;
132
+ }
133
+ // Build upstream request options
134
+ const upstreamHost = upstreamUrl.hostname;
135
+ const upstreamPort = upstreamUrl.port
136
+ ? parseInt(upstreamUrl.port, 10)
137
+ : upstreamUrl.protocol === 'https:'
138
+ ? 443
139
+ : 80;
140
+ const isHttps = upstreamUrl.protocol === 'https:';
141
+ // Map headers for the upstream provider
142
+ const mappedHeaders = mapHeaders(req.headers, routeMatch);
143
+ // Use pre-buffered body if provided (from server.ts policy evaluation flow),
144
+ // otherwise read from the request stream directly (backward compat)
145
+ let body;
146
+ if (bufferedBody !== undefined) {
147
+ body = bufferedBody;
148
+ }
149
+ else {
150
+ const bodyChunks = [];
151
+ try {
152
+ await new Promise((resolve, reject) => {
153
+ req.on('data', (chunk) => bodyChunks.push(chunk));
154
+ req.on('end', resolve);
155
+ req.on('error', reject);
156
+ });
157
+ }
158
+ catch {
159
+ sendErrorResponse(res, 500, 'Error reading request body', 'request_read_error');
160
+ return;
161
+ }
162
+ body = Buffer.concat(bodyChunks);
163
+ }
164
+ // Loop detection: check for repeated identical requests before forwarding
165
+ if (loopDetector && budgetEnforcer) {
166
+ const bodyHash = loopDetector.getRequestHash(body);
167
+ loopDetector.recordRequest(agentId, routeMatch.upstreamPath, bodyHash);
168
+ if (loopDetector.isLooping(agentId, routeMatch.upstreamPath, bodyHash)) {
169
+ // Get agent-specific cooldown (or default 300s)
170
+ const agentLoopConfig = loopDetector.getAgentConfig(agentId);
171
+ const cooldownSeconds = agentLoopConfig.cooldownSeconds;
172
+ budgetEnforcer.blockAgent(agentId, 'loop_detected', cooldownSeconds);
173
+ console.warn(`[govyn] Loop detected: agent=${agentId} path=${routeMatch.upstreamPath} bodyHash=${bodyHash} cooldown=${cooldownSeconds}s`);
174
+ govynEvents.emit('event', {
175
+ type: 'loop_detected',
176
+ agentId,
177
+ cooldownSeconds,
178
+ });
179
+ // Log the loop_detected event
180
+ if (actionLogger) {
181
+ const logEntry = {
182
+ id: crypto.randomUUID(),
183
+ timestamp: new Date().toISOString(),
184
+ agent_id: agentId,
185
+ provider: routeMatch.providerType,
186
+ target: routeMatch.upstreamPath,
187
+ model: null,
188
+ input_tokens: null,
189
+ output_tokens: null,
190
+ cost: null,
191
+ priced: false,
192
+ latency_ms: Date.now() - requestStart,
193
+ status: 429,
194
+ has_payload: false,
195
+ payload_id: null,
196
+ storage_region: 'auto',
197
+ };
198
+ actionLogger.log(logEntry);
199
+ }
200
+ sendLoopDetectedError(res, agentId, cooldownSeconds);
201
+ return;
202
+ }
203
+ }
204
+ // Update content-length to match actual body
205
+ if (body.length > 0) {
206
+ mappedHeaders['content-length'] = body.length.toString();
207
+ }
208
+ else {
209
+ delete mappedHeaders['content-length'];
210
+ }
211
+ const requestOptions = {
212
+ hostname: upstreamHost,
213
+ port: upstreamPort,
214
+ path: upstreamPath,
215
+ method: req.method ?? 'GET',
216
+ headers: mappedHeaders,
217
+ };
218
+ // Make the upstream request
219
+ return new Promise((resolve) => {
220
+ const transport = isHttps ? https : http;
221
+ const upstreamReq = transport.request(requestOptions, (upstreamRes) => {
222
+ const firstByteTime = Date.now();
223
+ const latency = firstByteTime - requestStart;
224
+ const statusCode = upstreamRes.statusCode ?? 200;
225
+ console.log(`[proxy] ${req.method} ${upstreamPath} -> ${provider.baseUrl} | status=${statusCode} | latency=${latency}ms`);
226
+ // Build response headers — forward ALL upstream headers verbatim.
227
+ // This is critical for rate-limit headers (Retry-After, x-ratelimit-*) per ADR-016.
228
+ // Upstream errors (4xx, 5xx) are passed through without modification.
229
+ const responseHeaders = {};
230
+ for (const [key, value] of Object.entries(upstreamRes.headers)) {
231
+ if (value !== undefined && !HOP_BY_HOP_RESPONSE_HEADERS.has(key.toLowerCase())) {
232
+ responseHeaders[key] = value;
233
+ }
234
+ }
235
+ // Build budget warning header value if applicable
236
+ const budgetWarningHeaderValue = budgetWarning
237
+ ? JSON.stringify({
238
+ percent_used: budgetWarning.percentUsed,
239
+ current_spend: budgetWarning.currentSpend,
240
+ limit: budgetWarning.limit,
241
+ resets_at: budgetWarning.resetsAt,
242
+ })
243
+ : undefined;
244
+ // Check if the upstream response is SSE (text/event-stream)
245
+ const contentType = upstreamRes.headers['content-type'] ?? '';
246
+ const isSSE = contentType.includes('text/event-stream');
247
+ if (isSSE) {
248
+ // Accumulate SSE chunks for token extraction (concurrent with piping to client)
249
+ const sseChunks = [];
250
+ upstreamRes.on('data', (chunk) => {
251
+ sseChunks.push(chunk.toString('utf8'));
252
+ });
253
+ // Build extra headers for SSE response (budget warning if applicable)
254
+ const sseExtraHeaders = budgetWarningHeaderValue
255
+ ? { 'x-govyn-budget-warning': budgetWarningHeaderValue }
256
+ : undefined;
257
+ // Delegate to streaming handler — sets its own headers (content-type, cache-control, connection)
258
+ // and pipes chunks without buffering
259
+ handleStreamingResponse(upstreamRes, res, statusCode, sseExtraHeaders);
260
+ // After the stream ends, extract tokens and record cost
261
+ // (happens after all data has been piped to client)
262
+ upstreamRes.on('end', () => {
263
+ const usage = extractTokenUsageFromSSE(sseChunks, routeMatch.providerType);
264
+ let costResult;
265
+ if (usage) {
266
+ costResult = calculateCost(usage, pricingTable);
267
+ const costRecord = {
268
+ agentId,
269
+ model: usage.model,
270
+ provider: routeMatch.providerType,
271
+ inputTokens: usage.inputTokens,
272
+ outputTokens: usage.outputTokens,
273
+ inputCost: costResult.inputCost,
274
+ outputCost: costResult.outputCost,
275
+ totalCost: costResult.totalCost,
276
+ priced: costResult.priced,
277
+ timestamp: Date.now(),
278
+ requestedModel: requestedModel ?? undefined,
279
+ };
280
+ aggregator.recordCost(costRecord);
281
+ // DB persistence (fire-and-forget, parallel with in-memory + JSONL)
282
+ dbWriter?.writeCostRecord(costRecord).catch(() => { });
283
+ const totalTokens = usage.inputTokens + usage.outputTokens;
284
+ console.log(`[govyn] Cost: agent=${agentId} model=${usage.model} tokens=${totalTokens} cost=$${costResult.totalCost.toFixed(6)} priced=${costResult.priced}`);
285
+ }
286
+ // Action logging (SSE path)
287
+ if (actionLogger) {
288
+ const mode = actionLogger.getMode(agentId);
289
+ const logId = crypto.randomUUID();
290
+ const payloadId = mode === 'full-payload' ? crypto.randomUUID() : null;
291
+ const logEntry = {
292
+ id: logId,
293
+ timestamp: new Date().toISOString(),
294
+ agent_id: agentId,
295
+ provider: routeMatch.providerType,
296
+ target: routeMatch.upstreamPath,
297
+ model: usage?.model ?? null,
298
+ input_tokens: usage?.inputTokens ?? null,
299
+ output_tokens: usage?.outputTokens ?? null,
300
+ cost: costResult?.totalCost ?? null,
301
+ priced: costResult?.priced ?? false,
302
+ latency_ms: Date.now() - requestStart,
303
+ status: statusCode,
304
+ has_payload: payloadId !== null,
305
+ payload_id: payloadId,
306
+ storage_region: 'auto',
307
+ requested_model: requestedModel ?? null,
308
+ actual_model: usage?.model ?? null,
309
+ policy_result: policyResult ? {
310
+ allowed: policyResult.allowed,
311
+ evaluated_count: policyResult.evaluatedCount,
312
+ matched_count: policyResult.matchedCount,
313
+ evaluation_time_ms: policyResult.evaluationTimeMs,
314
+ } : undefined,
315
+ };
316
+ actionLogger.log(logEntry);
317
+ if (payloadId) {
318
+ const sseResBody = Buffer.from(sseChunks.join(''), 'utf8');
319
+ const maxSize = actionLogger.config.maxBodySize;
320
+ const truncated = (body.length > maxSize) || (sseResBody.length > maxSize);
321
+ actionLogger.storePayload(payloadId, body, sseResBody, truncated);
322
+ }
323
+ }
324
+ });
325
+ // Resolve when the pipe ends (client close or upstream end)
326
+ res.on('finish', resolve);
327
+ res.on('close', resolve);
328
+ upstreamRes.on('error', resolve);
329
+ }
330
+ else {
331
+ // Non-streaming: forward status + all headers + body verbatim
332
+ // Add budget warning header if applicable
333
+ if (budgetWarningHeaderValue) {
334
+ responseHeaders['x-govyn-budget-warning'] = budgetWarningHeaderValue;
335
+ }
336
+ res.writeHead(statusCode, responseHeaders);
337
+ upstreamRes.pipe(res);
338
+ // Accumulate body for token extraction (concurrent with piping to client)
339
+ const responseBodyChunks = [];
340
+ upstreamRes.on('data', (chunk) => {
341
+ responseBodyChunks.push(chunk);
342
+ });
343
+ upstreamRes.on('end', () => {
344
+ // Extract tokens from the buffered response body
345
+ const responseBody = Buffer.concat(responseBodyChunks);
346
+ const responseBodyStr = responseBody.toString('utf8');
347
+ const usage = extractTokenUsage(responseBodyStr, routeMatch.providerType);
348
+ let costResult;
349
+ if (usage) {
350
+ costResult = calculateCost(usage, pricingTable);
351
+ const costRecord = {
352
+ agentId,
353
+ model: usage.model,
354
+ provider: routeMatch.providerType,
355
+ inputTokens: usage.inputTokens,
356
+ outputTokens: usage.outputTokens,
357
+ inputCost: costResult.inputCost,
358
+ outputCost: costResult.outputCost,
359
+ totalCost: costResult.totalCost,
360
+ priced: costResult.priced,
361
+ timestamp: Date.now(),
362
+ requestedModel: requestedModel ?? undefined,
363
+ };
364
+ aggregator.recordCost(costRecord);
365
+ // DB persistence (fire-and-forget, parallel with in-memory + JSONL)
366
+ dbWriter?.writeCostRecord(costRecord).catch(() => { });
367
+ const totalTokens = usage.inputTokens + usage.outputTokens;
368
+ console.log(`[govyn] Cost: agent=${agentId} model=${usage.model} tokens=${totalTokens} cost=$${costResult.totalCost.toFixed(6)} priced=${costResult.priced}`);
369
+ }
370
+ // Action logging (non-SSE path)
371
+ if (actionLogger) {
372
+ const mode = actionLogger.getMode(agentId);
373
+ const logId = crypto.randomUUID();
374
+ const payloadId = mode === 'full-payload' ? crypto.randomUUID() : null;
375
+ const logEntry = {
376
+ id: logId,
377
+ timestamp: new Date().toISOString(),
378
+ agent_id: agentId,
379
+ provider: routeMatch.providerType,
380
+ target: routeMatch.upstreamPath,
381
+ model: usage?.model ?? null,
382
+ input_tokens: usage?.inputTokens ?? null,
383
+ output_tokens: usage?.outputTokens ?? null,
384
+ cost: costResult?.totalCost ?? null,
385
+ priced: costResult?.priced ?? false,
386
+ latency_ms: Date.now() - requestStart,
387
+ status: statusCode,
388
+ has_payload: payloadId !== null,
389
+ payload_id: payloadId,
390
+ storage_region: 'auto',
391
+ requested_model: requestedModel ?? null,
392
+ actual_model: usage?.model ?? null,
393
+ policy_result: policyResult ? {
394
+ allowed: policyResult.allowed,
395
+ evaluated_count: policyResult.evaluatedCount,
396
+ matched_count: policyResult.matchedCount,
397
+ evaluation_time_ms: policyResult.evaluationTimeMs,
398
+ } : undefined,
399
+ };
400
+ actionLogger.log(logEntry);
401
+ if (payloadId) {
402
+ const maxSize = actionLogger.config.maxBodySize;
403
+ const truncated = (body.length > maxSize) || (responseBody.length > maxSize);
404
+ actionLogger.storePayload(payloadId, body, responseBody, truncated);
405
+ }
406
+ }
407
+ resolve();
408
+ });
409
+ upstreamRes.on('error', (err) => {
410
+ console.error('[proxy] upstream response error:', err.message);
411
+ if (!res.writableEnded) {
412
+ res.end();
413
+ }
414
+ resolve();
415
+ });
416
+ }
417
+ });
418
+ upstreamReq.on('error', (err) => {
419
+ console.error(`[proxy] upstream connection error: ${err.message}`);
420
+ // Log the connection error
421
+ if (actionLogger) {
422
+ const logEntry = {
423
+ id: crypto.randomUUID(),
424
+ timestamp: new Date().toISOString(),
425
+ agent_id: agentId,
426
+ provider: routeMatch.providerType,
427
+ target: routeMatch.upstreamPath,
428
+ model: null,
429
+ input_tokens: null,
430
+ output_tokens: null,
431
+ cost: null,
432
+ priced: false,
433
+ latency_ms: Date.now() - requestStart,
434
+ status: 502,
435
+ has_payload: false,
436
+ payload_id: null,
437
+ storage_region: 'auto',
438
+ };
439
+ actionLogger.log(logEntry);
440
+ }
441
+ sendErrorResponse(res, 502, 'Upstream connection failed', 'upstream_connection_error');
442
+ resolve();
443
+ });
444
+ upstreamReq.on('timeout', () => {
445
+ // Log the timeout error
446
+ if (actionLogger) {
447
+ const logEntry = {
448
+ id: crypto.randomUUID(),
449
+ timestamp: new Date().toISOString(),
450
+ agent_id: agentId,
451
+ provider: routeMatch.providerType,
452
+ target: routeMatch.upstreamPath,
453
+ model: null,
454
+ input_tokens: null,
455
+ output_tokens: null,
456
+ cost: null,
457
+ priced: false,
458
+ latency_ms: Date.now() - requestStart,
459
+ status: 502,
460
+ has_payload: false,
461
+ payload_id: null,
462
+ storage_region: 'auto',
463
+ };
464
+ actionLogger.log(logEntry);
465
+ }
466
+ upstreamReq.destroy();
467
+ sendErrorResponse(res, 502, 'Upstream request timed out', 'upstream_timeout');
468
+ resolve();
469
+ });
470
+ // Write request body to upstream
471
+ if (body.length > 0) {
472
+ upstreamReq.write(body);
473
+ }
474
+ upstreamReq.end();
475
+ });
476
+ }
477
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAM7C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC;IAC1C,YAAY;IACZ,YAAY;IACZ,oBAAoB;IACpB,qBAAqB;IACrB,IAAI;IACJ,SAAS;IACT,mBAAmB;IACnB,SAAS;CACV,CAAC,CAAC;AAEH;;GAEG;AACH,SAAS,UAAU,CACjB,eAAyC,EACzC,UAAsB;IAEtB,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,UAAU,CAAC;IAE9C,QAAQ,YAAY,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,gBAAgB,CAAC,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC/D,KAAK,WAAW;YACd,OAAO,mBAAmB,CAAC,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAClE,KAAK,QAAQ;YACX,OAAO,gBAAgB,CAAC,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC,CAAC;YACR,mBAAmB;YACnB,MAAM,WAAW,GAAU,YAAY,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CACxB,GAAmB,EACnB,UAAkB,EAClB,OAAe,EACf,IAAY;IAEZ,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE;YACxB,cAAc,EAAE,kBAAkB;YAClC,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;SACrD,CAAC,CAAC;IACL,CAAC;IACD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,qBAAqB,CAC5B,GAAmB,EACnB,OAAe,EACf,eAAuB;IAEvB,MAAM,iBAAiB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACtF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,KAAK,EAAE;YACL,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,qDAAqD;YAC9D,OAAO,EAAE;gBACP,QAAQ,EAAE,OAAO;gBACjB,gBAAgB,EAAE,eAAe;gBACjC,mBAAmB,EAAE,iBAAiB;aACvC;SACF;KACF,CAAC,CAAC;IACH,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,kBAAkB;QAClC,gBAAgB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;QACpD,aAAa,EAAE,eAAe,CAAC,QAAQ,EAAE;KAC1C,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAoB,EACpB,GAAmB,EACnB,UAAsB,EACtB,OAAe,EACf,YAA0B,EAC1B,UAA0B,EAC1B,aAA8F,EAC9F,YAA2B,EAC3B,cAA+B,EAC/B,YAA2B,EAC3B,YAAqB,EACrB,cAAuB,EACvB,YAA2G,EAC3G,QAAmB;IAEnB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,UAAU,CAAC;IAE9C,8BAA8B;IAC9B,IAAI,WAAgB,CAAC;IACrB,IAAI,CAAC;QACH,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,sCAAsC,EAAE,gBAAgB,CAAC,CAAC;QACtF,OAAO;IACT,CAAC;IAED,iCAAiC;IACjC,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC;IAC1C,MAAM,YAAY,GAChB,WAAW,CAAC,IAAI;QACd,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,CAAC,CAAC,WAAW,CAAC,QAAQ,KAAK,QAAQ;YACjC,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,EAAE,CAAC;IACX,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAElD,wCAAwC;IACxC,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAE1D,6EAA6E;IAC7E,oEAAoE;IACpE,IAAI,IAAY,CAAC;IACjB,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,GAAG,YAAY,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC1D,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBACvB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,4BAA4B,EAAE,oBAAoB,CAAC,CAAC;YAChF,OAAO;QACT,CAAC;QACD,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAED,0EAA0E;IAC1E,IAAI,YAAY,IAAI,cAAc,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QACnD,YAAY,CAAC,aAAa,CAAC,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QACvE,IAAI,YAAY,CAAC,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC;YACvE,gDAAgD;YAChD,MAAM,eAAe,GAAG,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAC7D,MAAM,eAAe,GAAG,eAAe,CAAC,eAAe,CAAC;YACxD,cAAc,CAAC,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;YACrE,OAAO,CAAC,IAAI,CACV,gCAAgC,OAAO,SAAS,UAAU,CAAC,YAAY,aAAa,QAAQ,aAAa,eAAe,GAAG,CAC5H,CAAC;YAEF,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE;gBACxB,IAAI,EAAE,eAAe;gBACrB,OAAO;gBACP,eAAe;aAChB,CAAC,CAAC;YAEH,8BAA8B;YAC9B,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAa;oBACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;oBACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,UAAU,CAAC,YAAY;oBACjC,MAAM,EAAE,UAAU,CAAC,YAAY;oBAC/B,KAAK,EAAE,IAAI;oBACX,YAAY,EAAE,IAAI;oBAClB,aAAa,EAAE,IAAI;oBACnB,IAAI,EAAE,IAAI;oBACV,MAAM,EAAE,KAAK;oBACb,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;oBACrC,MAAM,EAAE,GAAG;oBACX,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,IAAI;oBAChB,cAAc,EAAE,MAAM;iBACvB,CAAC;gBACF,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;YAED,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;YACrD,OAAO;QACT,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,aAAa,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,OAAO,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,cAAc,GAAwB;QAC1C,QAAQ,EAAE,YAAY;QACtB,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,YAAY;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK;QAC3B,OAAO,EAAE,aAAa;KACvB,CAAC;IAEF,4BAA4B;IAC5B,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAEzC,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,WAAW,EAAE,EAAE;YACpE,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,aAAa,GAAG,YAAY,CAAC;YAC7C,MAAM,UAAU,GAAG,WAAW,CAAC,UAAU,IAAI,GAAG,CAAC;YAEjD,OAAO,CAAC,GAAG,CACT,WAAW,GAAG,CAAC,MAAM,IAAI,YAAY,OAAO,QAAQ,CAAC,OAAO,aAAa,UAAU,cAAc,OAAO,IAAI,CAC7G,CAAC;YAEF,kEAAkE;YAClE,oFAAoF;YACpF,sEAAsE;YACtE,MAAM,eAAe,GAAsC,EAAE,CAAC;YAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/D,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;oBAC/E,eAAe,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAC/B,CAAC;YACH,CAAC;YAED,kDAAkD;YAClD,MAAM,wBAAwB,GAAG,aAAa;gBAC5C,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC;oBACb,YAAY,EAAE,aAAa,CAAC,WAAW;oBACvC,aAAa,EAAE,aAAa,CAAC,YAAY;oBACzC,KAAK,EAAE,aAAa,CAAC,KAAK;oBAC1B,SAAS,EAAE,aAAa,CAAC,QAAQ;iBAClC,CAAC;gBACJ,CAAC,CAAC,SAAS,CAAC;YAEd,4DAA4D;YAC5D,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YAC9D,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;YAExD,IAAI,KAAK,EAAE,CAAC;gBACV,gFAAgF;gBAChF,MAAM,SAAS,GAAa,EAAE,CAAC;gBAC/B,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;oBACvC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;gBACzC,CAAC,CAAC,CAAC;gBAEH,sEAAsE;gBACtE,MAAM,eAAe,GAAuC,wBAAwB;oBAClF,CAAC,CAAC,EAAE,wBAAwB,EAAE,wBAAwB,EAAE;oBACxD,CAAC,CAAC,SAAS,CAAC;gBAEd,iGAAiG;gBACjG,qCAAqC;gBACrC,uBAAuB,CAAC,WAAW,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC;gBAEvE,wDAAwD;gBACxD,oDAAoD;gBACpD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACzB,MAAM,KAAK,GAAG,wBAAwB,CAAC,SAAS,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;oBAC3E,IAAI,UAAqG,CAAC;oBAC1G,IAAI,KAAK,EAAE,CAAC;wBACV,UAAU,GAAG,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;wBAChD,MAAM,UAAU,GAAG;4BACjB,OAAO;4BACP,KAAK,EAAE,KAAK,CAAC,KAAK;4BAClB,QAAQ,EAAE,UAAU,CAAC,YAAY;4BACjC,WAAW,EAAE,KAAK,CAAC,WAAW;4BAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;4BAChC,SAAS,EAAE,UAAU,CAAC,SAAS;4BAC/B,UAAU,EAAE,UAAU,CAAC,UAAU;4BACjC,SAAS,EAAE,UAAU,CAAC,SAAS;4BAC/B,MAAM,EAAE,UAAU,CAAC,MAAM;4BACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;4BACrB,cAAc,EAAE,cAAc,IAAI,SAAS;yBAC5C,CAAC;wBACF,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;wBAElC,oEAAoE;wBACpE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;wBAEtD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;wBAC3D,OAAO,CAAC,GAAG,CACT,uBAAuB,OAAO,UAAU,KAAK,CAAC,KAAK,WAAW,WAAW,UAAU,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,UAAU,CAAC,MAAM,EAAE,CACjJ,CAAC;oBACJ,CAAC;oBAED,4BAA4B;oBAC5B,IAAI,YAAY,EAAE,CAAC;wBACjB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;wBAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;wBAClC,MAAM,SAAS,GAAG,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;wBAEvE,MAAM,QAAQ,GAAa;4BACzB,EAAE,EAAE,KAAK;4BACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BACnC,QAAQ,EAAE,OAAO;4BACjB,QAAQ,EAAE,UAAU,CAAC,YAAY;4BACjC,MAAM,EAAE,UAAU,CAAC,YAAY;4BAC/B,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;4BAC3B,YAAY,EAAE,KAAK,EAAE,WAAW,IAAI,IAAI;4BACxC,aAAa,EAAE,KAAK,EAAE,YAAY,IAAI,IAAI;4BAC1C,IAAI,EAAE,UAAU,EAAE,SAAS,IAAI,IAAI;4BACnC,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI,KAAK;4BACnC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;4BACrC,MAAM,EAAE,UAAU;4BAClB,WAAW,EAAE,SAAS,KAAK,IAAI;4BAC/B,UAAU,EAAE,SAAS;4BACrB,cAAc,EAAE,MAAM;4BACtB,eAAe,EAAE,cAAc,IAAI,IAAI;4BACvC,YAAY,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;4BAClC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;gCAC5B,OAAO,EAAE,YAAY,CAAC,OAAO;gCAC7B,eAAe,EAAE,YAAY,CAAC,cAAc;gCAC5C,aAAa,EAAE,YAAY,CAAC,YAAY;gCACxC,kBAAkB,EAAE,YAAY,CAAC,gBAAgB;6BAClD,CAAC,CAAC,CAAC,SAAS;yBACd,CAAC;wBAEF,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;wBAE3B,IAAI,SAAS,EAAE,CAAC;4BACd,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;4BAC3D,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC;4BAChD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;4BAC3E,YAAY,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;wBACpE,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,4DAA4D;gBAC5D,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC1B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACzB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,8DAA8D;gBAC9D,0CAA0C;gBAC1C,IAAI,wBAAwB,EAAE,CAAC;oBAC7B,eAAe,CAAC,wBAAwB,CAAC,GAAG,wBAAwB,CAAC;gBACvE,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;gBAC3C,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAEtB,0EAA0E;gBAC1E,MAAM,kBAAkB,GAAa,EAAE,CAAC;gBACxC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;oBACvC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjC,CAAC,CAAC,CAAC;gBAEH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACzB,iDAAiD;oBACjD,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;oBACvD,MAAM,eAAe,GAAG,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;oBACtD,MAAM,KAAK,GAAG,iBAAiB,CAAC,eAAe,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;oBAC1E,IAAI,UAAqG,CAAC;oBAC1G,IAAI,KAAK,EAAE,CAAC;wBACV,UAAU,GAAG,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;wBAChD,MAAM,UAAU,GAAG;4BACjB,OAAO;4BACP,KAAK,EAAE,KAAK,CAAC,KAAK;4BAClB,QAAQ,EAAE,UAAU,CAAC,YAAY;4BACjC,WAAW,EAAE,KAAK,CAAC,WAAW;4BAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;4BAChC,SAAS,EAAE,UAAU,CAAC,SAAS;4BAC/B,UAAU,EAAE,UAAU,CAAC,UAAU;4BACjC,SAAS,EAAE,UAAU,CAAC,SAAS;4BAC/B,MAAM,EAAE,UAAU,CAAC,MAAM;4BACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;4BACrB,cAAc,EAAE,cAAc,IAAI,SAAS;yBAC5C,CAAC;wBACF,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;wBAElC,oEAAoE;wBACpE,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;wBAEtD,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC;wBAC3D,OAAO,CAAC,GAAG,CACT,uBAAuB,OAAO,UAAU,KAAK,CAAC,KAAK,WAAW,WAAW,UAAU,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,UAAU,CAAC,MAAM,EAAE,CACjJ,CAAC;oBACJ,CAAC;oBAED,gCAAgC;oBAChC,IAAI,YAAY,EAAE,CAAC;wBACjB,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;wBAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;wBAClC,MAAM,SAAS,GAAG,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;wBAEvE,MAAM,QAAQ,GAAa;4BACzB,EAAE,EAAE,KAAK;4BACT,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BACnC,QAAQ,EAAE,OAAO;4BACjB,QAAQ,EAAE,UAAU,CAAC,YAAY;4BACjC,MAAM,EAAE,UAAU,CAAC,YAAY;4BAC/B,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;4BAC3B,YAAY,EAAE,KAAK,EAAE,WAAW,IAAI,IAAI;4BACxC,aAAa,EAAE,KAAK,EAAE,YAAY,IAAI,IAAI;4BAC1C,IAAI,EAAE,UAAU,EAAE,SAAS,IAAI,IAAI;4BACnC,MAAM,EAAE,UAAU,EAAE,MAAM,IAAI,KAAK;4BACnC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;4BACrC,MAAM,EAAE,UAAU;4BAClB,WAAW,EAAE,SAAS,KAAK,IAAI;4BAC/B,UAAU,EAAE,SAAS;4BACrB,cAAc,EAAE,MAAM;4BACtB,eAAe,EAAE,cAAc,IAAI,IAAI;4BACvC,YAAY,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;4BAClC,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;gCAC5B,OAAO,EAAE,YAAY,CAAC,OAAO;gCAC7B,eAAe,EAAE,YAAY,CAAC,cAAc;gCAC5C,aAAa,EAAE,YAAY,CAAC,YAAY;gCACxC,kBAAkB,EAAE,YAAY,CAAC,gBAAgB;6BAClD,CAAC,CAAC,CAAC,SAAS;yBACd,CAAC;wBAEF,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;wBAE3B,IAAI,SAAS,EAAE,CAAC;4BACd,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,WAAW,CAAC;4BAChD,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;4BAC7E,YAAY,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;wBACtE,CAAC;oBACH,CAAC;oBAED,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;gBAEH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;oBAC9B,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC/D,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;wBACvB,GAAG,CAAC,GAAG,EAAE,CAAC;oBACZ,CAAC;oBACD,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9B,OAAO,CAAC,KAAK,CAAC,sCAAsC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAEnE,2BAA2B;YAC3B,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAa;oBACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;oBACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,UAAU,CAAC,YAAY;oBACjC,MAAM,EAAE,UAAU,CAAC,YAAY;oBAC/B,KAAK,EAAE,IAAI;oBACX,YAAY,EAAE,IAAI;oBAClB,aAAa,EAAE,IAAI;oBACnB,IAAI,EAAE,IAAI;oBACV,MAAM,EAAE,KAAK;oBACb,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;oBACrC,MAAM,EAAE,GAAG;oBACX,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,IAAI;oBAChB,cAAc,EAAE,MAAM;iBACvB,CAAC;gBACF,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;YAED,iBAAiB,CACf,GAAG,EACH,GAAG,EACH,4BAA4B,EAC5B,2BAA2B,CAC5B,CAAC;YACF,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QAEH,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;YAC7B,wBAAwB;YACxB,IAAI,YAAY,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAa;oBACzB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;oBACvB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,UAAU,CAAC,YAAY;oBACjC,MAAM,EAAE,UAAU,CAAC,YAAY;oBAC/B,KAAK,EAAE,IAAI;oBACX,YAAY,EAAE,IAAI;oBAClB,aAAa,EAAE,IAAI;oBACnB,IAAI,EAAE,IAAI;oBACV,MAAM,EAAE,KAAK;oBACb,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY;oBACrC,MAAM,EAAE,GAAG;oBACX,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,IAAI;oBAChB,cAAc,EAAE,MAAM;iBACvB,CAAC;gBACF,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;YAED,WAAW,CAAC,OAAO,EAAE,CAAC;YACtB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,4BAA4B,EAAE,kBAAkB,CAAC,CAAC;YAC9E,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QAEH,iCAAiC;QACjC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QACD,WAAW,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * URL routing module for the Govyn proxy server.
3
+ *
4
+ * Parses incoming request URLs and matches them to provider configurations.
5
+ * Supported patterns:
6
+ * /v1/openai/* -> OpenAI provider
7
+ * /v1/anthropic/* -> Anthropic provider
8
+ * /v1/custom/:name/* -> Custom provider by name
9
+ */
10
+ import type { ProviderConfig, RouteMatch } from './types.js';
11
+ /**
12
+ * Parse a request URL and match it against configured providers.
13
+ *
14
+ * @param url - The incoming request URL (path + query string)
15
+ * @param providers - Map of provider name to ProviderConfig
16
+ * @returns RouteMatch if a provider is found, null otherwise
17
+ */
18
+ export declare function matchRoute(url: string, providers: Map<string, ProviderConfig>): RouteMatch | null;
19
+ /**
20
+ * Create a router function bound to a specific providers map.
21
+ * Returns a function that takes a URL and returns a RouteMatch or null.
22
+ */
23
+ export declare function createRouter(providers: Map<string, ProviderConfig>): (url: string) => RouteMatch | null;
package/dist/router.js ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * URL routing module for the Govyn proxy server.
3
+ *
4
+ * Parses incoming request URLs and matches them to provider configurations.
5
+ * Supported patterns:
6
+ * /v1/openai/* -> OpenAI provider
7
+ * /v1/anthropic/* -> Anthropic provider
8
+ * /v1/custom/:name/* -> Custom provider by name
9
+ */
10
+ /**
11
+ * Route prefixes for versioned API routing.
12
+ */
13
+ const ROUTE_OPENAI = '/v1/openai';
14
+ const ROUTE_ANTHROPIC = '/v1/anthropic';
15
+ const ROUTE_CUSTOM_PREFIX = '/v1/custom/';
16
+ /**
17
+ * Parse a request URL and match it against configured providers.
18
+ *
19
+ * @param url - The incoming request URL (path + query string)
20
+ * @param providers - Map of provider name to ProviderConfig
21
+ * @returns RouteMatch if a provider is found, null otherwise
22
+ */
23
+ export function matchRoute(url, providers) {
24
+ // Normalize: strip query string for routing, keep for upstream path
25
+ const questionMarkIdx = url.indexOf('?');
26
+ const path = questionMarkIdx >= 0 ? url.slice(0, questionMarkIdx) : url;
27
+ const queryString = questionMarkIdx >= 0 ? url.slice(questionMarkIdx) : '';
28
+ // Match /v1/openai/*
29
+ if (path.startsWith(ROUTE_OPENAI + '/') || path === ROUTE_OPENAI) {
30
+ const provider = providers.get('openai');
31
+ if (!provider)
32
+ return null;
33
+ // Strip /v1/openai prefix, keep the rest as upstream path
34
+ const upstreamPath = path.slice(ROUTE_OPENAI.length) + queryString;
35
+ return {
36
+ provider,
37
+ upstreamPath: upstreamPath || '/',
38
+ providerType: 'openai',
39
+ };
40
+ }
41
+ // Match /v1/anthropic/*
42
+ if (path.startsWith(ROUTE_ANTHROPIC + '/') || path === ROUTE_ANTHROPIC) {
43
+ const provider = providers.get('anthropic');
44
+ if (!provider)
45
+ return null;
46
+ // Strip /v1/anthropic prefix, keep the rest as upstream path
47
+ const upstreamPath = path.slice(ROUTE_ANTHROPIC.length) + queryString;
48
+ return {
49
+ provider,
50
+ upstreamPath: upstreamPath || '/',
51
+ providerType: 'anthropic',
52
+ };
53
+ }
54
+ // Match /v1/custom/:name/*
55
+ if (path.startsWith(ROUTE_CUSTOM_PREFIX)) {
56
+ const afterPrefix = path.slice(ROUTE_CUSTOM_PREFIX.length);
57
+ const slashIdx = afterPrefix.indexOf('/');
58
+ // Extract provider name and remaining path
59
+ let providerName;
60
+ let remainingPath;
61
+ if (slashIdx >= 0) {
62
+ providerName = afterPrefix.slice(0, slashIdx);
63
+ remainingPath = afterPrefix.slice(slashIdx) + queryString;
64
+ }
65
+ else {
66
+ providerName = afterPrefix;
67
+ remainingPath = '/' + queryString;
68
+ }
69
+ if (!providerName)
70
+ return null;
71
+ const provider = providers.get(providerName);
72
+ if (!provider)
73
+ return null;
74
+ return {
75
+ provider,
76
+ upstreamPath: remainingPath || '/',
77
+ providerType: 'custom',
78
+ };
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Create a router function bound to a specific providers map.
84
+ * Returns a function that takes a URL and returns a RouteMatch or null.
85
+ */
86
+ export function createRouter(providers) {
87
+ return (url) => matchRoute(url, providers);
88
+ }
89
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function startProxyRuntime(configPath?: string): Promise<void>;