@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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/server/dist/index.js +777 -4
  3. package/src/web/dist/assets/{About-BkGxQ3Pv.js → About-h86_iIp_.js} +2 -7
  4. package/src/web/dist/assets/{ApiKeys-GdImp523.js → ApiKeys-BIRTN-iK.js} +2 -2
  5. package/src/web/dist/assets/{Button-BkhovQFd.js → Button-C6S3zW93.js} +1 -1
  6. package/src/web/dist/assets/{Dashboard-DxSNZwZc.js → Dashboard-DmLLWaIV.js} +1 -1
  7. package/src/web/dist/assets/Events-Mwcsf-KX.js +21 -0
  8. package/src/web/dist/assets/{FormField-C-bAE13W.js → FormField-BvEZfMvp.js} +1 -1
  9. package/src/web/dist/assets/{Help-Bj3HXV4H.js → Help-CYNek8H2.js} +1 -1
  10. package/src/web/dist/assets/{Input-B6cOxhbI.js → Input-AJrkG4zi.js} +1 -1
  11. package/src/web/dist/assets/{Login-Btsbo17f.js → Login-LxTaOi4n.js} +1 -1
  12. package/src/web/dist/assets/{Logs-72HRZmFi.js → Logs-NqRB7oza.js} +1 -1
  13. package/src/web/dist/assets/ModelManagement-Dal9wmkK.js +1 -0
  14. package/src/web/dist/assets/{PageSection-Bq4tdag3.js → PageSection-spGMruiz.js} +1 -1
  15. package/src/web/dist/assets/{Settings-DPvX1pD_.js → Settings-DQhioOgh.js} +2 -2
  16. package/src/web/dist/assets/{StatusBadge-P00M_NBZ.js → StatusBadge-DbGmFWnc.js} +1 -1
  17. package/src/web/dist/assets/{copy-D321KBhI.js → copy-EYijAzxo.js} +1 -1
  18. package/src/web/dist/assets/{index-BLBh7aj6.js → index-Bxh3MQYN.js} +1 -1
  19. package/src/web/dist/assets/{index-DP6DzFEd.js → index-ByFicy6e.js} +36 -31
  20. package/src/web/dist/assets/index-CJMKkw2H.css +1 -0
  21. package/src/web/dist/assets/{info-CADQNr0Q.js → info-B2EbPInL.js} +1 -1
  22. package/src/web/dist/assets/refresh-cw-BmhVGS6k.js +6 -0
  23. package/src/web/dist/assets/useApiQuery-Dyao5WuV.js +1 -0
  24. package/src/web/dist/index.html +2 -2
  25. package/src/web/dist/assets/ModelManagement-cuAzyPgP.js +0 -1
  26. package/src/web/dist/assets/index-DEa23YLm.css +0 -1
  27. package/src/web/dist/assets/useApiQuery-C5jmZPyb.js +0 -1
@@ -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();