@chenpu17/cc-gw 0.5.2 → 0.5.3
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 +777 -4
- package/src/web/dist/assets/{About-BkGxQ3Pv.js → About-h86_iIp_.js} +2 -7
- package/src/web/dist/assets/{ApiKeys-GdImp523.js → ApiKeys-BIRTN-iK.js} +2 -2
- package/src/web/dist/assets/{Button-BkhovQFd.js → Button-C6S3zW93.js} +1 -1
- package/src/web/dist/assets/{Dashboard-DxSNZwZc.js → Dashboard-DmLLWaIV.js} +1 -1
- package/src/web/dist/assets/Events-Mwcsf-KX.js +21 -0
- package/src/web/dist/assets/{FormField-C-bAE13W.js → FormField-BvEZfMvp.js} +1 -1
- package/src/web/dist/assets/{Help-Bj3HXV4H.js → Help-CYNek8H2.js} +1 -1
- package/src/web/dist/assets/{Input-B6cOxhbI.js → Input-AJrkG4zi.js} +1 -1
- package/src/web/dist/assets/{Login-Btsbo17f.js → Login-LxTaOi4n.js} +1 -1
- package/src/web/dist/assets/{Logs-72HRZmFi.js → Logs-NqRB7oza.js} +1 -1
- package/src/web/dist/assets/ModelManagement-Dal9wmkK.js +1 -0
- package/src/web/dist/assets/{PageSection-Bq4tdag3.js → PageSection-spGMruiz.js} +1 -1
- package/src/web/dist/assets/{Settings-DPvX1pD_.js → Settings-DQhioOgh.js} +2 -2
- package/src/web/dist/assets/{StatusBadge-P00M_NBZ.js → StatusBadge-DbGmFWnc.js} +1 -1
- package/src/web/dist/assets/{copy-D321KBhI.js → copy-EYijAzxo.js} +1 -1
- package/src/web/dist/assets/{index-BLBh7aj6.js → index-Bxh3MQYN.js} +1 -1
- package/src/web/dist/assets/{index-DP6DzFEd.js → index-ByFicy6e.js} +36 -31
- package/src/web/dist/assets/index-CJMKkw2H.css +1 -0
- package/src/web/dist/assets/{info-CADQNr0Q.js → info-B2EbPInL.js} +1 -1
- package/src/web/dist/assets/refresh-cw-BmhVGS6k.js +6 -0
- package/src/web/dist/assets/useApiQuery-Dyao5WuV.js +1 -0
- package/src/web/dist/index.html +2 -2
- package/src/web/dist/assets/ModelManagement-cuAzyPgP.js +0 -1
- package/src/web/dist/assets/index-DEa23YLm.css +0 -1
- package/src/web/dist/assets/useApiQuery-C5jmZPyb.js +0 -1
package/src/server/dist/index.js
CHANGED
|
@@ -10047,13 +10047,35 @@ function sanitizeWebAuth(input) {
|
|
|
10047
10047
|
}
|
|
10048
10048
|
return config;
|
|
10049
10049
|
}
|
|
10050
|
+
function sanitizeEndpointValidation(value, fallback) {
|
|
10051
|
+
let mode = fallback?.mode ?? "off";
|
|
10052
|
+
let allowExperimental = fallback?.allowExperimentalBlocks;
|
|
10053
|
+
if (!value || typeof value !== "object") {
|
|
10054
|
+
return fallback;
|
|
10055
|
+
}
|
|
10056
|
+
const source = value;
|
|
10057
|
+
const modeRaw = source.mode;
|
|
10058
|
+
if (typeof modeRaw === "string") {
|
|
10059
|
+
const normalized = modeRaw.trim().toLowerCase();
|
|
10060
|
+
if (normalized === "off" || normalized === "claude-code" || normalized === "anthropic-strict") {
|
|
10061
|
+
mode = normalized;
|
|
10062
|
+
}
|
|
10063
|
+
}
|
|
10064
|
+
if ("allowExperimentalBlocks" in source) {
|
|
10065
|
+
allowExperimental = Boolean(source.allowExperimentalBlocks);
|
|
10066
|
+
}
|
|
10067
|
+
return { mode, allowExperimentalBlocks: allowExperimental };
|
|
10068
|
+
}
|
|
10050
10069
|
function resolveEndpointRouting(source, fallback) {
|
|
10051
10070
|
const sourceObject = typeof source === "object" && source !== null ? source : void 0;
|
|
10052
10071
|
const defaultsRaw = sourceObject?.defaults;
|
|
10053
10072
|
const routesRaw = sourceObject?.modelRoutes;
|
|
10073
|
+
const validationRaw = sourceObject?.validation;
|
|
10074
|
+
const validation = sanitizeEndpointValidation(validationRaw, fallback.validation);
|
|
10054
10075
|
return {
|
|
10055
10076
|
defaults: sanitizeDefaults(defaultsRaw ?? fallback.defaults),
|
|
10056
|
-
modelRoutes: sanitizeModelRoutes(routesRaw ?? fallback.modelRoutes)
|
|
10077
|
+
modelRoutes: sanitizeModelRoutes(routesRaw ?? fallback.modelRoutes),
|
|
10078
|
+
...validation !== void 0 ? { validation } : {}
|
|
10057
10079
|
};
|
|
10058
10080
|
}
|
|
10059
10081
|
function parseConfig(raw) {
|
|
@@ -11262,7 +11284,30 @@ async function ensureSchema(db) {
|
|
|
11262
11284
|
ip_address TEXT,
|
|
11263
11285
|
created_at TEXT NOT NULL,
|
|
11264
11286
|
FOREIGN KEY(api_key_id) REFERENCES api_keys(id) ON DELETE SET NULL
|
|
11265
|
-
)
|
|
11287
|
+
);
|
|
11288
|
+
|
|
11289
|
+
CREATE TABLE IF NOT EXISTS gateway_events (
|
|
11290
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
11291
|
+
created_at INTEGER NOT NULL,
|
|
11292
|
+
type TEXT NOT NULL,
|
|
11293
|
+
level TEXT NOT NULL DEFAULT 'info',
|
|
11294
|
+
source TEXT,
|
|
11295
|
+
title TEXT,
|
|
11296
|
+
message TEXT,
|
|
11297
|
+
endpoint TEXT,
|
|
11298
|
+
ip_address TEXT,
|
|
11299
|
+
api_key_id INTEGER,
|
|
11300
|
+
api_key_name TEXT,
|
|
11301
|
+
api_key_value TEXT,
|
|
11302
|
+
user_agent TEXT,
|
|
11303
|
+
mode TEXT,
|
|
11304
|
+
details TEXT
|
|
11305
|
+
);
|
|
11306
|
+
|
|
11307
|
+
CREATE INDEX IF NOT EXISTS idx_gateway_events_created_at ON gateway_events(created_at DESC);
|
|
11308
|
+
CREATE INDEX IF NOT EXISTS idx_gateway_events_type ON gateway_events(type);
|
|
11309
|
+
CREATE INDEX IF NOT EXISTS idx_gateway_events_level ON gateway_events(level);
|
|
11310
|
+
`
|
|
11266
11311
|
);
|
|
11267
11312
|
await maybeAddColumn(db, "request_logs", "client_model", "TEXT");
|
|
11268
11313
|
await maybeAddColumn(db, "request_logs", "cached_tokens", "INTEGER");
|
|
@@ -11338,6 +11383,114 @@ async function getAll(sql, params = []) {
|
|
|
11338
11383
|
const db = await getDb();
|
|
11339
11384
|
return all(db, sql, params);
|
|
11340
11385
|
}
|
|
11386
|
+
async function insertGatewayEvent(input) {
|
|
11387
|
+
const {
|
|
11388
|
+
createdAt,
|
|
11389
|
+
type,
|
|
11390
|
+
level = "info",
|
|
11391
|
+
source = null,
|
|
11392
|
+
title = null,
|
|
11393
|
+
message = null,
|
|
11394
|
+
endpoint = null,
|
|
11395
|
+
ipAddress = null,
|
|
11396
|
+
apiKeyId = null,
|
|
11397
|
+
apiKeyName = null,
|
|
11398
|
+
apiKeyValue = null,
|
|
11399
|
+
userAgent = null,
|
|
11400
|
+
mode = null,
|
|
11401
|
+
details = null
|
|
11402
|
+
} = input;
|
|
11403
|
+
const serializedDetails = details && Object.keys(details).length > 0 ? JSON.stringify(details) : null;
|
|
11404
|
+
const result = await runQuery(
|
|
11405
|
+
`INSERT INTO gateway_events (
|
|
11406
|
+
created_at,
|
|
11407
|
+
type,
|
|
11408
|
+
level,
|
|
11409
|
+
source,
|
|
11410
|
+
title,
|
|
11411
|
+
message,
|
|
11412
|
+
endpoint,
|
|
11413
|
+
ip_address,
|
|
11414
|
+
api_key_id,
|
|
11415
|
+
api_key_name,
|
|
11416
|
+
api_key_value,
|
|
11417
|
+
user_agent,
|
|
11418
|
+
mode,
|
|
11419
|
+
details
|
|
11420
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11421
|
+
[
|
|
11422
|
+
createdAt,
|
|
11423
|
+
type,
|
|
11424
|
+
level,
|
|
11425
|
+
source,
|
|
11426
|
+
title,
|
|
11427
|
+
message,
|
|
11428
|
+
endpoint,
|
|
11429
|
+
ipAddress,
|
|
11430
|
+
apiKeyId,
|
|
11431
|
+
apiKeyName,
|
|
11432
|
+
apiKeyValue,
|
|
11433
|
+
userAgent,
|
|
11434
|
+
mode,
|
|
11435
|
+
serializedDetails
|
|
11436
|
+
]
|
|
11437
|
+
);
|
|
11438
|
+
return result.lastID;
|
|
11439
|
+
}
|
|
11440
|
+
async function queryGatewayEvents(options) {
|
|
11441
|
+
const { limit, beforeId, level, type } = options;
|
|
11442
|
+
const clauses = [];
|
|
11443
|
+
const params = [];
|
|
11444
|
+
if (beforeId && Number.isFinite(beforeId)) {
|
|
11445
|
+
clauses.push("id < ?");
|
|
11446
|
+
params.push(beforeId);
|
|
11447
|
+
}
|
|
11448
|
+
if (level) {
|
|
11449
|
+
clauses.push("level = ?");
|
|
11450
|
+
params.push(level);
|
|
11451
|
+
}
|
|
11452
|
+
if (type) {
|
|
11453
|
+
clauses.push("type = ?");
|
|
11454
|
+
params.push(type);
|
|
11455
|
+
}
|
|
11456
|
+
const whereClause = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
11457
|
+
params.push(limit);
|
|
11458
|
+
const rows = await getAll(
|
|
11459
|
+
`SELECT
|
|
11460
|
+
id,
|
|
11461
|
+
created_at,
|
|
11462
|
+
type,
|
|
11463
|
+
level,
|
|
11464
|
+
source,
|
|
11465
|
+
title,
|
|
11466
|
+
message,
|
|
11467
|
+
endpoint,
|
|
11468
|
+
ip_address,
|
|
11469
|
+
api_key_id,
|
|
11470
|
+
api_key_name,
|
|
11471
|
+
api_key_value,
|
|
11472
|
+
user_agent,
|
|
11473
|
+
mode,
|
|
11474
|
+
details
|
|
11475
|
+
FROM gateway_events
|
|
11476
|
+
${whereClause}
|
|
11477
|
+
ORDER BY id DESC
|
|
11478
|
+
LIMIT ?`,
|
|
11479
|
+
params
|
|
11480
|
+
);
|
|
11481
|
+
return rows.map((row) => ({
|
|
11482
|
+
...row,
|
|
11483
|
+
details: (() => {
|
|
11484
|
+
if (!row.details)
|
|
11485
|
+
return null;
|
|
11486
|
+
try {
|
|
11487
|
+
return JSON.parse(row.details);
|
|
11488
|
+
} catch {
|
|
11489
|
+
return { raw: row.details };
|
|
11490
|
+
}
|
|
11491
|
+
})()
|
|
11492
|
+
}));
|
|
11493
|
+
}
|
|
11341
11494
|
async function getFileSize(filePath) {
|
|
11342
11495
|
try {
|
|
11343
11496
|
const stats = await fs2.promises.stat(filePath);
|
|
@@ -11911,6 +12064,484 @@ async function ensureWildcardMetadata() {
|
|
|
11911
12064
|
);
|
|
11912
12065
|
}
|
|
11913
12066
|
|
|
12067
|
+
// events/service.ts
|
|
12068
|
+
async function recordEvent(input) {
|
|
12069
|
+
const createdAt = input.timestamp ?? Date.now();
|
|
12070
|
+
const payload = {
|
|
12071
|
+
createdAt,
|
|
12072
|
+
type: input.type,
|
|
12073
|
+
level: input.level ?? "info",
|
|
12074
|
+
source: input.source ?? null,
|
|
12075
|
+
title: input.title ?? null,
|
|
12076
|
+
message: input.message ?? null,
|
|
12077
|
+
endpoint: input.endpoint ?? null,
|
|
12078
|
+
ipAddress: input.ipAddress ?? null,
|
|
12079
|
+
apiKeyId: input.apiKeyId ?? null,
|
|
12080
|
+
apiKeyName: input.apiKeyName ?? null,
|
|
12081
|
+
apiKeyValue: input.apiKeyValue ?? null,
|
|
12082
|
+
userAgent: input.userAgent ?? null,
|
|
12083
|
+
mode: input.mode ?? null,
|
|
12084
|
+
details: input.details ?? null
|
|
12085
|
+
};
|
|
12086
|
+
return insertGatewayEvent(payload);
|
|
12087
|
+
}
|
|
12088
|
+
async function listEvents(options = {}) {
|
|
12089
|
+
const limit = Math.min(Math.max(options.limit ?? 50, 1), 200);
|
|
12090
|
+
const queryOptions = {
|
|
12091
|
+
limit: limit + 1,
|
|
12092
|
+
beforeId: options.beforeId,
|
|
12093
|
+
level: options.level,
|
|
12094
|
+
type: options.type
|
|
12095
|
+
};
|
|
12096
|
+
const rows = await queryGatewayEvents(queryOptions);
|
|
12097
|
+
let nextCursor = null;
|
|
12098
|
+
let items = rows;
|
|
12099
|
+
if (rows.length > limit) {
|
|
12100
|
+
const last = rows[limit];
|
|
12101
|
+
nextCursor = last.id;
|
|
12102
|
+
items = rows.slice(0, limit);
|
|
12103
|
+
}
|
|
12104
|
+
return {
|
|
12105
|
+
events: items,
|
|
12106
|
+
nextCursor
|
|
12107
|
+
};
|
|
12108
|
+
}
|
|
12109
|
+
|
|
12110
|
+
// routes/anthropic-validator.ts
|
|
12111
|
+
var ALLOWED_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
12112
|
+
var KNOWN_BLOCK_TYPES = /* @__PURE__ */ new Set(["text", "tool_use", "tool_result", "thinking", "output_text", "input_text", "image"]);
|
|
12113
|
+
var ALLOWED_TYPE_PREFIXES = ["input_", "output_", "data_", "media_"];
|
|
12114
|
+
function isPlainObject(value) {
|
|
12115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12116
|
+
}
|
|
12117
|
+
function appendPath(base, segment) {
|
|
12118
|
+
if (!base)
|
|
12119
|
+
return segment;
|
|
12120
|
+
if (segment.startsWith("["))
|
|
12121
|
+
return `${base}${segment}`;
|
|
12122
|
+
return `${base}.${segment}`;
|
|
12123
|
+
}
|
|
12124
|
+
function fail(message, path5, code = "invalid_payload") {
|
|
12125
|
+
return { ok: false, message, path: path5, code };
|
|
12126
|
+
}
|
|
12127
|
+
function isSupportedBlockType(type, allowExperimental) {
|
|
12128
|
+
if (KNOWN_BLOCK_TYPES.has(type)) {
|
|
12129
|
+
return true;
|
|
12130
|
+
}
|
|
12131
|
+
if (allowExperimental) {
|
|
12132
|
+
return ALLOWED_TYPE_PREFIXES.some((prefix) => type.startsWith(prefix));
|
|
12133
|
+
}
|
|
12134
|
+
return false;
|
|
12135
|
+
}
|
|
12136
|
+
function validateCacheControl(value, path5) {
|
|
12137
|
+
if (value === void 0) {
|
|
12138
|
+
return null;
|
|
12139
|
+
}
|
|
12140
|
+
if (!isPlainObject(value)) {
|
|
12141
|
+
return fail("cache_control \u5FC5\u987B\u662F\u5BF9\u8C61", path5, "invalid_cache_control");
|
|
12142
|
+
}
|
|
12143
|
+
if (value.type !== void 0 && typeof value.type !== "string") {
|
|
12144
|
+
return fail("cache_control.type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, "type"), "invalid_cache_control");
|
|
12145
|
+
}
|
|
12146
|
+
return null;
|
|
12147
|
+
}
|
|
12148
|
+
function validateToolInput(value, path5) {
|
|
12149
|
+
if (value === void 0) {
|
|
12150
|
+
return fail("tool_use.input \u4E3A\u5FC5\u586B\u5B57\u6BB5", path5, "invalid_tool_use");
|
|
12151
|
+
}
|
|
12152
|
+
if (isPlainObject(value)) {
|
|
12153
|
+
return null;
|
|
12154
|
+
}
|
|
12155
|
+
if (typeof value === "string") {
|
|
12156
|
+
return null;
|
|
12157
|
+
}
|
|
12158
|
+
return fail("tool_use.input \u5FC5\u987B\u662F\u5BF9\u8C61\u6216\u5B57\u7B26\u4E32", path5, "invalid_tool_use");
|
|
12159
|
+
}
|
|
12160
|
+
function validateToolResultContent(value, path5) {
|
|
12161
|
+
if (value === void 0) {
|
|
12162
|
+
return fail("tool_result.content \u4E3A\u5FC5\u586B\u5B57\u6BB5", path5, "invalid_tool_result");
|
|
12163
|
+
}
|
|
12164
|
+
if (typeof value === "string") {
|
|
12165
|
+
return null;
|
|
12166
|
+
}
|
|
12167
|
+
if (Array.isArray(value)) {
|
|
12168
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
12169
|
+
const entry = value[i];
|
|
12170
|
+
if (typeof entry === "string") {
|
|
12171
|
+
continue;
|
|
12172
|
+
}
|
|
12173
|
+
if (!isPlainObject(entry)) {
|
|
12174
|
+
return fail("tool_result.content[i] \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5BF9\u8C61", appendPath(path5, `[${i}]`), "invalid_tool_result");
|
|
12175
|
+
}
|
|
12176
|
+
if (entry.type !== void 0 && typeof entry.type !== "string") {
|
|
12177
|
+
return fail("tool_result.content[i].type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, `[${i}].type`), "invalid_tool_result");
|
|
12178
|
+
}
|
|
12179
|
+
if (entry.text !== void 0 && typeof entry.text !== "string") {
|
|
12180
|
+
return fail("tool_result.content[i].text \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, `[${i}].text`), "invalid_tool_result");
|
|
12181
|
+
}
|
|
12182
|
+
}
|
|
12183
|
+
return null;
|
|
12184
|
+
}
|
|
12185
|
+
if (isPlainObject(value)) {
|
|
12186
|
+
if (typeof value.text !== "string") {
|
|
12187
|
+
return fail("tool_result.content.text \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, "text"), "invalid_tool_result");
|
|
12188
|
+
}
|
|
12189
|
+
return null;
|
|
12190
|
+
}
|
|
12191
|
+
return fail("tool_result.content \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3001\u6570\u7EC4\u6216\u5BF9\u8C61", path5, "invalid_tool_result");
|
|
12192
|
+
}
|
|
12193
|
+
function validateContentBlock(block, opts) {
|
|
12194
|
+
if (!isPlainObject(block)) {
|
|
12195
|
+
return fail("content \u5757\u5FC5\u987B\u662F\u5BF9\u8C61", opts.path, "invalid_content_block");
|
|
12196
|
+
}
|
|
12197
|
+
if (typeof block.type !== "string" || block.type.trim().length === 0) {
|
|
12198
|
+
return fail("content \u5757\u7F3A\u5C11\u6709\u6548\u7684 type \u5B57\u6BB5", appendPath(opts.path, "type"), "invalid_content_block");
|
|
12199
|
+
}
|
|
12200
|
+
const type = block.type;
|
|
12201
|
+
if (!isSupportedBlockType(type, opts.allowExperimental)) {
|
|
12202
|
+
return fail(`\u4E0D\u652F\u6301\u7684\u5185\u5BB9\u5757\u7C7B\u578B ${type}`, appendPath(opts.path, "type"), "unsupported_content_block");
|
|
12203
|
+
}
|
|
12204
|
+
const cacheValidation = validateCacheControl(block.cache_control, appendPath(opts.path, "cache_control"));
|
|
12205
|
+
if (cacheValidation) {
|
|
12206
|
+
return cacheValidation;
|
|
12207
|
+
}
|
|
12208
|
+
switch (type) {
|
|
12209
|
+
case "text":
|
|
12210
|
+
case "thinking":
|
|
12211
|
+
case "output_text":
|
|
12212
|
+
case "input_text": {
|
|
12213
|
+
if (block.text !== void 0 && typeof block.text !== "string") {
|
|
12214
|
+
return fail(`${type} \u5757\u7684 text \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`, appendPath(opts.path, "text"), "invalid_content_block");
|
|
12215
|
+
}
|
|
12216
|
+
if (block.id !== void 0 && typeof block.id !== "string") {
|
|
12217
|
+
return fail(`${type} \u5757\u7684 id \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`, appendPath(opts.path, "id"), "invalid_content_block");
|
|
12218
|
+
}
|
|
12219
|
+
break;
|
|
12220
|
+
}
|
|
12221
|
+
case "image": {
|
|
12222
|
+
if (!block.source || !isPlainObject(block.source)) {
|
|
12223
|
+
return fail("image \u5757\u5FC5\u987B\u5305\u542B source \u5BF9\u8C61", appendPath(opts.path, "source"), "invalid_content_block");
|
|
12224
|
+
}
|
|
12225
|
+
if (typeof block.source.type !== "string") {
|
|
12226
|
+
return fail("image \u5757 source.type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(opts.path, "source.type"), "invalid_content_block");
|
|
12227
|
+
}
|
|
12228
|
+
if (block.source.media_type !== void 0 && typeof block.source.media_type !== "string") {
|
|
12229
|
+
return fail("image \u5757 source.media_type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(opts.path, "source.media_type"), "invalid_content_block");
|
|
12230
|
+
}
|
|
12231
|
+
break;
|
|
12232
|
+
}
|
|
12233
|
+
case "tool_use": {
|
|
12234
|
+
if (typeof block.id !== "string" || block.id.trim().length === 0) {
|
|
12235
|
+
return fail("tool_use \u5757\u9700\u8981\u5B57\u7B26\u4E32\u7C7B\u578B\u7684 id", appendPath(opts.path, "id"), "invalid_tool_use");
|
|
12236
|
+
}
|
|
12237
|
+
if (typeof block.name !== "string" || block.name.trim().length === 0) {
|
|
12238
|
+
return fail("tool_use \u5757\u9700\u8981\u5B57\u7B26\u4E32\u7C7B\u578B\u7684 name", appendPath(opts.path, "name"), "invalid_tool_use");
|
|
12239
|
+
}
|
|
12240
|
+
const inputValidation = validateToolInput(block.input, appendPath(opts.path, "input"));
|
|
12241
|
+
if (inputValidation) {
|
|
12242
|
+
return inputValidation;
|
|
12243
|
+
}
|
|
12244
|
+
break;
|
|
12245
|
+
}
|
|
12246
|
+
case "tool_result": {
|
|
12247
|
+
if (typeof block.tool_use_id !== "string" || block.tool_use_id.trim().length === 0) {
|
|
12248
|
+
return fail("tool_result \u5757\u9700\u8981\u5B57\u7B26\u4E32\u7C7B\u578B\u7684 tool_use_id", appendPath(opts.path, "tool_use_id"));
|
|
12249
|
+
}
|
|
12250
|
+
const contentValidation = validateToolResultContent(block.content, appendPath(opts.path, "content"));
|
|
12251
|
+
if (contentValidation) {
|
|
12252
|
+
return contentValidation;
|
|
12253
|
+
}
|
|
12254
|
+
break;
|
|
12255
|
+
}
|
|
12256
|
+
default:
|
|
12257
|
+
break;
|
|
12258
|
+
}
|
|
12259
|
+
if (block.type === "tool_use" && opts.role === "user") {
|
|
12260
|
+
return fail("user \u6D88\u606F\u4E0D\u80FD\u5305\u542B tool_use \u5757", opts.path, "invalid_user_message");
|
|
12261
|
+
}
|
|
12262
|
+
if (block.type === "tool_result" && opts.role === "assistant") {
|
|
12263
|
+
return fail("assistant \u6D88\u606F\u4E0D\u80FD\u5305\u542B tool_result \u5757", opts.path, "invalid_assistant_message");
|
|
12264
|
+
}
|
|
12265
|
+
return null;
|
|
12266
|
+
}
|
|
12267
|
+
function validateMessageContent(message, role, path5, allowExperimental) {
|
|
12268
|
+
if (!("content" in message)) {
|
|
12269
|
+
return fail("messages.content \u4E3A\u5FC5\u586B\u5B57\u6BB5", path5, "invalid_message");
|
|
12270
|
+
}
|
|
12271
|
+
const content = message.content;
|
|
12272
|
+
if (typeof content === "string") {
|
|
12273
|
+
return null;
|
|
12274
|
+
}
|
|
12275
|
+
if (Array.isArray(content)) {
|
|
12276
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
12277
|
+
const block = content[i];
|
|
12278
|
+
const blockValidation = validateContentBlock(block, {
|
|
12279
|
+
path: appendPath(path5, `[${i}]`),
|
|
12280
|
+
allowExperimental,
|
|
12281
|
+
role
|
|
12282
|
+
});
|
|
12283
|
+
if (blockValidation) {
|
|
12284
|
+
return blockValidation;
|
|
12285
|
+
}
|
|
12286
|
+
}
|
|
12287
|
+
return null;
|
|
12288
|
+
}
|
|
12289
|
+
if (content === null) {
|
|
12290
|
+
return null;
|
|
12291
|
+
}
|
|
12292
|
+
return fail("messages.content \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5185\u5BB9\u5757\u6570\u7EC4", path5, "invalid_message");
|
|
12293
|
+
}
|
|
12294
|
+
function validateMessages(messages, opts) {
|
|
12295
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
12296
|
+
return fail("messages \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4", "messages", "invalid_message");
|
|
12297
|
+
}
|
|
12298
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
12299
|
+
const entry = messages[i];
|
|
12300
|
+
const path5 = `messages[${i}]`;
|
|
12301
|
+
if (!isPlainObject(entry)) {
|
|
12302
|
+
return fail("messages[i] \u5FC5\u987B\u662F\u5BF9\u8C61", path5, "invalid_message");
|
|
12303
|
+
}
|
|
12304
|
+
if (typeof entry.role !== "string" || !ALLOWED_ROLES.has(entry.role)) {
|
|
12305
|
+
return fail('messages[i].role \u5FC5\u987B\u662F "user" \u6216 "assistant"', appendPath(path5, "role"), "unsupported_role");
|
|
12306
|
+
}
|
|
12307
|
+
const role = entry.role;
|
|
12308
|
+
const contentValidation = validateMessageContent(entry, role, appendPath(path5, "content"), opts.allowExperimental);
|
|
12309
|
+
if (contentValidation) {
|
|
12310
|
+
return contentValidation;
|
|
12311
|
+
}
|
|
12312
|
+
if (entry.stop_reason !== void 0 && typeof entry.stop_reason !== "string" && entry.stop_reason !== null) {
|
|
12313
|
+
return fail("messages[i].stop_reason \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216 null", appendPath(path5, "stop_reason"), "invalid_stop_reason");
|
|
12314
|
+
}
|
|
12315
|
+
}
|
|
12316
|
+
return null;
|
|
12317
|
+
}
|
|
12318
|
+
function validateSystemField(system) {
|
|
12319
|
+
if (system === void 0 || system === null) {
|
|
12320
|
+
return null;
|
|
12321
|
+
}
|
|
12322
|
+
if (typeof system === "string") {
|
|
12323
|
+
return null;
|
|
12324
|
+
}
|
|
12325
|
+
if (Array.isArray(system)) {
|
|
12326
|
+
for (let i = 0; i < system.length; i += 1) {
|
|
12327
|
+
const block = system[i];
|
|
12328
|
+
const path5 = `system[${i}]`;
|
|
12329
|
+
if (!isPlainObject(block)) {
|
|
12330
|
+
return fail("system[i] \u5FC5\u987B\u662F\u5BF9\u8C61", path5, "invalid_system");
|
|
12331
|
+
}
|
|
12332
|
+
if (block.type !== void 0 && typeof block.type !== "string") {
|
|
12333
|
+
return fail("system[i].type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, "type"), "invalid_system");
|
|
12334
|
+
}
|
|
12335
|
+
if (block.type === "text" && block.text !== void 0 && typeof block.text !== "string") {
|
|
12336
|
+
return fail("system[i].text \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", appendPath(path5, "text"), "invalid_system");
|
|
12337
|
+
}
|
|
12338
|
+
}
|
|
12339
|
+
return null;
|
|
12340
|
+
}
|
|
12341
|
+
if (isPlainObject(system)) {
|
|
12342
|
+
if (typeof system.text === "string" || Array.isArray(system.text)) {
|
|
12343
|
+
return null;
|
|
12344
|
+
}
|
|
12345
|
+
if (system.type === "text" && typeof system.text === "string") {
|
|
12346
|
+
return null;
|
|
12347
|
+
}
|
|
12348
|
+
}
|
|
12349
|
+
return fail("system \u5B57\u6BB5\u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u3001\u5BF9\u8C61\u6216\u5185\u5BB9\u5757\u6570\u7EC4", "system", "invalid_system");
|
|
12350
|
+
}
|
|
12351
|
+
function validateTools(tools) {
|
|
12352
|
+
if (tools === void 0) {
|
|
12353
|
+
return null;
|
|
12354
|
+
}
|
|
12355
|
+
if (!Array.isArray(tools)) {
|
|
12356
|
+
return fail("tools \u5FC5\u987B\u662F\u6570\u7EC4", "tools");
|
|
12357
|
+
}
|
|
12358
|
+
for (let i = 0; i < tools.length; i += 1) {
|
|
12359
|
+
const tool = tools[i];
|
|
12360
|
+
const path5 = `tools[${i}]`;
|
|
12361
|
+
if (!isPlainObject(tool)) {
|
|
12362
|
+
return fail("tools[i] \u5FC5\u987B\u662F\u5BF9\u8C61", path5, "invalid_tool");
|
|
12363
|
+
}
|
|
12364
|
+
if (typeof tool.name !== "string" || tool.name.trim().length === 0) {
|
|
12365
|
+
return fail("tools[i].name \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", appendPath(path5, "name"), "invalid_tool");
|
|
12366
|
+
}
|
|
12367
|
+
if (tool.input_schema !== void 0 && !isPlainObject(tool.input_schema)) {
|
|
12368
|
+
return fail("tools[i].input_schema \u5FC5\u987B\u662F\u5BF9\u8C61", appendPath(path5, "input_schema"), "invalid_tool");
|
|
12369
|
+
}
|
|
12370
|
+
if (tool.parameters !== void 0 && !isPlainObject(tool.parameters)) {
|
|
12371
|
+
return fail("tools[i].parameters \u5FC5\u987B\u662F\u5BF9\u8C61", appendPath(path5, "parameters"), "invalid_tool");
|
|
12372
|
+
}
|
|
12373
|
+
}
|
|
12374
|
+
return null;
|
|
12375
|
+
}
|
|
12376
|
+
function validateMetadata(metadata) {
|
|
12377
|
+
if (metadata === void 0) {
|
|
12378
|
+
return null;
|
|
12379
|
+
}
|
|
12380
|
+
if (!isPlainObject(metadata)) {
|
|
12381
|
+
return fail("metadata \u5FC5\u987B\u662F\u5BF9\u8C61", "metadata", "invalid_metadata");
|
|
12382
|
+
}
|
|
12383
|
+
return null;
|
|
12384
|
+
}
|
|
12385
|
+
function validateToolChoice(toolChoice) {
|
|
12386
|
+
if (toolChoice === void 0) {
|
|
12387
|
+
return null;
|
|
12388
|
+
}
|
|
12389
|
+
if (typeof toolChoice === "string") {
|
|
12390
|
+
return null;
|
|
12391
|
+
}
|
|
12392
|
+
if (isPlainObject(toolChoice)) {
|
|
12393
|
+
if (toolChoice.type !== void 0 && typeof toolChoice.type !== "string") {
|
|
12394
|
+
return fail("tool_choice.type \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", "tool_choice.type");
|
|
12395
|
+
}
|
|
12396
|
+
if (toolChoice.name !== void 0 && typeof toolChoice.name !== "string") {
|
|
12397
|
+
return fail("tool_choice.name \u5FC5\u987B\u662F\u5B57\u7B26\u4E32", "tool_choice.name");
|
|
12398
|
+
}
|
|
12399
|
+
return null;
|
|
12400
|
+
}
|
|
12401
|
+
return fail("tool_choice \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216\u5BF9\u8C61", "tool_choice", "invalid_tool_choice");
|
|
12402
|
+
}
|
|
12403
|
+
function validateMaxTokens(maxTokens) {
|
|
12404
|
+
if (maxTokens === void 0) {
|
|
12405
|
+
return null;
|
|
12406
|
+
}
|
|
12407
|
+
if (typeof maxTokens !== "number" || !Number.isFinite(maxTokens) || maxTokens <= 0) {
|
|
12408
|
+
return fail("max_tokens \u5FC5\u987B\u662F\u6B63\u6570", "max_tokens", "invalid_max_tokens");
|
|
12409
|
+
}
|
|
12410
|
+
return null;
|
|
12411
|
+
}
|
|
12412
|
+
function validateTemperature(value) {
|
|
12413
|
+
if (value === void 0) {
|
|
12414
|
+
return null;
|
|
12415
|
+
}
|
|
12416
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
12417
|
+
return fail("temperature \u5FC5\u987B\u662F\u6570\u5B57", "temperature", "invalid_temperature");
|
|
12418
|
+
}
|
|
12419
|
+
return null;
|
|
12420
|
+
}
|
|
12421
|
+
function validateStream(value) {
|
|
12422
|
+
if (value === void 0) {
|
|
12423
|
+
return null;
|
|
12424
|
+
}
|
|
12425
|
+
if (typeof value !== "boolean") {
|
|
12426
|
+
return fail("stream \u5FC5\u987B\u662F\u5E03\u5C14\u503C", "stream", "invalid_stream_flag");
|
|
12427
|
+
}
|
|
12428
|
+
return null;
|
|
12429
|
+
}
|
|
12430
|
+
function normalizeHeaderValue(value) {
|
|
12431
|
+
if (!value)
|
|
12432
|
+
return void 0;
|
|
12433
|
+
if (typeof value === "string")
|
|
12434
|
+
return value;
|
|
12435
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
12436
|
+
return value.find((item) => typeof item === "string" && item.trim().length > 0);
|
|
12437
|
+
}
|
|
12438
|
+
return void 0;
|
|
12439
|
+
}
|
|
12440
|
+
function validateHttpRequest(ctx, mode) {
|
|
12441
|
+
if (ctx.method && ctx.method.toUpperCase() !== "POST") {
|
|
12442
|
+
return fail(`HTTP \u65B9\u6CD5\u5FC5\u987B\u662F POST,\u5F53\u524D\u4E3A ${ctx.method}`, void 0, "invalid_method");
|
|
12443
|
+
}
|
|
12444
|
+
if (ctx.query && ctx.query.trim().length > 0) {
|
|
12445
|
+
const raw = ctx.query.startsWith("?") ? ctx.query.slice(1) : ctx.query;
|
|
12446
|
+
const params = new URLSearchParams(raw);
|
|
12447
|
+
const allowedKeys = /* @__PURE__ */ new Set(["beta"]);
|
|
12448
|
+
for (const key of params.keys()) {
|
|
12449
|
+
if (!allowedKeys.has(key)) {
|
|
12450
|
+
return fail(`\u5B58\u5728\u4E0D\u5141\u8BB8\u7684 query \u53C2\u6570: ${key}`, void 0, "invalid_query");
|
|
12451
|
+
}
|
|
12452
|
+
}
|
|
12453
|
+
if (params.has("beta")) {
|
|
12454
|
+
const beta = params.getAll("beta");
|
|
12455
|
+
const invalid = beta.some((value) => value && value.toLowerCase() !== "true");
|
|
12456
|
+
if (invalid && mode === "anthropic-strict") {
|
|
12457
|
+
return fail("beta \u53C2\u6570\u4EC5\u652F\u6301 true \u6216\u7559\u7A7A", void 0, "invalid_query");
|
|
12458
|
+
}
|
|
12459
|
+
}
|
|
12460
|
+
}
|
|
12461
|
+
if (!ctx.headers) {
|
|
12462
|
+
return null;
|
|
12463
|
+
}
|
|
12464
|
+
const contentType = normalizeHeaderValue(ctx.headers["content-type"]);
|
|
12465
|
+
if (!contentType || !contentType.includes("application/json")) {
|
|
12466
|
+
return fail("Content-Type \u5FC5\u987B\u662F application/json", void 0, "invalid_headers");
|
|
12467
|
+
}
|
|
12468
|
+
const anthropicVersion = normalizeHeaderValue(ctx.headers["anthropic-version"]);
|
|
12469
|
+
if (!anthropicVersion || anthropicVersion.trim().length === 0) {
|
|
12470
|
+
return fail("\u7F3A\u5C11\u5FC5\u586B header: anthropic-version", void 0, "invalid_headers");
|
|
12471
|
+
}
|
|
12472
|
+
if (mode === "claude-code") {
|
|
12473
|
+
const userAgent = normalizeHeaderValue(ctx.headers["user-agent"]);
|
|
12474
|
+
if (!userAgent) {
|
|
12475
|
+
return fail("Claude Code \u8BF7\u6C42\u5FC5\u987B\u5305\u542B User-Agent header", void 0, "invalid_headers");
|
|
12476
|
+
}
|
|
12477
|
+
const isClaudeCode = userAgent.includes("claude-cli/") || userAgent.includes("Claude Code/");
|
|
12478
|
+
if (!isClaudeCode) {
|
|
12479
|
+
return fail(`User-Agent \u4E0D\u7B26\u5408 Claude Code \u89C4\u8303: ${userAgent}`, void 0, "invalid_headers");
|
|
12480
|
+
}
|
|
12481
|
+
}
|
|
12482
|
+
return null;
|
|
12483
|
+
}
|
|
12484
|
+
function validateAnthropicRequest(payload, ctx = {}) {
|
|
12485
|
+
if (!isPlainObject(payload)) {
|
|
12486
|
+
return fail("\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
|
|
12487
|
+
}
|
|
12488
|
+
if (typeof payload.model !== "string" || payload.model.trim().length === 0) {
|
|
12489
|
+
return fail("model \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32", "model");
|
|
12490
|
+
}
|
|
12491
|
+
const mode = ctx.mode ?? "claude-code";
|
|
12492
|
+
const allowExperimentalBlocks = ctx.allowExperimentalBlocks ?? mode === "claude-code";
|
|
12493
|
+
if (ctx.request) {
|
|
12494
|
+
const httpValidation = validateHttpRequest(ctx.request, mode);
|
|
12495
|
+
if (httpValidation) {
|
|
12496
|
+
return httpValidation;
|
|
12497
|
+
}
|
|
12498
|
+
}
|
|
12499
|
+
const messagesValidation = validateMessages(payload.messages, {
|
|
12500
|
+
allowExperimental: allowExperimentalBlocks
|
|
12501
|
+
});
|
|
12502
|
+
if (messagesValidation) {
|
|
12503
|
+
return messagesValidation;
|
|
12504
|
+
}
|
|
12505
|
+
const systemValidation = validateSystemField(payload.system);
|
|
12506
|
+
if (systemValidation) {
|
|
12507
|
+
return systemValidation;
|
|
12508
|
+
}
|
|
12509
|
+
const toolsValidation = validateTools(payload.tools);
|
|
12510
|
+
if (toolsValidation) {
|
|
12511
|
+
return toolsValidation;
|
|
12512
|
+
}
|
|
12513
|
+
const metadataValidation = validateMetadata(payload.metadata);
|
|
12514
|
+
if (metadataValidation) {
|
|
12515
|
+
return metadataValidation;
|
|
12516
|
+
}
|
|
12517
|
+
if (mode === "claude-code") {
|
|
12518
|
+
if (!payload.metadata || !isPlainObject(payload.metadata)) {
|
|
12519
|
+
return fail("Claude Code \u8BF7\u6C42\u5FC5\u987B\u5305\u542B metadata \u5BF9\u8C61", "metadata");
|
|
12520
|
+
}
|
|
12521
|
+
const userId = payload.metadata.user_id;
|
|
12522
|
+
if (typeof userId !== "string" || userId.trim().length === 0) {
|
|
12523
|
+
return fail("Claude Code \u8BF7\u6C42\u5FC5\u987B\u5305\u542B metadata.user_id \u5B57\u7B26\u4E32", "metadata.user_id");
|
|
12524
|
+
}
|
|
12525
|
+
}
|
|
12526
|
+
const toolChoiceValidation = validateToolChoice(payload.tool_choice);
|
|
12527
|
+
if (toolChoiceValidation) {
|
|
12528
|
+
return toolChoiceValidation;
|
|
12529
|
+
}
|
|
12530
|
+
const temperatureValidation = validateTemperature(payload.temperature);
|
|
12531
|
+
if (temperatureValidation) {
|
|
12532
|
+
return temperatureValidation;
|
|
12533
|
+
}
|
|
12534
|
+
const maxTokensValidation = validateMaxTokens(payload.max_tokens);
|
|
12535
|
+
if (maxTokensValidation) {
|
|
12536
|
+
return maxTokensValidation;
|
|
12537
|
+
}
|
|
12538
|
+
const streamValidation = validateStream(payload.stream);
|
|
12539
|
+
if (streamValidation) {
|
|
12540
|
+
return streamValidation;
|
|
12541
|
+
}
|
|
12542
|
+
return { ok: true };
|
|
12543
|
+
}
|
|
12544
|
+
|
|
11914
12545
|
// routes/messages.ts
|
|
11915
12546
|
function mapStopReason(reason) {
|
|
11916
12547
|
switch (reason) {
|
|
@@ -12150,6 +12781,66 @@ async function registerMessagesRoute(app) {
|
|
|
12150
12781
|
},
|
|
12151
12782
|
"received anthropic message request"
|
|
12152
12783
|
);
|
|
12784
|
+
const configSnapshot = getConfig();
|
|
12785
|
+
const validationConfig = configSnapshot.endpointRouting?.anthropic?.validation;
|
|
12786
|
+
const validationMode = validationConfig?.mode ?? "off";
|
|
12787
|
+
if (validationMode && validationMode !== "off") {
|
|
12788
|
+
const modeLabel = validationMode === "anthropic-strict" ? "Anthropic" : "Claude Code";
|
|
12789
|
+
const result = validateAnthropicRequest(payload, {
|
|
12790
|
+
mode: validationMode === "anthropic-strict" ? "anthropic-strict" : "claude-code",
|
|
12791
|
+
allowExperimentalBlocks: validationConfig?.allowExperimentalBlocks,
|
|
12792
|
+
request: {
|
|
12793
|
+
headers: request.headers,
|
|
12794
|
+
method: request.method,
|
|
12795
|
+
query: querySuffix
|
|
12796
|
+
}
|
|
12797
|
+
});
|
|
12798
|
+
if (!result.ok) {
|
|
12799
|
+
const detail = result.path ? `${result.message} (${result.path})` : result.message;
|
|
12800
|
+
request.log.warn(
|
|
12801
|
+
{
|
|
12802
|
+
event: "anthropic.invalid_request",
|
|
12803
|
+
reason: result.message,
|
|
12804
|
+
path: result.path,
|
|
12805
|
+
mode: validationMode,
|
|
12806
|
+
method: request.method,
|
|
12807
|
+
userAgent: request.headers["user-agent"],
|
|
12808
|
+
hasQuery: !!querySuffix
|
|
12809
|
+
},
|
|
12810
|
+
"rejected anthropic payload by schema validator"
|
|
12811
|
+
);
|
|
12812
|
+
void recordEvent({
|
|
12813
|
+
type: "claude_validation",
|
|
12814
|
+
level: "warn",
|
|
12815
|
+
source: "anthropic",
|
|
12816
|
+
title: "Claude Code \u8BF7\u6C42\u6821\u9A8C\u9632\u62A4\uFF08\u5B9E\u9A8C\u7279\u6027\uFF09\u62E6\u622A",
|
|
12817
|
+
message: detail,
|
|
12818
|
+
endpoint: "anthropic",
|
|
12819
|
+
ipAddress: request.ip,
|
|
12820
|
+
apiKeyId: apiKeyContext.id ?? null,
|
|
12821
|
+
apiKeyName: apiKeyContext.name ?? null,
|
|
12822
|
+
apiKeyValue: encryptedApiKeyValue,
|
|
12823
|
+
userAgent: typeof request.headers["user-agent"] === "string" ? request.headers["user-agent"] : null,
|
|
12824
|
+
mode: validationMode,
|
|
12825
|
+
details: {
|
|
12826
|
+
code: result.code,
|
|
12827
|
+
path: result.path ?? null,
|
|
12828
|
+
method: request.method,
|
|
12829
|
+
query: querySuffix,
|
|
12830
|
+
clientModel: payload.model ?? null
|
|
12831
|
+
}
|
|
12832
|
+
});
|
|
12833
|
+
reply.code(430);
|
|
12834
|
+
return {
|
|
12835
|
+
type: "error",
|
|
12836
|
+
error: {
|
|
12837
|
+
type: "invalid_request_error",
|
|
12838
|
+
code: result.code,
|
|
12839
|
+
message: `\u8BF7\u6C42\u4E0D\u7B26\u5408 ${modeLabel} \u89C4\u8303\uFF1A${detail}`
|
|
12840
|
+
}
|
|
12841
|
+
};
|
|
12842
|
+
}
|
|
12843
|
+
}
|
|
12153
12844
|
const normalized = normalizeClaudePayload(payload);
|
|
12154
12845
|
const requestedModel = typeof payload.model === "string" ? payload.model : void 0;
|
|
12155
12846
|
const target = resolveRoute({
|
|
@@ -12207,7 +12898,6 @@ async function registerMessagesRoute(app) {
|
|
|
12207
12898
|
}
|
|
12208
12899
|
const connector = getConnector(target.providerId);
|
|
12209
12900
|
const requestStart = Date.now();
|
|
12210
|
-
const configSnapshot = getConfig();
|
|
12211
12901
|
const storeRequestPayloads = configSnapshot.storeRequestPayloads !== false;
|
|
12212
12902
|
const storeResponsePayloads = configSnapshot.storeResponsePayloads !== false;
|
|
12213
12903
|
const logId = await recordLog({
|
|
@@ -17181,6 +17871,41 @@ async function registerAdminRoutes(app) {
|
|
|
17181
17871
|
}
|
|
17182
17872
|
var isEndpoint = (value) => value === "anthropic" || value === "openai";
|
|
17183
17873
|
|
|
17874
|
+
// routes/events.ts
|
|
17875
|
+
async function registerEventsRoutes(app) {
|
|
17876
|
+
app.get("/api/events", async (request) => {
|
|
17877
|
+
const query = request.query;
|
|
17878
|
+
const limit = Number.parseInt(query.limit ?? "", 10);
|
|
17879
|
+
const cursor = Number.parseInt(query.cursor ?? "", 10);
|
|
17880
|
+
const page = await listEvents({
|
|
17881
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 50,
|
|
17882
|
+
beforeId: Number.isFinite(cursor) && cursor > 0 ? cursor : void 0,
|
|
17883
|
+
level: query.level ? query.level.trim() : void 0,
|
|
17884
|
+
type: query.type ? query.type.trim() : void 0
|
|
17885
|
+
});
|
|
17886
|
+
return {
|
|
17887
|
+
events: page.events.map((event) => ({
|
|
17888
|
+
id: event.id,
|
|
17889
|
+
createdAt: event.created_at,
|
|
17890
|
+
type: event.type,
|
|
17891
|
+
level: event.level,
|
|
17892
|
+
source: event.source,
|
|
17893
|
+
title: event.title,
|
|
17894
|
+
message: event.message,
|
|
17895
|
+
endpoint: event.endpoint,
|
|
17896
|
+
ipAddress: event.ip_address,
|
|
17897
|
+
apiKeyId: event.api_key_id,
|
|
17898
|
+
apiKeyName: event.api_key_name,
|
|
17899
|
+
apiKeyValue: event.api_key_value,
|
|
17900
|
+
userAgent: event.user_agent,
|
|
17901
|
+
mode: event.mode,
|
|
17902
|
+
details: event.details
|
|
17903
|
+
})),
|
|
17904
|
+
nextCursor: page.nextCursor
|
|
17905
|
+
};
|
|
17906
|
+
});
|
|
17907
|
+
}
|
|
17908
|
+
|
|
17184
17909
|
// routes/auth.ts
|
|
17185
17910
|
async function registerAuthRoutes(app) {
|
|
17186
17911
|
app.get("/auth/session", async (request) => {
|
|
@@ -17498,6 +18223,9 @@ async function handleAnthropicProtocol(request, reply, endpoint, endpointId, app
|
|
|
17498
18223
|
if (!querySuffix && typeof request.querystring === "string") {
|
|
17499
18224
|
querySuffix = request.querystring || null;
|
|
17500
18225
|
}
|
|
18226
|
+
const configSnapshot = getConfig();
|
|
18227
|
+
const validationConfig = configSnapshot.endpointRouting?.anthropic?.validation;
|
|
18228
|
+
const validationMode = validationConfig?.mode ?? "off";
|
|
17501
18229
|
const providerHeaders = {};
|
|
17502
18230
|
const headersToForward = [
|
|
17503
18231
|
"anthropic-version",
|
|
@@ -17551,6 +18279,51 @@ async function handleAnthropicProtocol(request, reply, endpoint, endpointId, app
|
|
|
17551
18279
|
};
|
|
17552
18280
|
const normalized = normalizeClaudePayload(payload);
|
|
17553
18281
|
const requestedModel = typeof payload.model === "string" ? payload.model : void 0;
|
|
18282
|
+
if (validationMode && validationMode !== "off") {
|
|
18283
|
+
const modeLabel = validationMode === "anthropic-strict" ? "Anthropic" : "Claude Code";
|
|
18284
|
+
const validationResult = validateAnthropicRequest(payload, {
|
|
18285
|
+
mode: validationMode === "anthropic-strict" ? "anthropic-strict" : "claude-code",
|
|
18286
|
+
allowExperimentalBlocks: validationConfig?.allowExperimentalBlocks,
|
|
18287
|
+
request: {
|
|
18288
|
+
headers: request.headers,
|
|
18289
|
+
method: request.method,
|
|
18290
|
+
query: querySuffix
|
|
18291
|
+
}
|
|
18292
|
+
});
|
|
18293
|
+
if (!validationResult.ok) {
|
|
18294
|
+
const detail = validationResult.path ? `${validationResult.message} (${validationResult.path})` : validationResult.message;
|
|
18295
|
+
void recordEvent({
|
|
18296
|
+
type: "claude_validation",
|
|
18297
|
+
level: "warn",
|
|
18298
|
+
source: "custom-endpoint",
|
|
18299
|
+
title: "Claude Code \u8BF7\u6C42\u6821\u9A8C\u9632\u62A4\uFF08\u5B9E\u9A8C\u7279\u6027\uFF09\u62E6\u622A",
|
|
18300
|
+
message: detail,
|
|
18301
|
+
endpoint: endpointId,
|
|
18302
|
+
ipAddress: request.ip,
|
|
18303
|
+
apiKeyId: apiKeyContext.id ?? null,
|
|
18304
|
+
apiKeyName: apiKeyContext.name ?? null,
|
|
18305
|
+
apiKeyValue: encryptedApiKeyValue,
|
|
18306
|
+
userAgent: typeof request.headers["user-agent"] === "string" ? request.headers["user-agent"] : null,
|
|
18307
|
+
mode: validationMode,
|
|
18308
|
+
details: {
|
|
18309
|
+
code: validationResult.code,
|
|
18310
|
+
path: validationResult.path ?? null,
|
|
18311
|
+
method: request.method,
|
|
18312
|
+
query: querySuffix,
|
|
18313
|
+
clientModel: payload.model ?? null
|
|
18314
|
+
}
|
|
18315
|
+
});
|
|
18316
|
+
reply.code(430);
|
|
18317
|
+
return {
|
|
18318
|
+
type: "error",
|
|
18319
|
+
error: {
|
|
18320
|
+
type: "invalid_request_error",
|
|
18321
|
+
code: validationResult.code,
|
|
18322
|
+
message: `\u8BF7\u6C42\u4E0D\u7B26\u5408 ${modeLabel} \u89C4\u8303\uFF1A${detail}`
|
|
18323
|
+
}
|
|
18324
|
+
};
|
|
18325
|
+
}
|
|
18326
|
+
}
|
|
17554
18327
|
const target = resolveRoute({
|
|
17555
18328
|
payload: normalized,
|
|
17556
18329
|
requestedModel,
|
|
@@ -17558,7 +18331,6 @@ async function handleAnthropicProtocol(request, reply, endpoint, endpointId, app
|
|
|
17558
18331
|
customRouting: endpoint.routing
|
|
17559
18332
|
});
|
|
17560
18333
|
const requestStart = Date.now();
|
|
17561
|
-
const configSnapshot = getConfig();
|
|
17562
18334
|
const storeRequestPayloads = configSnapshot.storeRequestPayloads !== false;
|
|
17563
18335
|
const storeResponsePayloads = configSnapshot.storeResponsePayloads !== false;
|
|
17564
18336
|
const logId = await recordLog({
|
|
@@ -18689,6 +19461,7 @@ async function createServer(protocol = "http") {
|
|
|
18689
19461
|
await registerAuthRoutes(app);
|
|
18690
19462
|
await registerMessagesRoute(app);
|
|
18691
19463
|
await registerOpenAiRoutes(app);
|
|
19464
|
+
await registerEventsRoutes(app);
|
|
18692
19465
|
await syncCustomEndpoints(app, config);
|
|
18693
19466
|
await registerAdminRoutes(app);
|
|
18694
19467
|
startMaintenanceTimers();
|