@chenpu17/cc-gw 0.7.17 → 0.7.19
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/package.json +1 -1
- package/src/server/dist/index.js +420 -155
- package/src/web/dist/assets/{About-T8buLoTf.js → About-BA_VxEEw.js} +2 -2
- package/src/web/dist/assets/ApiKeys-9iuHXiuE.js +16 -0
- package/src/web/dist/assets/Dashboard-BV9q_ZEH.js +16 -0
- package/src/web/dist/assets/{Events-BY6QDHf6.js → Events-CVDX7Sdl.js} +2 -2
- package/src/web/dist/assets/{Help-CXlj66Zk.js → Help-Q8aahW1e.js} +1 -1
- package/src/web/dist/assets/{Login-C227e592.js → Login-Cc9PcXNz.js} +1 -1
- package/src/web/dist/assets/Logs-EvCIbsmH.js +6 -0
- package/src/web/dist/assets/ModelManagement-Bb8NWZJD.js +6 -0
- package/src/web/dist/assets/{PageHeader-BatC8G-O.js → PageHeader-DE87PlB_.js} +1 -1
- package/src/web/dist/assets/{PageSection-HTrZH-H0.js → PageSection-BW4UtaTE.js} +1 -1
- package/src/web/dist/assets/Settings-hk35KMN5.js +6 -0
- package/src/web/dist/assets/Skeleton-DKmc2Oau.js +1 -0
- package/src/web/dist/assets/{card-DQb2ediN.js → card-B5QaMdIj.js} +1 -1
- package/src/web/dist/assets/{dialog-B9ZBo_5P.js → dialog-Bl93c96O.js} +3 -3
- package/src/web/dist/assets/{index-CprjNJ8x.js → index-Bkl9YQUf.js} +1 -1
- package/src/web/dist/assets/index-DDEa11GU.css +1 -0
- package/src/web/dist/assets/index-iaYfODK6.js +280 -0
- package/src/web/dist/assets/{info-BSxQbQAp.js → info-Dtepvi_0.js} +1 -1
- package/src/web/dist/assets/{input-x0jTfvyD.js → input-CRfONI49.js} +1 -1
- package/src/web/dist/assets/{label-t5tthgxG.js → label-sf3QNge5.js} +1 -1
- package/src/web/dist/assets/{refresh-cw-DHAoO4El.js → refresh-cw-ZDTq8wum.js} +1 -1
- package/src/web/dist/assets/select-DfAJfUCL.js +11 -0
- package/src/web/dist/assets/shield-wbL9EF4o.js +11 -0
- package/src/web/dist/assets/switch-DTxmu4vA.js +1 -0
- package/src/web/dist/assets/{useApiQuery-CkT-JtpV.js → useApiQuery-LZxVJXA3.js} +1 -1
- package/src/web/dist/index.html +2 -2
- package/src/web/dist/assets/ApiKeys-kkOS5njK.js +0 -16
- package/src/web/dist/assets/Dashboard-BuFP7xgE.js +0 -16
- package/src/web/dist/assets/Logs-A1_44nYh.js +0 -6
- package/src/web/dist/assets/ModelManagement-CZsts9T9.js +0 -1
- package/src/web/dist/assets/Settings-DDaFQ7RV.js +0 -11
- package/src/web/dist/assets/copy-8Dwvprhw.js +0 -6
- package/src/web/dist/assets/index-CsJ3GRy4.css +0 -1
- package/src/web/dist/assets/index-D-F4C6n_.js +0 -280
- package/src/web/dist/assets/select-fri8gRof.js +0 -11
- package/src/web/dist/assets/switch-BrV6LX33.js +0 -1
package/src/server/dist/index.js
CHANGED
|
@@ -10975,7 +10975,7 @@ function buildProviderBody(payload, options = {}) {
|
|
|
10975
10975
|
const body = {
|
|
10976
10976
|
messages: buildMessages(payload)
|
|
10977
10977
|
};
|
|
10978
|
-
if (options.maxTokens) {
|
|
10978
|
+
if (options.maxTokens != null) {
|
|
10979
10979
|
if (payload.thinking) {
|
|
10980
10980
|
body.max_completion_tokens = options.maxTokens;
|
|
10981
10981
|
} else {
|
|
@@ -11094,7 +11094,7 @@ function buildAnthropicBody(payload, options = {}) {
|
|
|
11094
11094
|
messages,
|
|
11095
11095
|
stream: payload.stream
|
|
11096
11096
|
};
|
|
11097
|
-
if (options.maxTokens) {
|
|
11097
|
+
if (options.maxTokens != null) {
|
|
11098
11098
|
body.max_tokens = options.maxTokens;
|
|
11099
11099
|
}
|
|
11100
11100
|
if (typeof options.temperature === "number") {
|
|
@@ -12826,6 +12826,7 @@ async function ensureSchema(db) {
|
|
|
12826
12826
|
await maybeAddColumn(db, "api_keys", "request_count", "INTEGER DEFAULT 0");
|
|
12827
12827
|
await maybeAddColumn(db, "api_keys", "total_input_tokens", "INTEGER DEFAULT 0");
|
|
12828
12828
|
await maybeAddColumn(db, "api_keys", "total_output_tokens", "INTEGER DEFAULT 0");
|
|
12829
|
+
await maybeAddColumn(db, "api_keys", "allowed_endpoints", "TEXT DEFAULT NULL");
|
|
12829
12830
|
await migrateDailyMetricsTable(db);
|
|
12830
12831
|
await maybeAddColumn(db, "daily_metrics", "total_cached_tokens", "INTEGER DEFAULT 0");
|
|
12831
12832
|
await maybeAddColumn(db, "daily_metrics", "total_cache_read_tokens", "INTEGER DEFAULT 0");
|
|
@@ -13345,6 +13346,33 @@ function maskKey(prefix, suffix) {
|
|
|
13345
13346
|
const safeSuffix = suffix ?? "";
|
|
13346
13347
|
return `${safePrefix}****${safeSuffix}`;
|
|
13347
13348
|
}
|
|
13349
|
+
function parseEndpointList(value) {
|
|
13350
|
+
if (!Array.isArray(value)) {
|
|
13351
|
+
return { ok: false };
|
|
13352
|
+
}
|
|
13353
|
+
const normalized = [];
|
|
13354
|
+
for (const raw of value) {
|
|
13355
|
+
if (typeof raw !== "string") {
|
|
13356
|
+
return { ok: false };
|
|
13357
|
+
}
|
|
13358
|
+
const endpointId = raw.trim();
|
|
13359
|
+
if (!endpointId) {
|
|
13360
|
+
return { ok: false };
|
|
13361
|
+
}
|
|
13362
|
+
normalized.push(endpointId);
|
|
13363
|
+
}
|
|
13364
|
+
return { ok: true, value: [...new Set(normalized)] };
|
|
13365
|
+
}
|
|
13366
|
+
function normalizeAllowedEndpointsForStorage(value) {
|
|
13367
|
+
if (value == null) {
|
|
13368
|
+
return null;
|
|
13369
|
+
}
|
|
13370
|
+
const parsed = parseEndpointList(value);
|
|
13371
|
+
if (!parsed.ok) {
|
|
13372
|
+
throw new Error("allowedEndpoints must be an array of strings or null");
|
|
13373
|
+
}
|
|
13374
|
+
return parsed.value.length > 0 ? parsed.value : null;
|
|
13375
|
+
}
|
|
13348
13376
|
async function recordAuditLog(payload) {
|
|
13349
13377
|
const { apiKeyId = null, apiKeyName = null, operation, operator = null, details = null, ipAddress = null } = payload;
|
|
13350
13378
|
const serializedDetails = details ? JSON.stringify(details) : null;
|
|
@@ -13364,22 +13392,36 @@ function generateKey() {
|
|
|
13364
13392
|
};
|
|
13365
13393
|
}
|
|
13366
13394
|
async function listApiKeys() {
|
|
13367
|
-
const rows = await getAll("SELECT id, name, description, key_prefix, key_suffix, is_wildcard, enabled, created_at, last_used_at, request_count, total_input_tokens, total_output_tokens FROM api_keys ORDER BY is_wildcard DESC, created_at DESC");
|
|
13368
|
-
return rows.map((row) =>
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13395
|
+
const rows = await getAll("SELECT id, name, description, key_prefix, key_suffix, is_wildcard, enabled, created_at, last_used_at, request_count, total_input_tokens, total_output_tokens, allowed_endpoints FROM api_keys ORDER BY is_wildcard DESC, created_at DESC");
|
|
13396
|
+
return rows.map((row) => {
|
|
13397
|
+
let allowedEndpoints = null;
|
|
13398
|
+
if (row.allowed_endpoints) {
|
|
13399
|
+
try {
|
|
13400
|
+
const parsed = JSON.parse(row.allowed_endpoints);
|
|
13401
|
+
const endpointList = parseEndpointList(parsed);
|
|
13402
|
+
if (endpointList.ok && endpointList.value.length > 0) {
|
|
13403
|
+
allowedEndpoints = endpointList.value;
|
|
13404
|
+
}
|
|
13405
|
+
} catch {
|
|
13406
|
+
}
|
|
13407
|
+
}
|
|
13408
|
+
return {
|
|
13409
|
+
id: row.id,
|
|
13410
|
+
name: row.name,
|
|
13411
|
+
description: row.description ?? null,
|
|
13412
|
+
maskedKey: row.is_wildcard ? null : maskKey(row.key_prefix, row.key_suffix),
|
|
13413
|
+
isWildcard: Boolean(row.is_wildcard),
|
|
13414
|
+
enabled: Boolean(row.enabled),
|
|
13415
|
+
createdAt: toIsoOrNull(row.created_at),
|
|
13416
|
+
lastUsedAt: toIsoOrNull(row.last_used_at),
|
|
13417
|
+
requestCount: row.request_count ?? 0,
|
|
13418
|
+
totalInputTokens: row.total_input_tokens ?? 0,
|
|
13419
|
+
totalOutputTokens: row.total_output_tokens ?? 0,
|
|
13420
|
+
allowedEndpoints
|
|
13421
|
+
};
|
|
13422
|
+
});
|
|
13381
13423
|
}
|
|
13382
|
-
async function createApiKey(name, description, context) {
|
|
13424
|
+
async function createApiKey(name, description, context, allowedEndpoints) {
|
|
13383
13425
|
const trimmed = name.trim();
|
|
13384
13426
|
if (!trimmed) {
|
|
13385
13427
|
throw new Error("Name is required");
|
|
@@ -13396,9 +13438,11 @@ async function createApiKey(name, description, context) {
|
|
|
13396
13438
|
const hashed = hashKey(key);
|
|
13397
13439
|
const encrypted = encryptSecret(key);
|
|
13398
13440
|
const now = Date.now();
|
|
13399
|
-
const columns = ["name", "description", "key_hash", "key_ciphertext", "key_prefix", "key_suffix", "is_wildcard", "enabled", "created_at"];
|
|
13400
|
-
const placeholders = ["?", "?", "?", "?", "?", "?", "?", "?", "?"];
|
|
13401
|
-
const
|
|
13441
|
+
const columns = ["name", "description", "key_hash", "key_ciphertext", "key_prefix", "key_suffix", "is_wildcard", "enabled", "created_at", "allowed_endpoints"];
|
|
13442
|
+
const placeholders = ["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"];
|
|
13443
|
+
const normalizedEndpoints = normalizeAllowedEndpointsForStorage(allowedEndpoints);
|
|
13444
|
+
const serializedEndpoints = normalizedEndpoints ? JSON.stringify(normalizedEndpoints) : null;
|
|
13445
|
+
const values = [trimmed, trimmedDescription || null, hashed, encrypted, prefix, suffix, 0, 1, now, serializedEndpoints];
|
|
13402
13446
|
if (apiKeysHasUpdatedAt) {
|
|
13403
13447
|
columns.push("updated_at");
|
|
13404
13448
|
placeholders.push("?");
|
|
@@ -13423,24 +13467,55 @@ async function createApiKey(name, description, context) {
|
|
|
13423
13467
|
createdAt: new Date(now).toISOString()
|
|
13424
13468
|
};
|
|
13425
13469
|
}
|
|
13426
|
-
async function
|
|
13470
|
+
async function updateApiKeySettings(id, updates, context) {
|
|
13427
13471
|
await ensureApiKeysMetadataLoaded();
|
|
13428
13472
|
const existing = await getOne("SELECT id, name, is_wildcard, enabled FROM api_keys WHERE id = ?", [id]);
|
|
13429
13473
|
if (!existing) {
|
|
13430
13474
|
throw new Error("API key not found");
|
|
13431
13475
|
}
|
|
13476
|
+
const setClauses = [];
|
|
13477
|
+
const values = [];
|
|
13478
|
+
const logs = [];
|
|
13479
|
+
if (typeof updates.enabled === "boolean") {
|
|
13480
|
+
setClauses.push("enabled = ?");
|
|
13481
|
+
values.push(updates.enabled ? 1 : 0);
|
|
13482
|
+
logs.push({
|
|
13483
|
+
apiKeyId: existing.id,
|
|
13484
|
+
apiKeyName: existing.name,
|
|
13485
|
+
operation: updates.enabled ? "enable" : "disable",
|
|
13486
|
+
operator: context?.operator ?? null,
|
|
13487
|
+
ipAddress: context?.ipAddress ?? null
|
|
13488
|
+
});
|
|
13489
|
+
}
|
|
13490
|
+
if (updates.allowedEndpointsProvided) {
|
|
13491
|
+
if (existing.is_wildcard) {
|
|
13492
|
+
throw new Error("Cannot set endpoint restrictions on wildcard key");
|
|
13493
|
+
}
|
|
13494
|
+
const normalizedEndpoints = normalizeAllowedEndpointsForStorage(updates.allowedEndpoints);
|
|
13495
|
+
const serialized = normalizedEndpoints ? JSON.stringify(normalizedEndpoints) : null;
|
|
13496
|
+
setClauses.push("allowed_endpoints = ?");
|
|
13497
|
+
values.push(serialized);
|
|
13498
|
+
logs.push({
|
|
13499
|
+
apiKeyId: existing.id,
|
|
13500
|
+
apiKeyName: existing.name,
|
|
13501
|
+
operation: "update_endpoints",
|
|
13502
|
+
details: { allowedEndpoints: normalizedEndpoints },
|
|
13503
|
+
operator: context?.operator ?? null,
|
|
13504
|
+
ipAddress: context?.ipAddress ?? null
|
|
13505
|
+
});
|
|
13506
|
+
}
|
|
13507
|
+
if (setClauses.length === 0) {
|
|
13508
|
+
throw new Error("No updates provided");
|
|
13509
|
+
}
|
|
13432
13510
|
if (apiKeysHasUpdatedAt) {
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13511
|
+
setClauses.push("updated_at = ?");
|
|
13512
|
+
values.push(Date.now());
|
|
13513
|
+
}
|
|
13514
|
+
values.push(id);
|
|
13515
|
+
await runQuery(`UPDATE api_keys SET ${setClauses.join(", ")} WHERE id = ?`, values);
|
|
13516
|
+
for (const payload of logs) {
|
|
13517
|
+
await recordAuditLog(payload);
|
|
13436
13518
|
}
|
|
13437
|
-
await recordAuditLog({
|
|
13438
|
-
apiKeyId: existing.id,
|
|
13439
|
-
apiKeyName: existing.name,
|
|
13440
|
-
operation: enabled ? "enable" : "disable",
|
|
13441
|
-
operator: context?.operator ?? null,
|
|
13442
|
-
ipAddress: context?.ipAddress ?? null
|
|
13443
|
-
});
|
|
13444
13519
|
}
|
|
13445
13520
|
async function deleteApiKey(id, context) {
|
|
13446
13521
|
const existing = await getOne("SELECT id, name, is_wildcard FROM api_keys WHERE id = ?", [id]);
|
|
@@ -13483,7 +13558,7 @@ async function resolveApiKey(providedRaw, context) {
|
|
|
13483
13558
|
throw new ApiKeyError("API key is required", "missing");
|
|
13484
13559
|
}
|
|
13485
13560
|
const hashed = hashKey(provided);
|
|
13486
|
-
const existing = await getOne("SELECT id, name, enabled, is_wildcard FROM api_keys WHERE key_hash = ?", [hashed]);
|
|
13561
|
+
const existing = await getOne("SELECT id, name, enabled, is_wildcard, allowed_endpoints FROM api_keys WHERE key_hash = ?", [hashed]);
|
|
13487
13562
|
if (existing) {
|
|
13488
13563
|
if (!existing.enabled) {
|
|
13489
13564
|
await recordAuditLog({
|
|
@@ -13495,6 +13570,53 @@ async function resolveApiKey(providedRaw, context) {
|
|
|
13495
13570
|
});
|
|
13496
13571
|
throw new ApiKeyError("API key is disabled", "disabled");
|
|
13497
13572
|
}
|
|
13573
|
+
if (context?.endpointId && !existing.is_wildcard && existing.allowed_endpoints) {
|
|
13574
|
+
let parsed;
|
|
13575
|
+
try {
|
|
13576
|
+
parsed = JSON.parse(existing.allowed_endpoints);
|
|
13577
|
+
} catch {
|
|
13578
|
+
await recordAuditLog({
|
|
13579
|
+
apiKeyId: existing.id,
|
|
13580
|
+
apiKeyName: existing.name,
|
|
13581
|
+
operation: "auth_failure",
|
|
13582
|
+
details: { reason: "endpoint_policy_invalid", endpoint: context.endpointId },
|
|
13583
|
+
ipAddress: context?.ipAddress ?? null
|
|
13584
|
+
});
|
|
13585
|
+
throw new ApiKeyError("API key endpoint policy is invalid", "forbidden");
|
|
13586
|
+
}
|
|
13587
|
+
if (!Array.isArray(parsed)) {
|
|
13588
|
+
await recordAuditLog({
|
|
13589
|
+
apiKeyId: existing.id,
|
|
13590
|
+
apiKeyName: existing.name,
|
|
13591
|
+
operation: "auth_failure",
|
|
13592
|
+
details: { reason: "endpoint_policy_invalid", endpoint: context.endpointId },
|
|
13593
|
+
ipAddress: context?.ipAddress ?? null
|
|
13594
|
+
});
|
|
13595
|
+
throw new ApiKeyError("API key endpoint policy is invalid", "forbidden");
|
|
13596
|
+
}
|
|
13597
|
+
const endpointList = parseEndpointList(parsed);
|
|
13598
|
+
if (!endpointList.ok) {
|
|
13599
|
+
await recordAuditLog({
|
|
13600
|
+
apiKeyId: existing.id,
|
|
13601
|
+
apiKeyName: existing.name,
|
|
13602
|
+
operation: "auth_failure",
|
|
13603
|
+
details: { reason: "endpoint_policy_invalid", endpoint: context.endpointId },
|
|
13604
|
+
ipAddress: context?.ipAddress ?? null
|
|
13605
|
+
});
|
|
13606
|
+
throw new ApiKeyError("API key endpoint policy is invalid", "forbidden");
|
|
13607
|
+
}
|
|
13608
|
+
const allowed = endpointList.value;
|
|
13609
|
+
if (allowed.length > 0 && !allowed.includes(context.endpointId)) {
|
|
13610
|
+
await recordAuditLog({
|
|
13611
|
+
apiKeyId: existing.id,
|
|
13612
|
+
apiKeyName: existing.name,
|
|
13613
|
+
operation: "auth_failure",
|
|
13614
|
+
details: { reason: "forbidden", endpoint: context.endpointId },
|
|
13615
|
+
ipAddress: context?.ipAddress ?? null
|
|
13616
|
+
});
|
|
13617
|
+
throw new ApiKeyError("API key is not authorized for this endpoint", "forbidden");
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13498
13620
|
return {
|
|
13499
13621
|
id: existing.id,
|
|
13500
13622
|
name: existing.name,
|
|
@@ -14209,21 +14331,190 @@ function resolveCachedTokens(usage) {
|
|
|
14209
14331
|
if (!usage || typeof usage !== "object") {
|
|
14210
14332
|
return result;
|
|
14211
14333
|
}
|
|
14212
|
-
|
|
14334
|
+
const hasAnthropicRead = Number.isFinite(usage.cache_read_input_tokens);
|
|
14335
|
+
const hasAnthropicCreation = Number.isFinite(usage.cache_creation_input_tokens);
|
|
14336
|
+
if (hasAnthropicRead) {
|
|
14213
14337
|
result.read = usage.cache_read_input_tokens;
|
|
14214
14338
|
}
|
|
14215
|
-
if (
|
|
14339
|
+
if (hasAnthropicCreation) {
|
|
14216
14340
|
result.creation = usage.cache_creation_input_tokens;
|
|
14217
14341
|
}
|
|
14218
|
-
if (
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14342
|
+
if (!hasAnthropicRead) {
|
|
14343
|
+
const promptDetails = usage.prompt_tokens_details;
|
|
14344
|
+
const inputDetails = usage.input_tokens_details;
|
|
14345
|
+
if (promptDetails && Number.isFinite(promptDetails.cached_tokens)) {
|
|
14346
|
+
result.read = promptDetails.cached_tokens;
|
|
14347
|
+
} else if (inputDetails && Number.isFinite(inputDetails.cached_tokens)) {
|
|
14348
|
+
result.read = inputDetails.cached_tokens;
|
|
14349
|
+
} else if (typeof usage.cached_tokens === "number") {
|
|
14350
|
+
result.read = usage.cached_tokens;
|
|
14351
|
+
}
|
|
14224
14352
|
}
|
|
14225
14353
|
return result;
|
|
14226
14354
|
}
|
|
14355
|
+
function extractTextForTokenEstimate(value) {
|
|
14356
|
+
if (value == null)
|
|
14357
|
+
return "";
|
|
14358
|
+
if (typeof value === "string")
|
|
14359
|
+
return value;
|
|
14360
|
+
if (Array.isArray(value)) {
|
|
14361
|
+
return value.map((item) => extractTextForTokenEstimate(item)).filter(Boolean).join("\n");
|
|
14362
|
+
}
|
|
14363
|
+
if (typeof value === "object") {
|
|
14364
|
+
const payload = value;
|
|
14365
|
+
if (typeof payload.text === "string")
|
|
14366
|
+
return payload.text;
|
|
14367
|
+
if (typeof payload.content === "string")
|
|
14368
|
+
return payload.content;
|
|
14369
|
+
if (Array.isArray(payload.content))
|
|
14370
|
+
return extractTextForTokenEstimate(payload.content);
|
|
14371
|
+
if (typeof payload.output_text === "string")
|
|
14372
|
+
return payload.output_text;
|
|
14373
|
+
if (typeof payload.value === "string")
|
|
14374
|
+
return payload.value;
|
|
14375
|
+
}
|
|
14376
|
+
return "";
|
|
14377
|
+
}
|
|
14378
|
+
function parseSSEToSummary(chunks, format, modelId) {
|
|
14379
|
+
const allChunks = chunks.join("");
|
|
14380
|
+
const lines = allChunks.split("\n");
|
|
14381
|
+
let accumulatedContent = "";
|
|
14382
|
+
let finishReason = null;
|
|
14383
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
14384
|
+
let usagePrompt = null;
|
|
14385
|
+
let usageCompletion = null;
|
|
14386
|
+
let usageCached = null;
|
|
14387
|
+
for (const line of lines) {
|
|
14388
|
+
const trimmed = line.trim();
|
|
14389
|
+
if (!trimmed.startsWith("data:"))
|
|
14390
|
+
continue;
|
|
14391
|
+
const dataStr = trimmed.slice(5).trim();
|
|
14392
|
+
if (dataStr === "[DONE]")
|
|
14393
|
+
continue;
|
|
14394
|
+
try {
|
|
14395
|
+
const data = JSON.parse(dataStr);
|
|
14396
|
+
if (format === "chat") {
|
|
14397
|
+
const choice = data?.choices?.[0];
|
|
14398
|
+
if (choice) {
|
|
14399
|
+
const delta = choice.delta;
|
|
14400
|
+
if (delta?.content) {
|
|
14401
|
+
accumulatedContent += delta.content;
|
|
14402
|
+
}
|
|
14403
|
+
if (delta?.tool_calls) {
|
|
14404
|
+
for (const tc of delta.tool_calls) {
|
|
14405
|
+
const idx = tc.index ?? 0;
|
|
14406
|
+
const existing = toolCalls.get(idx);
|
|
14407
|
+
if (!existing && tc.id) {
|
|
14408
|
+
toolCalls.set(idx, {
|
|
14409
|
+
id: tc.id,
|
|
14410
|
+
type: tc.type || "function",
|
|
14411
|
+
name: tc.function?.name || "",
|
|
14412
|
+
arguments: tc.function?.arguments || ""
|
|
14413
|
+
});
|
|
14414
|
+
} else if (existing) {
|
|
14415
|
+
if (tc.function?.arguments) {
|
|
14416
|
+
existing.arguments += tc.function.arguments;
|
|
14417
|
+
}
|
|
14418
|
+
}
|
|
14419
|
+
}
|
|
14420
|
+
}
|
|
14421
|
+
if (choice.finish_reason) {
|
|
14422
|
+
finishReason = choice.finish_reason;
|
|
14423
|
+
}
|
|
14424
|
+
}
|
|
14425
|
+
if (data?.usage) {
|
|
14426
|
+
usagePrompt = data.usage.prompt_tokens ?? usagePrompt;
|
|
14427
|
+
usageCompletion = data.usage.completion_tokens ?? usageCompletion;
|
|
14428
|
+
const cached = resolveCachedTokens(data.usage);
|
|
14429
|
+
if (cached.read > 0 || cached.creation > 0) {
|
|
14430
|
+
usageCached = cached.read + cached.creation;
|
|
14431
|
+
}
|
|
14432
|
+
}
|
|
14433
|
+
} else {
|
|
14434
|
+
const eventType = data?.type;
|
|
14435
|
+
if (eventType === "response.output_text.delta") {
|
|
14436
|
+
const delta = data?.delta;
|
|
14437
|
+
if (typeof delta === "string") {
|
|
14438
|
+
accumulatedContent += delta;
|
|
14439
|
+
}
|
|
14440
|
+
} else if (eventType === "response.content_part.delta") {
|
|
14441
|
+
const delta = data?.delta?.text;
|
|
14442
|
+
if (typeof delta === "string") {
|
|
14443
|
+
accumulatedContent += delta;
|
|
14444
|
+
}
|
|
14445
|
+
} else if (eventType === "response.function_call_arguments.delta") {
|
|
14446
|
+
const itemId = data?.item_id;
|
|
14447
|
+
const delta = data?.delta;
|
|
14448
|
+
if (itemId && typeof delta === "string") {
|
|
14449
|
+
let found = false;
|
|
14450
|
+
for (const tc of toolCalls.values()) {
|
|
14451
|
+
if (tc.id === itemId) {
|
|
14452
|
+
tc.arguments += delta;
|
|
14453
|
+
found = true;
|
|
14454
|
+
break;
|
|
14455
|
+
}
|
|
14456
|
+
}
|
|
14457
|
+
if (!found) {
|
|
14458
|
+
toolCalls.set(toolCalls.size, {
|
|
14459
|
+
id: itemId,
|
|
14460
|
+
type: "function",
|
|
14461
|
+
name: "",
|
|
14462
|
+
arguments: delta
|
|
14463
|
+
});
|
|
14464
|
+
}
|
|
14465
|
+
}
|
|
14466
|
+
} else if (eventType === "response.output_item.added") {
|
|
14467
|
+
const item = data?.item;
|
|
14468
|
+
if (item?.type === "function_call" && item?.call_id) {
|
|
14469
|
+
toolCalls.set(toolCalls.size, {
|
|
14470
|
+
id: item.call_id,
|
|
14471
|
+
type: "function",
|
|
14472
|
+
name: item.name || "",
|
|
14473
|
+
arguments: ""
|
|
14474
|
+
});
|
|
14475
|
+
}
|
|
14476
|
+
} else if (eventType === "response.completed" || eventType === "response.done") {
|
|
14477
|
+
const response = data?.response;
|
|
14478
|
+
if (response?.status) {
|
|
14479
|
+
finishReason = response.status;
|
|
14480
|
+
}
|
|
14481
|
+
if (response?.usage) {
|
|
14482
|
+
usagePrompt = response.usage.input_tokens ?? response.usage.prompt_tokens ?? usagePrompt;
|
|
14483
|
+
usageCompletion = response.usage.output_tokens ?? response.usage.completion_tokens ?? usageCompletion;
|
|
14484
|
+
const cached = resolveCachedTokens(response.usage);
|
|
14485
|
+
if (cached.read > 0 || cached.creation > 0) {
|
|
14486
|
+
usageCached = cached.read + cached.creation;
|
|
14487
|
+
}
|
|
14488
|
+
}
|
|
14489
|
+
}
|
|
14490
|
+
}
|
|
14491
|
+
} catch {
|
|
14492
|
+
}
|
|
14493
|
+
}
|
|
14494
|
+
const summary = {
|
|
14495
|
+
content: accumulatedContent,
|
|
14496
|
+
model: modelId,
|
|
14497
|
+
finish_reason: finishReason
|
|
14498
|
+
};
|
|
14499
|
+
if (usagePrompt != null || usageCompletion != null || usageCached != null) {
|
|
14500
|
+
summary.usage = {
|
|
14501
|
+
prompt_tokens: usagePrompt,
|
|
14502
|
+
completion_tokens: usageCompletion,
|
|
14503
|
+
cached_tokens: usageCached
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
if (toolCalls.size > 0) {
|
|
14507
|
+
summary.tool_calls = Array.from(toolCalls.values()).map((tc) => ({
|
|
14508
|
+
id: tc.id,
|
|
14509
|
+
type: tc.type,
|
|
14510
|
+
function: {
|
|
14511
|
+
name: tc.name,
|
|
14512
|
+
arguments: tc.arguments
|
|
14513
|
+
}
|
|
14514
|
+
}));
|
|
14515
|
+
}
|
|
14516
|
+
return summary;
|
|
14517
|
+
}
|
|
14227
14518
|
var roundTwoDecimals = (value) => Math.round(value * 100) / 100;
|
|
14228
14519
|
function cloneOriginalPayload(value) {
|
|
14229
14520
|
const structuredCloneFn = globalThis.structuredClone;
|
|
@@ -14365,13 +14656,13 @@ async function registerModelsHandler(app, path5, endpointId) {
|
|
|
14365
14656
|
}
|
|
14366
14657
|
const providedApiKey = extractApiKeyFromRequest(request);
|
|
14367
14658
|
try {
|
|
14368
|
-
await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
14659
|
+
await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId });
|
|
14369
14660
|
} catch (error) {
|
|
14370
14661
|
if (error instanceof ApiKeyError) {
|
|
14371
|
-
reply.code(401);
|
|
14662
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
14372
14663
|
return {
|
|
14373
14664
|
error: {
|
|
14374
|
-
code: "invalid_api_key",
|
|
14665
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
14375
14666
|
message: error.message
|
|
14376
14667
|
}
|
|
14377
14668
|
};
|
|
@@ -14496,13 +14787,13 @@ async function handleAnthropicProtocol(request, reply, endpoint, endpointId, app
|
|
|
14496
14787
|
const providedApiKey = extractApiKeyFromRequest(request);
|
|
14497
14788
|
let apiKeyContext;
|
|
14498
14789
|
try {
|
|
14499
|
-
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
14790
|
+
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId });
|
|
14500
14791
|
} catch (error) {
|
|
14501
14792
|
if (error instanceof ApiKeyError) {
|
|
14502
|
-
reply.code(401);
|
|
14793
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
14503
14794
|
return {
|
|
14504
14795
|
error: {
|
|
14505
|
-
code: "invalid_api_key",
|
|
14796
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
14506
14797
|
message: error.message
|
|
14507
14798
|
}
|
|
14508
14799
|
};
|
|
@@ -15002,13 +15293,13 @@ async function handleAnthropicCountTokensProtocol(request, reply, endpoint, endp
|
|
|
15002
15293
|
}
|
|
15003
15294
|
const providedApiKey = extractApiKeyFromRequest(request);
|
|
15004
15295
|
try {
|
|
15005
|
-
await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
15296
|
+
await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId });
|
|
15006
15297
|
} catch (error) {
|
|
15007
15298
|
if (error instanceof ApiKeyError) {
|
|
15008
|
-
reply.code(401);
|
|
15299
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
15009
15300
|
return {
|
|
15010
15301
|
error: {
|
|
15011
|
-
code: "invalid_api_key",
|
|
15302
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
15012
15303
|
message: error.message
|
|
15013
15304
|
}
|
|
15014
15305
|
};
|
|
@@ -15078,13 +15369,13 @@ async function handleOpenAIChatProtocol(request, reply, endpoint, endpointId, ap
|
|
|
15078
15369
|
const providedApiKey = extractApiKeyFromRequest(request);
|
|
15079
15370
|
let apiKeyContext;
|
|
15080
15371
|
try {
|
|
15081
|
-
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
15372
|
+
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId });
|
|
15082
15373
|
} catch (error) {
|
|
15083
15374
|
if (error instanceof ApiKeyError) {
|
|
15084
|
-
reply.code(401);
|
|
15375
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
15085
15376
|
return {
|
|
15086
15377
|
error: {
|
|
15087
|
-
code: "invalid_api_key",
|
|
15378
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
15088
15379
|
message: error.message
|
|
15089
15380
|
}
|
|
15090
15381
|
};
|
|
@@ -15204,7 +15495,7 @@ async function handleOpenAIChatProtocol(request, reply, endpoint, endpointId, ap
|
|
|
15204
15495
|
const json = await readProviderBodyJson(upstream.body);
|
|
15205
15496
|
const usagePayload = json?.usage ?? null;
|
|
15206
15497
|
const inputTokens2 = usagePayload?.prompt_tokens ?? usagePayload?.input_tokens ?? target.tokenEstimate ?? estimateTokens(normalized, target.modelId);
|
|
15207
|
-
const outputTokens2 = usagePayload?.completion_tokens ?? usagePayload?.output_tokens ?? estimateTextTokens(json?.choices?.[0]?.message?.content
|
|
15498
|
+
const outputTokens2 = usagePayload?.completion_tokens ?? usagePayload?.output_tokens ?? estimateTextTokens(extractTextForTokenEstimate(json?.choices?.[0]?.message?.content), target.modelId);
|
|
15208
15499
|
const cached = resolveCachedTokens(usagePayload);
|
|
15209
15500
|
const cachedTokens = cached.read + cached.creation;
|
|
15210
15501
|
const latencyMs2 = Date.now() - requestStart;
|
|
@@ -15318,54 +15609,14 @@ async function handleOpenAIChatProtocol(request, reply, endpoint, endpointId, ap
|
|
|
15318
15609
|
requests: 1,
|
|
15319
15610
|
inputTokens,
|
|
15320
15611
|
outputTokens,
|
|
15612
|
+
cachedTokens: usageCached ?? 0,
|
|
15613
|
+
cacheReadTokens: usageCacheRead,
|
|
15614
|
+
cacheCreationTokens: usageCacheCreation,
|
|
15321
15615
|
latencyMs
|
|
15322
15616
|
});
|
|
15323
15617
|
if (storeResponsePayloads && capturedChunks) {
|
|
15324
15618
|
try {
|
|
15325
|
-
const
|
|
15326
|
-
let accumulatedText = "";
|
|
15327
|
-
let finishReason = null;
|
|
15328
|
-
const lines = allChunks.split("\n");
|
|
15329
|
-
for (const line of lines) {
|
|
15330
|
-
const trimmed = line.trim();
|
|
15331
|
-
if (trimmed.startsWith("data:")) {
|
|
15332
|
-
const dataStr = trimmed.slice(5).trim();
|
|
15333
|
-
if (dataStr !== "[DONE]") {
|
|
15334
|
-
try {
|
|
15335
|
-
const parsed = JSON.parse(dataStr);
|
|
15336
|
-
const choice = parsed?.choices?.[0];
|
|
15337
|
-
if (choice) {
|
|
15338
|
-
if (choice.delta?.content) {
|
|
15339
|
-
accumulatedText += choice.delta.content;
|
|
15340
|
-
}
|
|
15341
|
-
if (choice.finish_reason) {
|
|
15342
|
-
finishReason = choice.finish_reason;
|
|
15343
|
-
}
|
|
15344
|
-
}
|
|
15345
|
-
} catch {
|
|
15346
|
-
}
|
|
15347
|
-
}
|
|
15348
|
-
}
|
|
15349
|
-
}
|
|
15350
|
-
const responseSummary = {
|
|
15351
|
-
id: `chatcmpl_${Date.now()}`,
|
|
15352
|
-
object: "chat.completion",
|
|
15353
|
-
model: target.modelId,
|
|
15354
|
-
choices: [{
|
|
15355
|
-
index: 0,
|
|
15356
|
-
message: {
|
|
15357
|
-
role: "assistant",
|
|
15358
|
-
content: accumulatedText
|
|
15359
|
-
},
|
|
15360
|
-
finish_reason: finishReason ?? "stop"
|
|
15361
|
-
}],
|
|
15362
|
-
usage: {
|
|
15363
|
-
prompt_tokens: inputTokens,
|
|
15364
|
-
completion_tokens: outputTokens,
|
|
15365
|
-
total_tokens: inputTokens + outputTokens,
|
|
15366
|
-
cached_tokens: usageCached ?? 0
|
|
15367
|
-
}
|
|
15368
|
-
};
|
|
15619
|
+
const responseSummary = parseSSEToSummary(capturedChunks, "chat", target.modelId);
|
|
15369
15620
|
await upsertLogPayload(logId, { response: JSON.stringify(responseSummary) });
|
|
15370
15621
|
} catch {
|
|
15371
15622
|
}
|
|
@@ -15438,13 +15689,13 @@ async function handleOpenAIResponsesProtocol(request, reply, endpoint, endpointI
|
|
|
15438
15689
|
const providedApiKey = extractApiKeyFromRequest(request);
|
|
15439
15690
|
let apiKeyContext;
|
|
15440
15691
|
try {
|
|
15441
|
-
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
15692
|
+
apiKeyContext = await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId });
|
|
15442
15693
|
} catch (error) {
|
|
15443
15694
|
if (error instanceof ApiKeyError) {
|
|
15444
|
-
reply.code(401);
|
|
15695
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
15445
15696
|
return {
|
|
15446
15697
|
error: {
|
|
15447
|
-
code: "invalid_api_key",
|
|
15698
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
15448
15699
|
message: error.message
|
|
15449
15700
|
}
|
|
15450
15701
|
};
|
|
@@ -15568,8 +15819,10 @@ async function handleOpenAIResponsesProtocol(request, reply, endpoint, endpointI
|
|
|
15568
15819
|
const json = await readProviderBodyJson(upstream.body);
|
|
15569
15820
|
const usagePayload = json?.usage ?? null;
|
|
15570
15821
|
const inputTokens2 = usagePayload?.prompt_tokens ?? usagePayload?.input_tokens ?? target.tokenEstimate ?? estimateTokens(normalized, target.modelId);
|
|
15571
|
-
const
|
|
15572
|
-
|
|
15822
|
+
const outputTokens2 = usagePayload?.completion_tokens ?? usagePayload?.output_tokens ?? estimateTextTokens(
|
|
15823
|
+
extractTextForTokenEstimate(json?.response?.body?.content ?? json?.choices?.[0]?.message?.content),
|
|
15824
|
+
target.modelId
|
|
15825
|
+
);
|
|
15573
15826
|
const cached = resolveCachedTokens(usagePayload);
|
|
15574
15827
|
const cachedTokens = cached.read + cached.creation;
|
|
15575
15828
|
const latencyMs2 = Date.now() - requestStart;
|
|
@@ -15641,7 +15894,7 @@ async function handleOpenAIResponsesProtocol(request, reply, endpoint, endpointI
|
|
|
15641
15894
|
if (dataStr !== "[DONE]") {
|
|
15642
15895
|
try {
|
|
15643
15896
|
const parsed = JSON.parse(dataStr);
|
|
15644
|
-
const usage = parsed?.usage || null;
|
|
15897
|
+
const usage = parsed?.usage || parsed?.response?.usage || null;
|
|
15645
15898
|
if (usage) {
|
|
15646
15899
|
usagePrompt = usage.prompt_tokens ?? usage.input_tokens ?? usagePrompt;
|
|
15647
15900
|
usageCompletion = usage.completion_tokens ?? usage.output_tokens ?? usageCompletion;
|
|
@@ -15683,54 +15936,14 @@ async function handleOpenAIResponsesProtocol(request, reply, endpoint, endpointI
|
|
|
15683
15936
|
requests: 1,
|
|
15684
15937
|
inputTokens,
|
|
15685
15938
|
outputTokens,
|
|
15939
|
+
cachedTokens: usageCached ?? 0,
|
|
15940
|
+
cacheReadTokens: usageCacheRead,
|
|
15941
|
+
cacheCreationTokens: usageCacheCreation,
|
|
15686
15942
|
latencyMs
|
|
15687
15943
|
});
|
|
15688
15944
|
if (storeResponsePayloads && capturedChunks) {
|
|
15689
15945
|
try {
|
|
15690
|
-
const
|
|
15691
|
-
let accumulatedText = "";
|
|
15692
|
-
let finishReason = null;
|
|
15693
|
-
const lines = allChunks.split("\n");
|
|
15694
|
-
for (const line of lines) {
|
|
15695
|
-
const trimmed = line.trim();
|
|
15696
|
-
if (trimmed.startsWith("data:")) {
|
|
15697
|
-
const dataStr = trimmed.slice(5).trim();
|
|
15698
|
-
if (dataStr !== "[DONE]") {
|
|
15699
|
-
try {
|
|
15700
|
-
const parsed = JSON.parse(dataStr);
|
|
15701
|
-
const choice = parsed?.choices?.[0];
|
|
15702
|
-
if (choice) {
|
|
15703
|
-
if (choice.delta?.content) {
|
|
15704
|
-
accumulatedText += choice.delta.content;
|
|
15705
|
-
}
|
|
15706
|
-
if (choice.finish_reason) {
|
|
15707
|
-
finishReason = choice.finish_reason;
|
|
15708
|
-
}
|
|
15709
|
-
}
|
|
15710
|
-
} catch {
|
|
15711
|
-
}
|
|
15712
|
-
}
|
|
15713
|
-
}
|
|
15714
|
-
}
|
|
15715
|
-
const responseSummary = {
|
|
15716
|
-
id: `chatcmpl_${Date.now()}`,
|
|
15717
|
-
object: "chat.completion",
|
|
15718
|
-
model: target.modelId,
|
|
15719
|
-
choices: [{
|
|
15720
|
-
index: 0,
|
|
15721
|
-
message: {
|
|
15722
|
-
role: "assistant",
|
|
15723
|
-
content: accumulatedText
|
|
15724
|
-
},
|
|
15725
|
-
finish_reason: finishReason ?? "stop"
|
|
15726
|
-
}],
|
|
15727
|
-
usage: {
|
|
15728
|
-
prompt_tokens: inputTokens,
|
|
15729
|
-
completion_tokens: outputTokens,
|
|
15730
|
-
total_tokens: inputTokens + outputTokens,
|
|
15731
|
-
cached_tokens: usageCached ?? 0
|
|
15732
|
-
}
|
|
15733
|
-
};
|
|
15946
|
+
const responseSummary = parseSSEToSummary(capturedChunks, "responses", target.modelId);
|
|
15734
15947
|
await upsertLogPayload(logId, { response: JSON.stringify(responseSummary) });
|
|
15735
15948
|
} catch {
|
|
15736
15949
|
}
|
|
@@ -15804,13 +16017,13 @@ async function registerOpenAiRoutes(app) {
|
|
|
15804
16017
|
const handleModels = async (request, reply) => {
|
|
15805
16018
|
const providedApiKey = extractApiKeyFromRequest2(request);
|
|
15806
16019
|
try {
|
|
15807
|
-
await resolveApiKey(providedApiKey, { ipAddress: request.ip });
|
|
16020
|
+
await resolveApiKey(providedApiKey, { ipAddress: request.ip, endpointId: "openai" });
|
|
15808
16021
|
} catch (error) {
|
|
15809
16022
|
if (error instanceof ApiKeyError) {
|
|
15810
|
-
reply.code(401);
|
|
16023
|
+
reply.code(error.code === "forbidden" ? 403 : 401);
|
|
15811
16024
|
return {
|
|
15812
16025
|
error: {
|
|
15813
|
-
code: "invalid_api_key",
|
|
16026
|
+
code: error.code === "forbidden" ? "endpoint_forbidden" : "invalid_api_key",
|
|
15814
16027
|
message: error.message
|
|
15815
16028
|
}
|
|
15816
16029
|
};
|
|
@@ -17137,6 +17350,27 @@ async function registerAdminRoutes(app) {
|
|
|
17137
17350
|
const endpoint = typeof query.endpoint === "string" && query.endpoint.length > 0 ? query.endpoint : void 0;
|
|
17138
17351
|
return getApiKeyUsageMetrics(days, limit, endpoint);
|
|
17139
17352
|
});
|
|
17353
|
+
const normalizeAllowedEndpoints = (value) => {
|
|
17354
|
+
if (value == null) {
|
|
17355
|
+
return { ok: true, value: null };
|
|
17356
|
+
}
|
|
17357
|
+
if (!Array.isArray(value)) {
|
|
17358
|
+
return { ok: false, error: "allowedEndpoints must be an array of strings or null" };
|
|
17359
|
+
}
|
|
17360
|
+
const normalized = [];
|
|
17361
|
+
for (const raw of value) {
|
|
17362
|
+
if (typeof raw !== "string") {
|
|
17363
|
+
return { ok: false, error: "allowedEndpoints must be an array of strings or null" };
|
|
17364
|
+
}
|
|
17365
|
+
const endpointId = raw.trim();
|
|
17366
|
+
if (!endpointId) {
|
|
17367
|
+
return { ok: false, error: "allowedEndpoints must not contain empty endpoint IDs" };
|
|
17368
|
+
}
|
|
17369
|
+
normalized.push(endpointId);
|
|
17370
|
+
}
|
|
17371
|
+
const deduped = [...new Set(normalized)];
|
|
17372
|
+
return { ok: true, value: deduped.length > 0 ? deduped : null };
|
|
17373
|
+
};
|
|
17140
17374
|
app.get("/api/keys", async () => {
|
|
17141
17375
|
return listApiKeys();
|
|
17142
17376
|
});
|
|
@@ -17146,8 +17380,19 @@ async function registerAdminRoutes(app) {
|
|
|
17146
17380
|
reply.code(400);
|
|
17147
17381
|
return { error: "Name is required" };
|
|
17148
17382
|
}
|
|
17383
|
+
const hasAllowedEndpoints = Object.prototype.hasOwnProperty.call(body, "allowedEndpoints");
|
|
17384
|
+
const normalizedAllowedEndpoints = hasAllowedEndpoints ? normalizeAllowedEndpoints(body.allowedEndpoints) : { ok: true, value: null };
|
|
17385
|
+
if (!normalizedAllowedEndpoints.ok) {
|
|
17386
|
+
reply.code(400);
|
|
17387
|
+
return { error: normalizedAllowedEndpoints.error };
|
|
17388
|
+
}
|
|
17149
17389
|
try {
|
|
17150
|
-
return await createApiKey(
|
|
17390
|
+
return await createApiKey(
|
|
17391
|
+
body.name,
|
|
17392
|
+
body.description,
|
|
17393
|
+
{ ipAddress: request.ip },
|
|
17394
|
+
normalizedAllowedEndpoints.value
|
|
17395
|
+
);
|
|
17151
17396
|
} catch (error) {
|
|
17152
17397
|
reply.code(400);
|
|
17153
17398
|
return { error: error instanceof Error ? error.message : "Failed to create API key" };
|
|
@@ -17159,13 +17404,33 @@ async function registerAdminRoutes(app) {
|
|
|
17159
17404
|
reply.code(400);
|
|
17160
17405
|
return { error: "Invalid id" };
|
|
17161
17406
|
}
|
|
17162
|
-
const
|
|
17163
|
-
if (typeof
|
|
17407
|
+
const rawBody = request.body;
|
|
17408
|
+
if (!rawBody || typeof rawBody !== "object" || Array.isArray(rawBody)) {
|
|
17409
|
+
reply.code(400);
|
|
17410
|
+
return { error: "Invalid request body" };
|
|
17411
|
+
}
|
|
17412
|
+
const body = rawBody;
|
|
17413
|
+
const hasEnabled = typeof body?.enabled === "boolean";
|
|
17414
|
+
const hasAllowedEndpoints = Object.prototype.hasOwnProperty.call(body, "allowedEndpoints");
|
|
17415
|
+
if (!hasEnabled && !hasAllowedEndpoints) {
|
|
17164
17416
|
reply.code(400);
|
|
17165
|
-
return { error: "enabled
|
|
17417
|
+
return { error: "At least one of enabled or allowedEndpoints is required" };
|
|
17418
|
+
}
|
|
17419
|
+
const normalizedAllowedEndpoints = hasAllowedEndpoints ? normalizeAllowedEndpoints(body.allowedEndpoints) : { ok: true, value: null };
|
|
17420
|
+
if (!normalizedAllowedEndpoints.ok) {
|
|
17421
|
+
reply.code(400);
|
|
17422
|
+
return { error: normalizedAllowedEndpoints.error };
|
|
17166
17423
|
}
|
|
17167
17424
|
try {
|
|
17168
|
-
await
|
|
17425
|
+
await updateApiKeySettings(
|
|
17426
|
+
id,
|
|
17427
|
+
{
|
|
17428
|
+
enabled: hasEnabled ? body.enabled : void 0,
|
|
17429
|
+
allowedEndpoints: normalizedAllowedEndpoints.value,
|
|
17430
|
+
allowedEndpointsProvided: hasAllowedEndpoints
|
|
17431
|
+
},
|
|
17432
|
+
{ ipAddress: request.ip }
|
|
17433
|
+
);
|
|
17169
17434
|
return { success: true };
|
|
17170
17435
|
} catch (error) {
|
|
17171
17436
|
if (error instanceof Error && error.message === "API key not found") {
|