@jeffreycao/copilot-api 2.4.0 → 2.4.2
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 +9 -9
- package/README.zh-CN.md +9 -9
- package/dist/auth-BCZdnMmK.js +2 -0
- package/dist/{auth-DmscMMR2.js → auth-DvqwYkJr.js} +58 -13
- package/dist/auth-DvqwYkJr.js.map +1 -0
- package/dist/{config-ByEK-9s6.js → config-DVWu2hKk.js} +3 -3
- package/dist/config-DVWu2hKk.js.map +1 -0
- package/dist/{debug-BHOitPm3.js → debug-DwoWAiuC.js} +2 -2
- package/dist/{debug-BHOitPm3.js.map → debug-DwoWAiuC.js.map} +1 -1
- package/dist/main.js +3 -3
- package/dist/{models-BWI-BuVr.js → models-DKNAPa_j.js} +2 -2
- package/dist/{models-BWI-BuVr.js.map → models-DKNAPa_j.js.map} +1 -1
- package/dist/{server-d83RMV1W.js → server-BvDQf9Nd.js} +694 -174
- package/dist/server-BvDQf9Nd.js.map +1 -0
- package/dist/{start-BUPTR3fg.js → start-BGY-GA8y.js} +6 -6
- package/dist/{start-BUPTR3fg.js.map → start-BGY-GA8y.js.map} +1 -1
- package/dist/{token-BYTz7z9X.js → token-D0GGk3E8.js} +2 -2
- package/dist/{token-BYTz7z9X.js.map → token-D0GGk3E8.js.map} +1 -1
- package/package.json +4 -1
- package/pages/index.html +106 -32
- package/dist/auth-BsFMCDdr.js +0 -2
- package/dist/auth-DmscMMR2.js.map +0 -1
- package/dist/config-ByEK-9s6.js.map +0 -1
- package/dist/server-d83RMV1W.js.map +0 -1
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
import { A as isResponsesApiWebSocketEnabled, C as getClaudeTokenMultiplier, D as isAlphaSearchCodexPriorityEnabled, E as getResponsesTransportConfig, N as PATHS, O as isMessagesApiEnabled, S as getClaudeAutoModel, T as getMessageApiWebSearchModel, _ as resolveMappedModel, b as getAlphaSearchModel, d as getModelResponsesApiCompactThreshold$1, f as getReasoningEffortForModel, g as isGpt56OrAbove, h as isContextManagementEnabledForResponses, i as listEnabledProviders, k as isResponsesApiWebSearchEnabled, l as getExtraPromptForModel, m as isContextManagementEnabledForMessages, n as getRawProviderConfig, o as resolveEffectiveProviderType, p as getSmallModel, s as resolveProviderAuthType, t as getProviderConfig, u as getModelMappings, v as setModelMappings, x as getAnthropicApiKey } from "./config-
|
|
2
|
-
import { B as fetchResponsesWithLifecycle, C as compactAutoContinuePromptStarts, D as compactTextOnlyGuard, E as compactSystemPromptStarts, F as forwardCodexResponses, G as createWebSocketUrl, H as encodePoolKeyPart, I as generateTraceId, J as forwardError, K as state, L as requestContext, N as CODEX_API_BASE_URL, O as createAuthMiddleware, P as buildCodexRequestHeaders, R as resolveTraceId$1, U as isTerminalResponsesStreamChunk, V as createResponsesSafeStream, W as createPooledWebSocketStream, b as prepareInteractionHeaders, c as getUUID, d as isResponsesStream, f as parseUserIdMetadata, g as copilotHeaders, h as copilotBaseUrl, k as getConfiguredAdminApiKeys, l as isAsyncIterable, o as generateRequestIdFromPayload, p as getCopilotUsage, q as HTTPError, r as setupCodexToken, s as getRootSessionId, u as isNullish, v as copilotWebSocketHeaders, w as compactMessageSections, x as prepareMessageProxyHeaders, y as prepareForCompact, z as createResponsesHttpEventStream } from "./token-
|
|
1
|
+
import { A as isResponsesApiWebSocketEnabled, C as getClaudeTokenMultiplier, D as isAlphaSearchCodexPriorityEnabled, E as getResponsesTransportConfig, N as PATHS, O as isMessagesApiEnabled, S as getClaudeAutoModel, T as getMessageApiWebSearchModel, _ as resolveMappedModel, b as getAlphaSearchModel, d as getModelResponsesApiCompactThreshold$1, f as getReasoningEffortForModel, g as isGpt56OrAbove, h as isContextManagementEnabledForResponses, i as listEnabledProviders, k as isResponsesApiWebSearchEnabled, l as getExtraPromptForModel, m as isContextManagementEnabledForMessages, n as getRawProviderConfig, o as resolveEffectiveProviderType, p as getSmallModel, s as resolveProviderAuthType, t as getProviderConfig, u as getModelMappings, v as setModelMappings, x as getAnthropicApiKey } from "./config-DVWu2hKk.js";
|
|
2
|
+
import { B as fetchResponsesWithLifecycle, C as compactAutoContinuePromptStarts, D as compactTextOnlyGuard, E as compactSystemPromptStarts, F as forwardCodexResponses, G as createWebSocketUrl, H as encodePoolKeyPart, I as generateTraceId, J as forwardError, K as state, L as requestContext, N as CODEX_API_BASE_URL, O as createAuthMiddleware, P as buildCodexRequestHeaders, R as resolveTraceId$1, U as isTerminalResponsesStreamChunk, V as createResponsesSafeStream, W as createPooledWebSocketStream, b as prepareInteractionHeaders, c as getUUID, d as isResponsesStream, f as parseUserIdMetadata, g as copilotHeaders, h as copilotBaseUrl, k as getConfiguredAdminApiKeys, l as isAsyncIterable, o as generateRequestIdFromPayload, p as getCopilotUsage, q as HTTPError, r as setupCodexToken, s as getRootSessionId, u as isNullish, v as copilotWebSocketHeaders, w as compactMessageSections, x as prepareMessageProxyHeaders, y as prepareForCompact, z as createResponsesHttpEventStream } from "./token-D0GGk3E8.js";
|
|
3
3
|
import { a as isDeferredToolName, c as parseMcpToolSearchSentinel, d as shouldEnableResponsesToolSearch, i as isBridgeToolSearchName, l as resolveBridgeToolSearchName, o as listDeferredToolNames, r as formatToolSearchBridgeArguments, s as normalizeToolSearchBridgeArguments, t as BRIDGE_TOOL_SEARCH_NAME, u as selectDeferredToolsByNames } from "./tool-search-Ds1vbmGG.js";
|
|
4
|
-
import { i as toClientModelId, r as normalizeSdkModelId, t as findEndpointModel } from "./models-
|
|
4
|
+
import { i as toClientModelId, r as normalizeSdkModelId, t as findEndpointModel } from "./models-DKNAPa_j.js";
|
|
5
5
|
import consola from "consola";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import fs, { readFileSync } from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import fs$1 from "node:fs/promises";
|
|
7
|
+
import fs, { createWriteStream, readFileSync, rmSync } from "node:fs";
|
|
8
|
+
import path, { join } from "node:path";
|
|
9
|
+
import fs$1, { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
10
11
|
import { fetch as fetch$1, getGlobalDispatcher } from "undici";
|
|
11
12
|
import { events } from "fetch-event-stream";
|
|
12
13
|
import { z } from "zod";
|
|
13
14
|
import { Hono } from "hono";
|
|
14
15
|
import { cors } from "hono/cors";
|
|
15
16
|
import { logger } from "hono/logger";
|
|
16
|
-
import { decompress } from "fzstd";
|
|
17
17
|
import util from "node:util";
|
|
18
18
|
import { streamSSE } from "hono/streaming";
|
|
19
|
+
import { Busboy } from "@fastify/busboy";
|
|
20
|
+
import { Readable, Transform } from "node:stream";
|
|
21
|
+
import { pipeline } from "node:stream/promises";
|
|
22
|
+
import { decompress } from "fzstd";
|
|
19
23
|
//#region src/lib/trace.ts
|
|
20
24
|
const traceIdMiddleware = async (c, next) => {
|
|
21
25
|
const traceId = resolveTraceId$1(c.req.header("x-trace-id"));
|
|
@@ -32,58 +36,6 @@ const traceIdMiddleware = async (c, next) => {
|
|
|
32
36
|
});
|
|
33
37
|
};
|
|
34
38
|
//#endregion
|
|
35
|
-
//#region src/lib/zstd-request.ts
|
|
36
|
-
const ZSTD_CONTENT_ENCODING = "zstd";
|
|
37
|
-
const INVALID_BODY_STATUS = 400;
|
|
38
|
-
let nodeZlibPromise = null;
|
|
39
|
-
const zstdDecompressionMiddleware = async (c, next) => {
|
|
40
|
-
if (c.req.header("content-encoding")?.trim().toLowerCase() !== ZSTD_CONTENT_ENCODING) return next();
|
|
41
|
-
try {
|
|
42
|
-
const decompressedBody = await decompressZstd(new Uint8Array(await c.req.raw.arrayBuffer()));
|
|
43
|
-
const headers = new Headers(c.req.raw.headers);
|
|
44
|
-
headers.delete("content-encoding");
|
|
45
|
-
headers.delete("content-length");
|
|
46
|
-
c.req.raw = new Request(c.req.raw.url, {
|
|
47
|
-
body: decompressedBody,
|
|
48
|
-
headers,
|
|
49
|
-
method: c.req.raw.method,
|
|
50
|
-
signal: c.req.raw.signal
|
|
51
|
-
});
|
|
52
|
-
c.req.bodyCache = {};
|
|
53
|
-
} catch {
|
|
54
|
-
return c.json({ error: {
|
|
55
|
-
message: "Failed to decompress zstd request body.",
|
|
56
|
-
type: "invalid_request_error"
|
|
57
|
-
} }, INVALID_BODY_STATUS);
|
|
58
|
-
}
|
|
59
|
-
return next();
|
|
60
|
-
};
|
|
61
|
-
const decompressZstd = async (input) => {
|
|
62
|
-
const bun = getBunRuntime();
|
|
63
|
-
if (bun?.zstdDecompress) return toUint8Array(await bun.zstdDecompress(input));
|
|
64
|
-
const nodeZlib = await getNodeZlib();
|
|
65
|
-
if (nodeZlib?.zstdDecompress) return new Promise((resolve, reject) => {
|
|
66
|
-
nodeZlib.zstdDecompress?.(input, (error, result) => {
|
|
67
|
-
if (error) {
|
|
68
|
-
reject(error);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
resolve(toUint8Array(result));
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
return decompress(input);
|
|
75
|
-
};
|
|
76
|
-
const getBunRuntime = () => globalThis.Bun;
|
|
77
|
-
const getNodeZlib = async () => {
|
|
78
|
-
nodeZlibPromise ??= import("node:zlib").then((module) => module).catch(() => null);
|
|
79
|
-
return nodeZlibPromise;
|
|
80
|
-
};
|
|
81
|
-
const toUint8Array = (data) => {
|
|
82
|
-
if (data instanceof Uint8Array) return data;
|
|
83
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
84
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
85
|
-
};
|
|
86
|
-
//#endregion
|
|
87
39
|
//#region src/lib/process-cleanup.ts
|
|
88
40
|
const cleanupHandlers = /* @__PURE__ */ new Set();
|
|
89
41
|
let cleanupPromise = null;
|
|
@@ -351,10 +303,33 @@ const createFallbackModel = (modelId) => ({
|
|
|
351
303
|
});
|
|
352
304
|
//#endregion
|
|
353
305
|
//#region src/lib/provider-resolver.ts
|
|
306
|
+
const AZURE_OPENAI_SCOPE = "https://cognitiveservices.azure.com/.default";
|
|
307
|
+
let cachedAzureAccessTokenProvider = null;
|
|
308
|
+
/**
|
|
309
|
+
* Lazily creates the Entra token provider on first use and reuses it
|
|
310
|
+
* afterwards. Importing @azure/identity eagerly adds ~0.5s to every process
|
|
311
|
+
* startup, and constructing a new DefaultAzureCredential per call would drop
|
|
312
|
+
* the token cache, so both the import and the credential are singletons.
|
|
313
|
+
*/
|
|
314
|
+
async function getAzureAccessTokenProvider() {
|
|
315
|
+
cachedAzureAccessTokenProvider ??= (async () => {
|
|
316
|
+
try {
|
|
317
|
+
const { DefaultAzureCredential, getBearerTokenProvider } = await import("@azure/identity");
|
|
318
|
+
return getBearerTokenProvider(new DefaultAzureCredential(), AZURE_OPENAI_SCOPE);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
cachedAzureAccessTokenProvider = null;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
})();
|
|
324
|
+
return cachedAzureAccessTokenProvider;
|
|
325
|
+
}
|
|
326
|
+
async function getDefaultAzureAccessToken() {
|
|
327
|
+
return (await getAzureAccessTokenProvider())();
|
|
328
|
+
}
|
|
354
329
|
function isMissingCodexCredentialsError(error) {
|
|
355
330
|
return error instanceof Error && error.message === "Codex credentials not found. Run `copilot-api auth login --provider codex` first.";
|
|
356
331
|
}
|
|
357
|
-
async function resolveProviderConfig(providerName) {
|
|
332
|
+
async function resolveProviderConfig(providerName, getAzureAccessToken = getDefaultAzureAccessToken) {
|
|
358
333
|
const normalizedProviderName = providerName.trim();
|
|
359
334
|
if (!normalizedProviderName) return null;
|
|
360
335
|
if (normalizedProviderName === "codex") {
|
|
@@ -372,7 +347,16 @@ async function resolveProviderConfig(providerName) {
|
|
|
372
347
|
apiKey: state.codexAccessToken ?? providerConfig.apiKey
|
|
373
348
|
};
|
|
374
349
|
}
|
|
375
|
-
|
|
350
|
+
const providerConfig = getProviderConfig(normalizedProviderName);
|
|
351
|
+
if (providerConfig?.authType !== "azure-entra") return providerConfig;
|
|
352
|
+
try {
|
|
353
|
+
return {
|
|
354
|
+
...providerConfig,
|
|
355
|
+
apiKey: await getAzureAccessToken()
|
|
356
|
+
};
|
|
357
|
+
} catch (error) {
|
|
358
|
+
throw new Error(`Failed to acquire Azure Entra token for provider '${normalizedProviderName}': ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
359
|
+
}
|
|
376
360
|
}
|
|
377
361
|
/**
|
|
378
362
|
* Returns the parsed "provider/model" alias only when that provider is
|
|
@@ -1186,53 +1170,68 @@ async function flushTokenUsageEvents() {
|
|
|
1186
1170
|
}
|
|
1187
1171
|
}
|
|
1188
1172
|
function getPeriodRange(period, now = /* @__PURE__ */ new Date()) {
|
|
1173
|
+
const nowMs = now.getTime();
|
|
1174
|
+
if (period === "lifetime") return {
|
|
1175
|
+
endMs: nowMs + 1,
|
|
1176
|
+
startMs: nowMs + 1
|
|
1177
|
+
};
|
|
1189
1178
|
const start = new Date(now);
|
|
1190
1179
|
start.setHours(0, 0, 0, 0);
|
|
1191
1180
|
switch (period) {
|
|
1192
|
-
case "
|
|
1193
|
-
case "
|
|
1194
|
-
|
|
1181
|
+
case "today": break;
|
|
1182
|
+
case "this_week": {
|
|
1183
|
+
const daysSinceMonday = (start.getDay() + 6) % 7;
|
|
1184
|
+
start.setDate(start.getDate() - daysSinceMonday);
|
|
1195
1185
|
break;
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
default: break;
|
|
1200
|
-
}
|
|
1201
|
-
const end = new Date(start);
|
|
1202
|
-
switch (period) {
|
|
1203
|
-
case "day":
|
|
1204
|
-
end.setDate(end.getDate() + 1);
|
|
1186
|
+
}
|
|
1187
|
+
case "last_7_days":
|
|
1188
|
+
start.setDate(start.getDate() - 6);
|
|
1205
1189
|
break;
|
|
1206
|
-
case "
|
|
1207
|
-
|
|
1190
|
+
case "this_month":
|
|
1191
|
+
start.setDate(1);
|
|
1208
1192
|
break;
|
|
1209
|
-
case "
|
|
1210
|
-
|
|
1193
|
+
case "last_30_days":
|
|
1194
|
+
start.setDate(start.getDate() - 29);
|
|
1211
1195
|
break;
|
|
1212
1196
|
default: break;
|
|
1213
1197
|
}
|
|
1214
1198
|
return {
|
|
1215
|
-
endMs:
|
|
1199
|
+
endMs: nowMs + 1,
|
|
1216
1200
|
startMs: start.getTime()
|
|
1217
1201
|
};
|
|
1218
1202
|
}
|
|
1203
|
+
function getPeriodRangeFromDb(db, period, now = /* @__PURE__ */ new Date()) {
|
|
1204
|
+
if (period !== "lifetime") return getPeriodRange(period, now);
|
|
1205
|
+
const nowMs = now.getTime();
|
|
1206
|
+
const earliestEventMs = db.prepare(`
|
|
1207
|
+
SELECT MIN(created_at_ms) AS start_ms
|
|
1208
|
+
FROM token_usage_events
|
|
1209
|
+
WHERE created_at_ms <= ?
|
|
1210
|
+
`).get(nowMs)?.start_ms;
|
|
1211
|
+
const startMs = typeof earliestEventMs === "number" && Number.isFinite(earliestEventMs) ? earliestEventMs : nowMs + 1;
|
|
1212
|
+
return {
|
|
1213
|
+
endMs: nowMs + 1,
|
|
1214
|
+
startMs
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1219
1217
|
function formatLocalDate(date) {
|
|
1220
1218
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
1221
1219
|
}
|
|
1222
1220
|
function createDailyIntervals(range) {
|
|
1223
1221
|
const intervals = [];
|
|
1224
1222
|
const cursor = new Date(range.startMs);
|
|
1223
|
+
cursor.setHours(0, 0, 0, 0);
|
|
1225
1224
|
while (cursor.getTime() < range.endMs) {
|
|
1226
|
-
const startMs = cursor.getTime();
|
|
1225
|
+
const startMs = Math.max(cursor.getTime(), range.startMs);
|
|
1227
1226
|
const next = new Date(cursor);
|
|
1228
1227
|
next.setDate(next.getDate() + 1);
|
|
1229
1228
|
const endMs = Math.min(next.getTime(), range.endMs);
|
|
1230
|
-
intervals.push({
|
|
1229
|
+
if (startMs < endMs) intervals.push({
|
|
1231
1230
|
date: formatLocalDate(cursor),
|
|
1232
1231
|
endMs,
|
|
1233
1232
|
startMs
|
|
1234
1233
|
});
|
|
1235
|
-
cursor.setTime(
|
|
1234
|
+
cursor.setTime(next.getTime());
|
|
1236
1235
|
}
|
|
1237
1236
|
return intervals;
|
|
1238
1237
|
}
|
|
@@ -1487,6 +1486,58 @@ function getModelSummaries(db, range) {
|
|
|
1487
1486
|
return modelSummaryFromRow(row, costMap.get(model) ?? []);
|
|
1488
1487
|
});
|
|
1489
1488
|
}
|
|
1489
|
+
function getDailyModelSummaries(db, range) {
|
|
1490
|
+
const usageDate = "strftime('%Y-%m-%d', created_at_ms / 1000.0, 'unixepoch', 'localtime')";
|
|
1491
|
+
const rows = db.prepare(`
|
|
1492
|
+
SELECT
|
|
1493
|
+
${usageDate} AS usage_date,
|
|
1494
|
+
model,
|
|
1495
|
+
COUNT(*) AS request_count,
|
|
1496
|
+
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
1497
|
+
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
1498
|
+
COALESCE(SUM(cache_read_input_tokens), 0) AS cache_read_input_tokens,
|
|
1499
|
+
COALESCE(SUM(cache_creation_input_tokens), 0) AS cache_creation_input_tokens,
|
|
1500
|
+
SUM(total_nano_aiu) AS total_nano_aiu,
|
|
1501
|
+
COALESCE(SUM(total_tokens), 0) AS total_tokens
|
|
1502
|
+
FROM token_usage_events
|
|
1503
|
+
WHERE created_at_ms >= ? AND created_at_ms < ?
|
|
1504
|
+
GROUP BY usage_date, model
|
|
1505
|
+
ORDER BY usage_date ASC, total_tokens DESC, model ASC
|
|
1506
|
+
`).all(range.startMs, range.endMs);
|
|
1507
|
+
const costRows = db.prepare(`
|
|
1508
|
+
SELECT
|
|
1509
|
+
${usageDate} AS usage_date,
|
|
1510
|
+
model,
|
|
1511
|
+
cost_currency,
|
|
1512
|
+
COALESCE(SUM(total_cost_nanos), 0) AS total_cost_nanos
|
|
1513
|
+
FROM token_usage_events
|
|
1514
|
+
WHERE created_at_ms >= ?
|
|
1515
|
+
AND created_at_ms < ?
|
|
1516
|
+
AND cost_currency IS NOT NULL
|
|
1517
|
+
AND total_cost_nanos IS NOT NULL
|
|
1518
|
+
GROUP BY usage_date, model, cost_currency
|
|
1519
|
+
ORDER BY usage_date ASC, model ASC, cost_currency ASC
|
|
1520
|
+
`).all(range.startMs, range.endMs);
|
|
1521
|
+
const costsByDateAndModel = /* @__PURE__ */ new Map();
|
|
1522
|
+
for (const row of costRows) {
|
|
1523
|
+
const date = stringFromRow(row, "usage_date");
|
|
1524
|
+
const model = stringFromRow(row, "model") || "unknown";
|
|
1525
|
+
const cost = costFromRow(row);
|
|
1526
|
+
if (!date || !cost) continue;
|
|
1527
|
+
const key = `${date}\u0000${model}`;
|
|
1528
|
+
costsByDateAndModel.set(key, [...costsByDateAndModel.get(key) ?? [], cost]);
|
|
1529
|
+
}
|
|
1530
|
+
const summariesByDate = /* @__PURE__ */ new Map();
|
|
1531
|
+
for (const row of rows) {
|
|
1532
|
+
const date = stringFromRow(row, "usage_date");
|
|
1533
|
+
if (!date) continue;
|
|
1534
|
+
const model = stringFromRow(row, "model") || "unknown";
|
|
1535
|
+
const summaries = summariesByDate.get(date) ?? [];
|
|
1536
|
+
summaries.push(modelSummaryFromRow(row, costsByDateAndModel.get(`${date}\u0000${model}`) ?? []));
|
|
1537
|
+
summariesByDate.set(date, summaries);
|
|
1538
|
+
}
|
|
1539
|
+
return summariesByDate;
|
|
1540
|
+
}
|
|
1490
1541
|
function createDailyBucket(interval, byModel) {
|
|
1491
1542
|
const totals = createEmptyTotals();
|
|
1492
1543
|
for (const model of byModel) addTotals(totals, model);
|
|
@@ -1501,8 +1552,8 @@ function createDailyBucket(interval, byModel) {
|
|
|
1501
1552
|
async function getTokenUsageSummary(period) {
|
|
1502
1553
|
if (!isTokenUsageStorageEnabled()) return createEmptySummary(period);
|
|
1503
1554
|
await flushTokenUsageEvents();
|
|
1504
|
-
const range = getPeriodRange(period);
|
|
1505
1555
|
const db = await getDb();
|
|
1556
|
+
const range = getPeriodRangeFromDb(db, period);
|
|
1506
1557
|
const totalsRow = getTotalsRow(db, range);
|
|
1507
1558
|
return {
|
|
1508
1559
|
byModel: getModelSummaries(db, range),
|
|
@@ -1514,12 +1565,13 @@ async function getTokenUsageSummary(period) {
|
|
|
1514
1565
|
async function getTokenUsageDailySummary(period) {
|
|
1515
1566
|
if (!isTokenUsageStorageEnabled()) return createEmptyDailySummary(period);
|
|
1516
1567
|
await flushTokenUsageEvents();
|
|
1517
|
-
const range = getPeriodRange(period);
|
|
1518
1568
|
const db = await getDb();
|
|
1569
|
+
const range = getPeriodRangeFromDb(db, period);
|
|
1519
1570
|
const intervals = createDailyIntervals(range);
|
|
1571
|
+
const dailySummaries = getDailyModelSummaries(db, range);
|
|
1520
1572
|
return {
|
|
1521
1573
|
byModel: getModelSummaries(db, range),
|
|
1522
|
-
days: intervals.map((interval) => createDailyBucket(interval,
|
|
1574
|
+
days: intervals.map((interval) => createDailyBucket(interval, dailySummaries.get(interval.date) ?? [])),
|
|
1523
1575
|
period,
|
|
1524
1576
|
range: rangePayload(range),
|
|
1525
1577
|
totals: totalsFromRow(getTotalsRow(db, range), getCostRows(db, range))
|
|
@@ -1528,11 +1580,11 @@ async function getTokenUsageDailySummary(period) {
|
|
|
1528
1580
|
async function getTokenUsageEventsPage(input) {
|
|
1529
1581
|
if (!isTokenUsageStorageEnabled()) return createEmptyEventsPage(input);
|
|
1530
1582
|
await flushTokenUsageEvents();
|
|
1531
|
-
const range = getPeriodRange(input.period);
|
|
1532
1583
|
const page = Math.max(1, Math.floor(input.page));
|
|
1533
1584
|
const pageSize = Math.min(100, Math.max(1, Math.floor(input.pageSize)));
|
|
1534
1585
|
const offset = (page - 1) * pageSize;
|
|
1535
1586
|
const db = await getDb();
|
|
1587
|
+
const range = getPeriodRangeFromDb(db, input.period);
|
|
1536
1588
|
const totalRow = db.prepare(`
|
|
1537
1589
|
SELECT COUNT(*) AS total
|
|
1538
1590
|
FROM token_usage_events
|
|
@@ -2270,6 +2322,20 @@ function buildProviderUpstreamHeaders(providerConfig, requestHeaders) {
|
|
|
2270
2322
|
}
|
|
2271
2323
|
return headers;
|
|
2272
2324
|
}
|
|
2325
|
+
const OPENCODE_GO_PROVIDER_NAME = "opencode-go";
|
|
2326
|
+
const OPENCODE_SESSION_HEADER = "x-opencode-session";
|
|
2327
|
+
const resolveOpencodeMessagesSession = (payload) => {
|
|
2328
|
+
const sessionAffinity = requestContext.getStore()?.sessionAffinity?.trim();
|
|
2329
|
+
if (sessionAffinity) return sessionAffinity;
|
|
2330
|
+
const userId = payload.metadata?.user_id;
|
|
2331
|
+
if (!userId?.trim()) return;
|
|
2332
|
+
const { sessionId } = parseUserIdMetadata(userId);
|
|
2333
|
+
return sessionId ?? userId;
|
|
2334
|
+
};
|
|
2335
|
+
const applyOpencodeSessionHeader = (providerConfig, headers, session) => {
|
|
2336
|
+
if (providerConfig.name !== OPENCODE_GO_PROVIDER_NAME || !session) return;
|
|
2337
|
+
headers[OPENCODE_SESSION_HEADER] = session;
|
|
2338
|
+
};
|
|
2273
2339
|
function createProviderProxyResponse(upstreamResponse, body) {
|
|
2274
2340
|
const headers = new Headers(upstreamResponse.headers);
|
|
2275
2341
|
for (const headerName of STRIPPED_RESPONSE_HEADERS) headers.delete(headerName);
|
|
@@ -2281,26 +2347,32 @@ function createProviderProxyResponse(upstreamResponse, body) {
|
|
|
2281
2347
|
}
|
|
2282
2348
|
async function forwardProviderMessages(providerConfig, payload, requestHeaders) {
|
|
2283
2349
|
consola.log(`<-- model: ${payload.model}`);
|
|
2350
|
+
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2351
|
+
applyOpencodeSessionHeader(providerConfig, headers, resolveOpencodeMessagesSession(payload));
|
|
2284
2352
|
return await fetch(`${providerConfig.baseUrl}/v1/messages`, {
|
|
2285
2353
|
method: "POST",
|
|
2286
|
-
headers
|
|
2354
|
+
headers,
|
|
2287
2355
|
body: JSON.stringify(payload)
|
|
2288
2356
|
});
|
|
2289
2357
|
}
|
|
2290
2358
|
async function forwardProviderChatCompletions(providerConfig, payload, requestHeaders) {
|
|
2291
2359
|
consola.log(`<-- model: ${payload.model}`);
|
|
2360
|
+
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2361
|
+
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
2292
2362
|
return await fetch(`${providerConfig.baseUrl}/v1/chat/completions`, {
|
|
2293
2363
|
method: "POST",
|
|
2294
|
-
headers
|
|
2364
|
+
headers,
|
|
2295
2365
|
body: JSON.stringify(payload)
|
|
2296
2366
|
});
|
|
2297
2367
|
}
|
|
2298
2368
|
async function forwardProviderResponses(providerConfig, payload, requestHeaders, options = {}) {
|
|
2299
2369
|
consola.log(`<-- model: ${payload.model}`);
|
|
2300
2370
|
const transportConfig = getResponsesTransportConfig();
|
|
2371
|
+
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
2372
|
+
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
2301
2373
|
return await fetchResponsesWithLifecycle(`${providerConfig.baseUrl}/v1/responses`, {
|
|
2302
2374
|
method: "POST",
|
|
2303
|
-
headers
|
|
2375
|
+
headers,
|
|
2304
2376
|
body: JSON.stringify(payload)
|
|
2305
2377
|
}, {
|
|
2306
2378
|
headersTimeoutMs: transportConfig.headersTimeoutMs,
|
|
@@ -2354,7 +2426,7 @@ async function forwardProviderImages(providerConfig, request, operation) {
|
|
|
2354
2426
|
}
|
|
2355
2427
|
//#endregion
|
|
2356
2428
|
//#region src/routes/alpha-search/alpha-search-responses.ts
|
|
2357
|
-
const logger$
|
|
2429
|
+
const logger$17 = createHandlerLogger("alpha-search-responses-handler");
|
|
2358
2430
|
const SESSION_TTL_MS = 3600 * 1e3;
|
|
2359
2431
|
const MAX_SESSIONS = 128;
|
|
2360
2432
|
const MAX_URL_REFERENCES = 256;
|
|
@@ -2712,14 +2784,14 @@ function createRemoteResponsesPayload(request, model, instruction) {
|
|
|
2712
2784
|
};
|
|
2713
2785
|
}
|
|
2714
2786
|
async function requestProviderSearch(c, providerConfig, payload, model, sessionId) {
|
|
2715
|
-
debugJson(logger$
|
|
2787
|
+
debugJson(logger$17, "Alpha search provider Responses request:", {
|
|
2716
2788
|
payload,
|
|
2717
2789
|
provider: providerConfig.name
|
|
2718
2790
|
});
|
|
2719
2791
|
const upstreamResponse = await forwardProviderResponses(providerConfig, payload, c.req.raw.headers, { signal: c.req.raw.signal });
|
|
2720
2792
|
if (!upstreamResponse.ok) throw new HTTPError(`Failed to create ${providerConfig.name} responses for alpha search`, upstreamResponse);
|
|
2721
2793
|
const result = await upstreamResponse.json();
|
|
2722
|
-
debugJson(logger$
|
|
2794
|
+
debugJson(logger$17, "Alpha search provider Responses result:", {
|
|
2723
2795
|
provider: providerConfig.name,
|
|
2724
2796
|
result
|
|
2725
2797
|
});
|
|
@@ -2735,7 +2807,7 @@ async function requestProviderSearch(c, providerConfig, payload, model, sessionI
|
|
|
2735
2807
|
return result;
|
|
2736
2808
|
}
|
|
2737
2809
|
async function requestCopilotSearch(payload, model, requestId, sessionId, signal) {
|
|
2738
|
-
debugJson(logger$
|
|
2810
|
+
debugJson(logger$17, "Alpha search Copilot Responses request:", payload);
|
|
2739
2811
|
const result = await alphaSearchResponsesDependencies.createResponses(payload, {
|
|
2740
2812
|
vision: false,
|
|
2741
2813
|
initiator: "agent",
|
|
@@ -2744,7 +2816,7 @@ async function requestCopilotSearch(payload, model, requestId, sessionId, signal
|
|
|
2744
2816
|
sessionId,
|
|
2745
2817
|
signal
|
|
2746
2818
|
});
|
|
2747
|
-
debugJson(logger$
|
|
2819
|
+
debugJson(logger$17, "Alpha search Copilot Responses result:", result);
|
|
2748
2820
|
alphaSearchResponsesDependencies.createUsageRecorder(model, sessionId)({
|
|
2749
2821
|
...normalizeResponsesUsage(result.usage),
|
|
2750
2822
|
total_nano_aiu: normalizeOptionalToken(result.copilot_usage?.total_nano_aiu)
|
|
@@ -2859,7 +2931,7 @@ async function forwardCodexAlphaSearch(request) {
|
|
|
2859
2931
|
}
|
|
2860
2932
|
//#endregion
|
|
2861
2933
|
//#region src/routes/alpha-search/route.ts
|
|
2862
|
-
const logger$
|
|
2934
|
+
const logger$16 = createHandlerLogger("alpha-search-handler");
|
|
2863
2935
|
const alphaSearchRoutes = new Hono();
|
|
2864
2936
|
const alphaSearchRouteDependencies = {
|
|
2865
2937
|
findEndpointModel,
|
|
@@ -2879,9 +2951,9 @@ function parseDebugBody(body) {
|
|
|
2879
2951
|
}
|
|
2880
2952
|
}
|
|
2881
2953
|
async function forwardCodexAlphaSearchRequest(request) {
|
|
2882
|
-
await debugJsonAsync(logger$
|
|
2954
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
|
|
2883
2955
|
const upstreamResponse = await alphaSearchRouteDependencies.forwardCodexAlphaSearch(request);
|
|
2884
|
-
await debugJsonAsync(logger$
|
|
2956
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.response", async () => ({
|
|
2885
2957
|
body: parseDebugBody(await upstreamResponse.clone().text()),
|
|
2886
2958
|
statusCode: upstreamResponse.status
|
|
2887
2959
|
}));
|
|
@@ -2992,7 +3064,7 @@ alphaSearchRoutes.post("/", async (c) => {
|
|
|
2992
3064
|
try {
|
|
2993
3065
|
return await handleAlphaSearchRequest(c);
|
|
2994
3066
|
} catch (error) {
|
|
2995
|
-
logger$
|
|
3067
|
+
logger$16.error("alpha_search.error", { error });
|
|
2996
3068
|
return await forwardError(c, error);
|
|
2997
3069
|
}
|
|
2998
3070
|
});
|
|
@@ -3120,7 +3192,7 @@ const normalizeProviderResponsesReasoningEffort = (payload, providerConfig) => {
|
|
|
3120
3192
|
};
|
|
3121
3193
|
//#endregion
|
|
3122
3194
|
//#region src/routes/provider/chat-completions/handler.ts
|
|
3123
|
-
const logger$
|
|
3195
|
+
const logger$15 = createHandlerLogger("provider-chat-completions-handler");
|
|
3124
3196
|
async function handleProviderChatCompletionsForProvider(c, options) {
|
|
3125
3197
|
const { payload, provider } = options;
|
|
3126
3198
|
const providerConfig = await resolveProviderConfig(provider);
|
|
@@ -3134,13 +3206,13 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
3134
3206
|
applyProviderStreamOptions(payload);
|
|
3135
3207
|
applyDashScopePreserveThinkingDefault(payload, providerConfig);
|
|
3136
3208
|
applyProviderContextCache(payload, modelConfig, providerConfig);
|
|
3137
|
-
debugJson(logger$
|
|
3209
|
+
debugJson(logger$15, "provider.chat_completions.request", {
|
|
3138
3210
|
payload,
|
|
3139
3211
|
provider
|
|
3140
3212
|
});
|
|
3141
3213
|
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers);
|
|
3142
3214
|
if (!upstreamResponse.ok) {
|
|
3143
|
-
logger$
|
|
3215
|
+
logger$15.error("Failed to create provider chat completions", {
|
|
3144
3216
|
provider,
|
|
3145
3217
|
statusCode: upstreamResponse.status
|
|
3146
3218
|
});
|
|
@@ -3154,7 +3226,7 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
3154
3226
|
});
|
|
3155
3227
|
const responseBody = await upstreamResponse.clone().json();
|
|
3156
3228
|
recordUsage(normalizeOpenAIUsage(responseBody.usage));
|
|
3157
|
-
debugJson(logger$
|
|
3229
|
+
debugJson(logger$15, "provider.chat_completions.response", responseBody);
|
|
3158
3230
|
return createProviderProxyResponse(upstreamResponse);
|
|
3159
3231
|
}
|
|
3160
3232
|
const applyProviderStreamOptions = (payload) => {
|
|
@@ -3176,12 +3248,12 @@ const createProviderChatCompletionsUsageRecorder = (payload, provider, modelConf
|
|
|
3176
3248
|
providerName: provider
|
|
3177
3249
|
});
|
|
3178
3250
|
const streamProviderChatCompletions = (c, upstreamResponse, options) => {
|
|
3179
|
-
logger$
|
|
3251
|
+
logger$15.debug("provider.chat_completions.streaming", { provider: options.provider });
|
|
3180
3252
|
return streamSSE(c, async (stream) => {
|
|
3181
3253
|
let usage = {};
|
|
3182
3254
|
try {
|
|
3183
3255
|
for await (const chunk of events(upstreamResponse)) {
|
|
3184
|
-
debugJson(logger$
|
|
3256
|
+
debugJson(logger$15, "provider.chat_completions.stream_chunk", chunk);
|
|
3185
3257
|
if (chunk.data && chunk.data !== "[DONE]") {
|
|
3186
3258
|
const parsedChunk = parseChatCompletionChunkData(chunk.data);
|
|
3187
3259
|
if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
|
|
@@ -3235,7 +3307,7 @@ const createChatCompletions = async (payload, options) => {
|
|
|
3235
3307
|
};
|
|
3236
3308
|
//#endregion
|
|
3237
3309
|
//#region src/routes/chat-completions/handler.ts
|
|
3238
|
-
const logger$
|
|
3310
|
+
const logger$14 = createHandlerLogger("chat-completions-handler");
|
|
3239
3311
|
async function handleCompletion$1(c) {
|
|
3240
3312
|
let payload = await c.req.json();
|
|
3241
3313
|
const requestedModel = payload.model;
|
|
@@ -3249,7 +3321,7 @@ async function handleCompletion$1(c) {
|
|
|
3249
3321
|
provider: providerModelAlias.provider
|
|
3250
3322
|
});
|
|
3251
3323
|
}
|
|
3252
|
-
debugJson(logger$
|
|
3324
|
+
debugJson(logger$14, "Request payload:", payload);
|
|
3253
3325
|
const selectedModel = findEndpointModel(payload.model);
|
|
3254
3326
|
payload.model = selectedModel?.id ?? payload.model;
|
|
3255
3327
|
if (isNullish(payload.max_tokens) && isNullish(payload.max_completion_tokens)) {
|
|
@@ -3257,16 +3329,16 @@ async function handleCompletion$1(c) {
|
|
|
3257
3329
|
...payload,
|
|
3258
3330
|
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
|
3259
3331
|
};
|
|
3260
|
-
debugJson(logger$
|
|
3332
|
+
debugJson(logger$14, "Set max_tokens to:", payload.max_tokens);
|
|
3261
3333
|
}
|
|
3262
3334
|
if (payload.model.includes("gpt")) {
|
|
3263
3335
|
if (isNullish(payload.max_completion_tokens)) payload.max_completion_tokens = payload.max_tokens;
|
|
3264
3336
|
delete payload.max_tokens;
|
|
3265
3337
|
}
|
|
3266
3338
|
const requestId = generateRequestIdFromPayload(payload);
|
|
3267
|
-
logger$
|
|
3339
|
+
logger$14.debug("Generated request ID:", requestId);
|
|
3268
3340
|
const sessionId = getUUID(requestId);
|
|
3269
|
-
logger$
|
|
3341
|
+
logger$14.debug("Extracted session ID:", sessionId);
|
|
3270
3342
|
const recordUsage = createCopilotTokenUsageRecorder({
|
|
3271
3343
|
endpoint: "chat_completions",
|
|
3272
3344
|
fallbackSessionId: sessionId,
|
|
@@ -3277,18 +3349,18 @@ async function handleCompletion$1(c) {
|
|
|
3277
3349
|
sessionId
|
|
3278
3350
|
});
|
|
3279
3351
|
if (isNonStreaming$1(response)) {
|
|
3280
|
-
debugJson(logger$
|
|
3352
|
+
debugJson(logger$14, "Non-streaming response:", response);
|
|
3281
3353
|
recordUsage({
|
|
3282
3354
|
...normalizeOpenAIUsage(response.usage),
|
|
3283
3355
|
total_nano_aiu: normalizeOptionalToken(response.copilot_usage?.total_nano_aiu)
|
|
3284
3356
|
});
|
|
3285
3357
|
return c.json(response);
|
|
3286
3358
|
}
|
|
3287
|
-
logger$
|
|
3359
|
+
logger$14.debug("Streaming response");
|
|
3288
3360
|
return streamSSE(c, async (stream) => {
|
|
3289
3361
|
let usage = {};
|
|
3290
3362
|
for await (const chunk of response) {
|
|
3291
|
-
debugJson(logger$
|
|
3363
|
+
debugJson(logger$14, "Streaming chunk:", chunk);
|
|
3292
3364
|
const parsedChunk = parseChatCompletionChunk(chunk);
|
|
3293
3365
|
if (parsedChunk?.usage || parsedChunk?.copilot_usage) usage = {
|
|
3294
3366
|
...normalizeOpenAIUsage(parsedChunk.usage),
|
|
@@ -3377,6 +3449,32 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
3377
3449
|
}
|
|
3378
3450
|
});
|
|
3379
3451
|
//#endregion
|
|
3452
|
+
//#region src/routes/images/forward-provider-images.ts
|
|
3453
|
+
/**
|
|
3454
|
+
* Forwards an images request to a configured provider with debug logging.
|
|
3455
|
+
* Shared by the top-level images routes (model mapping) and the
|
|
3456
|
+
* provider-scoped images routes so both log and proxy identically.
|
|
3457
|
+
*/
|
|
3458
|
+
async function forwardProviderImagesWithLogging(providerConfig, request, operation, { logger, provider }) {
|
|
3459
|
+
if (operation === "generations") await debugJsonAsync(logger, `provider.images.${operation}.request`, async () => ({
|
|
3460
|
+
body: await request.clone().text(),
|
|
3461
|
+
provider
|
|
3462
|
+
}));
|
|
3463
|
+
else debugJson(logger, `provider.images.${operation}.request`, {
|
|
3464
|
+
contentType: request.headers.get("content-type"),
|
|
3465
|
+
contentLength: request.headers.get("content-length"),
|
|
3466
|
+
provider
|
|
3467
|
+
});
|
|
3468
|
+
const upstreamResponse = await forwardProviderImages(providerConfig, request, operation);
|
|
3469
|
+
debugJson(logger, `provider.images.${operation}.response`, {
|
|
3470
|
+
contentType: upstreamResponse.headers.get("content-type"),
|
|
3471
|
+
contentLength: upstreamResponse.headers.get("content-length"),
|
|
3472
|
+
provider,
|
|
3473
|
+
statusCode: upstreamResponse.status
|
|
3474
|
+
});
|
|
3475
|
+
return createProviderProxyResponse(upstreamResponse);
|
|
3476
|
+
}
|
|
3477
|
+
//#endregion
|
|
3380
3478
|
//#region src/services/codex/images.ts
|
|
3381
3479
|
const CODEX_IMAGES_TIMEOUT_MS = 900 * 1e3;
|
|
3382
3480
|
const codexImagesDispatcher = createTimeoutDispatcher(CODEX_IMAGES_TIMEOUT_MS);
|
|
@@ -3404,41 +3502,433 @@ async function forwardCodexImages(request, operation) {
|
|
|
3404
3502
|
});
|
|
3405
3503
|
}
|
|
3406
3504
|
//#endregion
|
|
3407
|
-
//#region src/routes/images/
|
|
3408
|
-
const logger$
|
|
3409
|
-
const
|
|
3410
|
-
|
|
3411
|
-
|
|
3505
|
+
//#region src/routes/images/shared.ts
|
|
3506
|
+
const logger$13 = createHandlerLogger("images-handler");
|
|
3507
|
+
const imageRouteDependencies = {
|
|
3508
|
+
debugJsonAsync,
|
|
3509
|
+
resolveMappedModel,
|
|
3510
|
+
resolveProviderConfig
|
|
3511
|
+
};
|
|
3512
|
+
function getContentMetadata(headers) {
|
|
3412
3513
|
return {
|
|
3413
3514
|
contentType: headers.get("content-type"),
|
|
3414
3515
|
contentLength: headers.get("content-length")
|
|
3415
3516
|
};
|
|
3416
3517
|
}
|
|
3417
3518
|
/**
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3519
|
+
* Snapshots request headers before the body is consumed: Bun drops
|
|
3520
|
+
* auto-generated body headers (like the multipart boundary) afterwards.
|
|
3521
|
+
*/
|
|
3522
|
+
function snapshotRequestHeaders(request) {
|
|
3523
|
+
return new Headers(request.headers);
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Rebuilds a request from primitives instead of cloning: under Node runtimes
|
|
3527
|
+
* srvx wraps incoming requests in a proxy class whose prototype chain
|
|
3528
|
+
* satisfies `instanceof Request` without the native internals, so
|
|
3529
|
+
* `new Request(request)` throws.
|
|
3420
3530
|
*/
|
|
3421
|
-
|
|
3531
|
+
function createForwardRequest(request, requestHeaders, body) {
|
|
3532
|
+
const init = {
|
|
3533
|
+
body,
|
|
3534
|
+
duplex: "half",
|
|
3535
|
+
headers: requestHeaders,
|
|
3536
|
+
method: request.method,
|
|
3537
|
+
signal: request.signal
|
|
3538
|
+
};
|
|
3539
|
+
return new Request(request.url, init);
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Handles Codex images proxying. Pass a resolvedProviderConfig when the
|
|
3543
|
+
* caller already resolved the codex provider to avoid a second resolve, and
|
|
3544
|
+
* pass a request when model mapping rebuilt the request body.
|
|
3545
|
+
*/
|
|
3546
|
+
async function handleCodexImages(c, operation, resolvedProviderConfig, request = c.req.raw) {
|
|
3422
3547
|
try {
|
|
3423
|
-
if (!(resolvedProviderConfig ?? await resolveProviderConfig("codex"))) return c.json({ error: {
|
|
3548
|
+
if (!(resolvedProviderConfig ?? await imageRouteDependencies.resolveProviderConfig("codex"))) return c.json({ error: {
|
|
3424
3549
|
message: "Provider 'codex' not found or disabled",
|
|
3425
3550
|
type: "invalid_request_error"
|
|
3426
3551
|
} }, 404);
|
|
3427
|
-
if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$
|
|
3428
|
-
else debugJson(logger$
|
|
3429
|
-
const upstreamResponse = await forwardCodexImages(
|
|
3430
|
-
debugJson(logger$
|
|
3431
|
-
...getContentMetadata
|
|
3552
|
+
if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$13, "images.generations.codex.request", async () => ({ body: await request.clone().text() }));
|
|
3553
|
+
else debugJson(logger$13, "images.edits.codex.request", getContentMetadata(request.headers));
|
|
3554
|
+
const upstreamResponse = await forwardCodexImages(request, operation);
|
|
3555
|
+
debugJson(logger$13, `images.${operation}.codex.response`, {
|
|
3556
|
+
...getContentMetadata(upstreamResponse.headers),
|
|
3432
3557
|
statusCode: upstreamResponse.status
|
|
3433
3558
|
});
|
|
3434
3559
|
return createProviderProxyResponse(upstreamResponse);
|
|
3435
3560
|
} catch (error) {
|
|
3436
|
-
logger$
|
|
3561
|
+
logger$13.error(`images.${operation}.codex.error`, { error });
|
|
3437
3562
|
return await forwardError(c, error);
|
|
3438
3563
|
}
|
|
3439
3564
|
}
|
|
3440
|
-
|
|
3441
|
-
|
|
3565
|
+
/**
|
|
3566
|
+
* Routes a parsed images request: applies the model mapping, forwards to a
|
|
3567
|
+
* mapped provider when configured, and otherwise falls back to Codex.
|
|
3568
|
+
*/
|
|
3569
|
+
async function routeImagesRequest(c, operation, parsed) {
|
|
3570
|
+
const requestedModel = parsed.model;
|
|
3571
|
+
const mappedModel = imageRouteDependencies.resolveMappedModel(requestedModel);
|
|
3572
|
+
if (mappedModel !== requestedModel) consola.debug(`Resolved model mapping: ${requestedModel} -> ${mappedModel}`);
|
|
3573
|
+
const providerModelAlias = parseProviderModelAlias(mappedModel);
|
|
3574
|
+
if (providerModelAlias) {
|
|
3575
|
+
const providerConfig = await imageRouteDependencies.resolveProviderConfig(providerModelAlias.provider);
|
|
3576
|
+
if (providerConfig) {
|
|
3577
|
+
const request = parsed.createRequest(providerModelAlias.model);
|
|
3578
|
+
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig, request);
|
|
3579
|
+
return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
|
|
3580
|
+
logger: logger$13,
|
|
3581
|
+
provider: providerModelAlias.provider
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3584
|
+
consola.debug(`Provider '${providerModelAlias.provider}' not found or disabled; forwarding the original model to Codex`);
|
|
3585
|
+
return await handleCodexImages(c, operation, void 0, parsed.originalRequest ?? parsed.createRequest(requestedModel));
|
|
3586
|
+
}
|
|
3587
|
+
if (parsed.originalRequest && mappedModel === requestedModel) return await handleCodexImages(c, operation, void 0, parsed.originalRequest);
|
|
3588
|
+
return await handleCodexImages(c, operation, void 0, parsed.createRequest(mappedModel));
|
|
3589
|
+
}
|
|
3590
|
+
//#endregion
|
|
3591
|
+
//#region src/routes/images/temp-form-data.ts
|
|
3592
|
+
const logger$12 = createHandlerLogger("images-temp-form-data");
|
|
3593
|
+
const multipartStagingDependencies = { createWriteStream };
|
|
3594
|
+
/**
|
|
3595
|
+
* Matches the images forwarding timeouts: an upstream upload may take that
|
|
3596
|
+
* long, so staged files must survive at least the whole forwarding window.
|
|
3597
|
+
*/
|
|
3598
|
+
const STAGED_FILES_TTL_MS = 900 * 1e3;
|
|
3599
|
+
const DEFAULT_MULTIPART_STAGING_LIMITS = {
|
|
3600
|
+
maxBodySizeBytes: 128 * 1024 * 1024,
|
|
3601
|
+
maxFieldSizeBytes: 10 * 1024 * 1024,
|
|
3602
|
+
maxFields: 64,
|
|
3603
|
+
maxFileSizeBytes: 64 * 1024 * 1024,
|
|
3604
|
+
maxFiles: 16,
|
|
3605
|
+
maxParts: 80
|
|
3606
|
+
};
|
|
3607
|
+
var InvalidMultipartBodyError = class extends Error {
|
|
3608
|
+
constructor(options) {
|
|
3609
|
+
super("Invalid multipart form data body", options);
|
|
3610
|
+
this.name = "InvalidMultipartBodyError";
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
var MultipartBodyTooLargeError = class extends Error {
|
|
3614
|
+
constructor() {
|
|
3615
|
+
super("Multipart form data body exceeds the configured upload limits");
|
|
3616
|
+
this.name = "MultipartBodyTooLargeError";
|
|
3617
|
+
}
|
|
3618
|
+
};
|
|
3619
|
+
function sanitizeFileName(name) {
|
|
3620
|
+
return name.split(/[\\/]/).pop()?.replaceAll("\0", "") || "file";
|
|
3621
|
+
}
|
|
3622
|
+
async function createDiskBackedBlob(filePath, type) {
|
|
3623
|
+
if (typeof Bun !== "undefined") return Bun.file(filePath, { type });
|
|
3624
|
+
const { openAsBlob } = await import("node:fs");
|
|
3625
|
+
return await openAsBlob(filePath, { type });
|
|
3626
|
+
}
|
|
3627
|
+
function toError(error) {
|
|
3628
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
3629
|
+
}
|
|
3630
|
+
function parseMultipartBody(body, contentType, directory, entries, limits) {
|
|
3631
|
+
return new Promise((resolve, reject) => {
|
|
3632
|
+
let busboy;
|
|
3633
|
+
try {
|
|
3634
|
+
busboy = new Busboy({
|
|
3635
|
+
headers: { "content-type": contentType },
|
|
3636
|
+
limits: {
|
|
3637
|
+
fieldSize: limits.maxFieldSizeBytes,
|
|
3638
|
+
fields: limits.maxFields,
|
|
3639
|
+
fileSize: limits.maxFileSizeBytes,
|
|
3640
|
+
files: limits.maxFiles,
|
|
3641
|
+
parts: limits.maxParts
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
} catch (error) {
|
|
3645
|
+
reject(new InvalidMultipartBodyError({ cause: toError(error) }));
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
const pendingWrites = [];
|
|
3649
|
+
const activeFileStreams = /* @__PURE__ */ new Set();
|
|
3650
|
+
const source = body === null ? Readable.from([]) : Readable.fromWeb(body);
|
|
3651
|
+
let bodySizeBytes = 0;
|
|
3652
|
+
const bodyLimiter = new Transform({ transform(chunk, _encoding, callback) {
|
|
3653
|
+
bodySizeBytes += chunk.byteLength;
|
|
3654
|
+
if (bodySizeBytes > limits.maxBodySizeBytes) {
|
|
3655
|
+
callback(new MultipartBodyTooLargeError());
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
callback(null, chunk);
|
|
3659
|
+
} });
|
|
3660
|
+
let fileIndex = 0;
|
|
3661
|
+
let failure = null;
|
|
3662
|
+
let inputPipeline = null;
|
|
3663
|
+
let settled = false;
|
|
3664
|
+
const settleStreams = () => Promise.allSettled([...inputPipeline ? [inputPipeline] : [], ...pendingWrites]);
|
|
3665
|
+
const fail = (error) => {
|
|
3666
|
+
if (failure || settled) return;
|
|
3667
|
+
const failureError = toError(error);
|
|
3668
|
+
failure = failureError;
|
|
3669
|
+
source.destroy(failureError);
|
|
3670
|
+
bodyLimiter.destroy(failureError);
|
|
3671
|
+
busboy.destroy(failureError);
|
|
3672
|
+
for (const stream of activeFileStreams) stream.destroy(failureError);
|
|
3673
|
+
settleStreams().then(() => {
|
|
3674
|
+
if (settled) return;
|
|
3675
|
+
settled = true;
|
|
3676
|
+
reject(failureError);
|
|
3677
|
+
});
|
|
3678
|
+
};
|
|
3679
|
+
const failLimit = () => fail(new MultipartBodyTooLargeError());
|
|
3680
|
+
busboy.on("field", (name, value, nameTruncated, valueTruncated) => {
|
|
3681
|
+
if (nameTruncated || valueTruncated) {
|
|
3682
|
+
failLimit();
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
entries.push({
|
|
3686
|
+
kind: "field",
|
|
3687
|
+
name,
|
|
3688
|
+
value
|
|
3689
|
+
});
|
|
3690
|
+
});
|
|
3691
|
+
busboy.on("file", (name, stream, fileName, _transferEncoding, mimeType) => {
|
|
3692
|
+
if (failure) {
|
|
3693
|
+
stream.resume();
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
const filePath = join(directory, `${fileIndex}.upload`);
|
|
3697
|
+
fileIndex += 1;
|
|
3698
|
+
entries.push({
|
|
3699
|
+
fileName: sanitizeFileName(fileName),
|
|
3700
|
+
filePath,
|
|
3701
|
+
kind: "file",
|
|
3702
|
+
mimeType,
|
|
3703
|
+
name
|
|
3704
|
+
});
|
|
3705
|
+
activeFileStreams.add(stream);
|
|
3706
|
+
stream.once("close", () => activeFileStreams.delete(stream));
|
|
3707
|
+
stream.once("limit", failLimit);
|
|
3708
|
+
const write = pipeline(stream, multipartStagingDependencies.createWriteStream(filePath));
|
|
3709
|
+
pendingWrites.push(write);
|
|
3710
|
+
write.catch(fail);
|
|
3711
|
+
});
|
|
3712
|
+
busboy.on("fieldsLimit", failLimit);
|
|
3713
|
+
busboy.on("filesLimit", failLimit);
|
|
3714
|
+
busboy.on("partsLimit", failLimit);
|
|
3715
|
+
inputPipeline = pipeline(source, bodyLimiter, busboy);
|
|
3716
|
+
inputPipeline.then(() => {
|
|
3717
|
+
Promise.allSettled(pendingWrites).then((results) => {
|
|
3718
|
+
if (failure || settled) return;
|
|
3719
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
3720
|
+
if (failed) fail(failed.reason);
|
|
3721
|
+
else {
|
|
3722
|
+
settled = true;
|
|
3723
|
+
resolve();
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
}, (error) => {
|
|
3727
|
+
fail(error instanceof MultipartBodyTooLargeError ? error : new InvalidMultipartBodyError({ cause: toError(error) }));
|
|
3728
|
+
});
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3731
|
+
function resolveStagingLimits(overrides) {
|
|
3732
|
+
const limits = {
|
|
3733
|
+
...DEFAULT_MULTIPART_STAGING_LIMITS,
|
|
3734
|
+
...overrides
|
|
3735
|
+
};
|
|
3736
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive safe integer`);
|
|
3737
|
+
return limits;
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Streams a multipart request body through the parser, writing every file
|
|
3741
|
+
* part to a temporary directory as it arrives and rebuilding the form data
|
|
3742
|
+
* with disk-backed blobs, so forwarding streams payloads from disk instead
|
|
3743
|
+
* of pinning the whole upload in memory.
|
|
3744
|
+
*/
|
|
3745
|
+
async function stageMultipartBodyToDisk(body, contentType, limitOverrides = {}) {
|
|
3746
|
+
const limits = resolveStagingLimits(limitOverrides);
|
|
3747
|
+
const directory = await mkdtemp(join(tmpdir(), "copilot-api-images-"));
|
|
3748
|
+
const entries = [];
|
|
3749
|
+
try {
|
|
3750
|
+
await parseMultipartBody(body, contentType, directory, entries, limits);
|
|
3751
|
+
} catch (error) {
|
|
3752
|
+
await rm(directory, {
|
|
3753
|
+
force: true,
|
|
3754
|
+
recursive: true
|
|
3755
|
+
});
|
|
3756
|
+
throw error;
|
|
3757
|
+
}
|
|
3758
|
+
const stagedFormData = new FormData();
|
|
3759
|
+
try {
|
|
3760
|
+
for (const entry of entries) if (entry.kind === "field") stagedFormData.append(entry.name, entry.value);
|
|
3761
|
+
else stagedFormData.append(entry.name, await createDiskBackedBlob(entry.filePath, entry.mimeType), entry.fileName);
|
|
3762
|
+
} catch (error) {
|
|
3763
|
+
await rm(directory, {
|
|
3764
|
+
force: true,
|
|
3765
|
+
recursive: true
|
|
3766
|
+
});
|
|
3767
|
+
throw error;
|
|
3768
|
+
}
|
|
3769
|
+
let cleanupPromise = null;
|
|
3770
|
+
let cleanupTimer = null;
|
|
3771
|
+
let unregisterProcessCleanup = null;
|
|
3772
|
+
const cleanup = () => {
|
|
3773
|
+
cleanupPromise ??= (async () => {
|
|
3774
|
+
if (cleanupTimer) {
|
|
3775
|
+
clearTimeout(cleanupTimer);
|
|
3776
|
+
cleanupTimer = null;
|
|
3777
|
+
}
|
|
3778
|
+
try {
|
|
3779
|
+
await rm(directory, {
|
|
3780
|
+
force: true,
|
|
3781
|
+
recursive: true
|
|
3782
|
+
});
|
|
3783
|
+
} catch (error) {
|
|
3784
|
+
logger$12.warn("Failed to remove staged images temp directory", {
|
|
3785
|
+
directory,
|
|
3786
|
+
error
|
|
3787
|
+
});
|
|
3788
|
+
} finally {
|
|
3789
|
+
unregisterProcessCleanup?.();
|
|
3790
|
+
unregisterProcessCleanup = null;
|
|
3791
|
+
}
|
|
3792
|
+
})();
|
|
3793
|
+
return cleanupPromise;
|
|
3794
|
+
};
|
|
3795
|
+
unregisterProcessCleanup = registerProcessCleanup(() => {
|
|
3796
|
+
if (cleanupTimer) {
|
|
3797
|
+
clearTimeout(cleanupTimer);
|
|
3798
|
+
cleanupTimer = null;
|
|
3799
|
+
}
|
|
3800
|
+
try {
|
|
3801
|
+
rmSync(directory, {
|
|
3802
|
+
force: true,
|
|
3803
|
+
recursive: true
|
|
3804
|
+
});
|
|
3805
|
+
} catch (error) {
|
|
3806
|
+
logger$12.warn("Failed to remove staged images temp directory on exit", {
|
|
3807
|
+
directory,
|
|
3808
|
+
error
|
|
3809
|
+
});
|
|
3810
|
+
} finally {
|
|
3811
|
+
unregisterProcessCleanup?.();
|
|
3812
|
+
unregisterProcessCleanup = null;
|
|
3813
|
+
}
|
|
3814
|
+
});
|
|
3815
|
+
return {
|
|
3816
|
+
directory,
|
|
3817
|
+
formData: stagedFormData,
|
|
3818
|
+
cleanup,
|
|
3819
|
+
scheduleCleanup: () => {
|
|
3820
|
+
if (cleanupPromise || cleanupTimer) return;
|
|
3821
|
+
cleanupTimer = setTimeout(() => void cleanup(), STAGED_FILES_TTL_MS);
|
|
3822
|
+
cleanupTimer.unref();
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3826
|
+
//#endregion
|
|
3827
|
+
//#region src/routes/images/edits-handler.ts
|
|
3828
|
+
const imageEditsRouteDependencies = { stageMultipartBodyToDisk };
|
|
3829
|
+
function createStagedFormDataRequest(request, requestHeaders, formData) {
|
|
3830
|
+
const headers = new Headers(requestHeaders);
|
|
3831
|
+
headers.delete("content-length");
|
|
3832
|
+
headers.delete("content-type");
|
|
3833
|
+
return createForwardRequest(request, headers, formData);
|
|
3834
|
+
}
|
|
3835
|
+
function createMultipartImagesRequest(request, requestHeaders, formData, model) {
|
|
3836
|
+
formData.set("model", model);
|
|
3837
|
+
return createStagedFormDataRequest(request, requestHeaders, formData);
|
|
3838
|
+
}
|
|
3839
|
+
/**
|
|
3840
|
+
* Streams an edits multipart body to disk, staging uploaded files so
|
|
3841
|
+
* forwarding streams them from the filesystem instead of pinning them in
|
|
3842
|
+
* memory. Non-multipart bodies come back as an untouched request stream;
|
|
3843
|
+
* malformed multipart bodies throw a typed error because the consumed stream
|
|
3844
|
+
* can no longer be forwarded.
|
|
3845
|
+
*/
|
|
3846
|
+
async function parseEditsRequest(request) {
|
|
3847
|
+
const requestHeaders = snapshotRequestHeaders(request);
|
|
3848
|
+
const contentType = requestHeaders.get("content-type");
|
|
3849
|
+
if (contentType?.split(";", 1)[0]?.trim().toLowerCase() !== "multipart/form-data" || !contentType) return createForwardRequest(request, requestHeaders, request.body);
|
|
3850
|
+
const staged = await imageEditsRouteDependencies.stageMultipartBodyToDisk(request.body, contentType);
|
|
3851
|
+
const model = staged.formData.get("model");
|
|
3852
|
+
return {
|
|
3853
|
+
model: typeof model === "string" ? model : void 0,
|
|
3854
|
+
requestHeaders,
|
|
3855
|
+
staged
|
|
3856
|
+
};
|
|
3857
|
+
}
|
|
3858
|
+
async function handleImagesEdits(c) {
|
|
3859
|
+
try {
|
|
3860
|
+
const parsed = await parseEditsRequest(c.req.raw);
|
|
3861
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed);
|
|
3862
|
+
const { model, requestHeaders, staged } = parsed;
|
|
3863
|
+
try {
|
|
3864
|
+
const response = model === void 0 ? await handleCodexImages(c, "edits", void 0, createStagedFormDataRequest(c.req.raw, requestHeaders, staged.formData)) : await routeImagesRequest(c, "edits", {
|
|
3865
|
+
createRequest: (mappedModel) => createMultipartImagesRequest(c.req.raw, requestHeaders, staged.formData, mappedModel),
|
|
3866
|
+
model
|
|
3867
|
+
});
|
|
3868
|
+
staged.scheduleCleanup();
|
|
3869
|
+
return response;
|
|
3870
|
+
} catch (error) {
|
|
3871
|
+
await staged.cleanup();
|
|
3872
|
+
throw error;
|
|
3873
|
+
}
|
|
3874
|
+
} catch (error) {
|
|
3875
|
+
if (error instanceof InvalidMultipartBodyError || error instanceof MultipartBodyTooLargeError) return c.json({ error: {
|
|
3876
|
+
message: error.message,
|
|
3877
|
+
type: "invalid_request_error"
|
|
3878
|
+
} }, error instanceof MultipartBodyTooLargeError ? 413 : 400);
|
|
3879
|
+
logger$13.error("images.edits.error", { error });
|
|
3880
|
+
return await forwardError(c, error);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
//#endregion
|
|
3884
|
+
//#region src/routes/images/generations-handler.ts
|
|
3885
|
+
function createJsonImagesRequest(request, requestHeaders, payload, model) {
|
|
3886
|
+
const headers = new Headers(requestHeaders);
|
|
3887
|
+
headers.delete("content-length");
|
|
3888
|
+
return createForwardRequest(request, headers, new TextEncoder().encode(JSON.stringify({
|
|
3889
|
+
...payload,
|
|
3890
|
+
model
|
|
3891
|
+
})));
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* Parses a generations request body as JSON. Generation payloads are small
|
|
3895
|
+
* JSON documents, so one byte copy covers both routing on the model field
|
|
3896
|
+
* and forwarding an unchanged body to Codex. Rebuilt bodies stay binary so
|
|
3897
|
+
* Node does not synthesize a text/plain content type.
|
|
3898
|
+
*/
|
|
3899
|
+
async function parseGenerationsRequest(request) {
|
|
3900
|
+
const requestHeaders = snapshotRequestHeaders(request);
|
|
3901
|
+
const body = new Uint8Array(await request.arrayBuffer());
|
|
3902
|
+
const originalRequest = createForwardRequest(request, requestHeaders, body);
|
|
3903
|
+
let payload;
|
|
3904
|
+
try {
|
|
3905
|
+
const bodyText = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
3906
|
+
payload = JSON.parse(bodyText);
|
|
3907
|
+
} catch {
|
|
3908
|
+
return originalRequest;
|
|
3909
|
+
}
|
|
3910
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
|
|
3911
|
+
return {
|
|
3912
|
+
createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
|
|
3913
|
+
model: payload.model,
|
|
3914
|
+
originalRequest
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
async function handleImagesGenerations(c) {
|
|
3918
|
+
try {
|
|
3919
|
+
const parsed = await parseGenerationsRequest(c.req.raw);
|
|
3920
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed);
|
|
3921
|
+
return await routeImagesRequest(c, "generations", parsed);
|
|
3922
|
+
} catch (error) {
|
|
3923
|
+
logger$13.error("images.generations.error", { error });
|
|
3924
|
+
return await forwardError(c, error);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
//#endregion
|
|
3928
|
+
//#region src/routes/images/route.ts
|
|
3929
|
+
const imageRoutes = new Hono();
|
|
3930
|
+
imageRoutes.post("/generations", handleImagesGenerations);
|
|
3931
|
+
imageRoutes.post("/edits", handleImagesEdits);
|
|
3442
3932
|
//#endregion
|
|
3443
3933
|
//#region src/lib/tokenizer.ts
|
|
3444
3934
|
const ENCODING_MAP = {
|
|
@@ -7444,6 +7934,10 @@ function getModels() {
|
|
|
7444
7934
|
//#region src/routes/provider/messages/handler.ts
|
|
7445
7935
|
const logger$10 = createHandlerLogger("provider-messages-handler");
|
|
7446
7936
|
const providerMessagesHandlerDependencies = { resolveProviderConfig };
|
|
7937
|
+
const resolveOverrideProviderAuthType = (providerConfig, effectiveType) => {
|
|
7938
|
+
if (providerConfig.authType === "azure-entra" || providerConfig.authType === "oauth2") return providerConfig.authType;
|
|
7939
|
+
return resolveProviderAuthType(providerConfig.name, void 0, effectiveType);
|
|
7940
|
+
};
|
|
7447
7941
|
async function handleProviderMessages(c) {
|
|
7448
7942
|
const provider = c.req.param("provider");
|
|
7449
7943
|
const payload = await c.req.json();
|
|
@@ -7510,7 +8004,7 @@ async function handleProviderMessagesForProvider(c, options) {
|
|
|
7510
8004
|
const upstreamResponse = await forwardProviderMessages(effectiveType === providerConfig.type ? providerConfig : {
|
|
7511
8005
|
...providerConfig,
|
|
7512
8006
|
type: effectiveType,
|
|
7513
|
-
authType:
|
|
8007
|
+
authType: resolveOverrideProviderAuthType(providerConfig, effectiveType)
|
|
7514
8008
|
}, payload, c.req.raw.headers);
|
|
7515
8009
|
if (!upstreamResponse.ok) {
|
|
7516
8010
|
logger$10.error("Failed to create responses", upstreamResponse);
|
|
@@ -9908,17 +10402,6 @@ function isDeepSeekModelId(modelId) {
|
|
|
9908
10402
|
function shouldInjectMessagesToolCallTips(userAgent, targetModel) {
|
|
9909
10403
|
return isCodexUserAgent(userAgent) && !isDeepSeekModelId(targetModel);
|
|
9910
10404
|
}
|
|
9911
|
-
async function logCodexModelsResponse(response) {
|
|
9912
|
-
try {
|
|
9913
|
-
const models = await response.clone().json();
|
|
9914
|
-
debugJson(logger$8, "models.codex.response", {
|
|
9915
|
-
statusCode: response.status,
|
|
9916
|
-
models
|
|
9917
|
-
});
|
|
9918
|
-
} catch (error) {
|
|
9919
|
-
logger$8.warn("models.codex.response_log_error", { error });
|
|
9920
|
-
}
|
|
9921
|
-
}
|
|
9922
10405
|
/**
|
|
9923
10406
|
* Proxies a models request to the fixed Codex upstream models endpoint.
|
|
9924
10407
|
* Returns a 404 JSON response when the codex provider is unavailable.
|
|
@@ -9930,9 +10413,7 @@ async function handleCodexModelsProxy(c, resolvedProviderConfig) {
|
|
|
9930
10413
|
message: "Provider 'codex' not found or disabled",
|
|
9931
10414
|
type: "invalid_request_error"
|
|
9932
10415
|
} }, 404);
|
|
9933
|
-
|
|
9934
|
-
await logCodexModelsResponse(upstreamResponse);
|
|
9935
|
-
return createProviderProxyResponse(upstreamResponse);
|
|
10416
|
+
return createProviderProxyResponse(await forwardCodexModels(c.req.url, c.req.raw.headers));
|
|
9936
10417
|
}
|
|
9937
10418
|
async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
9938
10419
|
const [upstreamCatalog, candidates] = await Promise.all([tryGetCodexCatalog(c), Promise.resolve(candidatesRequest).catch((error) => {
|
|
@@ -9971,12 +10452,6 @@ async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
|
9971
10452
|
...upstreamCatalog ?? {},
|
|
9972
10453
|
models
|
|
9973
10454
|
};
|
|
9974
|
-
debugJson(logger$8, "models.codex.merged_response", {
|
|
9975
|
-
upstreamCount: upstreamModels.length,
|
|
9976
|
-
codexProviderAliasCount: codexProviderAliases.length,
|
|
9977
|
-
syntheticCount: syntheticModels.length,
|
|
9978
|
-
models: response
|
|
9979
|
-
});
|
|
9980
10455
|
return c.json(response);
|
|
9981
10456
|
}
|
|
9982
10457
|
function createCatalogAlias(model, slug, providerName) {
|
|
@@ -10419,12 +10894,6 @@ providerAlphaSearchRoutes.post("/", async (c) => {
|
|
|
10419
10894
|
//#region src/routes/provider/images/route.ts
|
|
10420
10895
|
const logger$5 = createHandlerLogger("provider-images-handler");
|
|
10421
10896
|
const providerImageRoutes = new Hono();
|
|
10422
|
-
function getContentMetadata(headers) {
|
|
10423
|
-
return {
|
|
10424
|
-
contentType: headers.get("content-type"),
|
|
10425
|
-
contentLength: headers.get("content-length")
|
|
10426
|
-
};
|
|
10427
|
-
}
|
|
10428
10897
|
async function handleProviderImages(c, operation) {
|
|
10429
10898
|
const provider = c.req.param("provider") ?? "";
|
|
10430
10899
|
try {
|
|
@@ -10434,21 +10903,10 @@ async function handleProviderImages(c, operation) {
|
|
|
10434
10903
|
type: "invalid_request_error"
|
|
10435
10904
|
} }, 404);
|
|
10436
10905
|
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig);
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
provider
|
|
10440
|
-
}));
|
|
10441
|
-
else debugJson(logger$5, "provider.images.edits.request", {
|
|
10442
|
-
...getContentMetadata(c.req.raw.headers),
|
|
10906
|
+
return await forwardProviderImagesWithLogging(providerConfig, c.req.raw, operation, {
|
|
10907
|
+
logger: logger$5,
|
|
10443
10908
|
provider
|
|
10444
10909
|
});
|
|
10445
|
-
const upstreamResponse = await forwardProviderImages(providerConfig, c.req.raw, operation);
|
|
10446
|
-
debugJson(logger$5, `provider.images.${operation}.response`, {
|
|
10447
|
-
...getContentMetadata(upstreamResponse.headers),
|
|
10448
|
-
provider,
|
|
10449
|
-
statusCode: upstreamResponse.status
|
|
10450
|
-
});
|
|
10451
|
-
return createProviderProxyResponse(upstreamResponse);
|
|
10452
10910
|
} catch (error) {
|
|
10453
10911
|
logger$5.error(`provider.images.${operation}.error`, {
|
|
10454
10912
|
provider,
|
|
@@ -10512,6 +10970,58 @@ providerModelRoutes.get("/", async (c) => {
|
|
|
10512
10970
|
}
|
|
10513
10971
|
});
|
|
10514
10972
|
//#endregion
|
|
10973
|
+
//#region src/lib/zstd-request.ts
|
|
10974
|
+
const ZSTD_CONTENT_ENCODING = "zstd";
|
|
10975
|
+
const INVALID_BODY_STATUS = 400;
|
|
10976
|
+
let nodeZlibPromise = null;
|
|
10977
|
+
const zstdDecompressionMiddleware = async (c, next) => {
|
|
10978
|
+
if (c.req.header("content-encoding")?.trim().toLowerCase() !== ZSTD_CONTENT_ENCODING) return next();
|
|
10979
|
+
try {
|
|
10980
|
+
const decompressedBody = await decompressZstd(new Uint8Array(await c.req.raw.arrayBuffer()));
|
|
10981
|
+
const headers = new Headers(c.req.raw.headers);
|
|
10982
|
+
headers.delete("content-encoding");
|
|
10983
|
+
headers.delete("content-length");
|
|
10984
|
+
c.req.raw = new Request(c.req.raw.url, {
|
|
10985
|
+
body: decompressedBody,
|
|
10986
|
+
headers,
|
|
10987
|
+
method: c.req.raw.method,
|
|
10988
|
+
signal: c.req.raw.signal
|
|
10989
|
+
});
|
|
10990
|
+
c.req.bodyCache = {};
|
|
10991
|
+
} catch {
|
|
10992
|
+
return c.json({ error: {
|
|
10993
|
+
message: "Failed to decompress zstd request body.",
|
|
10994
|
+
type: "invalid_request_error"
|
|
10995
|
+
} }, INVALID_BODY_STATUS);
|
|
10996
|
+
}
|
|
10997
|
+
return next();
|
|
10998
|
+
};
|
|
10999
|
+
const decompressZstd = async (input) => {
|
|
11000
|
+
const bun = getBunRuntime();
|
|
11001
|
+
if (bun?.zstdDecompress) return toUint8Array(await bun.zstdDecompress(input));
|
|
11002
|
+
const nodeZlib = await getNodeZlib();
|
|
11003
|
+
if (nodeZlib?.zstdDecompress) return new Promise((resolve, reject) => {
|
|
11004
|
+
nodeZlib.zstdDecompress?.(input, (error, result) => {
|
|
11005
|
+
if (error) {
|
|
11006
|
+
reject(error);
|
|
11007
|
+
return;
|
|
11008
|
+
}
|
|
11009
|
+
resolve(toUint8Array(result));
|
|
11010
|
+
});
|
|
11011
|
+
});
|
|
11012
|
+
return decompress(input);
|
|
11013
|
+
};
|
|
11014
|
+
const getBunRuntime = () => globalThis.Bun;
|
|
11015
|
+
const getNodeZlib = async () => {
|
|
11016
|
+
nodeZlibPromise ??= import("node:zlib").then((module) => module).catch(() => null);
|
|
11017
|
+
return nodeZlibPromise;
|
|
11018
|
+
};
|
|
11019
|
+
const toUint8Array = (data) => {
|
|
11020
|
+
if (data instanceof Uint8Array) return data;
|
|
11021
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
11022
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
11023
|
+
};
|
|
11024
|
+
//#endregion
|
|
10515
11025
|
//#region src/routes/responses/custom-tool-input-stream-decoder.ts
|
|
10516
11026
|
const CUSTOM_TOOL_INPUT_PREFIX_TOKENS = [
|
|
10517
11027
|
"{",
|
|
@@ -11551,6 +12061,7 @@ const getResponsesEvents = (response, signal) => createResponsesSafeStream(creat
|
|
|
11551
12061
|
//#endregion
|
|
11552
12062
|
//#region src/routes/provider/responses/route.ts
|
|
11553
12063
|
const providerResponsesRoutes = new Hono();
|
|
12064
|
+
providerResponsesRoutes.use(zstdDecompressionMiddleware);
|
|
11554
12065
|
providerResponsesRoutes.post("/", async (c) => {
|
|
11555
12066
|
try {
|
|
11556
12067
|
const provider = c.req.param("provider") ?? "";
|
|
@@ -11802,6 +12313,7 @@ const getTrimmedHeader = (c, name) => {
|
|
|
11802
12313
|
//#endregion
|
|
11803
12314
|
//#region src/routes/responses/route.ts
|
|
11804
12315
|
const responsesRoutes = new Hono();
|
|
12316
|
+
responsesRoutes.use(zstdDecompressionMiddleware);
|
|
11805
12317
|
responsesRoutes.post("/", async (c) => {
|
|
11806
12318
|
try {
|
|
11807
12319
|
return await handleResponses(c);
|
|
@@ -11813,13 +12325,22 @@ responsesRoutes.post("/", async (c) => {
|
|
|
11813
12325
|
//#region src/routes/token-usage/route.ts
|
|
11814
12326
|
const tokenUsageRoute = new Hono();
|
|
11815
12327
|
const periods = new Set([
|
|
11816
|
-
"
|
|
11817
|
-
"
|
|
11818
|
-
"
|
|
12328
|
+
"today",
|
|
12329
|
+
"this_week",
|
|
12330
|
+
"last_7_days",
|
|
12331
|
+
"this_month",
|
|
12332
|
+
"last_30_days",
|
|
12333
|
+
"lifetime"
|
|
11819
12334
|
]);
|
|
11820
12335
|
const DEFAULT_EVENTS_PAGE_SIZE = 20;
|
|
12336
|
+
const legacyPeriods = {
|
|
12337
|
+
day: "today",
|
|
12338
|
+
week: "last_7_days",
|
|
12339
|
+
month: "last_30_days"
|
|
12340
|
+
};
|
|
11821
12341
|
function parsePeriod(value) {
|
|
11822
|
-
|
|
12342
|
+
if (periods.has(value)) return value;
|
|
12343
|
+
return (value ? legacyPeriods[value] : void 0) ?? "today";
|
|
11823
12344
|
}
|
|
11824
12345
|
function parsePositiveInt(value, fallback) {
|
|
11825
12346
|
const parsed = Number.parseInt(value ?? "", 10);
|
|
@@ -11873,7 +12394,6 @@ server.use("/admin/*", createAuthMiddleware({
|
|
|
11873
12394
|
allowUnauthenticatedPaths: [],
|
|
11874
12395
|
allowWhenNoApiKeys: false
|
|
11875
12396
|
}));
|
|
11876
|
-
server.use(zstdDecompressionMiddleware);
|
|
11877
12397
|
server.get("/", (c) => c.text("Server running"));
|
|
11878
12398
|
server.get("/usage-viewer", (c) => {
|
|
11879
12399
|
const usageViewerFileUrl = new URL("../pages/index.html", import.meta.url);
|
|
@@ -11908,4 +12428,4 @@ server.route("/:provider/images", providerImageRoutes);
|
|
|
11908
12428
|
//#endregion
|
|
11909
12429
|
export { server };
|
|
11910
12430
|
|
|
11911
|
-
//# sourceMappingURL=server-
|
|
12431
|
+
//# sourceMappingURL=server-BvDQf9Nd.js.map
|