ldrouter 1.13.1 → 1.14.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/CHANGELOG.md +13 -0
- package/dist/server/app.js +12 -2
- package/dist/server/auth/api-key.js +4 -4
- package/dist/server/logging/debug.js +18 -0
- package/dist/server/routes/admin/api-keys.js +2 -2
- package/dist/server/routing/capabilities.js +3 -3
- package/dist/server/routing/combo.js +0 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/) and the project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.14.1] - 2026-09-12
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Do not reject any model solely because its stored `reasoning` capability is `false`; reasoning support is advisory for OpenAI-compatible providers and must not block Claude/Hermes requests.
|
|
12
|
+
- Redact API-key digests and metadata from authentication debug logs.
|
|
13
|
+
|
|
14
|
+
## [1.14.0] - 2026-09-11
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Docker request logging**: every request now emits structured `info` logs when received and a completion log classified as `info` (2xx/3xx), `warn` (4xx), or `error` (5xx), with request ID, method, route, status, and duration. Query strings, headers, bodies, and secrets are excluded.
|
|
19
|
+
|
|
7
20
|
## [1.13.1] - 2026-09-11
|
|
8
21
|
|
|
9
22
|
### Fixed
|
package/dist/server/app.js
CHANGED
|
@@ -21,7 +21,7 @@ import { registerGatewayRoutes } from './routes/gateway.js';
|
|
|
21
21
|
import { registerHealthRoutes } from './routes/health.js';
|
|
22
22
|
import { registerAdminIpGate } from './security/admin-ip-gate.js';
|
|
23
23
|
import { metricsRegistry } from './metrics/registry.js';
|
|
24
|
-
import { fatal, lifecycle, formatError, getDebugFlags, errorLine } from './logging/debug.js';
|
|
24
|
+
import { fatal, lifecycle, formatError, getDebugFlags, errorLine, requestLogFields, requestLogLevel, requestLogMessage } from './logging/debug.js';
|
|
25
25
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
26
|
export async function buildApp(opts = {}) {
|
|
27
27
|
const cfg = loadConfig();
|
|
@@ -43,9 +43,19 @@ export async function buildApp(opts = {}) {
|
|
|
43
43
|
crossOriginEmbedderPolicy: false,
|
|
44
44
|
});
|
|
45
45
|
await app.register(cors, { origin: false, credentials: true });
|
|
46
|
-
//
|
|
46
|
+
// Every request is logged as structured JSON for Docker. Bodies and headers
|
|
47
|
+
// are intentionally excluded; sensitive values must never reach logs.
|
|
48
|
+
app.addHook('onRequest', async (req) => {
|
|
49
|
+
req.requestStartedAt = Date.now();
|
|
50
|
+
log.info({ requestId: req.id, method: req.method, url: req.url }, 'request received');
|
|
51
|
+
});
|
|
47
52
|
app.addHook('onResponse', async (req, reply) => {
|
|
48
53
|
reply.header('x-request-id', req.id);
|
|
54
|
+
const startedAt = req.requestStartedAt ?? Date.now();
|
|
55
|
+
const statusCode = reply.statusCode;
|
|
56
|
+
const fields = requestLogFields(String(req.id), req.method, req.url, statusCode, Date.now() - startedAt, req.routeOptions?.url);
|
|
57
|
+
const level = requestLogLevel(statusCode);
|
|
58
|
+
log[level](fields, requestLogMessage(statusCode));
|
|
49
59
|
});
|
|
50
60
|
app.setErrorHandler((err, req, reply) => {
|
|
51
61
|
// Zod validation failures surface as 400 with readable field messages;
|
|
@@ -37,17 +37,17 @@ export function authenticateGatewayKey(req) {
|
|
|
37
37
|
candidate = anthropic;
|
|
38
38
|
if (!candidate)
|
|
39
39
|
return null;
|
|
40
|
-
//
|
|
41
|
-
console.log(
|
|
40
|
+
// Never log API-key material, including partial values.
|
|
41
|
+
console.log('🔑 API KEY AUTH - Candidate received');
|
|
42
42
|
// Custom keys are stored verbatim (no prefix requirement); auto-generated
|
|
43
43
|
// keys start with ld-, but authentication must accept any stored secret.
|
|
44
44
|
const digest = sha256Hex(candidate);
|
|
45
|
-
console.log(
|
|
45
|
+
console.log('🔑 API KEY AUTH - Digest computed');
|
|
46
46
|
const db = getDb();
|
|
47
47
|
// DEBUG: Check total keys in database
|
|
48
48
|
const allKeys = db.select().from(schema.apiKeys).all();
|
|
49
49
|
console.log(`📊 TOTAL KEYS IN DB: ${allKeys.length}`);
|
|
50
|
-
console.log(
|
|
50
|
+
console.log('📋 API key metadata loaded');
|
|
51
51
|
const row = db.select().from(schema.apiKeys).where(eq(schema.apiKeys.keyDigest, digest)).get();
|
|
52
52
|
console.log(`❓ QUERY RESULT: Found match? ${!!row}`);
|
|
53
53
|
if (!row) {
|
|
@@ -14,6 +14,24 @@
|
|
|
14
14
|
// they emit at debug, so set LOG_LEVEL=debug to see them.
|
|
15
15
|
import process from 'node:process';
|
|
16
16
|
import { redactValue } from '../security/redact.js';
|
|
17
|
+
export function requestLogLevel(statusCode) {
|
|
18
|
+
if (statusCode >= 500)
|
|
19
|
+
return 'error';
|
|
20
|
+
if (statusCode >= 400)
|
|
21
|
+
return 'warn';
|
|
22
|
+
return 'info';
|
|
23
|
+
}
|
|
24
|
+
export function requestLogMessage(statusCode) {
|
|
25
|
+
if (statusCode >= 500)
|
|
26
|
+
return 'request completed with server error';
|
|
27
|
+
if (statusCode >= 400)
|
|
28
|
+
return 'request completed with client error';
|
|
29
|
+
return 'request completed';
|
|
30
|
+
}
|
|
31
|
+
export function requestLogFields(requestId, method, url, statusCode, durationMs, route) {
|
|
32
|
+
const safeUrl = url.split('?')[0] ?? url;
|
|
33
|
+
return { requestId, method, url: safeUrl, route: route ?? safeUrl, statusCode, durationMs };
|
|
34
|
+
}
|
|
17
35
|
function envFlag(name) {
|
|
18
36
|
const v = process.env[name];
|
|
19
37
|
return v === '1' || v === 'true' || v === 'yes';
|
|
@@ -114,8 +114,8 @@ export async function registerApiKeyRoutes(app) {
|
|
|
114
114
|
const enc = encryptSecret(secret);
|
|
115
115
|
// DEBUG: Log what we're about to insert
|
|
116
116
|
console.log(`📝 CREATE API KEY - Name: ${body.name}, Prefix: ${keyPrefix}`);
|
|
117
|
-
console.log(
|
|
118
|
-
console.log(
|
|
117
|
+
console.log('📝 CREATE API KEY - Secret: [REDACTED]');
|
|
118
|
+
console.log('📝 CREATE API KEY - Digest: [REDACTED]');
|
|
119
119
|
db.insert(schema.apiKeys).values({
|
|
120
120
|
id,
|
|
121
121
|
name: body.name,
|
|
@@ -38,7 +38,9 @@ export function deriveRequiredCapabilities(req) {
|
|
|
38
38
|
* IMPORTANT: Treat undefined as "unknown" rather than "unsupported".
|
|
39
39
|
* For generic OpenAI-compatible providers where capabilities weren't explicitly imported,
|
|
40
40
|
* undefined means we don't know, so we should assume it's potentially supported.
|
|
41
|
-
* Explicit false means "known unsupported"
|
|
41
|
+
* Explicit false means "known unsupported" for protocol capabilities such as
|
|
42
|
+
* tools, images, and streaming. Reasoning is advisory metadata because the
|
|
43
|
+
* upstream may support it even when discovery cannot identify it.
|
|
42
44
|
*/
|
|
43
45
|
export function modelMeets(caps, req) {
|
|
44
46
|
// Only reject if capability is explicitly false, not if unknown (undefined)
|
|
@@ -52,8 +54,6 @@ export function modelMeets(caps, req) {
|
|
|
52
54
|
return false;
|
|
53
55
|
if (req.audioInput && caps.audio_input === false)
|
|
54
56
|
return false;
|
|
55
|
-
if (req.reasoning && caps.reasoning === false)
|
|
56
|
-
return false;
|
|
57
57
|
if (req.responses && caps.responses === false)
|
|
58
58
|
return false;
|
|
59
59
|
return true;
|
|
@@ -69,8 +69,6 @@ function capabilityRejection(caps, req) {
|
|
|
69
69
|
return 'image_input';
|
|
70
70
|
if (req.audioInput && caps.audio_input === false)
|
|
71
71
|
return 'audio_input';
|
|
72
|
-
if (req.reasoning && caps.reasoning === false)
|
|
73
|
-
return 'reasoning';
|
|
74
72
|
if (req.responses && caps.responses === false)
|
|
75
73
|
return 'responses';
|
|
76
74
|
return 'capability_mismatch';
|