@nick3/copilot-api 1.5.6 → 1.6.0
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/README.md +41 -31
- package/README.zh-CN.md +953 -0
- package/dist/{account-AacnHem5.js → account-B2tSWtVS.js} +12 -4
- package/dist/account-B2tSWtVS.js.map +1 -0
- package/dist/accounts-manager-CAyZJSn8.js +2932 -0
- package/dist/accounts-manager-CAyZJSn8.js.map +1 -0
- package/dist/admin/assets/index-BESw8Vvd.css +1 -0
- package/dist/admin/assets/index-Ddo9RHg-.js +101 -0
- package/dist/admin/index.html +2 -2
- package/dist/{auth-B7x3wjry.js → auth-BXCeDjRG.js} +3 -3
- package/dist/{auth-B7x3wjry.js.map → auth-BXCeDjRG.js.map} +1 -1
- package/dist/{check-usage-B1cbDEOI.js → check-usage-CQxXYfUx.js} +3 -3
- package/dist/check-usage-CQxXYfUx.js.map +1 -0
- package/dist/{get-copilot-token-cha9rQwA.js → get-copilot-token-p17sJyPU.js} +2 -2
- package/dist/{get-copilot-token-cha9rQwA.js.map → get-copilot-token-p17sJyPU.js.map} +1 -1
- package/dist/main.js +3 -3
- package/dist/{poll-access-token-DFooFWhY.js → poll-access-token-Bc6VwWab.js} +105 -40
- package/dist/poll-access-token-Bc6VwWab.js.map +1 -0
- package/dist/{server-DVpkQrk2.js → server-CFijvv3C.js} +969 -945
- package/dist/server-CFijvv3C.js.map +1 -0
- package/dist/{start-fPbCDj4c.js → start-DQlnH71A.js} +7 -6
- package/dist/start-DQlnH71A.js.map +1 -0
- package/package.json +1 -1
- package/dist/account-AacnHem5.js.map +0 -1
- package/dist/accounts-manager-BE-Dq5Wn.js +0 -1494
- package/dist/accounts-manager-BE-Dq5Wn.js.map +0 -1
- package/dist/admin/assets/index-CdoHTemy.css +0 -1
- package/dist/admin/assets/index-wcoGQpIM.js +0 -66
- package/dist/check-usage-B1cbDEOI.js.map +0 -1
- package/dist/poll-access-token-DFooFWhY.js.map +0 -1
- package/dist/server-DVpkQrk2.js.map +0 -1
- package/dist/start-fPbCDj4c.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import {
|
|
1
|
+
import { A as captureOutboundHeadersSnapshot, D as prepareMessageProxyHeaders, E as prepareInteractionHeaders, M as requestContext, N as resolveTraceId, O as accountFromState, T as prepareForCompact, _ as HTTPError, b as copilotHeaders, c as getUUID, d as parseUserIdMetadata, f as resolveAffinityKey, g as getCopilotUsage, h as getDeviceCode, k as state, l as isNullish, m as getGitHubUser, o as generateRequestIdFromPayload, p as sleep, s as getRootSessionId, t as pollAccessToken, u as normalizeStableSessionId, v as forwardError, w as normalizeDomain, y as copilotBaseUrl } from "./poll-access-token-Bc6VwWab.js";
|
|
2
|
+
import { _ as DEFAULT_IDENTITY_ENTERPRISE_DOMAIN, a as getAccountClientIdentityByLoginAndApp, b as getCurrentIdentityEnvironment, d as loadRegistry, g as saveRegistry, h as saveAccountToken, l as listAccountsFromRegistry, m as removeAccountToken, p as removeAccountFromRegistry, r as addAccountToRegistry, t as isAccountType } from "./account-B2tSWtVS.js";
|
|
3
3
|
import { r as ensurePaths, t as PATHS } from "./paths-DGlr310R.js";
|
|
4
|
-
import "./get-copilot-token-
|
|
5
|
-
import {
|
|
4
|
+
import "./get-copilot-token-p17sJyPU.js";
|
|
5
|
+
import { A as isResponsesApiWebSearchEnabled, C as getReasoningEffortForModel, D as isMessageStartInputTokensFallbackEnabled, E as isForceAgentEnabled, M as resolveModelAlias, N as shouldCompactUseSmallModel, O as isMessagesApiEnabled, S as getProviderConfig, T as isAccountAffinityEnabled, _ as getExtraPromptForModel, a as getClientIpInfo, b as getModelAliasesInfo, c as normalizeChatCompletionsUsage, d as toLocalDateString, f as PROVIDER_TYPE_ANTHROPIC, g as getConfig, h as getClaudeTokenMultiplier, i as extractResponsesUsageFromStreamEvent, j as mergeConfigWithDefaults, k as isResponsesApiContextManagementModel, l as normalizeEmbeddingsUsage, m as getAnthropicApiKey, n as applySharedSessionAffinityRetention, o as getRequestHistoryStore, p as getAliasTargetSet, r as extractResponsesUsageFromResult, s as getStatsStore, t as accountsManager, u as normalizeMessagesUsage, v as getLogLevel, w as getSmallModel, x as getModelRefreshIntervalMs, y as getModelAliases } from "./accounts-manager-CAyZJSn8.js";
|
|
6
6
|
import consola from "consola";
|
|
7
7
|
import fs, { readFile } from "node:fs/promises";
|
|
8
8
|
import { randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -12,7 +12,6 @@ import { Hono } from "hono";
|
|
|
12
12
|
import { cors } from "hono/cors";
|
|
13
13
|
import { logger } from "hono/logger";
|
|
14
14
|
import fs$1, { existsSync } from "node:fs";
|
|
15
|
-
import { Database } from "bun:sqlite";
|
|
16
15
|
import { fileURLToPath } from "node:url";
|
|
17
16
|
import { streamSSE } from "hono/streaming";
|
|
18
17
|
import util from "node:util";
|
|
@@ -121,583 +120,6 @@ const traceIdMiddleware = async (c, next) => {
|
|
|
121
120
|
});
|
|
122
121
|
};
|
|
123
122
|
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region src/lib/admin-db.ts
|
|
126
|
-
const DEFAULT_DB_PATH = path.join(PATHS.APP_DIR, "admin.sqlite");
|
|
127
|
-
let sharedDb = null;
|
|
128
|
-
let initialized = false;
|
|
129
|
-
const INIT_WARN_THROTTLE_MS = 3e4;
|
|
130
|
-
let lastInitWarnAtMs = 0;
|
|
131
|
-
let suppressedInitWarnCount = 0;
|
|
132
|
-
function warnAdminDbInitFailure(error) {
|
|
133
|
-
const now = Date.now();
|
|
134
|
-
if (now - lastInitWarnAtMs < INIT_WARN_THROTTLE_MS) {
|
|
135
|
-
suppressedInitWarnCount++;
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
const suppressed = suppressedInitWarnCount;
|
|
139
|
-
suppressedInitWarnCount = 0;
|
|
140
|
-
lastInitWarnAtMs = now;
|
|
141
|
-
const suffix = suppressed > 0 ? ` (suppressed ${suppressed} similar errors)` : "";
|
|
142
|
-
consola.warn(`Failed to initialize admin DB; admin features disabled${suffix}`, error);
|
|
143
|
-
}
|
|
144
|
-
function getAdminDbPath() {
|
|
145
|
-
return DEFAULT_DB_PATH;
|
|
146
|
-
}
|
|
147
|
-
function openAdminDb(filePath = DEFAULT_DB_PATH) {
|
|
148
|
-
return new Database(filePath);
|
|
149
|
-
}
|
|
150
|
-
function initAdminDb(db) {
|
|
151
|
-
db.run("PRAGMA journal_mode = WAL;");
|
|
152
|
-
db.run("PRAGMA synchronous = NORMAL;");
|
|
153
|
-
db.run("PRAGMA busy_timeout = 3000;");
|
|
154
|
-
db.run("PRAGMA foreign_keys = ON;");
|
|
155
|
-
migrateAdminDb(db);
|
|
156
|
-
}
|
|
157
|
-
function getAdminDb() {
|
|
158
|
-
if (!sharedDb) sharedDb = openAdminDb();
|
|
159
|
-
if (!initialized) try {
|
|
160
|
-
initAdminDb(sharedDb);
|
|
161
|
-
initialized = true;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
warnAdminDbInitFailure(error);
|
|
164
|
-
}
|
|
165
|
-
return sharedDb;
|
|
166
|
-
}
|
|
167
|
-
function getAdminDbUserVersion(db = getAdminDb()) {
|
|
168
|
-
try {
|
|
169
|
-
return db.query("PRAGMA user_version;").get()?.user_version ?? 0;
|
|
170
|
-
} catch {
|
|
171
|
-
return 0;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function migrateV1(db) {
|
|
175
|
-
db.run(`
|
|
176
|
-
CREATE TABLE IF NOT EXISTS request_log (
|
|
177
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
178
|
-
request_id TEXT NOT NULL UNIQUE,
|
|
179
|
-
|
|
180
|
-
started_at_ms INTEGER NOT NULL,
|
|
181
|
-
finished_at_ms INTEGER,
|
|
182
|
-
duration_ms INTEGER,
|
|
183
|
-
ttfb_ms INTEGER,
|
|
184
|
-
|
|
185
|
-
method TEXT NOT NULL,
|
|
186
|
-
path TEXT NOT NULL,
|
|
187
|
-
upstream_endpoint TEXT,
|
|
188
|
-
stream INTEGER NOT NULL DEFAULT 0,
|
|
189
|
-
|
|
190
|
-
account_id TEXT,
|
|
191
|
-
account_type TEXT,
|
|
192
|
-
cost_units REAL,
|
|
193
|
-
client_model TEXT,
|
|
194
|
-
upstream_model TEXT,
|
|
195
|
-
|
|
196
|
-
client_ip TEXT,
|
|
197
|
-
client_ip_source TEXT,
|
|
198
|
-
user_agent TEXT,
|
|
199
|
-
|
|
200
|
-
tokens_input INTEGER,
|
|
201
|
-
tokens_output INTEGER,
|
|
202
|
-
tokens_total INTEGER,
|
|
203
|
-
tokens_cached_input INTEGER,
|
|
204
|
-
usage_json TEXT,
|
|
205
|
-
|
|
206
|
-
premium_remaining_before REAL,
|
|
207
|
-
premium_remaining_after REAL,
|
|
208
|
-
premium_remaining_diff REAL,
|
|
209
|
-
premium_unlimited_before INTEGER,
|
|
210
|
-
premium_unlimited_after INTEGER,
|
|
211
|
-
|
|
212
|
-
http_status INTEGER,
|
|
213
|
-
error_name TEXT,
|
|
214
|
-
error_status INTEGER,
|
|
215
|
-
error_message TEXT,
|
|
216
|
-
selection_failure_reason TEXT
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_started_at
|
|
220
|
-
ON request_log(started_at_ms DESC);
|
|
221
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_account_started_at
|
|
222
|
-
ON request_log(account_id, started_at_ms DESC);
|
|
223
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_model_started_at
|
|
224
|
-
ON request_log(upstream_model, started_at_ms DESC);
|
|
225
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_endpoint_started_at
|
|
226
|
-
ON request_log(upstream_endpoint, started_at_ms DESC);
|
|
227
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_status_started_at
|
|
228
|
-
ON request_log(http_status, started_at_ms DESC);
|
|
229
|
-
|
|
230
|
-
PRAGMA user_version = 1;
|
|
231
|
-
`);
|
|
232
|
-
}
|
|
233
|
-
function migrateAdminDb(db) {
|
|
234
|
-
const current = db.query("PRAGMA user_version;").get()?.user_version ?? 0;
|
|
235
|
-
if (current >= 6) return;
|
|
236
|
-
if (current < 1) migrateV1(db);
|
|
237
|
-
if (current < 2) db.run(`
|
|
238
|
-
ALTER TABLE request_log ADD COLUMN user_id TEXT;
|
|
239
|
-
ALTER TABLE request_log ADD COLUMN safety_identifier TEXT;
|
|
240
|
-
ALTER TABLE request_log ADD COLUMN prompt_cache_key TEXT;
|
|
241
|
-
ALTER TABLE request_log ADD COLUMN initiator TEXT;
|
|
242
|
-
ALTER TABLE request_log ADD COLUMN upstream_request_id TEXT;
|
|
243
|
-
|
|
244
|
-
PRAGMA user_version = 2;
|
|
245
|
-
`);
|
|
246
|
-
if (current < 3) db.run(`
|
|
247
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_session_finished
|
|
248
|
-
ON request_log(
|
|
249
|
-
prompt_cache_key,
|
|
250
|
-
safety_identifier,
|
|
251
|
-
finished_at_ms DESC
|
|
252
|
-
)
|
|
253
|
-
WHERE finished_at_ms IS NOT NULL
|
|
254
|
-
AND tokens_input IS NOT NULL;
|
|
255
|
-
|
|
256
|
-
PRAGMA user_version = 3;
|
|
257
|
-
`);
|
|
258
|
-
if (current < 4) db.run(`
|
|
259
|
-
CREATE INDEX IF NOT EXISTS idx_request_log_session_finished_by_client_model
|
|
260
|
-
ON request_log(
|
|
261
|
-
prompt_cache_key,
|
|
262
|
-
safety_identifier,
|
|
263
|
-
client_model,
|
|
264
|
-
finished_at_ms DESC
|
|
265
|
-
)
|
|
266
|
-
WHERE finished_at_ms IS NOT NULL
|
|
267
|
-
AND tokens_input IS NOT NULL;
|
|
268
|
-
|
|
269
|
-
PRAGMA user_version = 4;
|
|
270
|
-
`);
|
|
271
|
-
if (current < 5) db.run(`
|
|
272
|
-
ALTER TABLE request_log ADD COLUMN affinity_hit INTEGER;
|
|
273
|
-
ALTER TABLE request_log ADD COLUMN affinity_cache_key TEXT;
|
|
274
|
-
|
|
275
|
-
PRAGMA user_version = 5;
|
|
276
|
-
`);
|
|
277
|
-
if (current < 6) {
|
|
278
|
-
if (!db.query("PRAGMA table_info(request_log);").all().some((row) => row.name === "is_subagent")) db.run("ALTER TABLE request_log ADD COLUMN is_subagent INTEGER;");
|
|
279
|
-
db.run("PRAGMA user_version = 6;");
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
//#endregion
|
|
284
|
-
//#region src/lib/request-history.ts
|
|
285
|
-
const DEFAULT_RETENTION_DAYS = 14;
|
|
286
|
-
const DEFAULT_MAX_ROWS = 2e5;
|
|
287
|
-
const INSERT_WARN_THROTTLE_MS = 3e4;
|
|
288
|
-
let lastInsertWarnAtMs = 0;
|
|
289
|
-
let suppressedInsertWarnCount = 0;
|
|
290
|
-
function warnInsertFailure(error) {
|
|
291
|
-
const now = Date.now();
|
|
292
|
-
if (now - lastInsertWarnAtMs < INSERT_WARN_THROTTLE_MS) {
|
|
293
|
-
suppressedInsertWarnCount++;
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const suppressed = suppressedInsertWarnCount;
|
|
297
|
-
suppressedInsertWarnCount = 0;
|
|
298
|
-
lastInsertWarnAtMs = now;
|
|
299
|
-
const suffix = suppressed > 0 ? ` (suppressed ${suppressed} similar errors)` : "";
|
|
300
|
-
consola.warn(`Failed to insert request log${suffix}`, error);
|
|
301
|
-
}
|
|
302
|
-
function toDbNull(value) {
|
|
303
|
-
return value === void 0 ? null : value;
|
|
304
|
-
}
|
|
305
|
-
function toDbBool(value) {
|
|
306
|
-
if (value === true) return 1;
|
|
307
|
-
if (value === false) return 0;
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
function getClientIpInfo(c) {
|
|
311
|
-
const cf = c.req.header("cf-connecting-ip");
|
|
312
|
-
if (cf) return {
|
|
313
|
-
ip: cf.trim(),
|
|
314
|
-
source: "cf-connecting-ip"
|
|
315
|
-
};
|
|
316
|
-
const xff = c.req.header("x-forwarded-for");
|
|
317
|
-
if (xff) {
|
|
318
|
-
const first = xff.split(",")[0]?.trim();
|
|
319
|
-
if (first) return {
|
|
320
|
-
ip: first,
|
|
321
|
-
source: "x-forwarded-for"
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
const xri = c.req.header("x-real-ip");
|
|
325
|
-
if (xri) return {
|
|
326
|
-
ip: xri.trim(),
|
|
327
|
-
source: "x-real-ip"
|
|
328
|
-
};
|
|
329
|
-
return {};
|
|
330
|
-
}
|
|
331
|
-
function normalizeChatCompletionsUsage(usage) {
|
|
332
|
-
if (!usage) return {};
|
|
333
|
-
const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
334
|
-
const prompt = usage.prompt_tokens;
|
|
335
|
-
const completion = usage.completion_tokens;
|
|
336
|
-
const total = usage.total_tokens;
|
|
337
|
-
return {
|
|
338
|
-
tokensCachedInput: cached,
|
|
339
|
-
tokensInput: Math.max(0, prompt - cached),
|
|
340
|
-
tokensOutput: completion,
|
|
341
|
-
tokensTotal: total,
|
|
342
|
-
usageJson: JSON.stringify(usage)
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
function normalizeResponsesUsage(usage) {
|
|
346
|
-
if (!usage) return {};
|
|
347
|
-
const cached = usage.input_tokens_details?.cached_tokens ?? 0;
|
|
348
|
-
const input = usage.input_tokens;
|
|
349
|
-
const output = usage.output_tokens ?? 0;
|
|
350
|
-
const total = usage.total_tokens;
|
|
351
|
-
return {
|
|
352
|
-
tokensCachedInput: cached,
|
|
353
|
-
tokensInput: Math.max(0, input - cached),
|
|
354
|
-
tokensOutput: output,
|
|
355
|
-
tokensTotal: total,
|
|
356
|
-
usageJson: JSON.stringify(usage)
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
function normalizeMessagesUsage(usage) {
|
|
360
|
-
if (!usage) return {};
|
|
361
|
-
const cached = usage.cache_read_input_tokens ?? 0;
|
|
362
|
-
const input = usage.input_tokens;
|
|
363
|
-
const output = usage.output_tokens;
|
|
364
|
-
const hasInput = typeof input === "number";
|
|
365
|
-
const hasOutput = typeof output === "number";
|
|
366
|
-
return {
|
|
367
|
-
tokensCachedInput: cached,
|
|
368
|
-
tokensInput: hasInput ? Math.max(0, input - cached) : void 0,
|
|
369
|
-
tokensOutput: hasOutput ? output : void 0,
|
|
370
|
-
tokensTotal: hasInput || hasOutput ? (input ?? 0) + (output ?? 0) : void 0,
|
|
371
|
-
usageJson: JSON.stringify(usage)
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
function normalizeEmbeddingsUsage(usage) {
|
|
375
|
-
if (!usage) return {};
|
|
376
|
-
return {
|
|
377
|
-
tokensCachedInput: 0,
|
|
378
|
-
tokensInput: usage.prompt_tokens,
|
|
379
|
-
tokensOutput: 0,
|
|
380
|
-
tokensTotal: usage.total_tokens,
|
|
381
|
-
usageJson: JSON.stringify(usage)
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
var RequestHistoryStore = class {
|
|
385
|
-
db;
|
|
386
|
-
insertStmt;
|
|
387
|
-
getByRequestIdStmt;
|
|
388
|
-
getLastCompletedUsageBySessionStmt;
|
|
389
|
-
constructor(db) {
|
|
390
|
-
this.db = db;
|
|
391
|
-
this.insertStmt = db.query(`
|
|
392
|
-
INSERT INTO request_log (
|
|
393
|
-
request_id,
|
|
394
|
-
started_at_ms,
|
|
395
|
-
finished_at_ms,
|
|
396
|
-
duration_ms,
|
|
397
|
-
ttfb_ms,
|
|
398
|
-
method,
|
|
399
|
-
path,
|
|
400
|
-
upstream_endpoint,
|
|
401
|
-
stream,
|
|
402
|
-
account_id,
|
|
403
|
-
account_type,
|
|
404
|
-
cost_units,
|
|
405
|
-
client_model,
|
|
406
|
-
upstream_model,
|
|
407
|
-
client_ip,
|
|
408
|
-
client_ip_source,
|
|
409
|
-
user_agent,
|
|
410
|
-
user_id,
|
|
411
|
-
safety_identifier,
|
|
412
|
-
prompt_cache_key,
|
|
413
|
-
initiator,
|
|
414
|
-
is_subagent,
|
|
415
|
-
upstream_request_id,
|
|
416
|
-
tokens_input,
|
|
417
|
-
tokens_output,
|
|
418
|
-
tokens_total,
|
|
419
|
-
tokens_cached_input,
|
|
420
|
-
usage_json,
|
|
421
|
-
premium_remaining_before,
|
|
422
|
-
premium_remaining_after,
|
|
423
|
-
premium_remaining_diff,
|
|
424
|
-
premium_unlimited_before,
|
|
425
|
-
premium_unlimited_after,
|
|
426
|
-
http_status,
|
|
427
|
-
error_name,
|
|
428
|
-
error_status,
|
|
429
|
-
error_message,
|
|
430
|
-
selection_failure_reason,
|
|
431
|
-
affinity_hit,
|
|
432
|
-
affinity_cache_key
|
|
433
|
-
) VALUES (
|
|
434
|
-
?,?,?,?,?,?,?,?,
|
|
435
|
-
?,?,?,?,?,?,?,?,
|
|
436
|
-
?,?,?,?,?,?,?,?,
|
|
437
|
-
?,?,?,?,?,?,?,?,
|
|
438
|
-
?,?,?,?,?,?,?,?
|
|
439
|
-
);
|
|
440
|
-
`);
|
|
441
|
-
this.getByRequestIdStmt = db.query("SELECT * FROM request_log WHERE request_id = ? LIMIT 1;");
|
|
442
|
-
this.getLastCompletedUsageBySessionStmt = db.query(`
|
|
443
|
-
SELECT
|
|
444
|
-
tokens_input,
|
|
445
|
-
tokens_output,
|
|
446
|
-
tokens_total,
|
|
447
|
-
tokens_cached_input
|
|
448
|
-
FROM request_log
|
|
449
|
-
WHERE prompt_cache_key = ?
|
|
450
|
-
AND safety_identifier = ?
|
|
451
|
-
AND client_model = ?
|
|
452
|
-
AND finished_at_ms IS NOT NULL
|
|
453
|
-
AND tokens_input IS NOT NULL
|
|
454
|
-
ORDER BY finished_at_ms DESC
|
|
455
|
-
LIMIT 1;
|
|
456
|
-
`);
|
|
457
|
-
}
|
|
458
|
-
insert(record) {
|
|
459
|
-
try {
|
|
460
|
-
const args = [
|
|
461
|
-
record.requestId,
|
|
462
|
-
record.startedAtMs,
|
|
463
|
-
toDbNull(record.finishedAtMs),
|
|
464
|
-
toDbNull(record.durationMs),
|
|
465
|
-
toDbNull(record.ttfbMs),
|
|
466
|
-
record.method,
|
|
467
|
-
record.path,
|
|
468
|
-
toDbNull(record.upstreamEndpoint),
|
|
469
|
-
record.stream ? 1 : 0,
|
|
470
|
-
toDbNull(record.accountId),
|
|
471
|
-
toDbNull(record.accountType),
|
|
472
|
-
toDbNull(record.costUnits),
|
|
473
|
-
toDbNull(record.clientModel),
|
|
474
|
-
toDbNull(record.upstreamModel),
|
|
475
|
-
toDbNull(record.clientIp),
|
|
476
|
-
toDbNull(record.clientIpSource),
|
|
477
|
-
toDbNull(record.userAgent),
|
|
478
|
-
toDbNull(record.userId),
|
|
479
|
-
toDbNull(record.safetyIdentifier),
|
|
480
|
-
toDbNull(record.promptCacheKey),
|
|
481
|
-
toDbNull(record.initiator),
|
|
482
|
-
toDbBool(record.isSubagent),
|
|
483
|
-
toDbNull(record.upstreamRequestId),
|
|
484
|
-
toDbNull(record.tokensInput),
|
|
485
|
-
toDbNull(record.tokensOutput),
|
|
486
|
-
toDbNull(record.tokensTotal),
|
|
487
|
-
toDbNull(record.tokensCachedInput),
|
|
488
|
-
toDbNull(record.usageJson),
|
|
489
|
-
toDbNull(record.premiumRemainingBefore),
|
|
490
|
-
toDbNull(record.premiumRemainingAfter),
|
|
491
|
-
toDbNull(record.premiumRemainingDiff),
|
|
492
|
-
toDbBool(record.premiumUnlimitedBefore),
|
|
493
|
-
toDbBool(record.premiumUnlimitedAfter),
|
|
494
|
-
toDbNull(record.httpStatus),
|
|
495
|
-
toDbNull(record.errorName),
|
|
496
|
-
toDbNull(record.errorStatus),
|
|
497
|
-
toDbNull(record.errorMessage),
|
|
498
|
-
toDbNull(record.selectionFailureReason),
|
|
499
|
-
toDbBool(record.affinityHit),
|
|
500
|
-
toDbNull(record.affinityCacheKey)
|
|
501
|
-
];
|
|
502
|
-
this.insertStmt.run(...args);
|
|
503
|
-
} catch (error) {
|
|
504
|
-
warnInsertFailure(error);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
getByRequestId(requestId) {
|
|
508
|
-
try {
|
|
509
|
-
return this.getByRequestIdStmt.get(requestId) ?? null;
|
|
510
|
-
} catch (error) {
|
|
511
|
-
consola.debug("Failed to fetch request log by request_id", error);
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
getLastCompletedUsageBySession(session) {
|
|
516
|
-
if (!session.promptCacheKey || !session.safetyIdentifier || !session.clientModel) return null;
|
|
517
|
-
try {
|
|
518
|
-
const row = this.getLastCompletedUsageBySessionStmt.get(session.promptCacheKey, session.safetyIdentifier, session.clientModel);
|
|
519
|
-
if (!row || row.tokens_input === null) return null;
|
|
520
|
-
const tokensOutput = row.tokens_output === null ? void 0 : row.tokens_output;
|
|
521
|
-
const tokensTotal = row.tokens_total === null ? void 0 : row.tokens_total;
|
|
522
|
-
const tokensCachedInput = row.tokens_cached_input === null ? void 0 : row.tokens_cached_input;
|
|
523
|
-
return {
|
|
524
|
-
tokensInput: row.tokens_input,
|
|
525
|
-
tokensOutput,
|
|
526
|
-
tokensTotal,
|
|
527
|
-
tokensCachedInput
|
|
528
|
-
};
|
|
529
|
-
} catch (error) {
|
|
530
|
-
consola.debug("Failed to fetch last completed usage by session", error);
|
|
531
|
-
return null;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
query(params) {
|
|
535
|
-
const limit = Math.max(1, Math.min(params.limit, 200));
|
|
536
|
-
const where = [];
|
|
537
|
-
const values = [];
|
|
538
|
-
if (params.cursorId !== void 0) {
|
|
539
|
-
where.push("id < ?");
|
|
540
|
-
values.push(params.cursorId);
|
|
541
|
-
}
|
|
542
|
-
if (params.accountId) {
|
|
543
|
-
where.push("account_id = ?");
|
|
544
|
-
values.push(params.accountId);
|
|
545
|
-
}
|
|
546
|
-
if (params.upstreamModel) {
|
|
547
|
-
where.push("upstream_model = ?");
|
|
548
|
-
values.push(params.upstreamModel);
|
|
549
|
-
}
|
|
550
|
-
if (params.clientModel) {
|
|
551
|
-
where.push("client_model = ?");
|
|
552
|
-
values.push(params.clientModel);
|
|
553
|
-
}
|
|
554
|
-
if (params.upstreamEndpoint) {
|
|
555
|
-
where.push("upstream_endpoint = ?");
|
|
556
|
-
values.push(params.upstreamEndpoint);
|
|
557
|
-
}
|
|
558
|
-
if (params.path) {
|
|
559
|
-
where.push("path = ?");
|
|
560
|
-
values.push(params.path);
|
|
561
|
-
}
|
|
562
|
-
if (params.status !== void 0) {
|
|
563
|
-
where.push("http_status = ?");
|
|
564
|
-
values.push(params.status);
|
|
565
|
-
}
|
|
566
|
-
if (params.hasError === true) where.push("http_status >= 400");
|
|
567
|
-
if (params.hasError === false) where.push("http_status < 400");
|
|
568
|
-
if (params.fromMs !== void 0) {
|
|
569
|
-
where.push("started_at_ms >= ?");
|
|
570
|
-
values.push(params.fromMs);
|
|
571
|
-
}
|
|
572
|
-
if (params.toMs !== void 0) {
|
|
573
|
-
where.push("started_at_ms <= ?");
|
|
574
|
-
values.push(params.toMs);
|
|
575
|
-
}
|
|
576
|
-
const sql = `
|
|
577
|
-
SELECT *
|
|
578
|
-
FROM request_log
|
|
579
|
-
${where.length > 0 ? `WHERE ${where.join(" AND ")}` : ""}
|
|
580
|
-
ORDER BY id DESC
|
|
581
|
-
LIMIT ?;
|
|
582
|
-
`;
|
|
583
|
-
const rows = this.db.query(sql).all(...values, limit + 1);
|
|
584
|
-
const items = rows.slice(0, limit);
|
|
585
|
-
const hasMore = rows.length > limit;
|
|
586
|
-
return {
|
|
587
|
-
items,
|
|
588
|
-
nextCursorId: hasMore ? items.at(-1)?.id : void 0,
|
|
589
|
-
hasMore
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
getAccountStatsSince(sinceMs) {
|
|
593
|
-
try {
|
|
594
|
-
const rows = this.db.query(`
|
|
595
|
-
SELECT
|
|
596
|
-
account_id,
|
|
597
|
-
COUNT(*) AS request_count,
|
|
598
|
-
SUM(CASE WHEN http_status >= 400 THEN 1 ELSE 0 END) AS error_count,
|
|
599
|
-
COALESCE(SUM(tokens_total), 0) AS tokens_total,
|
|
600
|
-
COALESCE(AVG(duration_ms), 0) AS avg_duration_ms,
|
|
601
|
-
COALESCE(MAX(started_at_ms), 0) AS last_request_at_ms
|
|
602
|
-
FROM request_log
|
|
603
|
-
WHERE started_at_ms >= ? AND account_id IS NOT NULL
|
|
604
|
-
GROUP BY account_id;
|
|
605
|
-
`).all(sinceMs);
|
|
606
|
-
const map = {};
|
|
607
|
-
for (const row of rows) map[row.account_id] = row;
|
|
608
|
-
return map;
|
|
609
|
-
} catch (error) {
|
|
610
|
-
consola.debug("Failed to fetch account stats", error);
|
|
611
|
-
return {};
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
cleanupRetention(retentionDays = DEFAULT_RETENTION_DAYS, maxRows = DEFAULT_MAX_ROWS) {
|
|
615
|
-
try {
|
|
616
|
-
const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
617
|
-
this.db.query("DELETE FROM request_log WHERE started_at_ms < ?;").run(cutoffMs);
|
|
618
|
-
if (this.db.query("SELECT COUNT(*) AS c FROM request_log;").get().c <= maxRows) return;
|
|
619
|
-
const offset = maxRows - 1;
|
|
620
|
-
const thresholdId = this.db.query("SELECT id FROM request_log ORDER BY id DESC LIMIT 1 OFFSET ?;").get(offset)?.id;
|
|
621
|
-
if (!thresholdId) return;
|
|
622
|
-
this.db.query("DELETE FROM request_log WHERE id < ?;").run(thresholdId);
|
|
623
|
-
} catch (error) {
|
|
624
|
-
consola.debug("Failed to cleanup request_log retention", error);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
meta() {
|
|
628
|
-
return {
|
|
629
|
-
dbPath: getAdminDbPath(),
|
|
630
|
-
userVersion: getAdminDbUserVersion(this.db),
|
|
631
|
-
retentionDays: DEFAULT_RETENTION_DAYS,
|
|
632
|
-
maxRows: DEFAULT_MAX_ROWS
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
|
-
};
|
|
636
|
-
const STORE_INIT_WARN_THROTTLE_MS = 3e4;
|
|
637
|
-
const STORE_INIT_RETRY_DELAY_MS = 3e4;
|
|
638
|
-
let lastStoreInitWarnAtMs = 0;
|
|
639
|
-
let suppressedStoreInitWarnCount = 0;
|
|
640
|
-
let nextStoreRetryAtMs = 0;
|
|
641
|
-
function warnStoreInitFailure(error) {
|
|
642
|
-
const now = Date.now();
|
|
643
|
-
if (now - lastStoreInitWarnAtMs < STORE_INIT_WARN_THROTTLE_MS) {
|
|
644
|
-
suppressedStoreInitWarnCount++;
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
const suppressed = suppressedStoreInitWarnCount;
|
|
648
|
-
suppressedStoreInitWarnCount = 0;
|
|
649
|
-
lastStoreInitWarnAtMs = now;
|
|
650
|
-
const suffix = suppressed > 0 ? ` (suppressed ${suppressed} similar errors)` : "";
|
|
651
|
-
consola.warn(`Request history store is disabled${suffix}`, error);
|
|
652
|
-
}
|
|
653
|
-
const disabledStore = {
|
|
654
|
-
insert: () => {},
|
|
655
|
-
getByRequestId: () => null,
|
|
656
|
-
getLastCompletedUsageBySession: () => null,
|
|
657
|
-
query: () => ({
|
|
658
|
-
items: [],
|
|
659
|
-
hasMore: false
|
|
660
|
-
}),
|
|
661
|
-
getAccountStatsSince: () => ({}),
|
|
662
|
-
cleanupRetention: () => {},
|
|
663
|
-
meta: () => ({
|
|
664
|
-
dbPath: getAdminDbPath(),
|
|
665
|
-
userVersion: 0,
|
|
666
|
-
retentionDays: DEFAULT_RETENTION_DAYS,
|
|
667
|
-
maxRows: DEFAULT_MAX_ROWS
|
|
668
|
-
})
|
|
669
|
-
};
|
|
670
|
-
let sharedStore = null;
|
|
671
|
-
let maintenanceStarted = false;
|
|
672
|
-
function getRequestHistoryStore() {
|
|
673
|
-
if (sharedStore) return sharedStore;
|
|
674
|
-
const now = Date.now();
|
|
675
|
-
if (now < nextStoreRetryAtMs) return disabledStore;
|
|
676
|
-
try {
|
|
677
|
-
sharedStore = new RequestHistoryStore(getAdminDb());
|
|
678
|
-
if (!maintenanceStarted) {
|
|
679
|
-
maintenanceStarted = true;
|
|
680
|
-
sharedStore.cleanupRetention();
|
|
681
|
-
setInterval(() => {
|
|
682
|
-
sharedStore?.cleanupRetention();
|
|
683
|
-
}, 1440 * 60 * 1e3);
|
|
684
|
-
}
|
|
685
|
-
return sharedStore;
|
|
686
|
-
} catch (error) {
|
|
687
|
-
nextStoreRetryAtMs = now + STORE_INIT_RETRY_DELAY_MS;
|
|
688
|
-
warnStoreInitFailure(error);
|
|
689
|
-
return disabledStore;
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
function extractResponsesUsageFromStreamEvent(event) {
|
|
693
|
-
if (event.type === "response.completed" || event.type === "response.incomplete") return normalizeResponsesUsage(event.response.usage);
|
|
694
|
-
if (event.type === "response.failed") return normalizeResponsesUsage(event.response.usage);
|
|
695
|
-
return {};
|
|
696
|
-
}
|
|
697
|
-
function extractResponsesUsageFromResult(result) {
|
|
698
|
-
return normalizeResponsesUsage(result.usage);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
123
|
//#endregion
|
|
702
124
|
//#region src/routes/admin-api/auth-sessions.ts
|
|
703
125
|
function buildOauthUrls(enterpriseDomain) {
|
|
@@ -917,6 +339,7 @@ const CONFIG_KEYS = new Set([
|
|
|
917
339
|
"auth",
|
|
918
340
|
"extraPrompts",
|
|
919
341
|
"smallModel",
|
|
342
|
+
"logLevel",
|
|
920
343
|
"accountAffinity",
|
|
921
344
|
"apiKey",
|
|
922
345
|
"anthropicApiKey",
|
|
@@ -930,6 +353,7 @@ const CONFIG_KEYS = new Set([
|
|
|
930
353
|
"compactUseSmallModel",
|
|
931
354
|
"messageStartInputTokensFallback",
|
|
932
355
|
"modelRefreshIntervalHours",
|
|
356
|
+
"sessionAffinityRetentionDays",
|
|
933
357
|
"useMessagesApi",
|
|
934
358
|
"useResponsesApiWebSearch"
|
|
935
359
|
]);
|
|
@@ -941,6 +365,12 @@ const REASONING_EFFORTS = new Set([
|
|
|
941
365
|
"high",
|
|
942
366
|
"xhigh"
|
|
943
367
|
]);
|
|
368
|
+
const LOG_LEVELS = new Set([
|
|
369
|
+
"error",
|
|
370
|
+
"warn",
|
|
371
|
+
"info",
|
|
372
|
+
"debug"
|
|
373
|
+
]);
|
|
944
374
|
const BLOCKED_KEYS = new Set([
|
|
945
375
|
"__proto__",
|
|
946
376
|
"constructor",
|
|
@@ -959,6 +389,13 @@ function parseOptionalString(value, field) {
|
|
|
959
389
|
if (!trimmed) return { clear: true };
|
|
960
390
|
return { value: trimmed };
|
|
961
391
|
}
|
|
392
|
+
function parseOptionalLogLevel(value) {
|
|
393
|
+
const parsed = parseOptionalString(value, "logLevel");
|
|
394
|
+
if ("error" in parsed) return parsed;
|
|
395
|
+
if ("clear" in parsed) return parsed;
|
|
396
|
+
if (!LOG_LEVELS.has(parsed.value)) return { error: `logLevel must be one of: ${[...LOG_LEVELS].join(", ")}` };
|
|
397
|
+
return { value: parsed.value };
|
|
398
|
+
}
|
|
962
399
|
function parseOptionalBoolean(value, field) {
|
|
963
400
|
if (value === null || value === void 0) return { clear: true };
|
|
964
401
|
if (typeof value !== "boolean") return { error: `${field} must be a boolean` };
|
|
@@ -1238,6 +675,15 @@ function applyAuthConfig(next, value) {
|
|
|
1238
675
|
}
|
|
1239
676
|
next.auth = parsed.value;
|
|
1240
677
|
}
|
|
678
|
+
function applyLogLevel(next, value) {
|
|
679
|
+
const parsed = parseOptionalLogLevel(value);
|
|
680
|
+
if ("error" in parsed) return parsed.error;
|
|
681
|
+
if ("clear" in parsed) {
|
|
682
|
+
next.logLevel = void 0;
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
next.logLevel = parsed.value;
|
|
686
|
+
}
|
|
1241
687
|
function applyOptionalBoolean(next, key, value) {
|
|
1242
688
|
const parsed = parseOptionalBoolean(value, key);
|
|
1243
689
|
if ("error" in parsed) return parsed.error;
|
|
@@ -1305,6 +751,7 @@ const CONFIG_PATCH_HANDLERS = {
|
|
|
1305
751
|
auth: applyAuthConfig,
|
|
1306
752
|
extraPrompts: applyExtraPrompts,
|
|
1307
753
|
smallModel: (next, value) => applyOptionalString(next, "smallModel", value),
|
|
754
|
+
logLevel: applyLogLevel,
|
|
1308
755
|
accountAffinity: (next, value) => applyOptionalBoolean(next, "accountAffinity", value),
|
|
1309
756
|
apiKey: (next, value) => applyOptionalString(next, "apiKey", value),
|
|
1310
757
|
anthropicApiKey: (next, value) => applyOptionalString(next, "anthropicApiKey", value),
|
|
@@ -1318,6 +765,7 @@ const CONFIG_PATCH_HANDLERS = {
|
|
|
1318
765
|
compactUseSmallModel: (next, value) => applyOptionalBoolean(next, "compactUseSmallModel", value),
|
|
1319
766
|
messageStartInputTokensFallback: (next, value) => applyOptionalBoolean(next, "messageStartInputTokensFallback", value),
|
|
1320
767
|
modelRefreshIntervalHours: (next, value) => applyOptionalNumber(next, "modelRefreshIntervalHours", value),
|
|
768
|
+
sessionAffinityRetentionDays: (next, value) => applyOptionalNumber(next, "sessionAffinityRetentionDays", value),
|
|
1321
769
|
useMessagesApi: (next, value) => applyOptionalBoolean(next, "useMessagesApi", value),
|
|
1322
770
|
useResponsesApiWebSearch: (next, value) => applyOptionalBoolean(next, "useResponsesApiWebSearch", value)
|
|
1323
771
|
};
|
|
@@ -1400,6 +848,7 @@ adminApiRoutes.post("/config", async (c) => {
|
|
|
1400
848
|
const merged = mergeConfigWithDefaults();
|
|
1401
849
|
accountsManager.setAccountAffinityEnabled(isAccountAffinityEnabled());
|
|
1402
850
|
accountsManager.setModelsRefreshIntervalMs(getModelRefreshIntervalMs());
|
|
851
|
+
applySharedSessionAffinityRetention();
|
|
1403
852
|
return c.json({
|
|
1404
853
|
...merged,
|
|
1405
854
|
_configPath: PATHS.CONFIG_PATH
|
|
@@ -1548,7 +997,8 @@ adminApiRoutes.get("/accounts", async (c) => {
|
|
|
1548
997
|
remaining: s.remaining,
|
|
1549
998
|
unlimited: s.unlimited,
|
|
1550
999
|
failed: s.failed,
|
|
1551
|
-
failureReason: s.failureReason
|
|
1000
|
+
failureReason: s.failureReason,
|
|
1001
|
+
enabled: s.enabled
|
|
1552
1002
|
},
|
|
1553
1003
|
stats
|
|
1554
1004
|
};
|
|
@@ -1608,11 +1058,7 @@ adminApiRoutes.post("/accounts/auth/start", async (c) => {
|
|
|
1608
1058
|
});
|
|
1609
1059
|
const enterpriseDomainRaw = payload.enterpriseDomain;
|
|
1610
1060
|
let enterpriseDomain;
|
|
1611
|
-
if (accountType === "enterprise" && typeof enterpriseDomainRaw === "string") enterpriseDomain = enterpriseDomainRaw.trim();
|
|
1612
|
-
if (accountType === "enterprise" && !enterpriseDomain) return jsonError(c, 400, {
|
|
1613
|
-
message: "enterpriseDomain is required for enterprise accounts.",
|
|
1614
|
-
type: "bad_request"
|
|
1615
|
-
});
|
|
1061
|
+
if (accountType === "enterprise" && typeof enterpriseDomainRaw === "string") enterpriseDomain = enterpriseDomainRaw.trim() || void 0;
|
|
1616
1062
|
try {
|
|
1617
1063
|
const result = await authSessionManager.startAuth({
|
|
1618
1064
|
accountType,
|
|
@@ -1643,6 +1089,40 @@ adminApiRoutes.post("/accounts/auth/cancel/:sessionId", (c) => {
|
|
|
1643
1089
|
});
|
|
1644
1090
|
return c.json({ cancelled: true });
|
|
1645
1091
|
});
|
|
1092
|
+
adminApiRoutes.patch("/accounts/:id", async (c) => {
|
|
1093
|
+
const accountId = c.req.param("id");
|
|
1094
|
+
let payload;
|
|
1095
|
+
try {
|
|
1096
|
+
payload = await c.req.json();
|
|
1097
|
+
} catch {
|
|
1098
|
+
return jsonError(c, 400, {
|
|
1099
|
+
message: "Invalid JSON body.",
|
|
1100
|
+
type: "bad_request"
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload) || typeof payload.enabled !== "boolean") return jsonError(c, 400, {
|
|
1104
|
+
message: "Request body must contain { enabled: boolean }.",
|
|
1105
|
+
type: "bad_request"
|
|
1106
|
+
});
|
|
1107
|
+
const enabled = payload.enabled;
|
|
1108
|
+
try {
|
|
1109
|
+
const registry = await loadRegistry();
|
|
1110
|
+
const entry = registry.accounts.find((a) => a.id === accountId);
|
|
1111
|
+
if (!entry) return jsonError(c, 404, {
|
|
1112
|
+
message: "Account not found.",
|
|
1113
|
+
type: "not_found"
|
|
1114
|
+
});
|
|
1115
|
+
entry.enabled = enabled;
|
|
1116
|
+
await saveRegistry(registry);
|
|
1117
|
+
await accountsManager.reloadRegistryNow();
|
|
1118
|
+
return c.json({ success: true });
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
return jsonError(c, 500, {
|
|
1121
|
+
message: `Failed to update account: ${error instanceof Error ? error.message : String(error)}`,
|
|
1122
|
+
type: "internal_error"
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1646
1126
|
adminApiRoutes.delete("/accounts/:id", async (c) => {
|
|
1647
1127
|
const accountId = c.req.param("id");
|
|
1648
1128
|
try {
|
|
@@ -1656,6 +1136,7 @@ adminApiRoutes.delete("/accounts/:id", async (c) => {
|
|
|
1656
1136
|
} catch (error) {
|
|
1657
1137
|
console.error(`Account ${accountId} deleted but token cleanup failed.`, error);
|
|
1658
1138
|
}
|
|
1139
|
+
await accountsManager.reloadRegistryNow();
|
|
1659
1140
|
return c.json({
|
|
1660
1141
|
deleted: true,
|
|
1661
1142
|
accountId
|
|
@@ -1679,10 +1160,6 @@ adminApiRoutes.post("/accounts/:id/reauth", async (c) => {
|
|
|
1679
1160
|
const resolvedEnterpriseDomain = (await getAccountClientIdentityByLoginAndApp(accountId, oauthApp))?.enterpriseDomain;
|
|
1680
1161
|
let enterpriseDomain;
|
|
1681
1162
|
if (resolvedEnterpriseDomain && resolvedEnterpriseDomain !== DEFAULT_IDENTITY_ENTERPRISE_DOMAIN) enterpriseDomain = resolvedEnterpriseDomain;
|
|
1682
|
-
if (account.accountType === "enterprise" && !enterpriseDomain) return jsonError(c, 400, {
|
|
1683
|
-
message: "Cannot re-authenticate enterprise account: enterprise domain could not be resolved from stored identity.",
|
|
1684
|
-
type: "bad_request"
|
|
1685
|
-
});
|
|
1686
1163
|
const result = await authSessionManager.startAuth({
|
|
1687
1164
|
accountType: account.accountType,
|
|
1688
1165
|
enterpriseDomain,
|
|
@@ -1696,6 +1173,81 @@ adminApiRoutes.post("/accounts/:id/reauth", async (c) => {
|
|
|
1696
1173
|
});
|
|
1697
1174
|
}
|
|
1698
1175
|
});
|
|
1176
|
+
adminApiRoutes.get("/stats/premium-daily", (c) => {
|
|
1177
|
+
const p = new URL(c.req.url, "http://local").searchParams;
|
|
1178
|
+
const from = p.get("from") || void 0;
|
|
1179
|
+
const to = p.get("to") || void 0;
|
|
1180
|
+
const accountId = p.get("account_id") || void 0;
|
|
1181
|
+
const granularity = p.get("granularity") === "hour" ? "hour" : "day";
|
|
1182
|
+
const now = /* @__PURE__ */ new Date();
|
|
1183
|
+
const todayStr = toLocalDateString(now.getTime());
|
|
1184
|
+
const resolvedFrom = from || toLocalDateString(new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6).getTime());
|
|
1185
|
+
const resolvedTo = to || todayStr;
|
|
1186
|
+
function parseLocalDate(dateStr) {
|
|
1187
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
1188
|
+
return new Date(y, m - 1, d);
|
|
1189
|
+
}
|
|
1190
|
+
/** DST-safe end-of-day: next local midnight minus 1 ms. */
|
|
1191
|
+
function localDateEndMs(dateStr) {
|
|
1192
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
1193
|
+
return new Date(y, m - 1, d + 1).getTime() - 1;
|
|
1194
|
+
}
|
|
1195
|
+
const statsStore = getStatsStore();
|
|
1196
|
+
if (!statsStore) return c.json({
|
|
1197
|
+
daily: [],
|
|
1198
|
+
by_account: [],
|
|
1199
|
+
range: {
|
|
1200
|
+
from: resolvedFrom,
|
|
1201
|
+
to: resolvedTo,
|
|
1202
|
+
granularity
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
if (granularity === "hour") {
|
|
1206
|
+
const clientFromMs = parseFiniteNumber(p.get("from_ms"));
|
|
1207
|
+
const clientToMs = parseFiniteNumber(p.get("to_ms"));
|
|
1208
|
+
let fromMs;
|
|
1209
|
+
let toMs;
|
|
1210
|
+
if (clientFromMs !== void 0 && clientToMs !== void 0) {
|
|
1211
|
+
fromMs = clientFromMs;
|
|
1212
|
+
toMs = clientToMs;
|
|
1213
|
+
} else {
|
|
1214
|
+
fromMs = parseLocalDate(resolvedFrom).getTime();
|
|
1215
|
+
toMs = localDateEndMs(resolvedTo);
|
|
1216
|
+
}
|
|
1217
|
+
if (toMs - fromMs > 840 * 60 * 60 * 1e3) return jsonError(c, 400, {
|
|
1218
|
+
message: "Hourly granularity is only supported for ranges up to 35 days.",
|
|
1219
|
+
type: "bad_request"
|
|
1220
|
+
});
|
|
1221
|
+
const result$1 = statsStore.getHourlyPremiumStats({
|
|
1222
|
+
fromMs,
|
|
1223
|
+
toMs,
|
|
1224
|
+
accountId
|
|
1225
|
+
});
|
|
1226
|
+
return c.json({
|
|
1227
|
+
daily: result$1.daily,
|
|
1228
|
+
by_account: result$1.byAccount,
|
|
1229
|
+
range: {
|
|
1230
|
+
from: resolvedFrom,
|
|
1231
|
+
to: resolvedTo,
|
|
1232
|
+
granularity
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
const result = statsStore.getDailyPremiumStats({
|
|
1237
|
+
from: resolvedFrom,
|
|
1238
|
+
to: resolvedTo,
|
|
1239
|
+
accountId
|
|
1240
|
+
});
|
|
1241
|
+
return c.json({
|
|
1242
|
+
daily: result.daily,
|
|
1243
|
+
by_account: result.byAccount,
|
|
1244
|
+
range: {
|
|
1245
|
+
from: resolvedFrom,
|
|
1246
|
+
to: resolvedTo,
|
|
1247
|
+
granularity
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
});
|
|
1699
1251
|
|
|
1700
1252
|
//#endregion
|
|
1701
1253
|
//#region src/routes/admin/route.ts
|
|
@@ -2365,39 +1917,122 @@ function toAccountContext(account) {
|
|
|
2365
1917
|
clientSessionId: account.clientSessionId
|
|
2366
1918
|
};
|
|
2367
1919
|
}
|
|
1920
|
+
const OWNERSHIP_MISMATCH_PATTERN = /does not belong to this connection/i;
|
|
1921
|
+
const UUID_PATTERN = /\b[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi;
|
|
1922
|
+
const OPAQUE_ID_PATTERN = /\b(?:msg|resp|chatcmpl|out|toolu|call|item)_[\w-]+\b/g;
|
|
1923
|
+
const UPSTREAM_ERROR_MAX_LENGTH = 1e3;
|
|
1924
|
+
const UPSTREAM_ERROR_READ_FAILED_SENTINEL = "[upstream body read failed]";
|
|
1925
|
+
function sanitizeUpstreamErrorMessage(value) {
|
|
1926
|
+
return truncate(value.trim().replaceAll(UUID_PATTERN, "<uuid>").replaceAll(OPAQUE_ID_PATTERN, "<opaque_id>"), UPSTREAM_ERROR_MAX_LENGTH);
|
|
1927
|
+
}
|
|
1928
|
+
function readErrorField(value) {
|
|
1929
|
+
return typeof value === "string" ? value : void 0;
|
|
1930
|
+
}
|
|
1931
|
+
function formatUpstreamErrorBody(payload) {
|
|
1932
|
+
if (typeof payload === "string") {
|
|
1933
|
+
const text = payload.trim();
|
|
1934
|
+
return text.length > 0 ? text : void 0;
|
|
1935
|
+
}
|
|
1936
|
+
if (!payload || typeof payload !== "object") return;
|
|
1937
|
+
const root = payload;
|
|
1938
|
+
const message = readErrorField(root.error?.message) ?? readErrorField(root.message);
|
|
1939
|
+
const code = readErrorField(root.error?.code) ?? readErrorField(root.code);
|
|
1940
|
+
if (message && code) return `${message} [code:${code}]`;
|
|
1941
|
+
return message ?? (code ? `[code:${code}]` : void 0);
|
|
1942
|
+
}
|
|
1943
|
+
async function extractUpstreamErrorMessageRaw(error) {
|
|
1944
|
+
if (!(error instanceof HTTPError)) return { upstreamErrorMessageReadFailed: false };
|
|
1945
|
+
try {
|
|
1946
|
+
const bodyText = await error.response.clone().text();
|
|
1947
|
+
if (!bodyText) return { upstreamErrorMessageReadFailed: false };
|
|
1948
|
+
let candidate;
|
|
1949
|
+
try {
|
|
1950
|
+
candidate = formatUpstreamErrorBody(JSON.parse(bodyText));
|
|
1951
|
+
} catch {
|
|
1952
|
+
candidate = bodyText;
|
|
1953
|
+
}
|
|
1954
|
+
if (!candidate) return { upstreamErrorMessageReadFailed: false };
|
|
1955
|
+
const sanitized = sanitizeUpstreamErrorMessage(candidate);
|
|
1956
|
+
return {
|
|
1957
|
+
upstreamErrorMessageRaw: sanitized.length > 0 ? sanitized : void 0,
|
|
1958
|
+
upstreamErrorMessageReadFailed: false
|
|
1959
|
+
};
|
|
1960
|
+
} catch (readError) {
|
|
1961
|
+
consola.warn("Failed to read upstream HTTP error response body:", {
|
|
1962
|
+
status: error.response.status,
|
|
1963
|
+
readError
|
|
1964
|
+
});
|
|
1965
|
+
return { upstreamErrorMessageReadFailed: true };
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
2368
1968
|
function extractErrorDetails(error) {
|
|
2369
1969
|
const errorName = error instanceof Error ? error.name : "Error";
|
|
2370
1970
|
const errorMessage = error instanceof Error ? truncate(error.message) : truncate(String(error));
|
|
2371
1971
|
const errorStatus = error instanceof HTTPError ? error.response.status : void 0;
|
|
1972
|
+
const httpStatus = errorStatus ?? 500;
|
|
1973
|
+
const unauthorized = errorStatus === 401;
|
|
2372
1974
|
return {
|
|
2373
|
-
httpStatus
|
|
1975
|
+
httpStatus,
|
|
2374
1976
|
errorName,
|
|
2375
1977
|
errorStatus,
|
|
2376
1978
|
errorMessage,
|
|
2377
|
-
unauthorized
|
|
1979
|
+
unauthorized,
|
|
1980
|
+
ownershipMismatch: unauthorized && OWNERSHIP_MISMATCH_PATTERN.test(errorMessage)
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
async function extractErrorObservability(error) {
|
|
1984
|
+
const details = extractErrorDetails(error);
|
|
1985
|
+
const { upstreamErrorMessageRaw, upstreamErrorMessageReadFailed } = await extractUpstreamErrorMessageRaw(error);
|
|
1986
|
+
const observableUpstreamErrorMessageRaw = upstreamErrorMessageRaw ?? (upstreamErrorMessageReadFailed ? UPSTREAM_ERROR_READ_FAILED_SENTINEL : void 0);
|
|
1987
|
+
return {
|
|
1988
|
+
...details,
|
|
1989
|
+
ownershipMismatch: details.ownershipMismatch || details.unauthorized && OWNERSHIP_MISMATCH_PATTERN.test(upstreamErrorMessageRaw ?? ""),
|
|
1990
|
+
upstreamErrorMessageRaw: observableUpstreamErrorMessageRaw,
|
|
1991
|
+
upstreamErrorMessageReadFailed
|
|
2378
1992
|
};
|
|
2379
1993
|
}
|
|
1994
|
+
function getUserVisibleErrorMessage(details) {
|
|
1995
|
+
if (details.upstreamErrorMessageReadFailed) return details.errorMessage;
|
|
1996
|
+
return details.upstreamErrorMessageRaw ?? details.errorMessage;
|
|
1997
|
+
}
|
|
1998
|
+
/**
|
|
1999
|
+
* Returns true when the 401 indicates a genuine auth/token failure
|
|
2000
|
+
* (not an ownership mismatch) and the account should be marked failed.
|
|
2001
|
+
*/
|
|
2002
|
+
function shouldMarkAccountFailed(details) {
|
|
2003
|
+
return details.unauthorized && !details.ownershipMismatch && details.upstreamErrorMessageReadFailed !== true;
|
|
2004
|
+
}
|
|
2380
2005
|
|
|
2381
2006
|
//#endregion
|
|
2382
2007
|
//#region src/lib/logger.ts
|
|
2383
2008
|
const LOG_RETENTION_MS = 10080 * 60 * 1e3;
|
|
2384
2009
|
const CLEANUP_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
2385
|
-
const LOG_DIR = path.join(PATHS.APP_DIR, "logs");
|
|
2386
2010
|
const FLUSH_INTERVAL_MS = 1e3;
|
|
2387
2011
|
const MAX_BUFFER_SIZE = 100;
|
|
2012
|
+
const FILE_LOG_LEVEL_PRIORITY = {
|
|
2013
|
+
error: 0,
|
|
2014
|
+
warn: 1,
|
|
2015
|
+
info: 2,
|
|
2016
|
+
debug: 3
|
|
2017
|
+
};
|
|
2018
|
+
let logDir = path.join(PATHS.APP_DIR, "logs");
|
|
2019
|
+
let testLogLevelOverride;
|
|
2388
2020
|
const logStreams = /* @__PURE__ */ new Map();
|
|
2389
2021
|
const logBuffers = /* @__PURE__ */ new Map();
|
|
2390
2022
|
let runtimeInitialized = false;
|
|
2391
2023
|
let flushInterval;
|
|
2392
2024
|
let cleanupInterval;
|
|
2025
|
+
let exitHandler;
|
|
2026
|
+
let sigintHandler;
|
|
2027
|
+
let sigtermHandler;
|
|
2393
2028
|
const ensureLogDirectory = () => {
|
|
2394
|
-
if (!fs$1.existsSync(
|
|
2029
|
+
if (!fs$1.existsSync(logDir)) fs$1.mkdirSync(logDir, { recursive: true });
|
|
2395
2030
|
};
|
|
2396
2031
|
const cleanupOldLogs = () => {
|
|
2397
|
-
if (!fs$1.existsSync(
|
|
2032
|
+
if (!fs$1.existsSync(logDir)) return;
|
|
2398
2033
|
const now = Date.now();
|
|
2399
|
-
for (const entry of fs$1.readdirSync(
|
|
2400
|
-
const filePath = path.join(
|
|
2034
|
+
for (const entry of fs$1.readdirSync(logDir)) {
|
|
2035
|
+
const filePath = path.join(logDir, entry);
|
|
2401
2036
|
let stats;
|
|
2402
2037
|
try {
|
|
2403
2038
|
stats = fs$1.statSync(filePath);
|
|
@@ -2420,6 +2055,14 @@ const sanitizeName = (name) => {
|
|
|
2420
2055
|
const normalized = name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
2421
2056
|
return normalized === "" ? "handler" : normalized;
|
|
2422
2057
|
};
|
|
2058
|
+
const getHandlerLogFilePath = (name, date) => {
|
|
2059
|
+
const dateKey = [
|
|
2060
|
+
date.getFullYear(),
|
|
2061
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
2062
|
+
String(date.getDate()).padStart(2, "0")
|
|
2063
|
+
].join("-");
|
|
2064
|
+
return path.join(logDir, `${sanitizeName(name)}-${dateKey}.log`);
|
|
2065
|
+
};
|
|
2423
2066
|
const maybeUnref = (timer) => {
|
|
2424
2067
|
timer.unref();
|
|
2425
2068
|
};
|
|
@@ -2459,15 +2102,18 @@ const initializeLoggerRuntime = () => {
|
|
|
2459
2102
|
maybeUnref(flushInterval);
|
|
2460
2103
|
cleanupInterval = setInterval(cleanupOldLogs, CLEANUP_INTERVAL_MS);
|
|
2461
2104
|
maybeUnref(cleanupInterval);
|
|
2462
|
-
|
|
2463
|
-
|
|
2105
|
+
exitHandler = cleanup;
|
|
2106
|
+
sigintHandler = () => {
|
|
2464
2107
|
cleanup();
|
|
2465
2108
|
process.exit(0);
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2109
|
+
};
|
|
2110
|
+
sigtermHandler = () => {
|
|
2468
2111
|
cleanup();
|
|
2469
2112
|
process.exit(0);
|
|
2470
|
-
}
|
|
2113
|
+
};
|
|
2114
|
+
process.once("exit", exitHandler);
|
|
2115
|
+
process.once("SIGINT", sigintHandler);
|
|
2116
|
+
process.once("SIGTERM", sigtermHandler);
|
|
2471
2117
|
};
|
|
2472
2118
|
const getLogStream = (filePath) => {
|
|
2473
2119
|
initializeLoggerRuntime();
|
|
@@ -2491,8 +2137,21 @@ const appendLine = (filePath, line) => {
|
|
|
2491
2137
|
buffer.push(line);
|
|
2492
2138
|
if (buffer.length >= MAX_BUFFER_SIZE) flushBuffer(filePath);
|
|
2493
2139
|
};
|
|
2140
|
+
const normalizeLogTypeToLevel = (type) => {
|
|
2141
|
+
switch (type) {
|
|
2142
|
+
case "error": return "error";
|
|
2143
|
+
case "warn": return "warn";
|
|
2144
|
+
case "debug": return "debug";
|
|
2145
|
+
default: return "info";
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
const shouldWriteFileLog = (type, logLevel = resolveLogLevel()) => {
|
|
2149
|
+
return FILE_LOG_LEVEL_PRIORITY[normalizeLogTypeToLevel(type)] <= FILE_LOG_LEVEL_PRIORITY[logLevel];
|
|
2150
|
+
};
|
|
2151
|
+
const resolveLogLevel = () => testLogLevelOverride ?? getLogLevel();
|
|
2152
|
+
const isDebugFileLoggingEnabled = () => resolveLogLevel() === "debug";
|
|
2494
2153
|
const debugLazy = (logger$7, factory) => {
|
|
2495
|
-
if (!
|
|
2154
|
+
if (!isDebugFileLoggingEnabled()) return;
|
|
2496
2155
|
logger$7.debug(...factory());
|
|
2497
2156
|
};
|
|
2498
2157
|
const debugJson = (logger$7, label, value) => {
|
|
@@ -2501,18 +2160,31 @@ const debugJson = (logger$7, label, value) => {
|
|
|
2501
2160
|
const debugJsonTail = (logger$7, label, { value, tailLength = 400 }) => {
|
|
2502
2161
|
debugLazy(logger$7, () => [label, JSON.stringify(value).slice(-tailLength)]);
|
|
2503
2162
|
};
|
|
2163
|
+
const getConsolaLevel = () => {
|
|
2164
|
+
const logLevel = resolveLogLevel();
|
|
2165
|
+
switch (logLevel) {
|
|
2166
|
+
case "error": return 0;
|
|
2167
|
+
case "warn": return 1;
|
|
2168
|
+
case "info": return 2;
|
|
2169
|
+
case "debug": return 4;
|
|
2170
|
+
default: return logLevel;
|
|
2171
|
+
}
|
|
2172
|
+
};
|
|
2504
2173
|
const createHandlerLogger = (name) => {
|
|
2505
|
-
const sanitizedName = sanitizeName(name);
|
|
2506
2174
|
const instance = consola.withTag(name);
|
|
2507
|
-
|
|
2175
|
+
Object.defineProperty(instance, "level", {
|
|
2176
|
+
get: getConsolaLevel,
|
|
2177
|
+
configurable: true
|
|
2178
|
+
});
|
|
2508
2179
|
instance.setReporters([]);
|
|
2509
2180
|
instance.addReporter({ log(logObj) {
|
|
2181
|
+
const fileLogLevel = resolveLogLevel();
|
|
2182
|
+
if (!shouldWriteFileLog(logObj.type, fileLogLevel)) return;
|
|
2510
2183
|
initializeLoggerRuntime();
|
|
2511
2184
|
const traceId = requestContext.getStore()?.traceId;
|
|
2512
2185
|
const date = logObj.date;
|
|
2513
|
-
const dateKey = date.toLocaleDateString("sv-SE");
|
|
2514
2186
|
const timestamp = date.toLocaleString("sv-SE", { hour12: false });
|
|
2515
|
-
const filePath =
|
|
2187
|
+
const filePath = getHandlerLogFilePath(name, date);
|
|
2516
2188
|
const message = formatArgs(logObj.args);
|
|
2517
2189
|
const traceIdStr = traceId ? ` [${traceId}]` : "";
|
|
2518
2190
|
appendLine(filePath, `[${timestamp}] [${logObj.type}] [${logObj.tag || name}]${traceIdStr}${message ? ` ${message}` : ""}`);
|
|
@@ -2761,6 +2433,13 @@ const getTokenCount = async (payload, model) => {
|
|
|
2761
2433
|
};
|
|
2762
2434
|
};
|
|
2763
2435
|
|
|
2436
|
+
//#endregion
|
|
2437
|
+
//#region src/lib/request-initiator.ts
|
|
2438
|
+
function resolveEffectiveInitiator(baseInitiator, options) {
|
|
2439
|
+
if (options.isCompact || options.isSubagent) return "agent";
|
|
2440
|
+
return baseInitiator;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2764
2443
|
//#endregion
|
|
2765
2444
|
//#region src/services/copilot/create-chat-completions.ts
|
|
2766
2445
|
function isGpt5MiniFamily(modelId) {
|
|
@@ -2784,14 +2463,18 @@ const createChatCompletions = async (payload, account, options) => {
|
|
|
2784
2463
|
const ctx = account ?? accountFromState();
|
|
2785
2464
|
if (!ctx.copilotToken) throw new Error("Copilot token not found");
|
|
2786
2465
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x$1) => x$1.type === "image_url"));
|
|
2787
|
-
const
|
|
2466
|
+
const effectiveInitiator = resolveEffectiveInitiator(options?.initiator ?? getChatInitiator(payload.messages), {
|
|
2467
|
+
isCompact: Boolean(options?.compactType),
|
|
2468
|
+
isSubagent: Boolean(options?.subagentMarker)
|
|
2469
|
+
});
|
|
2788
2470
|
const headers = {
|
|
2789
2471
|
...copilotHeaders(ctx, enableVision, options?.upstreamRequestId),
|
|
2790
|
-
"x-initiator":
|
|
2472
|
+
"x-initiator": effectiveInitiator
|
|
2791
2473
|
};
|
|
2792
2474
|
prepareInteractionHeaders(options?.sessionId, Boolean(options?.subagentMarker), headers);
|
|
2793
2475
|
const upstreamPayload = applyDefaultReasoningEffort(payload);
|
|
2794
|
-
prepareForCompact(headers, options?.
|
|
2476
|
+
prepareForCompact(headers, options?.compactType);
|
|
2477
|
+
captureOutboundHeadersSnapshot(headers);
|
|
2795
2478
|
const response = await fetch(`${copilotBaseUrl(ctx)}/chat/completions`, {
|
|
2796
2479
|
method: "POST",
|
|
2797
2480
|
headers,
|
|
@@ -2806,100 +2489,10 @@ const createChatCompletions = async (payload, account, options) => {
|
|
|
2806
2489
|
};
|
|
2807
2490
|
|
|
2808
2491
|
//#endregion
|
|
2809
|
-
//#region src/routes/chat-completions/
|
|
2810
|
-
const logger$6 = createHandlerLogger("chat-completions-handler");
|
|
2492
|
+
//#region src/routes/chat-completions/support.ts
|
|
2811
2493
|
const CHAT_COMPLETIONS_ENDPOINT$1 = "/chat/completions";
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
const store = getRequestHistoryStore();
|
|
2815
|
-
const request = buildRequestContext$1(c);
|
|
2816
|
-
const payload = await c.req.json();
|
|
2817
|
-
const clientModel = payload.model;
|
|
2818
|
-
const streamRequested = Boolean(payload.stream);
|
|
2819
|
-
const initiator = getChatInitiator(payload.messages);
|
|
2820
|
-
const userId = payload.user ?? void 0;
|
|
2821
|
-
const { safetyIdentifier, sessionId: promptCacheKey } = parseUserIdMetadata(userId);
|
|
2822
|
-
const normalizedSafetyIdentifier = safetyIdentifier ?? void 0;
|
|
2823
|
-
const normalizedPromptCacheKey = promptCacheKey ?? void 0;
|
|
2824
|
-
request.userId = userId;
|
|
2825
|
-
request.safetyIdentifier = normalizedSafetyIdentifier;
|
|
2826
|
-
request.promptCacheKey = normalizedPromptCacheKey;
|
|
2827
|
-
request.initiator = initiator;
|
|
2828
|
-
if (getAliasTargetSet().has(clientModel.toLowerCase())) {
|
|
2829
|
-
recordSelectionFailure$2(store, {
|
|
2830
|
-
request,
|
|
2831
|
-
clientModel,
|
|
2832
|
-
stream: streamRequested,
|
|
2833
|
-
reason: "MODEL_NOT_SUPPORTED"
|
|
2834
|
-
});
|
|
2835
|
-
return selectionFailureResponse$2(c, {
|
|
2836
|
-
clientModel,
|
|
2837
|
-
reason: "MODEL_NOT_SUPPORTED"
|
|
2838
|
-
});
|
|
2839
|
-
}
|
|
2840
|
-
debugJsonTail(logger$6, "Request payload:", {
|
|
2841
|
-
value: payload,
|
|
2842
|
-
tailLength: 400
|
|
2843
|
-
});
|
|
2844
|
-
const upstreamRequestId = generateRequestIdFromPayload(payload, normalizedPromptCacheKey);
|
|
2845
|
-
const selection = await accountsManager.selectAccountForRequest([{
|
|
2846
|
-
modelId: clientModel,
|
|
2847
|
-
endpoint: CHAT_COMPLETIONS_ENDPOINT$1
|
|
2848
|
-
}], { requestId: upstreamRequestId });
|
|
2849
|
-
if (!selection.ok) {
|
|
2850
|
-
recordSelectionFailure$2(store, {
|
|
2851
|
-
request,
|
|
2852
|
-
clientModel,
|
|
2853
|
-
stream: streamRequested,
|
|
2854
|
-
reason: selection.reason
|
|
2855
|
-
});
|
|
2856
|
-
return selectionFailureResponse$2(c, {
|
|
2857
|
-
clientModel,
|
|
2858
|
-
reason: selection.reason
|
|
2859
|
-
});
|
|
2860
|
-
}
|
|
2861
|
-
const { account, selectedModel } = selection;
|
|
2862
|
-
request.affinityHit = selection.affinityHit;
|
|
2863
|
-
request.affinityCacheKey = selection.affinityCacheKey;
|
|
2864
|
-
const upstreamPayload = {
|
|
2865
|
-
...payload,
|
|
2866
|
-
model: selectedModel.id
|
|
2867
|
-
};
|
|
2868
|
-
const premiumRemainingBefore = account.premiumRemaining;
|
|
2869
|
-
const premiumUnlimitedBefore = account.unlimited;
|
|
2870
|
-
await logTokenCountForRequest({
|
|
2871
|
-
payload: upstreamPayload,
|
|
2872
|
-
selectedModel
|
|
2873
|
-
});
|
|
2874
|
-
if (state.manualApprove) await awaitApproval();
|
|
2875
|
-
const payloadWithMaxTokens = applyDefaultMaxTokens(upstreamPayload, selectedModel);
|
|
2876
|
-
const accountCtx = toAccountContext(account);
|
|
2877
|
-
const upstreamSessionId = getUUID(upstreamRequestId);
|
|
2878
|
-
request.upstreamRequestId = upstreamRequestId;
|
|
2879
|
-
request.upstreamSessionId = upstreamSessionId;
|
|
2880
|
-
if (streamRequested) return handleStreamingRequest({
|
|
2881
|
-
c,
|
|
2882
|
-
store,
|
|
2883
|
-
request,
|
|
2884
|
-
payload: payloadWithMaxTokens,
|
|
2885
|
-
selection,
|
|
2886
|
-
accountCtx,
|
|
2887
|
-
clientModel,
|
|
2888
|
-
premiumRemainingBefore,
|
|
2889
|
-
premiumUnlimitedBefore
|
|
2890
|
-
});
|
|
2891
|
-
return handleNonStreamingRequest({
|
|
2892
|
-
c,
|
|
2893
|
-
store,
|
|
2894
|
-
request,
|
|
2895
|
-
payload: payloadWithMaxTokens,
|
|
2896
|
-
selection,
|
|
2897
|
-
accountCtx,
|
|
2898
|
-
clientModel,
|
|
2899
|
-
premiumRemainingBefore,
|
|
2900
|
-
premiumUnlimitedBefore
|
|
2901
|
-
});
|
|
2902
|
-
}
|
|
2494
|
+
const GPT_5_4_MODEL_ID = "gpt-5.4";
|
|
2495
|
+
const GPT_5_4_CHAT_COMPLETIONS_MESSAGE = "Please use `/v1/responses` or `/v1/messages` API";
|
|
2903
2496
|
function buildRequestContext$1(c) {
|
|
2904
2497
|
const requestId = randomUUID();
|
|
2905
2498
|
const startedAtMs = Date.now();
|
|
@@ -2930,34 +2523,254 @@ function insertRequestLog$2(store, request, record) {
|
|
|
2930
2523
|
promptCacheKey: request.promptCacheKey,
|
|
2931
2524
|
initiator: request.initiator,
|
|
2932
2525
|
upstreamRequestId: request.upstreamRequestId,
|
|
2526
|
+
affinityKeyUsed: request.affinityKeyUsed,
|
|
2527
|
+
affinityKeySource: request.affinityKeySource,
|
|
2528
|
+
selectionReason: request.selectionReason,
|
|
2933
2529
|
affinityHit: request.affinityHit,
|
|
2934
2530
|
affinityCacheKey: request.affinityCacheKey,
|
|
2935
2531
|
...record
|
|
2936
2532
|
});
|
|
2937
|
-
}
|
|
2938
|
-
function
|
|
2939
|
-
const { request, stream, clientModel,
|
|
2940
|
-
const finishedAtMs = Date.now();
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2533
|
+
}
|
|
2534
|
+
function recordUnsupportedChatCompletionsModel(store, params) {
|
|
2535
|
+
const { request, stream, clientModel, upstreamModel, accountId, accountType, costUnits, premiumRemainingBefore, premiumRemainingAfter, premiumUnlimitedBefore, premiumUnlimitedAfter } = params;
|
|
2536
|
+
const finishedAtMs = Date.now();
|
|
2537
|
+
let premiumRemainingDiff;
|
|
2538
|
+
if (premiumRemainingBefore !== void 0 && premiumRemainingAfter !== void 0) premiumRemainingDiff = premiumRemainingAfter - premiumRemainingBefore;
|
|
2539
|
+
insertRequestLog$2(store, request, {
|
|
2540
|
+
finishedAtMs,
|
|
2541
|
+
durationMs: finishedAtMs - request.startedAtMs,
|
|
2542
|
+
upstreamEndpoint: CHAT_COMPLETIONS_ENDPOINT$1,
|
|
2543
|
+
stream,
|
|
2544
|
+
accountId,
|
|
2545
|
+
accountType,
|
|
2546
|
+
costUnits,
|
|
2547
|
+
clientModel,
|
|
2548
|
+
upstreamModel,
|
|
2549
|
+
premiumRemainingBefore,
|
|
2550
|
+
premiumRemainingAfter,
|
|
2551
|
+
premiumRemainingDiff,
|
|
2552
|
+
premiumUnlimitedBefore,
|
|
2553
|
+
premiumUnlimitedAfter,
|
|
2554
|
+
httpStatus: 400,
|
|
2555
|
+
errorName: "invalid_request_error",
|
|
2556
|
+
errorMessage: GPT_5_4_CHAT_COMPLETIONS_MESSAGE
|
|
2557
|
+
});
|
|
2558
|
+
}
|
|
2559
|
+
function unsupportedChatCompletionsModelResponse(c) {
|
|
2560
|
+
return c.json({ error: {
|
|
2561
|
+
message: GPT_5_4_CHAT_COMPLETIONS_MESSAGE,
|
|
2562
|
+
type: "invalid_request_error"
|
|
2563
|
+
} }, 400);
|
|
2564
|
+
}
|
|
2565
|
+
function getSelectionFailureStatus(reason) {
|
|
2566
|
+
if (reason === "MODEL_NOT_SUPPORTED") return 400;
|
|
2567
|
+
if (reason === "NO_ACCOUNTS") return 503;
|
|
2568
|
+
return 429;
|
|
2569
|
+
}
|
|
2570
|
+
function recordSelectionFailure$2(store, params) {
|
|
2571
|
+
const { request, stream, clientModel, reason } = params;
|
|
2572
|
+
const finishedAtMs = Date.now();
|
|
2573
|
+
insertRequestLog$2(store, request, {
|
|
2574
|
+
finishedAtMs,
|
|
2575
|
+
durationMs: finishedAtMs - request.startedAtMs,
|
|
2576
|
+
upstreamEndpoint: CHAT_COMPLETIONS_ENDPOINT$1,
|
|
2577
|
+
stream,
|
|
2578
|
+
clientModel,
|
|
2579
|
+
httpStatus: getSelectionFailureStatus(reason),
|
|
2580
|
+
selectionFailureReason: reason
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
function selectionFailureResponse$2(c, params) {
|
|
2584
|
+
const { clientModel, reason } = params;
|
|
2585
|
+
if (reason === "MODEL_NOT_SUPPORTED") return c.json({ error: {
|
|
2586
|
+
message: `Model "${clientModel}" is not available for any configured account.`,
|
|
2587
|
+
type: "invalid_request_error"
|
|
2588
|
+
} }, 400);
|
|
2589
|
+
if (reason === "NO_ACCOUNTS") return c.json({ error: {
|
|
2590
|
+
message: "No enabled Copilot accounts are configured. Add or re-enable an account.",
|
|
2591
|
+
type: "service_unavailable_error"
|
|
2592
|
+
} }, 503);
|
|
2593
|
+
return c.json({ error: {
|
|
2594
|
+
message: "All accounts have exhausted their quota. Please wait for quota refresh or add additional accounts.",
|
|
2595
|
+
type: "rate_limit_error"
|
|
2596
|
+
} }, 429);
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
//#endregion
|
|
2600
|
+
//#region src/routes/chat-completions/handler.ts
|
|
2601
|
+
const logger$6 = createHandlerLogger("chat-completions-handler");
|
|
2602
|
+
function buildChatCompletionCandidates(clientModel) {
|
|
2603
|
+
return [{
|
|
2604
|
+
modelId: clientModel,
|
|
2605
|
+
endpoint: CHAT_COMPLETIONS_ENDPOINT$1
|
|
2606
|
+
}];
|
|
2607
|
+
}
|
|
2608
|
+
function isUnsupportedChatCompletionsModel(modelId) {
|
|
2609
|
+
return resolveModelAlias(modelId).toLowerCase() === GPT_5_4_MODEL_ID;
|
|
2610
|
+
}
|
|
2611
|
+
function maybeRejectChatCompletionsClientModel(c, store, params) {
|
|
2612
|
+
const { request, clientModel, streamRequested } = params;
|
|
2613
|
+
if (isUnsupportedChatCompletionsModel(clientModel)) {
|
|
2614
|
+
recordUnsupportedChatCompletionsModel(store, {
|
|
2615
|
+
request,
|
|
2616
|
+
clientModel,
|
|
2617
|
+
stream: streamRequested
|
|
2618
|
+
});
|
|
2619
|
+
return unsupportedChatCompletionsModelResponse(c);
|
|
2620
|
+
}
|
|
2621
|
+
if (getAliasTargetSet().has(clientModel.toLowerCase())) {
|
|
2622
|
+
recordSelectionFailure$2(store, {
|
|
2623
|
+
request,
|
|
2624
|
+
clientModel,
|
|
2625
|
+
stream: streamRequested,
|
|
2626
|
+
reason: "MODEL_NOT_SUPPORTED"
|
|
2627
|
+
});
|
|
2628
|
+
return selectionFailureResponse$2(c, {
|
|
2629
|
+
clientModel,
|
|
2630
|
+
reason: "MODEL_NOT_SUPPORTED"
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
return null;
|
|
2634
|
+
}
|
|
2635
|
+
async function handleCompletion$1(c) {
|
|
2636
|
+
await checkRateLimit(state);
|
|
2637
|
+
const store = getRequestHistoryStore();
|
|
2638
|
+
const request = buildRequestContext$1(c);
|
|
2639
|
+
const payload = await c.req.json();
|
|
2640
|
+
const clientModel = payload.model;
|
|
2641
|
+
const streamRequested = Boolean(payload.stream);
|
|
2642
|
+
const normalizedPromptCacheKey = applyChatRequestMetadata(request, payload, getChatInitiator(payload.messages));
|
|
2643
|
+
const blockedResponse = maybeRejectChatCompletionsClientModel(c, store, {
|
|
2644
|
+
request,
|
|
2645
|
+
clientModel,
|
|
2646
|
+
streamRequested
|
|
2647
|
+
});
|
|
2648
|
+
if (blockedResponse) return blockedResponse;
|
|
2649
|
+
const selectionResult = await selectChatCompletionAccount({
|
|
2650
|
+
c,
|
|
2651
|
+
store,
|
|
2652
|
+
request,
|
|
2653
|
+
payload,
|
|
2654
|
+
clientModel,
|
|
2655
|
+
streamRequested,
|
|
2656
|
+
normalizedPromptCacheKey
|
|
2657
|
+
});
|
|
2658
|
+
if (selectionResult instanceof Response) return selectionResult;
|
|
2659
|
+
const { headerSessionId, selection, upstreamRequestId } = selectionResult;
|
|
2660
|
+
const { account, reservation, selectedModel } = selection;
|
|
2661
|
+
request.affinityHit = selection.affinityHit;
|
|
2662
|
+
request.affinityCacheKey = selection.affinityCacheKey;
|
|
2663
|
+
request.selectionReason = selection.selectionReason;
|
|
2664
|
+
const premiumRemainingBefore = account.premiumRemaining;
|
|
2665
|
+
const premiumUnlimitedBefore = account.unlimited;
|
|
2666
|
+
if (selectedModel.id === GPT_5_4_MODEL_ID) {
|
|
2667
|
+
await accountsManager.finalizeQuota(account, reservation);
|
|
2668
|
+
recordUnsupportedChatCompletionsModel(store, {
|
|
2669
|
+
request,
|
|
2670
|
+
clientModel,
|
|
2671
|
+
stream: streamRequested,
|
|
2672
|
+
upstreamModel: selectedModel.id,
|
|
2673
|
+
accountId: account.id,
|
|
2674
|
+
accountType: account.accountType,
|
|
2675
|
+
costUnits: selection.costUnits,
|
|
2676
|
+
premiumRemainingBefore,
|
|
2677
|
+
premiumRemainingAfter: account.premiumRemaining,
|
|
2678
|
+
premiumUnlimitedBefore,
|
|
2679
|
+
premiumUnlimitedAfter: account.unlimited
|
|
2680
|
+
});
|
|
2681
|
+
return unsupportedChatCompletionsModelResponse(c);
|
|
2682
|
+
}
|
|
2683
|
+
const upstreamPayload = {
|
|
2684
|
+
...payload,
|
|
2685
|
+
model: selectedModel.id
|
|
2686
|
+
};
|
|
2687
|
+
await logTokenCountForRequest({
|
|
2688
|
+
payload: upstreamPayload,
|
|
2689
|
+
selectedModel
|
|
2690
|
+
});
|
|
2691
|
+
if (state.manualApprove) await awaitApproval();
|
|
2692
|
+
const payloadWithMaxTokens = applyDefaultMaxTokens(upstreamPayload, selectedModel);
|
|
2693
|
+
const accountCtx = toAccountContext(account);
|
|
2694
|
+
const upstreamSessionId = getUUID(normalizedPromptCacheKey ?? headerSessionId ?? upstreamRequestId);
|
|
2695
|
+
request.upstreamRequestId = upstreamRequestId;
|
|
2696
|
+
request.upstreamSessionId = upstreamSessionId;
|
|
2697
|
+
if (streamRequested) return handleStreamingRequest({
|
|
2698
|
+
c,
|
|
2699
|
+
store,
|
|
2700
|
+
request,
|
|
2701
|
+
payload: payloadWithMaxTokens,
|
|
2702
|
+
selection,
|
|
2703
|
+
accountCtx,
|
|
2704
|
+
clientModel,
|
|
2705
|
+
premiumRemainingBefore,
|
|
2706
|
+
premiumUnlimitedBefore
|
|
2707
|
+
});
|
|
2708
|
+
return handleNonStreamingRequest({
|
|
2709
|
+
c,
|
|
2710
|
+
store,
|
|
2711
|
+
request,
|
|
2712
|
+
payload: payloadWithMaxTokens,
|
|
2713
|
+
selection,
|
|
2714
|
+
accountCtx,
|
|
2946
2715
|
clientModel,
|
|
2947
|
-
|
|
2948
|
-
|
|
2716
|
+
premiumRemainingBefore,
|
|
2717
|
+
premiumUnlimitedBefore
|
|
2949
2718
|
});
|
|
2950
2719
|
}
|
|
2951
|
-
function
|
|
2952
|
-
const
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2720
|
+
function applyChatRequestMetadata(request, payload, initiator) {
|
|
2721
|
+
const userId = payload.user ?? void 0;
|
|
2722
|
+
const { safetyIdentifier, sessionId: promptCacheKey } = parseUserIdMetadata(userId);
|
|
2723
|
+
const normalizedPromptCacheKey = promptCacheKey ?? void 0;
|
|
2724
|
+
request.userId = userId;
|
|
2725
|
+
request.safetyIdentifier = safetyIdentifier ?? void 0;
|
|
2726
|
+
request.promptCacheKey = normalizedPromptCacheKey;
|
|
2727
|
+
request.initiator = initiator;
|
|
2728
|
+
return normalizedPromptCacheKey;
|
|
2729
|
+
}
|
|
2730
|
+
async function writeChatCompletionsStreamError(stream, message) {
|
|
2731
|
+
try {
|
|
2732
|
+
await stream.writeSSE({ data: JSON.stringify({ error: {
|
|
2733
|
+
message,
|
|
2734
|
+
type: "error"
|
|
2735
|
+
} }) });
|
|
2736
|
+
await stream.writeSSE({ data: "[DONE]" });
|
|
2737
|
+
} catch (streamError) {
|
|
2738
|
+
logger$6.warn("Failed to write chat completions stream error event:", streamError);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
async function selectChatCompletionAccount(params) {
|
|
2742
|
+
const { c, store, request, payload, clientModel, streamRequested, normalizedPromptCacheKey } = params;
|
|
2743
|
+
debugJsonTail(logger$6, "Request payload:", {
|
|
2744
|
+
value: payload,
|
|
2745
|
+
tailLength: 400
|
|
2746
|
+
});
|
|
2747
|
+
const upstreamRequestId = generateRequestIdFromPayload(payload, normalizedPromptCacheKey);
|
|
2748
|
+
const headerSessionId = c.req.header("x-session-id") ?? null;
|
|
2749
|
+
const affinityKey = resolveAffinityKey({
|
|
2750
|
+
metadataSessionId: normalizedPromptCacheKey,
|
|
2751
|
+
headerSessionId,
|
|
2752
|
+
upstreamRequestId
|
|
2753
|
+
});
|
|
2754
|
+
request.affinityKeyUsed = affinityKey.affinityKeyUsed;
|
|
2755
|
+
request.affinityKeySource = affinityKey.affinityKeySource;
|
|
2756
|
+
const selection = await accountsManager.selectAccountForRequest(buildChatCompletionCandidates(clientModel), { requestId: affinityKey.requestId });
|
|
2757
|
+
if (!selection.ok) {
|
|
2758
|
+
recordSelectionFailure$2(store, {
|
|
2759
|
+
request,
|
|
2760
|
+
clientModel,
|
|
2761
|
+
stream: streamRequested,
|
|
2762
|
+
reason: selection.reason
|
|
2763
|
+
});
|
|
2764
|
+
return selectionFailureResponse$2(c, {
|
|
2765
|
+
clientModel,
|
|
2766
|
+
reason: selection.reason
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
headerSessionId,
|
|
2771
|
+
selection,
|
|
2772
|
+
upstreamRequestId
|
|
2773
|
+
};
|
|
2961
2774
|
}
|
|
2962
2775
|
async function logTokenCountForRequest(params) {
|
|
2963
2776
|
try {
|
|
@@ -3022,8 +2835,8 @@ async function handleUpstreamCreateError$1(params) {
|
|
|
3022
2835
|
const { store, request, selection, clientModel, premiumRemainingBefore, premiumUnlimitedBefore, error } = params;
|
|
3023
2836
|
const { account, reservation, selectedModel, endpoint, costUnits } = selection;
|
|
3024
2837
|
const finishedAtMs = Date.now();
|
|
3025
|
-
const details =
|
|
3026
|
-
if (details
|
|
2838
|
+
const details = await extractErrorObservability(error);
|
|
2839
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
3027
2840
|
await accountsManager.finalizeQuota(account, reservation);
|
|
3028
2841
|
const premiumRemainingAfter = account.premiumRemaining;
|
|
3029
2842
|
const premiumUnlimitedAfter = account.unlimited;
|
|
@@ -3045,7 +2858,8 @@ async function handleUpstreamCreateError$1(params) {
|
|
|
3045
2858
|
httpStatus: details.httpStatus,
|
|
3046
2859
|
errorName: details.errorName,
|
|
3047
2860
|
errorStatus: details.errorStatus,
|
|
3048
|
-
errorMessage: details.errorMessage
|
|
2861
|
+
errorMessage: details.errorMessage,
|
|
2862
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
3049
2863
|
});
|
|
3050
2864
|
throw error;
|
|
3051
2865
|
}
|
|
@@ -3057,16 +2871,18 @@ async function handleNonStreamingUpstreamResponse(params) {
|
|
|
3057
2871
|
let errorName;
|
|
3058
2872
|
let errorStatus;
|
|
3059
2873
|
let errorMessage;
|
|
2874
|
+
let upstreamErrorMessageRaw;
|
|
3060
2875
|
const finishedAtMs = Date.now();
|
|
3061
2876
|
try {
|
|
3062
2877
|
debugJson(logger$6, "Non-streaming response:", response);
|
|
3063
2878
|
return c.json(response);
|
|
3064
2879
|
} catch (error) {
|
|
3065
|
-
const details =
|
|
2880
|
+
const details = await extractErrorObservability(error);
|
|
3066
2881
|
httpStatus = details.httpStatus;
|
|
3067
2882
|
errorName = details.errorName;
|
|
3068
2883
|
errorStatus = details.errorStatus;
|
|
3069
2884
|
errorMessage = details.errorMessage;
|
|
2885
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
3070
2886
|
throw error;
|
|
3071
2887
|
} finally {
|
|
3072
2888
|
await accountsManager.finalizeQuota(account, reservation);
|
|
@@ -3091,7 +2907,8 @@ async function handleNonStreamingUpstreamResponse(params) {
|
|
|
3091
2907
|
httpStatus,
|
|
3092
2908
|
errorName,
|
|
3093
2909
|
errorStatus,
|
|
3094
|
-
errorMessage
|
|
2910
|
+
errorMessage,
|
|
2911
|
+
upstreamErrorMessageRaw
|
|
3095
2912
|
});
|
|
3096
2913
|
}
|
|
3097
2914
|
}
|
|
@@ -3103,6 +2920,7 @@ async function streamChatCompletionsAndLog$1(params) {
|
|
|
3103
2920
|
let errorName;
|
|
3104
2921
|
let errorStatus;
|
|
3105
2922
|
let errorMessage;
|
|
2923
|
+
let upstreamErrorMessageRaw;
|
|
3106
2924
|
try {
|
|
3107
2925
|
for await (const rawChunk of response) {
|
|
3108
2926
|
const chunk = rawChunk;
|
|
@@ -3113,11 +2931,14 @@ async function streamChatCompletionsAndLog$1(params) {
|
|
|
3113
2931
|
await stream.writeSSE(chunk);
|
|
3114
2932
|
}
|
|
3115
2933
|
} catch (error) {
|
|
3116
|
-
const details =
|
|
2934
|
+
const details = await extractErrorObservability(error);
|
|
3117
2935
|
errorName = details.errorName;
|
|
3118
2936
|
errorStatus = details.errorStatus;
|
|
3119
2937
|
errorMessage = details.errorMessage;
|
|
2938
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
3120
2939
|
logger$6.warn("Streaming error:", error);
|
|
2940
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
2941
|
+
await writeChatCompletionsStreamError(stream, getUserVisibleErrorMessage(details));
|
|
3121
2942
|
} finally {
|
|
3122
2943
|
const finishedAtMs = Date.now();
|
|
3123
2944
|
await accountsManager.finalizeQuota(account, reservation);
|
|
@@ -3143,18 +2964,29 @@ async function streamChatCompletionsAndLog$1(params) {
|
|
|
3143
2964
|
httpStatus: errorStatus ?? (errorName ? 500 : 200),
|
|
3144
2965
|
errorName,
|
|
3145
2966
|
errorStatus,
|
|
3146
|
-
errorMessage
|
|
2967
|
+
errorMessage,
|
|
2968
|
+
upstreamErrorMessageRaw
|
|
3147
2969
|
});
|
|
3148
2970
|
}
|
|
3149
2971
|
}
|
|
3150
2972
|
async function extractUsageFromChunk(chunk) {
|
|
3151
|
-
|
|
2973
|
+
let data;
|
|
2974
|
+
try {
|
|
2975
|
+
data = typeof chunk.data === "string" ? chunk.data : await chunk.data;
|
|
2976
|
+
} catch (error) {
|
|
2977
|
+
logger$6.warn("Failed to read chat completions usage chunk:", error);
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
3152
2980
|
if (!data || data === "[DONE]") return;
|
|
3153
2981
|
try {
|
|
3154
2982
|
const parsed = JSON.parse(data);
|
|
3155
2983
|
if (!parsed.usage) return void 0;
|
|
3156
2984
|
return normalizeChatCompletionsUsage(parsed.usage);
|
|
3157
|
-
} catch {
|
|
2985
|
+
} catch (error) {
|
|
2986
|
+
logger$6.warn("Failed to parse chat completions usage chunk:", {
|
|
2987
|
+
error,
|
|
2988
|
+
data
|
|
2989
|
+
});
|
|
3158
2990
|
return;
|
|
3159
2991
|
}
|
|
3160
2992
|
}
|
|
@@ -3166,31 +2998,28 @@ async function handleNonStreamingRequest(params) {
|
|
|
3166
2998
|
let errorName;
|
|
3167
2999
|
let errorStatus;
|
|
3168
3000
|
let errorMessage;
|
|
3001
|
+
let upstreamErrorMessageRaw;
|
|
3169
3002
|
let finishedAtMs;
|
|
3170
3003
|
try {
|
|
3171
3004
|
const response = await createChatCompletions(payload, accountCtx, {
|
|
3172
3005
|
upstreamRequestId: request.upstreamRequestId,
|
|
3173
3006
|
sessionId: request.upstreamSessionId
|
|
3174
3007
|
});
|
|
3008
|
+
if (!isNonStreaming$1(response)) throw new Error("Upstream returned a stream unexpectedly");
|
|
3175
3009
|
selection.confirmAffinity?.();
|
|
3176
3010
|
finishedAtMs = Date.now();
|
|
3177
|
-
if (!isNonStreaming$1(response)) {
|
|
3178
|
-
logger$6.debug("Unexpected streaming response");
|
|
3179
|
-
return streamSSE(c, async (stream) => {
|
|
3180
|
-
for await (const chunk of response) await stream.writeSSE(chunk);
|
|
3181
|
-
});
|
|
3182
|
-
}
|
|
3183
3011
|
usage = normalizeChatCompletionsUsage(response.usage);
|
|
3184
3012
|
debugJson(logger$6, "Non-streaming response:", response);
|
|
3185
3013
|
return c.json(response);
|
|
3186
3014
|
} catch (error) {
|
|
3187
3015
|
finishedAtMs = Date.now();
|
|
3188
|
-
const details =
|
|
3016
|
+
const details = await extractErrorObservability(error);
|
|
3189
3017
|
httpStatus = details.httpStatus;
|
|
3190
3018
|
errorName = details.errorName;
|
|
3191
3019
|
errorStatus = details.errorStatus;
|
|
3192
3020
|
errorMessage = details.errorMessage;
|
|
3193
|
-
|
|
3021
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
3022
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
3194
3023
|
throw error;
|
|
3195
3024
|
} finally {
|
|
3196
3025
|
const finishedAtMsFinal = finishedAtMs ?? Date.now();
|
|
@@ -3216,7 +3045,8 @@ async function handleNonStreamingRequest(params) {
|
|
|
3216
3045
|
httpStatus,
|
|
3217
3046
|
errorName,
|
|
3218
3047
|
errorStatus,
|
|
3219
|
-
errorMessage
|
|
3048
|
+
errorMessage,
|
|
3049
|
+
upstreamErrorMessageRaw
|
|
3220
3050
|
});
|
|
3221
3051
|
}
|
|
3222
3052
|
}
|
|
@@ -3238,9 +3068,11 @@ completionRoutes.post("/", async (c) => {
|
|
|
3238
3068
|
const createEmbeddings = async (payload, account) => {
|
|
3239
3069
|
const ctx = account ?? accountFromState();
|
|
3240
3070
|
if (!ctx.copilotToken) throw new Error("Copilot token not found");
|
|
3071
|
+
const headers = copilotHeaders(ctx);
|
|
3072
|
+
captureOutboundHeadersSnapshot(headers);
|
|
3241
3073
|
const response = await fetch(`${copilotBaseUrl(ctx)}/embeddings`, {
|
|
3242
3074
|
method: "POST",
|
|
3243
|
-
headers
|
|
3075
|
+
headers,
|
|
3244
3076
|
body: JSON.stringify(payload)
|
|
3245
3077
|
});
|
|
3246
3078
|
if (!response.ok) throw new HTTPError("Failed to create embeddings", response);
|
|
@@ -3344,6 +3176,7 @@ async function runEmbeddingsWithAccount({ c, store, ctx, payload, clientModel, s
|
|
|
3344
3176
|
let errorName;
|
|
3345
3177
|
let errorStatus;
|
|
3346
3178
|
let errorMessage;
|
|
3179
|
+
let upstreamErrorMessageRaw;
|
|
3347
3180
|
let finishedAtMs;
|
|
3348
3181
|
try {
|
|
3349
3182
|
const response = await createEmbeddings(payload, toAccountContext(account));
|
|
@@ -3352,12 +3185,13 @@ async function runEmbeddingsWithAccount({ c, store, ctx, payload, clientModel, s
|
|
|
3352
3185
|
return c.json(response);
|
|
3353
3186
|
} catch (error) {
|
|
3354
3187
|
finishedAtMs = Date.now();
|
|
3355
|
-
const details =
|
|
3188
|
+
const details = await extractErrorObservability(error);
|
|
3356
3189
|
httpStatus = details.httpStatus;
|
|
3357
3190
|
errorName = details.errorName;
|
|
3358
3191
|
errorStatus = details.errorStatus;
|
|
3359
3192
|
errorMessage = details.errorMessage;
|
|
3360
|
-
|
|
3193
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
3194
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
3361
3195
|
throw error;
|
|
3362
3196
|
} finally {
|
|
3363
3197
|
const finishedAtMsFinal = finishedAtMs ?? Date.now();
|
|
@@ -3390,22 +3224,23 @@ async function runEmbeddingsWithAccount({ c, store, ctx, payload, clientModel, s
|
|
|
3390
3224
|
httpStatus,
|
|
3391
3225
|
errorName,
|
|
3392
3226
|
errorStatus,
|
|
3393
|
-
errorMessage
|
|
3227
|
+
errorMessage,
|
|
3228
|
+
upstreamErrorMessageRaw
|
|
3394
3229
|
});
|
|
3395
3230
|
}
|
|
3396
3231
|
}
|
|
3397
3232
|
|
|
3398
3233
|
//#endregion
|
|
3399
3234
|
//#region src/lib/models.ts
|
|
3235
|
+
const getAvailableModels = () => (accountsManager.getFirstAccountModels()?.data ?? []).filter((model) => model.model_picker_enabled || model.capabilities.type === "embeddings");
|
|
3400
3236
|
const findEndpointModel = (sdkModelId) => {
|
|
3401
|
-
const models =
|
|
3237
|
+
const models = getAvailableModels();
|
|
3402
3238
|
const exactMatch = models.find((m) => m.id === sdkModelId);
|
|
3403
3239
|
if (exactMatch) return exactMatch;
|
|
3404
3240
|
const normalized = _normalizeSdkModelId(sdkModelId);
|
|
3405
3241
|
if (!normalized) return;
|
|
3406
3242
|
const modelName = `claude-${normalized.family}-${normalized.version}`;
|
|
3407
|
-
|
|
3408
|
-
if (model) return model;
|
|
3243
|
+
return models.find((m) => m.id === modelName);
|
|
3409
3244
|
};
|
|
3410
3245
|
/**
|
|
3411
3246
|
* Normalizes an SDK model ID to extract the model family and version.
|
|
@@ -3447,12 +3282,23 @@ const _normalizeSdkModelId = (sdkModelId) => {
|
|
|
3447
3282
|
};
|
|
3448
3283
|
|
|
3449
3284
|
//#endregion
|
|
3450
|
-
//#region src/
|
|
3285
|
+
//#region src/lib/compact.ts
|
|
3286
|
+
const COMPACT_REQUEST = 1;
|
|
3287
|
+
const COMPACT_AUTO_CONTINUE = 2;
|
|
3451
3288
|
const compactSystemPromptStart = "You are a helpful AI assistant tasked with summarizing conversations";
|
|
3452
3289
|
const compactTextOnlyGuard = "CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.";
|
|
3453
3290
|
const compactSummaryPromptStart = "Your task is to create a detailed summary of the conversation so far";
|
|
3291
|
+
const compactAutoContinueClaudeCodePromptStart = "This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.";
|
|
3292
|
+
const compactAutoContinueOpenCodePromptStart = "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed.";
|
|
3293
|
+
const compactAutoContinuePromptStarts = [compactAutoContinueClaudeCodePromptStart, compactAutoContinueOpenCodePromptStart];
|
|
3454
3294
|
const compactMessageSections = ["Pending Tasks:", "Current Work:"];
|
|
3295
|
+
|
|
3296
|
+
//#endregion
|
|
3297
|
+
//#region src/routes/messages/preprocess.ts
|
|
3455
3298
|
const TOOL_REFERENCE_TURN_BOUNDARY = "Tool loaded.";
|
|
3299
|
+
const IDE_EXECUTE_CODE_TOOL = "mcp__ide__executeCode";
|
|
3300
|
+
const IDE_GET_DIAGNOSTICS_TOOL = "mcp__ide__getDiagnostics";
|
|
3301
|
+
const IDE_GET_DIAGNOSTICS_DESCRIPTION = "Get language diagnostics from VS Code. Returns errors, warnings, information, and hints for files in the workspace.";
|
|
3456
3302
|
const getAnthropicEffortForModel = (model) => {
|
|
3457
3303
|
const reasoningEffort = getReasoningEffortForModel(model);
|
|
3458
3304
|
if (reasoningEffort === "xhigh") return "max";
|
|
@@ -3469,13 +3315,19 @@ const isCompactMessage = (lastMessage) => {
|
|
|
3469
3315
|
if (!text) return false;
|
|
3470
3316
|
return text.includes(compactTextOnlyGuard) && text.includes(compactSummaryPromptStart) && compactMessageSections.some((section) => text.includes(section));
|
|
3471
3317
|
};
|
|
3472
|
-
const
|
|
3318
|
+
const isCompactAutoContinueMessage = (lastMessage) => {
|
|
3319
|
+
const text = getCompactCandidateText(lastMessage);
|
|
3320
|
+
return Boolean(text) && compactAutoContinuePromptStarts.some((promptStart) => text.startsWith(promptStart));
|
|
3321
|
+
};
|
|
3322
|
+
const getCompactType = (anthropicPayload) => {
|
|
3473
3323
|
const lastMessage = anthropicPayload.messages.at(-1);
|
|
3474
|
-
if (lastMessage && isCompactMessage(lastMessage)) return
|
|
3324
|
+
if (lastMessage && isCompactMessage(lastMessage)) return COMPACT_REQUEST;
|
|
3325
|
+
if (lastMessage && isCompactAutoContinueMessage(lastMessage)) return COMPACT_AUTO_CONTINUE;
|
|
3475
3326
|
const system = anthropicPayload.system;
|
|
3476
|
-
if (typeof system === "string") return system.startsWith(compactSystemPromptStart);
|
|
3477
|
-
if (!Array.isArray(system)) return
|
|
3478
|
-
|
|
3327
|
+
if (typeof system === "string") return system.startsWith(compactSystemPromptStart) ? COMPACT_REQUEST : 0;
|
|
3328
|
+
if (!Array.isArray(system)) return 0;
|
|
3329
|
+
if (system.some((msg) => typeof msg.text === "string" && msg.text.startsWith(compactSystemPromptStart))) return COMPACT_REQUEST;
|
|
3330
|
+
return 0;
|
|
3479
3331
|
};
|
|
3480
3332
|
const mergeContentWithText = (tr, textBlock) => {
|
|
3481
3333
|
if (typeof tr.content === "string") return {
|
|
@@ -3514,8 +3366,10 @@ const stripToolReferenceTurnBoundary = (anthropicPayload) => {
|
|
|
3514
3366
|
msg.content = msg.content.filter((block) => block.type !== "text" || block.text.trim() !== TOOL_REFERENCE_TURN_BOUNDARY);
|
|
3515
3367
|
}
|
|
3516
3368
|
};
|
|
3517
|
-
const mergeToolResultForClaude = (anthropicPayload) => {
|
|
3518
|
-
|
|
3369
|
+
const mergeToolResultForClaude = (anthropicPayload, options) => {
|
|
3370
|
+
const lastMessageIndex = anthropicPayload.messages.length - 1;
|
|
3371
|
+
for (const [index, msg] of anthropicPayload.messages.entries()) {
|
|
3372
|
+
if (options?.skipLastMessage && index === lastMessageIndex) continue;
|
|
3519
3373
|
if (msg.role !== "user" || !Array.isArray(msg.content)) continue;
|
|
3520
3374
|
const toolResults = [];
|
|
3521
3375
|
const textBlocks = [];
|
|
@@ -3530,6 +3384,17 @@ const mergeToolResultForClaude = (anthropicPayload) => {
|
|
|
3530
3384
|
msg.content = mergeToolResult(toolResults, textBlocks);
|
|
3531
3385
|
}
|
|
3532
3386
|
};
|
|
3387
|
+
const sanitizeIdeTools = (payload) => {
|
|
3388
|
+
if (!payload.tools || payload.tools.length === 0) return;
|
|
3389
|
+
payload.tools = payload.tools.flatMap((tool) => {
|
|
3390
|
+
if (tool.name === IDE_EXECUTE_CODE_TOOL) return [];
|
|
3391
|
+
if (tool.name === IDE_GET_DIAGNOSTICS_TOOL) return [{
|
|
3392
|
+
...tool,
|
|
3393
|
+
description: IDE_GET_DIAGNOSTICS_DESCRIPTION
|
|
3394
|
+
}];
|
|
3395
|
+
return [tool];
|
|
3396
|
+
});
|
|
3397
|
+
};
|
|
3533
3398
|
const hasToolRef = (block) => {
|
|
3534
3399
|
return Array.isArray(block.content) && block.content.some((c) => c.type === "tool_reference");
|
|
3535
3400
|
};
|
|
@@ -3568,10 +3433,12 @@ const filterAssistantThinkingBlocks = (payload) => {
|
|
|
3568
3433
|
const prepareMessagesApiPayload = (payload, selectedModel) => {
|
|
3569
3434
|
stripCacheControl(payload);
|
|
3570
3435
|
filterAssistantThinkingBlocks(payload);
|
|
3436
|
+
const hasThinking = Boolean(payload.thinking);
|
|
3571
3437
|
const toolChoice = payload.tool_choice;
|
|
3572
3438
|
const disableThink = toolChoice?.type === "any" || toolChoice?.type === "tool";
|
|
3573
3439
|
if (selectedModel?.capabilities.supports.adaptive_thinking && !disableThink) {
|
|
3574
3440
|
payload.thinking = { type: "adaptive" };
|
|
3441
|
+
if (!hasThinking) payload.thinking.display = "summarized";
|
|
3575
3442
|
payload.output_config = { effort: getAnthropicEffortForModel(payload.model) };
|
|
3576
3443
|
}
|
|
3577
3444
|
};
|
|
@@ -3611,7 +3478,7 @@ const isWarmupProbeRequest = (payload) => {
|
|
|
3611
3478
|
return false;
|
|
3612
3479
|
};
|
|
3613
3480
|
const handleSelectionFailure = (context) => {
|
|
3614
|
-
const { c, store, requestId, startedAtMs, method, path: path$2, streamRequested, clientModel, clientIp, clientIpSource, userAgent, userId, safetyIdentifier, promptCacheKey, initiator, isSubagent, selection } = context;
|
|
3481
|
+
const { c, store, requestId, startedAtMs, method, path: path$2, streamRequested, clientModel, clientIp, clientIpSource, userAgent, userId, safetyIdentifier, promptCacheKey, initiator, isSubagent, affinityKeyUsed, affinityKeySource, selectionReason, selection } = context;
|
|
3615
3482
|
const finishedAtMs = Date.now();
|
|
3616
3483
|
store.insert({
|
|
3617
3484
|
requestId,
|
|
@@ -3630,7 +3497,10 @@ const handleSelectionFailure = (context) => {
|
|
|
3630
3497
|
promptCacheKey,
|
|
3631
3498
|
initiator,
|
|
3632
3499
|
isSubagent,
|
|
3500
|
+
affinityKeyUsed,
|
|
3501
|
+
affinityKeySource,
|
|
3633
3502
|
httpStatus: selection.reason === "MODEL_NOT_SUPPORTED" ? 400 : 429,
|
|
3503
|
+
selectionReason: selectionReason ?? selection.selectionReason,
|
|
3634
3504
|
selectionFailureReason: selection.reason
|
|
3635
3505
|
});
|
|
3636
3506
|
if (selection.reason === "MODEL_NOT_SUPPORTED") return c.json({ error: {
|
|
@@ -3658,8 +3528,7 @@ const maybeBlockOriginalModelName = (context) => {
|
|
|
3658
3528
|
const THINKING_TEXT = "Thinking...";
|
|
3659
3529
|
function translateToOpenAI(payload) {
|
|
3660
3530
|
const modelId = payload.model;
|
|
3661
|
-
const
|
|
3662
|
-
const thinkingBudget = getThinkingBudget(payload, model);
|
|
3531
|
+
const thinkingBudget = getThinkingBudget(payload, getAvailableModels().find((m) => m.id === modelId));
|
|
3663
3532
|
return {
|
|
3664
3533
|
model: modelId,
|
|
3665
3534
|
messages: translateAnthropicMessagesToOpenAI(payload, modelId, thinkingBudget),
|
|
@@ -3952,16 +3821,21 @@ async function handleCountTokens(c) {
|
|
|
3952
3821
|
|
|
3953
3822
|
//#endregion
|
|
3954
3823
|
//#region src/services/copilot/create-responses.ts
|
|
3955
|
-
const createResponses = async (payload, { vision, initiator, upstreamRequestId, subagentMarker, sessionId,
|
|
3824
|
+
const createResponses = async (payload, { vision, initiator, upstreamRequestId, subagentMarker, sessionId, compactType }, account) => {
|
|
3956
3825
|
const ctx = account ?? accountFromState();
|
|
3957
3826
|
if (!ctx.copilotToken) throw new Error("Copilot token not found");
|
|
3827
|
+
const effectiveInitiator = resolveEffectiveInitiator(initiator, {
|
|
3828
|
+
isCompact: Boolean(compactType),
|
|
3829
|
+
isSubagent: Boolean(subagentMarker)
|
|
3830
|
+
});
|
|
3958
3831
|
const headers = {
|
|
3959
3832
|
...copilotHeaders(ctx, vision, upstreamRequestId),
|
|
3960
|
-
"x-initiator":
|
|
3833
|
+
"x-initiator": effectiveInitiator
|
|
3961
3834
|
};
|
|
3962
3835
|
prepareInteractionHeaders(sessionId, Boolean(subagentMarker), headers);
|
|
3963
|
-
prepareForCompact(headers,
|
|
3836
|
+
prepareForCompact(headers, compactType);
|
|
3964
3837
|
payload.service_tier = null;
|
|
3838
|
+
captureOutboundHeadersSnapshot(headers);
|
|
3965
3839
|
const response = await fetch(`${copilotBaseUrl(ctx)}/responses`, {
|
|
3966
3840
|
method: "POST",
|
|
3967
3841
|
headers,
|
|
@@ -4882,6 +4756,44 @@ const getPayloadItems = (payload) => {
|
|
|
4882
4756
|
if (Array.isArray(input)) result.push(...input);
|
|
4883
4757
|
return result;
|
|
4884
4758
|
};
|
|
4759
|
+
const useFunctionApplyPatch = (payload) => {
|
|
4760
|
+
if (!(getConfig().useFunctionApplyPatch ?? true)) return;
|
|
4761
|
+
if (Array.isArray(payload.tools)) {
|
|
4762
|
+
const toolsArr = payload.tools;
|
|
4763
|
+
for (let i = 0; i < toolsArr.length; i++) {
|
|
4764
|
+
const t = toolsArr[i];
|
|
4765
|
+
if (t.type === "custom" && t.name === "apply_patch") toolsArr[i] = {
|
|
4766
|
+
type: "function",
|
|
4767
|
+
name: t.name,
|
|
4768
|
+
description: "Use the `apply_patch` tool to edit files",
|
|
4769
|
+
parameters: {
|
|
4770
|
+
type: "object",
|
|
4771
|
+
properties: { input: {
|
|
4772
|
+
type: "string",
|
|
4773
|
+
description: "The entire contents of the apply_patch command"
|
|
4774
|
+
} },
|
|
4775
|
+
required: ["input"]
|
|
4776
|
+
},
|
|
4777
|
+
strict: false
|
|
4778
|
+
};
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4782
|
+
const removeWebSearchTool = (payload) => {
|
|
4783
|
+
if (!Array.isArray(payload.tools) || payload.tools.length === 0) return;
|
|
4784
|
+
payload.tools = payload.tools.filter((t) => {
|
|
4785
|
+
return t.type !== "web_search";
|
|
4786
|
+
});
|
|
4787
|
+
};
|
|
4788
|
+
function getStreamChunkFields(chunk) {
|
|
4789
|
+
const c = chunk;
|
|
4790
|
+
return {
|
|
4791
|
+
id: c.id,
|
|
4792
|
+
event: c.event,
|
|
4793
|
+
data: c.data
|
|
4794
|
+
};
|
|
4795
|
+
}
|
|
4796
|
+
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
4885
4797
|
const containsVisionContent = (value) => {
|
|
4886
4798
|
if (!value) return false;
|
|
4887
4799
|
if (Array.isArray(value)) return value.some((entry) => containsVisionContent(entry));
|
|
@@ -4909,18 +4821,18 @@ const getMessagesInitiator = (payload) => {
|
|
|
4909
4821
|
return lastMessage.content.some((block) => block.type !== "tool_result") ? "user" : "agent";
|
|
4910
4822
|
};
|
|
4911
4823
|
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
4824
|
+
const ADVANCED_TOOL_USE_BETA = "advanced-tool-use-2025-11-20";
|
|
4912
4825
|
const allowedAnthropicBetas = new Set([
|
|
4913
4826
|
INTERLEAVED_THINKING_BETA,
|
|
4914
4827
|
"context-management-2025-06-27",
|
|
4915
|
-
|
|
4828
|
+
ADVANCED_TOOL_USE_BETA
|
|
4916
4829
|
]);
|
|
4917
4830
|
const buildAnthropicBetaHeader = (anthropicBetaHeader, thinking) => {
|
|
4918
4831
|
const isAdaptiveThinking = thinking?.type === "adaptive";
|
|
4919
4832
|
if (anthropicBetaHeader) {
|
|
4920
|
-
const filteredBeta = anthropicBetaHeader.split(",").map((item) => item.trim()).filter((item) => item.length > 0).filter((item) => allowedAnthropicBetas.has(item));
|
|
4921
|
-
const uniqueFilteredBetas = [...new Set(filteredBeta)];
|
|
4922
|
-
|
|
4923
|
-
if (finalFilteredBetas.length > 0) return finalFilteredBetas.join(",");
|
|
4833
|
+
const filteredBeta = anthropicBetaHeader.split(",").map((item) => item.trim()).filter((item) => item.length > 0).filter((item) => allowedAnthropicBetas.has(item)).filter((item) => !isAdaptiveThinking || item !== INTERLEAVED_THINKING_BETA);
|
|
4834
|
+
const uniqueFilteredBetas = [...new Set([ADVANCED_TOOL_USE_BETA, ...filteredBeta])];
|
|
4835
|
+
if (uniqueFilteredBetas.length > 0) return uniqueFilteredBetas.join(",");
|
|
4924
4836
|
return;
|
|
4925
4837
|
}
|
|
4926
4838
|
if (thinking?.budget_tokens && !isAdaptiveThinking) return INTERLEAVED_THINKING_BETA;
|
|
@@ -4931,12 +4843,16 @@ const shouldUseMessageProxyHeaders = (payload) => {
|
|
|
4931
4843
|
return Boolean(safetyIdentifier && sessionId);
|
|
4932
4844
|
};
|
|
4933
4845
|
const buildMessagesHeaders = ({ ctx, enableVision, initiator, options, payload }) => {
|
|
4846
|
+
const effectiveInitiator = resolveEffectiveInitiator(initiator, {
|
|
4847
|
+
isCompact: Boolean(options?.compactType),
|
|
4848
|
+
isSubagent: Boolean(options?.subagentMarker)
|
|
4849
|
+
});
|
|
4934
4850
|
const headers = {
|
|
4935
4851
|
...copilotHeaders(ctx, enableVision, options?.upstreamRequestId),
|
|
4936
|
-
"x-initiator":
|
|
4852
|
+
"x-initiator": effectiveInitiator
|
|
4937
4853
|
};
|
|
4938
4854
|
prepareInteractionHeaders(options?.sessionId, Boolean(options?.subagentMarker), headers);
|
|
4939
|
-
prepareForCompact(headers, options?.
|
|
4855
|
+
prepareForCompact(headers, options?.compactType);
|
|
4940
4856
|
if (shouldUseMessageProxyHeaders(payload)) prepareMessageProxyHeaders(headers);
|
|
4941
4857
|
const anthropicBeta = buildAnthropicBetaHeader(options?.anthropicBetaHeader, payload.thinking);
|
|
4942
4858
|
if (anthropicBeta) headers["anthropic-beta"] = anthropicBeta;
|
|
@@ -4952,6 +4868,7 @@ const createMessages = async (payload, account, options) => {
|
|
|
4952
4868
|
options,
|
|
4953
4869
|
payload
|
|
4954
4870
|
});
|
|
4871
|
+
captureOutboundHeadersSnapshot(headers);
|
|
4955
4872
|
const response = await fetch(`${copilotBaseUrl(ctx)}/v1/messages`, {
|
|
4956
4873
|
method: "POST",
|
|
4957
4874
|
headers,
|
|
@@ -5223,36 +5140,78 @@ function closeThinkingBlockIfOpen(state$1, events$1) {
|
|
|
5223
5140
|
}
|
|
5224
5141
|
|
|
5225
5142
|
//#endregion
|
|
5226
|
-
//#region src/
|
|
5143
|
+
//#region src/lib/subagent.ts
|
|
5227
5144
|
const subagentMarkerPrefix = "__SUBAGENT_MARKER__";
|
|
5145
|
+
|
|
5146
|
+
//#endregion
|
|
5147
|
+
//#region src/routes/messages/subagent-marker.ts
|
|
5148
|
+
const subagentStartContextPrefix = "SubagentStart hook additional context:";
|
|
5228
5149
|
const REMINDER_RE = /<system-reminder>([\s\S]*?)<\/system-reminder>/g;
|
|
5229
|
-
const
|
|
5150
|
+
const NONE_INSPECTION = {
|
|
5151
|
+
kind: "none",
|
|
5152
|
+
marker: null
|
|
5153
|
+
};
|
|
5154
|
+
const INVALID_INSPECTION = {
|
|
5155
|
+
kind: "invalid",
|
|
5156
|
+
marker: null
|
|
5157
|
+
};
|
|
5158
|
+
const isSubagentMarker = (value) => {
|
|
5159
|
+
if (!value || typeof value !== "object") return false;
|
|
5160
|
+
const candidate = value;
|
|
5161
|
+
return typeof candidate.session_id === "string" && typeof candidate.agent_id === "string" && typeof candidate.agent_type === "string" && candidate.session_id.trim().length > 0 && candidate.agent_id.trim().length > 0 && candidate.agent_type.trim().length > 0;
|
|
5162
|
+
};
|
|
5163
|
+
const inspectSubagentMarkerFromFirstUser = (payload) => {
|
|
5230
5164
|
const firstUserMessage = payload.messages.find((msg) => msg.role === "user" && Array.isArray(msg.content));
|
|
5231
|
-
if (!firstUserMessage || !Array.isArray(firstUserMessage.content)) return
|
|
5165
|
+
if (!firstUserMessage || !Array.isArray(firstUserMessage.content)) return NONE_INSPECTION;
|
|
5166
|
+
let sawInvalidMarker = false;
|
|
5232
5167
|
for (const block of firstUserMessage.content) {
|
|
5233
5168
|
if (block.type !== "text") continue;
|
|
5234
|
-
const
|
|
5235
|
-
if (
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5169
|
+
const inspection = inspectSubagentMarkerFromSystemReminder(block.text);
|
|
5170
|
+
if (inspection.kind === "valid") return inspection;
|
|
5171
|
+
if (inspection.kind === "invalid") sawInvalidMarker = true;
|
|
5172
|
+
}
|
|
5173
|
+
return sawInvalidMarker ? INVALID_INSPECTION : NONE_INSPECTION;
|
|
5174
|
+
};
|
|
5175
|
+
const extractMarkerPayloadFromReminderLine = (line) => {
|
|
5176
|
+
const trimmedLine = line.trim();
|
|
5177
|
+
if (!trimmedLine) return null;
|
|
5178
|
+
let markerLine = trimmedLine;
|
|
5179
|
+
if (markerLine.startsWith(subagentStartContextPrefix)) markerLine = markerLine.slice(38).trimStart();
|
|
5180
|
+
if (!markerLine.startsWith(subagentMarkerPrefix)) return null;
|
|
5181
|
+
return markerLine.slice(subagentMarkerPrefix.length).trimStart();
|
|
5182
|
+
};
|
|
5183
|
+
const inspectSubagentMarkerFromSystemReminder = (text) => {
|
|
5184
|
+
let sawInvalidMarker = false;
|
|
5240
5185
|
for (const [, content] of text.matchAll(REMINDER_RE)) {
|
|
5241
|
-
const
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5186
|
+
const lines = content.split(/\r?\n/);
|
|
5187
|
+
for (const line of lines) {
|
|
5188
|
+
const markerPayload = extractMarkerPayloadFromReminderLine(line);
|
|
5189
|
+
if (markerPayload === null) continue;
|
|
5190
|
+
if (!markerPayload.startsWith("{")) {
|
|
5191
|
+
sawInvalidMarker = true;
|
|
5192
|
+
continue;
|
|
5193
|
+
}
|
|
5194
|
+
const json = extractBalancedJson(markerPayload);
|
|
5195
|
+
if (!json) {
|
|
5196
|
+
sawInvalidMarker = true;
|
|
5197
|
+
continue;
|
|
5198
|
+
}
|
|
5199
|
+
try {
|
|
5200
|
+
const parsed = JSON.parse(json);
|
|
5201
|
+
if (!isSubagentMarker(parsed)) {
|
|
5202
|
+
sawInvalidMarker = true;
|
|
5203
|
+
continue;
|
|
5204
|
+
}
|
|
5205
|
+
return {
|
|
5206
|
+
kind: "valid",
|
|
5207
|
+
marker: parsed
|
|
5208
|
+
};
|
|
5209
|
+
} catch {
|
|
5210
|
+
sawInvalidMarker = true;
|
|
5211
|
+
}
|
|
5253
5212
|
}
|
|
5254
5213
|
}
|
|
5255
|
-
return
|
|
5214
|
+
return sawInvalidMarker ? INVALID_INSPECTION : NONE_INSPECTION;
|
|
5256
5215
|
};
|
|
5257
5216
|
/** Extract the first balanced `{...}` object from text that starts with `{`. */
|
|
5258
5217
|
const extractBalancedJson = (text) => {
|
|
@@ -5305,19 +5264,25 @@ async function handleCompletion(c) {
|
|
|
5305
5264
|
const { ip: clientIp, source: clientIpSource } = getClientIpInfo(c);
|
|
5306
5265
|
const userAgent = c.req.header("user-agent") ?? void 0;
|
|
5307
5266
|
const anthropicPayload = await c.req.json();
|
|
5267
|
+
sanitizeIdeTools(anthropicPayload);
|
|
5308
5268
|
debugJson(logger$5, "Anthropic request payload:", anthropicPayload);
|
|
5309
|
-
const
|
|
5310
|
-
const
|
|
5269
|
+
const markerInspection = inspectSubagentMarkerFromFirstUser(anthropicPayload);
|
|
5270
|
+
const subagentMarker = markerInspection.kind === "valid" ? markerInspection.marker : null;
|
|
5271
|
+
const isSubagentRequest = subagentMarker !== null;
|
|
5272
|
+
const invalidSubagentMarkerSelectionReason = markerInspection.kind === "invalid" ? "subagent_marker_invalid_fallback" : void 0;
|
|
5311
5273
|
if (subagentMarker) debugJson(logger$5, "Detected Subagent marker:", subagentMarker);
|
|
5312
5274
|
const sessionId = getRootSessionId(anthropicPayload, c);
|
|
5313
5275
|
logger$5.debug("Extracted session ID:", sessionId);
|
|
5276
|
+
const ownershipLookupSessionId = markerInspection.kind === "valid" ? normalizeStableSessionId(markerInspection.marker.session_id) : void 0;
|
|
5277
|
+
const ownershipWriteSessionId = markerInspection.kind === "none" ? sessionId : void 0;
|
|
5314
5278
|
const anthropicBeta = c.req.header("anthropic-beta");
|
|
5315
|
-
const
|
|
5279
|
+
const compactType = getCompactType(anthropicPayload);
|
|
5280
|
+
const isCompact = compactType !== 0;
|
|
5281
|
+
const originalRequestModel = anthropicPayload.model;
|
|
5316
5282
|
if (anthropicBeta && isWarmupProbeRequest(anthropicPayload)) anthropicPayload.model = getSmallModel();
|
|
5317
|
-
if (
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
} else {
|
|
5283
|
+
if (compactType !== 0) logger$5.debug("Compact request type:", compactType);
|
|
5284
|
+
if (compactType === COMPACT_REQUEST && shouldCompactUseSmallModel()) anthropicPayload.model = getSmallModel();
|
|
5285
|
+
if (compactType === 0) {
|
|
5321
5286
|
stripToolReferenceTurnBoundary(anthropicPayload);
|
|
5322
5287
|
mergeToolResultForClaude(anthropicPayload);
|
|
5323
5288
|
}
|
|
@@ -5330,6 +5295,11 @@ async function handleCompletion(c) {
|
|
|
5330
5295
|
const { safetyIdentifier, sessionId: promptCacheKey } = parseUserIdMetadata(userId);
|
|
5331
5296
|
const normalizedSafetyIdentifier = safetyIdentifier ?? void 0;
|
|
5332
5297
|
const normalizedPromptCacheKey = promptCacheKey ?? void 0;
|
|
5298
|
+
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
5299
|
+
const fallbackInitiator = resolveEffectiveInitiator(getChatInitiator(openAIPayload.messages), {
|
|
5300
|
+
isCompact,
|
|
5301
|
+
isSubagent: isSubagentRequest
|
|
5302
|
+
});
|
|
5333
5303
|
const blockedResponse = maybeBlockOriginalModelName({
|
|
5334
5304
|
c,
|
|
5335
5305
|
store,
|
|
@@ -5345,14 +5315,14 @@ async function handleCompletion(c) {
|
|
|
5345
5315
|
userId,
|
|
5346
5316
|
safetyIdentifier: normalizedSafetyIdentifier,
|
|
5347
5317
|
promptCacheKey: normalizedPromptCacheKey,
|
|
5348
|
-
initiator:
|
|
5349
|
-
isSubagent:
|
|
5318
|
+
initiator: fallbackInitiator,
|
|
5319
|
+
isSubagent: isSubagentRequest,
|
|
5320
|
+
selectionReason: invalidSubagentMarkerSelectionReason
|
|
5350
5321
|
});
|
|
5351
5322
|
if (blockedResponse) return blockedResponse;
|
|
5352
|
-
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
5353
|
-
const fallbackInitiator = initiatorOverride ?? getChatInitiator(openAIPayload.messages);
|
|
5354
5323
|
const endpointModel = findEndpointModel(clientModel);
|
|
5355
5324
|
const resolvedClientModel = endpointModel?.id ?? clientModel;
|
|
5325
|
+
const affinityModelId = clientModel !== originalRequestModel ? findEndpointModel(originalRequestModel)?.id ?? originalRequestModel : void 0;
|
|
5356
5326
|
const useMessagesApi = isMessagesApiEnabled();
|
|
5357
5327
|
const candidates = [];
|
|
5358
5328
|
if (useMessagesApi) candidates.push({
|
|
@@ -5366,7 +5336,18 @@ async function handleCompletion(c) {
|
|
|
5366
5336
|
modelId: endpointModel?.id ?? openAIPayload.model,
|
|
5367
5337
|
endpoint: CHAT_COMPLETIONS_ENDPOINT
|
|
5368
5338
|
});
|
|
5369
|
-
const
|
|
5339
|
+
const affinityKey = resolveAffinityKey({
|
|
5340
|
+
metadataSessionId: promptCacheKey,
|
|
5341
|
+
headerSessionId: c.req.header("x-session-id") ?? null,
|
|
5342
|
+
upstreamRequestId
|
|
5343
|
+
});
|
|
5344
|
+
const selection = await accountsManager.selectAccountForRequest(candidates, {
|
|
5345
|
+
requestId: affinityKey.requestId,
|
|
5346
|
+
affinityModelId,
|
|
5347
|
+
ownershipLookupSessionId,
|
|
5348
|
+
ownershipWriteSessionId
|
|
5349
|
+
});
|
|
5350
|
+
const selectionReason = invalidSubagentMarkerSelectionReason ?? selection.selectionReason;
|
|
5370
5351
|
if (!selection.ok) return handleSelectionFailure({
|
|
5371
5352
|
c,
|
|
5372
5353
|
store,
|
|
@@ -5383,7 +5364,10 @@ async function handleCompletion(c) {
|
|
|
5383
5364
|
safetyIdentifier: normalizedSafetyIdentifier,
|
|
5384
5365
|
promptCacheKey: normalizedPromptCacheKey,
|
|
5385
5366
|
initiator: fallbackInitiator,
|
|
5386
|
-
isSubagent:
|
|
5367
|
+
isSubagent: isSubagentRequest,
|
|
5368
|
+
affinityKeyUsed: affinityKey.affinityKeyUsed,
|
|
5369
|
+
affinityKeySource: affinityKey.affinityKeySource,
|
|
5370
|
+
selectionReason,
|
|
5387
5371
|
selection
|
|
5388
5372
|
});
|
|
5389
5373
|
const { account, reservation, selectedModel, endpoint, costUnits } = selection;
|
|
@@ -5404,7 +5388,7 @@ async function handleCompletion(c) {
|
|
|
5404
5388
|
userId,
|
|
5405
5389
|
safetyIdentifier: normalizedSafetyIdentifier,
|
|
5406
5390
|
promptCacheKey: normalizedPromptCacheKey,
|
|
5407
|
-
isSubagent:
|
|
5391
|
+
isSubagent: isSubagentRequest,
|
|
5408
5392
|
clientModel,
|
|
5409
5393
|
account,
|
|
5410
5394
|
reservation,
|
|
@@ -5415,58 +5399,63 @@ async function handleCompletion(c) {
|
|
|
5415
5399
|
premiumRemainingBefore,
|
|
5416
5400
|
premiumUnlimitedBefore,
|
|
5417
5401
|
confirmAffinity: selection.confirmAffinity,
|
|
5402
|
+
confirmOwnership: selection.confirmOwnership,
|
|
5418
5403
|
affinityHit: selection.affinityHit,
|
|
5419
|
-
affinityCacheKey: selection.affinityCacheKey
|
|
5404
|
+
affinityCacheKey: selection.affinityCacheKey,
|
|
5405
|
+
affinityKeyUsed: affinityKey.affinityKeyUsed,
|
|
5406
|
+
affinityKeySource: affinityKey.affinityKeySource,
|
|
5407
|
+
selectionReason
|
|
5420
5408
|
};
|
|
5421
5409
|
if (endpoint === MESSAGES_ENDPOINT) return await handleWithMessagesApi({
|
|
5422
5410
|
c,
|
|
5423
5411
|
anthropicPayload,
|
|
5424
5412
|
anthropicBetaHeader: anthropicBeta ?? void 0,
|
|
5425
|
-
initiatorOverride,
|
|
5426
5413
|
subagentMarker,
|
|
5427
5414
|
sessionId,
|
|
5428
5415
|
instr,
|
|
5429
5416
|
selectedModel,
|
|
5430
|
-
|
|
5417
|
+
compactType
|
|
5431
5418
|
});
|
|
5432
5419
|
if (endpoint === RESPONSES_ENDPOINT$1) return await handleWithResponsesApi({
|
|
5433
5420
|
c,
|
|
5434
5421
|
anthropicPayload,
|
|
5435
5422
|
openAIPayload,
|
|
5436
|
-
initiatorOverride,
|
|
5437
5423
|
subagentMarker,
|
|
5438
5424
|
sessionId,
|
|
5439
5425
|
selectedModel,
|
|
5440
5426
|
instr,
|
|
5441
|
-
|
|
5427
|
+
compactType
|
|
5442
5428
|
});
|
|
5443
5429
|
return await handleWithChatCompletions({
|
|
5444
5430
|
c,
|
|
5445
5431
|
openAIPayload,
|
|
5446
|
-
initiatorOverride,
|
|
5447
5432
|
subagentMarker,
|
|
5448
5433
|
sessionId,
|
|
5449
5434
|
selectedModel,
|
|
5450
5435
|
instr,
|
|
5451
|
-
|
|
5436
|
+
compactType
|
|
5452
5437
|
});
|
|
5453
5438
|
}
|
|
5454
5439
|
const handleWithChatCompletions = async (params) => {
|
|
5455
|
-
const { c, openAIPayload,
|
|
5440
|
+
const { c, openAIPayload, subagentMarker, sessionId, selectedModel, instr, compactType } = params;
|
|
5456
5441
|
debugJson(logger$5, "Translated OpenAI request payload:", openAIPayload);
|
|
5457
5442
|
const ctx = toAccountContext(instr.account);
|
|
5458
|
-
const
|
|
5459
|
-
|
|
5443
|
+
const effectiveInitiator = resolveEffectiveInitiator(getChatInitiator(openAIPayload.messages), {
|
|
5444
|
+
isCompact: compactType !== 0,
|
|
5445
|
+
isSubagent: Boolean(subagentMarker)
|
|
5446
|
+
});
|
|
5447
|
+
instr.initiator = effectiveInitiator;
|
|
5460
5448
|
let response;
|
|
5461
5449
|
try {
|
|
5462
5450
|
response = await createChatCompletions(openAIPayload, ctx, {
|
|
5463
5451
|
upstreamRequestId: instr.upstreamRequestId,
|
|
5464
|
-
initiator,
|
|
5452
|
+
initiator: effectiveInitiator,
|
|
5465
5453
|
subagentMarker,
|
|
5466
5454
|
sessionId,
|
|
5467
|
-
|
|
5455
|
+
compactType
|
|
5468
5456
|
});
|
|
5469
5457
|
instr.confirmAffinity?.();
|
|
5458
|
+
instr.confirmOwnership?.();
|
|
5470
5459
|
} catch (error) {
|
|
5471
5460
|
return await handleChatCompletionsCreateError({
|
|
5472
5461
|
error,
|
|
@@ -5496,26 +5485,30 @@ const handleWithChatCompletions = async (params) => {
|
|
|
5496
5485
|
}));
|
|
5497
5486
|
};
|
|
5498
5487
|
const handleWithResponsesApi = async (params) => {
|
|
5499
|
-
const { c, anthropicPayload, openAIPayload,
|
|
5488
|
+
const { c, anthropicPayload, openAIPayload, subagentMarker, sessionId, selectedModel, instr, compactType } = params;
|
|
5500
5489
|
const responsesPayload = translateAnthropicMessagesToResponsesPayload(anthropicPayload, selectedModel.id);
|
|
5501
5490
|
applyResponsesApiContextManagement(responsesPayload, selectedModel.capabilities.limits.max_prompt_tokens);
|
|
5502
5491
|
compactInputByLatestCompaction(responsesPayload);
|
|
5503
5492
|
debugJson(logger$5, "Translated Responses payload:", responsesPayload);
|
|
5504
5493
|
const { vision, initiator } = getResponsesRequestOptions(responsesPayload);
|
|
5505
|
-
const
|
|
5494
|
+
const effectiveInitiator = resolveEffectiveInitiator(initiator, {
|
|
5495
|
+
isCompact: compactType !== 0,
|
|
5496
|
+
isSubagent: Boolean(subagentMarker)
|
|
5497
|
+
});
|
|
5506
5498
|
const ctx = toAccountContext(instr.account);
|
|
5507
|
-
instr.initiator =
|
|
5499
|
+
instr.initiator = effectiveInitiator;
|
|
5508
5500
|
let response;
|
|
5509
5501
|
try {
|
|
5510
5502
|
response = await createResponses(responsesPayload, {
|
|
5511
5503
|
vision,
|
|
5512
|
-
initiator:
|
|
5504
|
+
initiator: effectiveInitiator,
|
|
5513
5505
|
upstreamRequestId: instr.upstreamRequestId,
|
|
5514
5506
|
subagentMarker,
|
|
5515
5507
|
sessionId,
|
|
5516
|
-
|
|
5508
|
+
compactType
|
|
5517
5509
|
}, ctx);
|
|
5518
5510
|
instr.confirmAffinity?.();
|
|
5511
|
+
instr.confirmOwnership?.();
|
|
5519
5512
|
} catch (error) {
|
|
5520
5513
|
return await handleResponsesCreateError({
|
|
5521
5514
|
error,
|
|
@@ -5564,6 +5557,9 @@ function insertRequestLog$1(instr, record) {
|
|
|
5564
5557
|
upstreamRequestId: instr.upstreamRequestId,
|
|
5565
5558
|
affinityHit: instr.affinityHit,
|
|
5566
5559
|
affinityCacheKey: instr.affinityCacheKey,
|
|
5560
|
+
affinityKeyUsed: instr.affinityKeyUsed,
|
|
5561
|
+
affinityKeySource: instr.affinityKeySource,
|
|
5562
|
+
selectionReason: instr.selectionReason,
|
|
5567
5563
|
clientModel,
|
|
5568
5564
|
upstreamEndpoint,
|
|
5569
5565
|
accountId: account.id,
|
|
@@ -5587,8 +5583,8 @@ async function finalizeQuotaAndGetPremiumSnapshot(instr) {
|
|
|
5587
5583
|
async function handleChatCompletionsCreateError(params) {
|
|
5588
5584
|
const { error, instr, stream } = params;
|
|
5589
5585
|
const finishedAtMs = Date.now();
|
|
5590
|
-
const details =
|
|
5591
|
-
if (details
|
|
5586
|
+
const details = await extractErrorObservability(error);
|
|
5587
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5592
5588
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
5593
5589
|
insertRequestLog$1(instr, {
|
|
5594
5590
|
finishedAtMs,
|
|
@@ -5600,7 +5596,8 @@ async function handleChatCompletionsCreateError(params) {
|
|
|
5600
5596
|
httpStatus: details.httpStatus,
|
|
5601
5597
|
errorName: details.errorName,
|
|
5602
5598
|
errorStatus: details.errorStatus,
|
|
5603
|
-
errorMessage: details.errorMessage
|
|
5599
|
+
errorMessage: details.errorMessage,
|
|
5600
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
5604
5601
|
});
|
|
5605
5602
|
throw error;
|
|
5606
5603
|
}
|
|
@@ -5611,6 +5608,7 @@ async function handleChatCompletionsNonStreaming(params) {
|
|
|
5611
5608
|
let errorName;
|
|
5612
5609
|
let errorStatus;
|
|
5613
5610
|
let errorMessage;
|
|
5611
|
+
let upstreamErrorMessageRaw;
|
|
5614
5612
|
const finishedAtMs = Date.now();
|
|
5615
5613
|
try {
|
|
5616
5614
|
logger$5.debug("Non-streaming response from Copilot:", JSON.stringify(response));
|
|
@@ -5618,12 +5616,13 @@ async function handleChatCompletionsNonStreaming(params) {
|
|
|
5618
5616
|
debugJson(logger$5, "Translated Anthropic response:", anthropicResponse);
|
|
5619
5617
|
return c.json(anthropicResponse);
|
|
5620
5618
|
} catch (error) {
|
|
5621
|
-
const details =
|
|
5619
|
+
const details = await extractErrorObservability(error);
|
|
5622
5620
|
httpStatus = details.httpStatus;
|
|
5623
5621
|
errorName = details.errorName;
|
|
5624
5622
|
errorStatus = details.errorStatus;
|
|
5625
5623
|
errorMessage = details.errorMessage;
|
|
5626
|
-
|
|
5624
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5625
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5627
5626
|
throw error;
|
|
5628
5627
|
} finally {
|
|
5629
5628
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5638,7 +5637,8 @@ async function handleChatCompletionsNonStreaming(params) {
|
|
|
5638
5637
|
httpStatus,
|
|
5639
5638
|
errorName,
|
|
5640
5639
|
errorStatus,
|
|
5641
|
-
errorMessage
|
|
5640
|
+
errorMessage,
|
|
5641
|
+
upstreamErrorMessageRaw
|
|
5642
5642
|
});
|
|
5643
5643
|
}
|
|
5644
5644
|
}
|
|
@@ -5649,6 +5649,7 @@ async function streamChatCompletionsAndLog(params) {
|
|
|
5649
5649
|
let errorName;
|
|
5650
5650
|
let errorStatus;
|
|
5651
5651
|
let errorMessage;
|
|
5652
|
+
let upstreamErrorMessageRaw;
|
|
5652
5653
|
const streamState = {
|
|
5653
5654
|
messageStartSent: false,
|
|
5654
5655
|
contentBlockIndex: 0,
|
|
@@ -5680,12 +5681,14 @@ async function streamChatCompletionsAndLog(params) {
|
|
|
5680
5681
|
}
|
|
5681
5682
|
}
|
|
5682
5683
|
} catch (error) {
|
|
5683
|
-
const details =
|
|
5684
|
+
const details = await extractErrorObservability(error);
|
|
5684
5685
|
errorName = details.errorName;
|
|
5685
5686
|
errorStatus = details.errorStatus;
|
|
5686
5687
|
errorMessage = details.errorMessage;
|
|
5688
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5687
5689
|
logger$5.warn("Streaming error:", error);
|
|
5688
|
-
if (details
|
|
5690
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5691
|
+
await writeAnthropicStreamError(stream, getUserVisibleErrorMessage(details));
|
|
5689
5692
|
} finally {
|
|
5690
5693
|
const finishedAtMs = Date.now();
|
|
5691
5694
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5701,15 +5704,16 @@ async function streamChatCompletionsAndLog(params) {
|
|
|
5701
5704
|
httpStatus: errorStatus ?? (errorName ? 500 : 200),
|
|
5702
5705
|
errorName,
|
|
5703
5706
|
errorStatus,
|
|
5704
|
-
errorMessage
|
|
5707
|
+
errorMessage,
|
|
5708
|
+
upstreamErrorMessageRaw
|
|
5705
5709
|
});
|
|
5706
5710
|
}
|
|
5707
5711
|
}
|
|
5708
5712
|
async function handleResponsesCreateError(params) {
|
|
5709
5713
|
const { error, instr, stream } = params;
|
|
5710
5714
|
const finishedAtMs = Date.now();
|
|
5711
|
-
const details =
|
|
5712
|
-
if (details
|
|
5715
|
+
const details = await extractErrorObservability(error);
|
|
5716
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5713
5717
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
5714
5718
|
insertRequestLog$1(instr, {
|
|
5715
5719
|
finishedAtMs,
|
|
@@ -5721,7 +5725,8 @@ async function handleResponsesCreateError(params) {
|
|
|
5721
5725
|
httpStatus: details.httpStatus,
|
|
5722
5726
|
errorName: details.errorName,
|
|
5723
5727
|
errorStatus: details.errorStatus,
|
|
5724
|
-
errorMessage: details.errorMessage
|
|
5728
|
+
errorMessage: details.errorMessage,
|
|
5729
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
5725
5730
|
});
|
|
5726
5731
|
throw error;
|
|
5727
5732
|
}
|
|
@@ -5732,6 +5737,7 @@ async function handleResponsesNonStreaming(params) {
|
|
|
5732
5737
|
let errorName;
|
|
5733
5738
|
let errorStatus;
|
|
5734
5739
|
let errorMessage;
|
|
5740
|
+
let upstreamErrorMessageRaw;
|
|
5735
5741
|
const finishedAtMs = Date.now();
|
|
5736
5742
|
try {
|
|
5737
5743
|
usage = extractResponsesUsageFromResult(result);
|
|
@@ -5740,12 +5746,13 @@ async function handleResponsesNonStreaming(params) {
|
|
|
5740
5746
|
debugJson(logger$5, "Translated Anthropic response:", anthropicResponse);
|
|
5741
5747
|
return c.json(anthropicResponse);
|
|
5742
5748
|
} catch (error) {
|
|
5743
|
-
const details =
|
|
5749
|
+
const details = await extractErrorObservability(error);
|
|
5744
5750
|
httpStatus = details.httpStatus;
|
|
5745
5751
|
errorName = details.errorName;
|
|
5746
5752
|
errorStatus = details.errorStatus;
|
|
5747
5753
|
errorMessage = details.errorMessage;
|
|
5748
|
-
|
|
5754
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5755
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5749
5756
|
throw error;
|
|
5750
5757
|
} finally {
|
|
5751
5758
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5760,7 +5767,8 @@ async function handleResponsesNonStreaming(params) {
|
|
|
5760
5767
|
httpStatus,
|
|
5761
5768
|
errorName,
|
|
5762
5769
|
errorStatus,
|
|
5763
|
-
errorMessage
|
|
5770
|
+
errorMessage,
|
|
5771
|
+
upstreamErrorMessageRaw
|
|
5764
5772
|
});
|
|
5765
5773
|
}
|
|
5766
5774
|
}
|
|
@@ -5776,6 +5784,17 @@ async function ensureResponsesStreamCompleted(params) {
|
|
|
5776
5784
|
data: JSON.stringify(errorEvent)
|
|
5777
5785
|
});
|
|
5778
5786
|
}
|
|
5787
|
+
async function writeAnthropicStreamError(stream, message) {
|
|
5788
|
+
try {
|
|
5789
|
+
const errorEvent = buildErrorEvent(message);
|
|
5790
|
+
await stream.writeSSE({
|
|
5791
|
+
event: errorEvent.type,
|
|
5792
|
+
data: JSON.stringify(errorEvent)
|
|
5793
|
+
});
|
|
5794
|
+
} catch (streamError) {
|
|
5795
|
+
logger$5.warn("Failed to write Anthropic stream error event:", streamError);
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5779
5798
|
async function streamResponsesAndLog$1(params) {
|
|
5780
5799
|
const { stream, response, instr, estimatedInputTokens, historicalUsage } = params;
|
|
5781
5800
|
let ttfbMs;
|
|
@@ -5783,6 +5802,7 @@ async function streamResponsesAndLog$1(params) {
|
|
|
5783
5802
|
let errorName;
|
|
5784
5803
|
let errorStatus;
|
|
5785
5804
|
let errorMessage;
|
|
5805
|
+
let upstreamErrorMessageRaw;
|
|
5786
5806
|
const streamState = createResponsesStreamState();
|
|
5787
5807
|
streamState.estimatedInputTokens = estimatedInputTokens;
|
|
5788
5808
|
streamState.historicalInputTokens = historicalUsage?.tokensInput;
|
|
@@ -5827,12 +5847,14 @@ async function streamResponsesAndLog$1(params) {
|
|
|
5827
5847
|
}
|
|
5828
5848
|
});
|
|
5829
5849
|
} catch (error) {
|
|
5830
|
-
const details =
|
|
5850
|
+
const details = await extractErrorObservability(error);
|
|
5831
5851
|
errorName = details.errorName;
|
|
5832
5852
|
errorStatus = details.errorStatus;
|
|
5833
5853
|
errorMessage = details.errorMessage;
|
|
5854
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5834
5855
|
logger$5.warn("Streaming error:", error);
|
|
5835
|
-
if (details
|
|
5856
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5857
|
+
await writeAnthropicStreamError(stream, getUserVisibleErrorMessage(details));
|
|
5836
5858
|
} finally {
|
|
5837
5859
|
const finishedAtMs = Date.now();
|
|
5838
5860
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5848,15 +5870,16 @@ async function streamResponsesAndLog$1(params) {
|
|
|
5848
5870
|
httpStatus: errorStatus ?? (errorName ? 500 : 200),
|
|
5849
5871
|
errorName,
|
|
5850
5872
|
errorStatus,
|
|
5851
|
-
errorMessage
|
|
5873
|
+
errorMessage,
|
|
5874
|
+
upstreamErrorMessageRaw
|
|
5852
5875
|
});
|
|
5853
5876
|
}
|
|
5854
5877
|
}
|
|
5855
5878
|
async function handleMessagesCreateError(params) {
|
|
5856
5879
|
const { error, instr, stream } = params;
|
|
5857
5880
|
const finishedAtMs = Date.now();
|
|
5858
|
-
const details =
|
|
5859
|
-
if (details
|
|
5881
|
+
const details = await extractErrorObservability(error);
|
|
5882
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5860
5883
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
5861
5884
|
insertRequestLog$1(instr, {
|
|
5862
5885
|
finishedAtMs,
|
|
@@ -5868,7 +5891,8 @@ async function handleMessagesCreateError(params) {
|
|
|
5868
5891
|
httpStatus: details.httpStatus,
|
|
5869
5892
|
errorName: details.errorName,
|
|
5870
5893
|
errorStatus: details.errorStatus,
|
|
5871
|
-
errorMessage: details.errorMessage
|
|
5894
|
+
errorMessage: details.errorMessage,
|
|
5895
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
5872
5896
|
});
|
|
5873
5897
|
throw error;
|
|
5874
5898
|
}
|
|
@@ -5879,17 +5903,19 @@ async function handleMessagesNonStreaming(params) {
|
|
|
5879
5903
|
let errorName;
|
|
5880
5904
|
let errorStatus;
|
|
5881
5905
|
let errorMessage;
|
|
5906
|
+
let upstreamErrorMessageRaw;
|
|
5882
5907
|
const finishedAtMs = Date.now();
|
|
5883
5908
|
try {
|
|
5884
5909
|
logger$5.debug("Non-streaming Messages result:", JSON.stringify(response).slice(-400));
|
|
5885
5910
|
return c.json(response);
|
|
5886
5911
|
} catch (error) {
|
|
5887
|
-
const details =
|
|
5912
|
+
const details = await extractErrorObservability(error);
|
|
5888
5913
|
httpStatus = details.httpStatus;
|
|
5889
5914
|
errorName = details.errorName;
|
|
5890
5915
|
errorStatus = details.errorStatus;
|
|
5891
5916
|
errorMessage = details.errorMessage;
|
|
5892
|
-
|
|
5917
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5918
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5893
5919
|
throw error;
|
|
5894
5920
|
} finally {
|
|
5895
5921
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5904,7 +5930,8 @@ async function handleMessagesNonStreaming(params) {
|
|
|
5904
5930
|
httpStatus,
|
|
5905
5931
|
errorName,
|
|
5906
5932
|
errorStatus,
|
|
5907
|
-
errorMessage
|
|
5933
|
+
errorMessage,
|
|
5934
|
+
upstreamErrorMessageRaw
|
|
5908
5935
|
});
|
|
5909
5936
|
}
|
|
5910
5937
|
}
|
|
@@ -5926,6 +5953,7 @@ async function streamMessagesAndLog(params) {
|
|
|
5926
5953
|
let errorName;
|
|
5927
5954
|
let errorStatus;
|
|
5928
5955
|
let errorMessage;
|
|
5956
|
+
let upstreamErrorMessageRaw;
|
|
5929
5957
|
try {
|
|
5930
5958
|
for await (const rawEvent of response) {
|
|
5931
5959
|
if (ttfbMs === void 0) ttfbMs = Date.now() - instr.startedAtMs;
|
|
@@ -5941,12 +5969,14 @@ async function streamMessagesAndLog(params) {
|
|
|
5941
5969
|
});
|
|
5942
5970
|
}
|
|
5943
5971
|
} catch (error) {
|
|
5944
|
-
const details =
|
|
5972
|
+
const details = await extractErrorObservability(error);
|
|
5945
5973
|
errorName = details.errorName;
|
|
5946
5974
|
errorStatus = details.errorStatus;
|
|
5947
5975
|
errorMessage = details.errorMessage;
|
|
5976
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
5948
5977
|
logger$5.warn("Streaming error:", error);
|
|
5949
|
-
if (details
|
|
5978
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(instr.account.id, "Unauthorized (401)");
|
|
5979
|
+
await writeAnthropicStreamError(stream, getUserVisibleErrorMessage(details));
|
|
5950
5980
|
} finally {
|
|
5951
5981
|
const finishedAtMs = Date.now();
|
|
5952
5982
|
const { premiumRemainingAfter, premiumUnlimitedAfter, premiumRemainingDiff } = await finalizeQuotaAndGetPremiumSnapshot(instr);
|
|
@@ -5962,28 +5992,33 @@ async function streamMessagesAndLog(params) {
|
|
|
5962
5992
|
httpStatus: errorStatus ?? (errorName ? 500 : 200),
|
|
5963
5993
|
errorName,
|
|
5964
5994
|
errorStatus,
|
|
5965
|
-
errorMessage
|
|
5995
|
+
errorMessage,
|
|
5996
|
+
upstreamErrorMessageRaw
|
|
5966
5997
|
});
|
|
5967
5998
|
}
|
|
5968
5999
|
}
|
|
5969
6000
|
const handleWithMessagesApi = async (params) => {
|
|
5970
|
-
const { c, anthropicPayload, anthropicBetaHeader,
|
|
6001
|
+
const { c, anthropicPayload, anthropicBetaHeader, subagentMarker, sessionId, instr, selectedModel, compactType } = params;
|
|
5971
6002
|
prepareMessagesApiPayload(anthropicPayload, selectedModel);
|
|
5972
6003
|
debugJson(logger$5, "Translated Messages payload:", anthropicPayload);
|
|
5973
6004
|
const ctx = toAccountContext(instr.account);
|
|
5974
|
-
const
|
|
5975
|
-
|
|
6005
|
+
const effectiveInitiator = resolveEffectiveInitiator(getMessagesInitiator(anthropicPayload), {
|
|
6006
|
+
isCompact: compactType !== 0,
|
|
6007
|
+
isSubagent: Boolean(subagentMarker)
|
|
6008
|
+
});
|
|
6009
|
+
instr.initiator = effectiveInitiator;
|
|
5976
6010
|
let response;
|
|
5977
6011
|
try {
|
|
5978
6012
|
response = await createMessages(anthropicPayload, ctx, {
|
|
5979
6013
|
anthropicBetaHeader,
|
|
5980
6014
|
upstreamRequestId: instr.upstreamRequestId,
|
|
5981
|
-
initiator,
|
|
6015
|
+
initiator: effectiveInitiator,
|
|
5982
6016
|
subagentMarker,
|
|
5983
6017
|
sessionId,
|
|
5984
|
-
|
|
6018
|
+
compactType
|
|
5985
6019
|
});
|
|
5986
6020
|
instr.confirmAffinity?.();
|
|
6021
|
+
instr.confirmOwnership?.();
|
|
5987
6022
|
} catch (error) {
|
|
5988
6023
|
return await handleMessagesCreateError({
|
|
5989
6024
|
error,
|
|
@@ -6031,9 +6066,8 @@ messageRoutes.post("/count_tokens", async (c) => {
|
|
|
6031
6066
|
const modelRoutes = new Hono();
|
|
6032
6067
|
modelRoutes.get("/", async (c) => {
|
|
6033
6068
|
try {
|
|
6034
|
-
if (!state.models) await cacheModels();
|
|
6035
6069
|
const blockedTargets = getAliasTargetSet();
|
|
6036
|
-
const models =
|
|
6070
|
+
const models = getAvailableModels().filter((model) => !blockedTargets.has(model.id.toLowerCase())).map((model) => ({
|
|
6037
6071
|
id: model.id,
|
|
6038
6072
|
object: "model",
|
|
6039
6073
|
type: "model",
|
|
@@ -6041,7 +6075,7 @@ modelRoutes.get("/", async (c) => {
|
|
|
6041
6075
|
created_at: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
6042
6076
|
owned_by: model.vendor,
|
|
6043
6077
|
display_name: model.name
|
|
6044
|
-
}))
|
|
6078
|
+
}));
|
|
6045
6079
|
const aliasModels = Object.keys(getModelAliases()).map((alias) => ({
|
|
6046
6080
|
id: alias,
|
|
6047
6081
|
object: "model",
|
|
@@ -6090,7 +6124,7 @@ async function handleProviderCountTokens(c) {
|
|
|
6090
6124
|
const anthropicPayload = await c.req.json();
|
|
6091
6125
|
const openAIPayload = translateToOpenAI(anthropicPayload);
|
|
6092
6126
|
const modelId = anthropicPayload.model.trim();
|
|
6093
|
-
let selectedModel =
|
|
6127
|
+
let selectedModel = getAvailableModels().find((model) => model.id === modelId);
|
|
6094
6128
|
if (!selectedModel && modelId) selectedModel = createFallbackModel(modelId);
|
|
6095
6129
|
if (!selectedModel) {
|
|
6096
6130
|
logger$4.warn("provider.count_tokens.model_not_found", {
|
|
@@ -6350,9 +6384,10 @@ const handleResponses = async (c) => {
|
|
|
6350
6384
|
const streamRequested = Boolean(payload.stream);
|
|
6351
6385
|
const { initiator: initialInitiator } = getResponsesRequestOptions(payload);
|
|
6352
6386
|
const userId = payload.metadata?.user_id;
|
|
6353
|
-
const
|
|
6387
|
+
const requestBodyPromptCacheKey = typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : null;
|
|
6388
|
+
const { safetyIdentifier, sessionId: metadataSessionId } = parseUserIdMetadata(userId);
|
|
6354
6389
|
const normalizedSafetyIdentifier = safetyIdentifier ?? void 0;
|
|
6355
|
-
const normalizedPromptCacheKey =
|
|
6390
|
+
const normalizedPromptCacheKey = requestBodyPromptCacheKey ?? metadataSessionId ?? void 0;
|
|
6356
6391
|
request.userId = userId;
|
|
6357
6392
|
request.safetyIdentifier = normalizedSafetyIdentifier;
|
|
6358
6393
|
request.promptCacheKey = normalizedPromptCacheKey;
|
|
@@ -6370,10 +6405,19 @@ const handleResponses = async (c) => {
|
|
|
6370
6405
|
});
|
|
6371
6406
|
}
|
|
6372
6407
|
const upstreamRequestId = generateRequestIdFromPayload({ messages: payload.input }, normalizedPromptCacheKey);
|
|
6408
|
+
const headerSessionId = c.req.header("x-session-id") ?? null;
|
|
6409
|
+
const affinityKey = resolveAffinityKey({
|
|
6410
|
+
promptCacheKey: requestBodyPromptCacheKey,
|
|
6411
|
+
metadataSessionId,
|
|
6412
|
+
headerSessionId,
|
|
6413
|
+
upstreamRequestId
|
|
6414
|
+
});
|
|
6415
|
+
request.affinityKeyUsed = affinityKey.affinityKeyUsed;
|
|
6416
|
+
request.affinityKeySource = affinityKey.affinityKeySource;
|
|
6373
6417
|
const selection = await accountsManager.selectAccountForRequest([{
|
|
6374
6418
|
modelId: clientModel,
|
|
6375
6419
|
endpoint: RESPONSES_ENDPOINT
|
|
6376
|
-
}], { requestId:
|
|
6420
|
+
}], { requestId: affinityKey.requestId });
|
|
6377
6421
|
if (!selection.ok) {
|
|
6378
6422
|
recordSelectionFailure(store, {
|
|
6379
6423
|
request,
|
|
@@ -6386,6 +6430,7 @@ const handleResponses = async (c) => {
|
|
|
6386
6430
|
const { account, selectedModel } = selection;
|
|
6387
6431
|
request.affinityHit = selection.affinityHit;
|
|
6388
6432
|
request.affinityCacheKey = selection.affinityCacheKey;
|
|
6433
|
+
request.selectionReason = selection.selectionReason;
|
|
6389
6434
|
const upstreamPayload = {
|
|
6390
6435
|
...payload,
|
|
6391
6436
|
model: selectedModel.id
|
|
@@ -6399,7 +6444,7 @@ const handleResponses = async (c) => {
|
|
|
6399
6444
|
request.initiator = initiator;
|
|
6400
6445
|
if (state.manualApprove) await awaitApproval();
|
|
6401
6446
|
const accountCtx = toAccountContext(account);
|
|
6402
|
-
const upstreamSessionId = getUUID(upstreamRequestId);
|
|
6447
|
+
const upstreamSessionId = getUUID(normalizedPromptCacheKey ?? headerSessionId ?? upstreamRequestId);
|
|
6403
6448
|
request.upstreamRequestId = upstreamRequestId;
|
|
6404
6449
|
request.upstreamSessionId = upstreamSessionId;
|
|
6405
6450
|
if (streamRequested) return handleStreamingResponses({
|
|
@@ -6429,6 +6474,37 @@ const handleResponses = async (c) => {
|
|
|
6429
6474
|
premiumUnlimitedBefore
|
|
6430
6475
|
});
|
|
6431
6476
|
};
|
|
6477
|
+
async function observeRequestError(accountId, error) {
|
|
6478
|
+
const details = await extractErrorObservability(error);
|
|
6479
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(accountId, "Unauthorized (401)");
|
|
6480
|
+
return {
|
|
6481
|
+
httpStatus: details.httpStatus,
|
|
6482
|
+
errorName: details.errorName,
|
|
6483
|
+
errorStatus: details.errorStatus,
|
|
6484
|
+
errorMessage: details.errorMessage,
|
|
6485
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
function buildResponsesStreamError(message) {
|
|
6489
|
+
return {
|
|
6490
|
+
type: "error",
|
|
6491
|
+
code: null,
|
|
6492
|
+
message,
|
|
6493
|
+
param: null,
|
|
6494
|
+
sequence_number: 0
|
|
6495
|
+
};
|
|
6496
|
+
}
|
|
6497
|
+
async function writeResponsesStreamError(stream, message) {
|
|
6498
|
+
try {
|
|
6499
|
+
const errorEvent = buildResponsesStreamError(message);
|
|
6500
|
+
await stream.writeSSE({
|
|
6501
|
+
event: errorEvent.type,
|
|
6502
|
+
data: JSON.stringify(errorEvent)
|
|
6503
|
+
});
|
|
6504
|
+
} catch (streamError) {
|
|
6505
|
+
logger$1.warn("Failed to write Responses stream error event:", streamError);
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6432
6508
|
function buildRequestContext(c) {
|
|
6433
6509
|
const requestId = randomUUID();
|
|
6434
6510
|
const startedAtMs = Date.now();
|
|
@@ -6459,6 +6535,9 @@ function insertRequestLog(store, request, record) {
|
|
|
6459
6535
|
promptCacheKey: request.promptCacheKey,
|
|
6460
6536
|
initiator: request.initiator,
|
|
6461
6537
|
upstreamRequestId: request.upstreamRequestId,
|
|
6538
|
+
affinityKeyUsed: request.affinityKeyUsed,
|
|
6539
|
+
affinityKeySource: request.affinityKeySource,
|
|
6540
|
+
selectionReason: request.selectionReason,
|
|
6462
6541
|
affinityHit: request.affinityHit,
|
|
6463
6542
|
affinityCacheKey: request.affinityCacheKey,
|
|
6464
6543
|
...record
|
|
@@ -6497,14 +6576,6 @@ function extractUsageFromChunkData(data) {
|
|
|
6497
6576
|
return;
|
|
6498
6577
|
}
|
|
6499
6578
|
}
|
|
6500
|
-
function getStreamChunkFields(chunk) {
|
|
6501
|
-
const c = chunk;
|
|
6502
|
-
return {
|
|
6503
|
-
id: c.id,
|
|
6504
|
-
event: c.event,
|
|
6505
|
-
data: c.data
|
|
6506
|
-
};
|
|
6507
|
-
}
|
|
6508
6579
|
async function handleStreamingResponses(params) {
|
|
6509
6580
|
const { c, store, request, payload, selection, clientModel, accountCtx, vision, initiator, premiumRemainingBefore, premiumUnlimitedBefore } = params;
|
|
6510
6581
|
let response;
|
|
@@ -6555,8 +6626,8 @@ async function handleUpstreamCreateError(params) {
|
|
|
6555
6626
|
const { store, request, selection, clientModel, premiumRemainingBefore, premiumUnlimitedBefore, error } = params;
|
|
6556
6627
|
const { account, reservation, selectedModel, endpoint, costUnits } = selection;
|
|
6557
6628
|
const finishedAtMs = Date.now();
|
|
6558
|
-
const details =
|
|
6559
|
-
if (details
|
|
6629
|
+
const details = await extractErrorObservability(error);
|
|
6630
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
6560
6631
|
await accountsManager.finalizeQuota(account, reservation);
|
|
6561
6632
|
const premiumRemainingAfter = account.premiumRemaining;
|
|
6562
6633
|
const premiumUnlimitedAfter = account.unlimited;
|
|
@@ -6578,7 +6649,8 @@ async function handleUpstreamCreateError(params) {
|
|
|
6578
6649
|
httpStatus: details.httpStatus,
|
|
6579
6650
|
errorName: details.errorName,
|
|
6580
6651
|
errorStatus: details.errorStatus,
|
|
6581
|
-
errorMessage: details.errorMessage
|
|
6652
|
+
errorMessage: details.errorMessage,
|
|
6653
|
+
upstreamErrorMessageRaw: details.upstreamErrorMessageRaw
|
|
6582
6654
|
});
|
|
6583
6655
|
throw error;
|
|
6584
6656
|
}
|
|
@@ -6590,6 +6662,7 @@ async function handleNonStreamingUpstreamResult(params) {
|
|
|
6590
6662
|
let errorName;
|
|
6591
6663
|
let errorStatus;
|
|
6592
6664
|
let errorMessage;
|
|
6665
|
+
let upstreamErrorMessageRaw;
|
|
6593
6666
|
const finishedAtMs = Date.now();
|
|
6594
6667
|
try {
|
|
6595
6668
|
debugJsonTail(logger$1, "Forwarding native Responses result:", {
|
|
@@ -6598,11 +6671,12 @@ async function handleNonStreamingUpstreamResult(params) {
|
|
|
6598
6671
|
});
|
|
6599
6672
|
return c.json(result);
|
|
6600
6673
|
} catch (error) {
|
|
6601
|
-
const details =
|
|
6674
|
+
const details = await extractErrorObservability(error);
|
|
6602
6675
|
httpStatus = details.httpStatus;
|
|
6603
6676
|
errorName = details.errorName;
|
|
6604
6677
|
errorStatus = details.errorStatus;
|
|
6605
6678
|
errorMessage = details.errorMessage;
|
|
6679
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
6606
6680
|
throw error;
|
|
6607
6681
|
} finally {
|
|
6608
6682
|
await accountsManager.finalizeQuota(account, reservation);
|
|
@@ -6627,7 +6701,8 @@ async function handleNonStreamingUpstreamResult(params) {
|
|
|
6627
6701
|
httpStatus,
|
|
6628
6702
|
errorName,
|
|
6629
6703
|
errorStatus,
|
|
6630
|
-
errorMessage
|
|
6704
|
+
errorMessage,
|
|
6705
|
+
upstreamErrorMessageRaw
|
|
6631
6706
|
});
|
|
6632
6707
|
}
|
|
6633
6708
|
}
|
|
@@ -6640,6 +6715,7 @@ async function streamResponsesAndLog(params) {
|
|
|
6640
6715
|
let errorName;
|
|
6641
6716
|
let errorStatus;
|
|
6642
6717
|
let errorMessage;
|
|
6718
|
+
let upstreamErrorMessageRaw;
|
|
6643
6719
|
try {
|
|
6644
6720
|
for await (const chunk of response) {
|
|
6645
6721
|
if (ttfbMs === void 0) ttfbMs = Date.now() - request.startedAtMs;
|
|
@@ -6655,11 +6731,14 @@ async function streamResponsesAndLog(params) {
|
|
|
6655
6731
|
});
|
|
6656
6732
|
}
|
|
6657
6733
|
} catch (error) {
|
|
6658
|
-
const details =
|
|
6734
|
+
const details = await extractErrorObservability(error);
|
|
6659
6735
|
errorName = details.errorName;
|
|
6660
6736
|
errorStatus = details.errorStatus;
|
|
6661
6737
|
errorMessage = details.errorMessage;
|
|
6738
|
+
upstreamErrorMessageRaw = details.upstreamErrorMessageRaw;
|
|
6662
6739
|
logger$1.warn("Responses streaming error:", error);
|
|
6740
|
+
if (shouldMarkAccountFailed(details)) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
6741
|
+
await writeResponsesStreamError(stream, getUserVisibleErrorMessage(details));
|
|
6663
6742
|
} finally {
|
|
6664
6743
|
const finishedAtMs = Date.now();
|
|
6665
6744
|
await accountsManager.finalizeQuota(account, reservation);
|
|
@@ -6685,18 +6764,16 @@ async function streamResponsesAndLog(params) {
|
|
|
6685
6764
|
httpStatus: errorStatus ?? (errorName ? 500 : 200),
|
|
6686
6765
|
errorName,
|
|
6687
6766
|
errorStatus,
|
|
6688
|
-
errorMessage
|
|
6767
|
+
errorMessage,
|
|
6768
|
+
upstreamErrorMessageRaw
|
|
6689
6769
|
});
|
|
6690
6770
|
}
|
|
6691
6771
|
}
|
|
6692
6772
|
async function handleNonStreamingResponses(params) {
|
|
6693
6773
|
const { c, store, request, payload, selection, clientModel, accountCtx, vision, initiator, premiumRemainingBefore, premiumUnlimitedBefore } = params;
|
|
6694
6774
|
const { account, reservation, selectedModel, endpoint, costUnits } = selection;
|
|
6695
|
-
let httpStatus = 200;
|
|
6696
6775
|
let usage = {};
|
|
6697
|
-
let
|
|
6698
|
-
let errorStatus;
|
|
6699
|
-
let errorMessage;
|
|
6776
|
+
let errorState = { httpStatus: 200 };
|
|
6700
6777
|
let finishedAtMs;
|
|
6701
6778
|
try {
|
|
6702
6779
|
const response = await createResponses(payload, {
|
|
@@ -6705,10 +6782,9 @@ async function handleNonStreamingResponses(params) {
|
|
|
6705
6782
|
upstreamRequestId: request.upstreamRequestId,
|
|
6706
6783
|
sessionId: request.upstreamSessionId
|
|
6707
6784
|
}, accountCtx);
|
|
6785
|
+
if (isAsyncIterable(response)) throw new Error("Upstream returned a stream unexpectedly");
|
|
6708
6786
|
selection.confirmAffinity?.();
|
|
6709
6787
|
finishedAtMs = Date.now();
|
|
6710
|
-
const streamResponse = handleUnexpectedResponsesStream(c, response);
|
|
6711
|
-
if (streamResponse) return streamResponse;
|
|
6712
6788
|
const result = response;
|
|
6713
6789
|
usage = extractResponsesUsageFromResult(result);
|
|
6714
6790
|
debugJsonTail(logger$1, "Forwarding native Responses result:", {
|
|
@@ -6718,12 +6794,7 @@ async function handleNonStreamingResponses(params) {
|
|
|
6718
6794
|
return c.json(result);
|
|
6719
6795
|
} catch (error) {
|
|
6720
6796
|
finishedAtMs = Date.now();
|
|
6721
|
-
|
|
6722
|
-
httpStatus = details.httpStatus;
|
|
6723
|
-
errorName = details.errorName;
|
|
6724
|
-
errorStatus = details.errorStatus;
|
|
6725
|
-
errorMessage = details.errorMessage;
|
|
6726
|
-
if (details.unauthorized) accountsManager.markAccountFailed(account.id, "Unauthorized (401)");
|
|
6797
|
+
errorState = await observeRequestError(account.id, error);
|
|
6727
6798
|
throw error;
|
|
6728
6799
|
} finally {
|
|
6729
6800
|
const finishedAtMsFinal = finishedAtMs ?? Date.now();
|
|
@@ -6746,61 +6817,14 @@ async function handleNonStreamingResponses(params) {
|
|
|
6746
6817
|
premiumRemainingDiff: computeDiff(premiumRemainingBefore, premiumRemainingAfter),
|
|
6747
6818
|
premiumUnlimitedBefore,
|
|
6748
6819
|
premiumUnlimitedAfter,
|
|
6749
|
-
httpStatus,
|
|
6750
|
-
errorName,
|
|
6751
|
-
errorStatus,
|
|
6752
|
-
errorMessage
|
|
6820
|
+
httpStatus: errorState.httpStatus,
|
|
6821
|
+
errorName: errorState.errorName,
|
|
6822
|
+
errorStatus: errorState.errorStatus,
|
|
6823
|
+
errorMessage: errorState.errorMessage,
|
|
6824
|
+
upstreamErrorMessageRaw: errorState.upstreamErrorMessageRaw
|
|
6753
6825
|
});
|
|
6754
6826
|
}
|
|
6755
6827
|
}
|
|
6756
|
-
function handleUnexpectedResponsesStream(c, response) {
|
|
6757
|
-
if (!isAsyncIterable(response)) return null;
|
|
6758
|
-
logger$1.debug("Forwarding native Responses stream (unexpected)");
|
|
6759
|
-
return streamSSE(c, async (stream) => {
|
|
6760
|
-
const idTracker = createStreamIdTracker();
|
|
6761
|
-
for await (const chunk of response) {
|
|
6762
|
-
const { id, event, data } = getStreamChunkFields(chunk);
|
|
6763
|
-
const processedData = fixStreamIds(data ?? "", event, idTracker);
|
|
6764
|
-
debugJson(logger$1, "Responses stream chunk:", chunk);
|
|
6765
|
-
await stream.writeSSE({
|
|
6766
|
-
id,
|
|
6767
|
-
event,
|
|
6768
|
-
data: processedData
|
|
6769
|
-
});
|
|
6770
|
-
}
|
|
6771
|
-
});
|
|
6772
|
-
}
|
|
6773
|
-
const isAsyncIterable = (value) => Boolean(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
6774
|
-
const useFunctionApplyPatch = (payload) => {
|
|
6775
|
-
if (!(getConfig().useFunctionApplyPatch ?? true)) return;
|
|
6776
|
-
logger$1.debug("Using function tool apply_patch for responses");
|
|
6777
|
-
if (Array.isArray(payload.tools)) {
|
|
6778
|
-
const toolsArr = payload.tools;
|
|
6779
|
-
for (let i = 0; i < toolsArr.length; i++) {
|
|
6780
|
-
const t = toolsArr[i];
|
|
6781
|
-
if (t.type === "custom" && t.name === "apply_patch") toolsArr[i] = {
|
|
6782
|
-
type: "function",
|
|
6783
|
-
name: t.name,
|
|
6784
|
-
description: "Use the `apply_patch` tool to edit files",
|
|
6785
|
-
parameters: {
|
|
6786
|
-
type: "object",
|
|
6787
|
-
properties: { input: {
|
|
6788
|
-
type: "string",
|
|
6789
|
-
description: "The entire contents of the apply_patch command"
|
|
6790
|
-
} },
|
|
6791
|
-
required: ["input"]
|
|
6792
|
-
},
|
|
6793
|
-
strict: false
|
|
6794
|
-
};
|
|
6795
|
-
}
|
|
6796
|
-
}
|
|
6797
|
-
};
|
|
6798
|
-
const removeWebSearchTool = (payload) => {
|
|
6799
|
-
if (!Array.isArray(payload.tools) || payload.tools.length === 0) return;
|
|
6800
|
-
payload.tools = payload.tools.filter((t) => {
|
|
6801
|
-
return t.type !== "web_search";
|
|
6802
|
-
});
|
|
6803
|
-
};
|
|
6804
6828
|
|
|
6805
6829
|
//#endregion
|
|
6806
6830
|
//#region src/routes/responses/route.ts
|
|
@@ -6894,4 +6918,4 @@ server.route("/:provider/v1/models", providerModelRoutes);
|
|
|
6894
6918
|
|
|
6895
6919
|
//#endregion
|
|
6896
6920
|
export { server };
|
|
6897
|
-
//# sourceMappingURL=server-
|
|
6921
|
+
//# sourceMappingURL=server-CFijvv3C.js.map
|