xiaodcs-copilot-api 2.4.1-public.1 → 2.5.1-public.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 +33 -9
- package/README.zh-CN.md +33 -9
- package/dist/{auth-CZhugmTr.js → auth-D-M3qCeV.js} +24 -6
- package/dist/auth-DIGdDJx3.js +2 -0
- package/dist/{config-A1SyzmBJ.js → config-DnMKoKrg.js} +37 -27
- package/dist/{debug-Dnic5hxU.js → debug-CuLfQpTq.js} +3 -3
- package/dist/main.js +6 -6
- package/dist/{mcp-fpSlKZxK.js → mcp-DqslOG3W.js} +1 -1
- package/dist/{mcp-server-BeNu_Edl.js → mcp-server-B-_dWuXe.js} +1 -1
- package/dist/mcp-server-Byq5DomJ.js +2 -0
- package/dist/protocol-BTFLbI8m.js +257 -0
- package/dist/provider-proxy-BbLwvtD_.js +263 -0
- package/dist/runtime-CWU5BNBf.js +442 -0
- package/dist/{server-wD8xXlqc.js → server-DmKNMBLd.js} +660 -478
- package/dist/{start-DCKDnLr6.js → start-WdcRgBSL.js} +110 -11
- package/dist/{token-CQyKjtuM.js → token-CQftvMU4.js} +37 -24
- package/dist/transport-DE8Q0wU-.js +252 -0
- package/dist/transport-HOWgkXsv.js +2 -0
- package/package.json +18 -8
- package/qoder-bridge/README.md +112 -0
- package/dist/auth-D-Wpolvg.js +0 -2
- package/dist/mcp-server-DQ4r-fAy.js +0 -2
- package/dist/models-CmzVpYlg.js +0 -88
- /package/dist/{electron-fetch-BRX-ug5E.js → electron-fetch-D5uOld7F.js} +0 -0
- /package/dist/{tls-Aq1Dd8E2.js → tls-B5VJAJJM.js} +0 -0
- /package/dist/{tool-search-Ds1vbmGG.js → tool-search-DCb3HbWj.js} +0 -0
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import {
|
|
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$1, 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-
|
|
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-
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
1
|
+
import { C as getAnthropicApiKey, D as getMessageApiWebSearchModel, F as isResponsesApiWebSocketEnabled, M as isMessagesApiEnabled, N as isResponsesApiCompactionRecoveryEnabled, O as getResponsesTransportConfig, P as isResponsesApiWebSearchEnabled, R as PATHS, S as getAlphaSearchModel, T as getClaudeTokenMultiplier, _ as isContextManagementEnabledForResponses, b as setModelMappings, d as getExtraPromptForModel, f as getModelMappings, g as isContextManagementEnabledForMessages, h as getSmallModel, i as listEnabledProviders, j as isCopilotFastTierEnabled, k as isAlphaSearchCodexPriorityEnabled, m as getReasoningEffortForModel, n as getRawProviderConfig, o as resolveEffectiveProviderType, p as getModelResponsesApiCompactThreshold$1, s as resolveProviderAuthType, t as getProviderConfig, u as isQoderSdkProvider, v as isGpt56OrAbove, w as getClaudeAutoModel, y as resolveMappedModel } from "./config-DnMKoKrg.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$1, 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-CQftvMU4.js";
|
|
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-DCb3HbWj.js";
|
|
4
|
+
import { a as forwardProviderMessages, c as createTimeoutDispatcher, d as normalizeSdkModelId, f as toClientModelId, i as forwardProviderImages, l as findEndpointModel, n as forwardProviderAlphaSearch, o as forwardProviderModels, r as forwardProviderChatCompletions, s as forwardProviderResponses, t as createProviderProxyResponse } from "./provider-proxy-BbLwvtD_.js";
|
|
5
|
+
import { n as forwardQoder } from "./transport-DE8Q0wU-.js";
|
|
6
|
+
import fs, { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import path, { join } from "node:path";
|
|
6
9
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import fs$1 from "node:fs
|
|
10
|
-
import { fetch as fetch$1
|
|
10
|
+
import { Readable, Transform } from "node:stream";
|
|
11
|
+
import consola from "consola";
|
|
12
|
+
import fs$1, { createWriteStream, readFileSync, rmSync } from "node:fs";
|
|
13
|
+
import { fetch as fetch$1 } from "undici";
|
|
11
14
|
import { events } from "fetch-event-stream";
|
|
12
15
|
import { z } from "zod";
|
|
13
16
|
import { Hono } from "hono";
|
|
14
17
|
import { cors } from "hono/cors";
|
|
15
18
|
import { logger } from "hono/logger";
|
|
16
|
-
import { decompress } from "fzstd";
|
|
17
19
|
import util from "node:util";
|
|
18
20
|
import { streamSSE } from "hono/streaming";
|
|
19
|
-
import {
|
|
21
|
+
import { Busboy } from "@fastify/busboy";
|
|
22
|
+
import { pipeline } from "node:stream/promises";
|
|
23
|
+
import { decompress } from "fzstd";
|
|
20
24
|
//#region src/lib/trace.ts
|
|
21
25
|
const traceIdMiddleware = async (c, next) => {
|
|
22
26
|
const traceId = resolveTraceId$1(c.req.header("x-trace-id"));
|
|
@@ -33,58 +37,6 @@ const traceIdMiddleware = async (c, next) => {
|
|
|
33
37
|
});
|
|
34
38
|
};
|
|
35
39
|
//#endregion
|
|
36
|
-
//#region src/lib/zstd-request.ts
|
|
37
|
-
const ZSTD_CONTENT_ENCODING = "zstd";
|
|
38
|
-
const INVALID_BODY_STATUS = 400;
|
|
39
|
-
let nodeZlibPromise = null;
|
|
40
|
-
const zstdDecompressionMiddleware = async (c, next) => {
|
|
41
|
-
if (c.req.header("content-encoding")?.trim().toLowerCase() !== ZSTD_CONTENT_ENCODING) return next();
|
|
42
|
-
try {
|
|
43
|
-
const decompressedBody = await decompressZstd(new Uint8Array(await c.req.raw.arrayBuffer()));
|
|
44
|
-
const headers = new Headers(c.req.raw.headers);
|
|
45
|
-
headers.delete("content-encoding");
|
|
46
|
-
headers.delete("content-length");
|
|
47
|
-
c.req.raw = new Request(c.req.raw.url, {
|
|
48
|
-
body: decompressedBody,
|
|
49
|
-
headers,
|
|
50
|
-
method: c.req.raw.method,
|
|
51
|
-
signal: c.req.raw.signal
|
|
52
|
-
});
|
|
53
|
-
c.req.bodyCache = {};
|
|
54
|
-
} catch {
|
|
55
|
-
return c.json({ error: {
|
|
56
|
-
message: "Failed to decompress zstd request body.",
|
|
57
|
-
type: "invalid_request_error"
|
|
58
|
-
} }, INVALID_BODY_STATUS);
|
|
59
|
-
}
|
|
60
|
-
return next();
|
|
61
|
-
};
|
|
62
|
-
const decompressZstd = async (input) => {
|
|
63
|
-
const bun = getBunRuntime();
|
|
64
|
-
if (bun?.zstdDecompress) return toUint8Array(await bun.zstdDecompress(input));
|
|
65
|
-
const nodeZlib = await getNodeZlib();
|
|
66
|
-
if (nodeZlib?.zstdDecompress) return new Promise((resolve, reject) => {
|
|
67
|
-
nodeZlib.zstdDecompress?.(input, (error, result) => {
|
|
68
|
-
if (error) {
|
|
69
|
-
reject(error);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
resolve(toUint8Array(result));
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
return decompress(input);
|
|
76
|
-
};
|
|
77
|
-
const getBunRuntime = () => globalThis.Bun;
|
|
78
|
-
const getNodeZlib = async () => {
|
|
79
|
-
nodeZlibPromise ??= import("node:zlib").then((module) => module).catch(() => null);
|
|
80
|
-
return nodeZlibPromise;
|
|
81
|
-
};
|
|
82
|
-
const toUint8Array = (data) => {
|
|
83
|
-
if (data instanceof Uint8Array) return data;
|
|
84
|
-
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
85
|
-
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
86
|
-
};
|
|
87
|
-
//#endregion
|
|
88
40
|
//#region src/lib/process-cleanup.ts
|
|
89
41
|
const cleanupHandlers = /* @__PURE__ */ new Set();
|
|
90
42
|
let cleanupPromise = null;
|
|
@@ -152,7 +104,7 @@ let cleanupInterval;
|
|
|
152
104
|
let currentLogDateKey;
|
|
153
105
|
const ensureLogDirectory = () => {
|
|
154
106
|
const logDir = getLogDir();
|
|
155
|
-
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
107
|
+
if (!fs$1.existsSync(logDir)) fs$1.mkdirSync(logDir, { recursive: true });
|
|
156
108
|
};
|
|
157
109
|
const formatArgs = (args) => args.map((arg) => typeof arg === "string" ? arg : util.inspect(arg, {
|
|
158
110
|
depth: null,
|
|
@@ -204,13 +156,13 @@ const closePreviousDateLogFiles = (dateKey) => {
|
|
|
204
156
|
};
|
|
205
157
|
const cleanupOldLogs = async () => {
|
|
206
158
|
const logDir = getLogDir();
|
|
207
|
-
if (!fs.existsSync(logDir)) return;
|
|
159
|
+
if (!fs$1.existsSync(logDir)) return;
|
|
208
160
|
const now = Date.now();
|
|
209
|
-
for (const entry of fs.readdirSync(logDir)) {
|
|
161
|
+
for (const entry of fs$1.readdirSync(logDir)) {
|
|
210
162
|
const filePath = path.join(logDir, entry);
|
|
211
163
|
let stats;
|
|
212
164
|
try {
|
|
213
|
-
stats = fs.statSync(filePath);
|
|
165
|
+
stats = fs$1.statSync(filePath);
|
|
214
166
|
} catch {
|
|
215
167
|
continue;
|
|
216
168
|
}
|
|
@@ -222,7 +174,7 @@ const cleanupOldLogs = async () => {
|
|
|
222
174
|
continue;
|
|
223
175
|
}
|
|
224
176
|
try {
|
|
225
|
-
fs.rmSync(filePath);
|
|
177
|
+
fs$1.rmSync(filePath);
|
|
226
178
|
} catch (error) {
|
|
227
179
|
console.warn(`Failed to remove old handler log: ${filePath}`, error);
|
|
228
180
|
}
|
|
@@ -267,7 +219,7 @@ const getLogStream = (filePath) => {
|
|
|
267
219
|
initializeLoggerRuntime();
|
|
268
220
|
let stream = logStreams.get(filePath);
|
|
269
221
|
if (!stream || stream.destroyed) {
|
|
270
|
-
const createdStream = fs.createWriteStream(filePath, { flags: "a" });
|
|
222
|
+
const createdStream = fs$1.createWriteStream(filePath, { flags: "a" });
|
|
271
223
|
stream = createdStream;
|
|
272
224
|
logStreams.set(filePath, createdStream);
|
|
273
225
|
createdStream.on("error", (error) => {
|
|
@@ -1063,7 +1015,7 @@ async function openNodeDatabase(dbPath) {
|
|
|
1063
1015
|
}
|
|
1064
1016
|
async function openSqliteDatabase(dbPath) {
|
|
1065
1017
|
const dir = path.dirname(dbPath);
|
|
1066
|
-
if (dbPath !== ":memory:" && dir !== ".") await fs
|
|
1018
|
+
if (dbPath !== ":memory:" && dir !== ".") await fs.mkdir(dir, { recursive: true });
|
|
1067
1019
|
return isBunRuntime() ? openBunDatabase(dbPath) : openNodeDatabase(dbPath);
|
|
1068
1020
|
}
|
|
1069
1021
|
var SqliteDbStore = class {
|
|
@@ -2695,11 +2647,11 @@ var CompactionRecoveryStore = class {
|
|
|
2695
2647
|
async prepareStorageDirectory() {
|
|
2696
2648
|
if (this.dbPath === ":memory:" || this.storagePrepared) return;
|
|
2697
2649
|
const directory = path.dirname(this.dbPath);
|
|
2698
|
-
await fs
|
|
2650
|
+
await fs.mkdir(directory, {
|
|
2699
2651
|
mode: this.secureDirectory ? 448 : void 0,
|
|
2700
2652
|
recursive: true
|
|
2701
2653
|
});
|
|
2702
|
-
if (this.secureDirectory) await fs
|
|
2654
|
+
if (this.secureDirectory) await fs.chmod(directory, 448);
|
|
2703
2655
|
this.storagePrepared = true;
|
|
2704
2656
|
}
|
|
2705
2657
|
async secureDatabaseFiles() {
|
|
@@ -2710,7 +2662,7 @@ var CompactionRecoveryStore = class {
|
|
|
2710
2662
|
];
|
|
2711
2663
|
await Promise.all(candidates.map(async (candidate) => {
|
|
2712
2664
|
try {
|
|
2713
|
-
await fs
|
|
2665
|
+
await fs.chmod(candidate, 384);
|
|
2714
2666
|
} catch (error) {
|
|
2715
2667
|
if (!isMissingFileError(error)) throw error;
|
|
2716
2668
|
}
|
|
@@ -3521,160 +3473,8 @@ const createResponsesWebSocketStreamChunk = (data) => {
|
|
|
3521
3473
|
}
|
|
3522
3474
|
};
|
|
3523
3475
|
//#endregion
|
|
3524
|
-
//#region src/lib/timeout-dispatcher.ts
|
|
3525
|
-
/**
|
|
3526
|
-
* Node's global fetch keeps Undici's shorter default headers/body timeouts.
|
|
3527
|
-
* Wrap the global dispatcher so long-running requests (e.g. image
|
|
3528
|
-
* generation) get an explicit cap.
|
|
3529
|
-
*/
|
|
3530
|
-
const createTimeoutDispatcher = (timeoutMs) => ({ dispatch(options, handler) {
|
|
3531
|
-
return getGlobalDispatcher().dispatch({
|
|
3532
|
-
...options,
|
|
3533
|
-
bodyTimeout: timeoutMs,
|
|
3534
|
-
headersTimeout: timeoutMs
|
|
3535
|
-
}, handler);
|
|
3536
|
-
} });
|
|
3537
|
-
//#endregion
|
|
3538
|
-
//#region src/services/providers/provider-proxy.ts
|
|
3539
|
-
const SHARED_FORWARDABLE_HEADERS = ["accept", "user-agent"];
|
|
3540
|
-
const ANTHROPIC_FORWARDABLE_HEADERS = ["anthropic-version", "anthropic-beta"];
|
|
3541
|
-
const STRIPPED_RESPONSE_HEADERS = [
|
|
3542
|
-
"connection",
|
|
3543
|
-
"content-encoding",
|
|
3544
|
-
"content-length",
|
|
3545
|
-
"keep-alive",
|
|
3546
|
-
"proxy-authenticate",
|
|
3547
|
-
"proxy-authorization",
|
|
3548
|
-
"te",
|
|
3549
|
-
"trailer",
|
|
3550
|
-
"transfer-encoding",
|
|
3551
|
-
"upgrade"
|
|
3552
|
-
];
|
|
3553
|
-
function buildProviderUpstreamHeaders(providerConfig, requestHeaders) {
|
|
3554
|
-
const authHeaders = {};
|
|
3555
|
-
if (providerConfig.authType === "x-api-key") authHeaders["x-api-key"] = providerConfig.apiKey;
|
|
3556
|
-
else authHeaders.authorization = `Bearer ${providerConfig.apiKey}`;
|
|
3557
|
-
const headers = {
|
|
3558
|
-
"content-type": "application/json",
|
|
3559
|
-
accept: "application/json",
|
|
3560
|
-
...authHeaders
|
|
3561
|
-
};
|
|
3562
|
-
for (const headerName of SHARED_FORWARDABLE_HEADERS) {
|
|
3563
|
-
const headerValue = requestHeaders.get(headerName);
|
|
3564
|
-
if (headerValue) headers[headerName] = headerValue;
|
|
3565
|
-
}
|
|
3566
|
-
if (providerConfig.type !== "anthropic") return headers;
|
|
3567
|
-
for (const headerName of ANTHROPIC_FORWARDABLE_HEADERS) {
|
|
3568
|
-
const headerValue = requestHeaders.get(headerName);
|
|
3569
|
-
if (headerValue) headers[headerName] = headerValue;
|
|
3570
|
-
}
|
|
3571
|
-
return headers;
|
|
3572
|
-
}
|
|
3573
|
-
const OPENCODE_GO_PROVIDER_NAME = "opencode-go";
|
|
3574
|
-
const OPENCODE_SESSION_HEADER = "x-opencode-session";
|
|
3575
|
-
const resolveOpencodeMessagesSession = (payload) => {
|
|
3576
|
-
const sessionAffinity = requestContext.getStore()?.sessionAffinity?.trim();
|
|
3577
|
-
if (sessionAffinity) return sessionAffinity;
|
|
3578
|
-
const userId = payload.metadata?.user_id;
|
|
3579
|
-
if (!userId?.trim()) return;
|
|
3580
|
-
const { sessionId } = parseUserIdMetadata(userId);
|
|
3581
|
-
return sessionId ?? userId;
|
|
3582
|
-
};
|
|
3583
|
-
const applyOpencodeSessionHeader = (providerConfig, headers, session) => {
|
|
3584
|
-
if (providerConfig.name !== OPENCODE_GO_PROVIDER_NAME || !session) return;
|
|
3585
|
-
headers[OPENCODE_SESSION_HEADER] = session;
|
|
3586
|
-
};
|
|
3587
|
-
function createProviderProxyResponse(upstreamResponse, body) {
|
|
3588
|
-
const headers = new Headers(upstreamResponse.headers);
|
|
3589
|
-
for (const headerName of STRIPPED_RESPONSE_HEADERS) headers.delete(headerName);
|
|
3590
|
-
return new Response(body ?? upstreamResponse.body, {
|
|
3591
|
-
headers,
|
|
3592
|
-
status: upstreamResponse.status,
|
|
3593
|
-
statusText: upstreamResponse.statusText
|
|
3594
|
-
});
|
|
3595
|
-
}
|
|
3596
|
-
async function forwardProviderMessages(providerConfig, payload, requestHeaders) {
|
|
3597
|
-
consola.log(`<-- model: ${payload.model}`);
|
|
3598
|
-
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
3599
|
-
applyOpencodeSessionHeader(providerConfig, headers, resolveOpencodeMessagesSession(payload));
|
|
3600
|
-
return await fetch(`${providerConfig.baseUrl}/v1/messages`, {
|
|
3601
|
-
method: "POST",
|
|
3602
|
-
headers,
|
|
3603
|
-
body: JSON.stringify(payload)
|
|
3604
|
-
});
|
|
3605
|
-
}
|
|
3606
|
-
async function forwardProviderChatCompletions(providerConfig, payload, requestHeaders) {
|
|
3607
|
-
consola.log(`<-- model: ${payload.model}`);
|
|
3608
|
-
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
3609
|
-
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
3610
|
-
return await fetch(`${providerConfig.baseUrl}/v1/chat/completions`, {
|
|
3611
|
-
method: "POST",
|
|
3612
|
-
headers,
|
|
3613
|
-
body: JSON.stringify(payload)
|
|
3614
|
-
});
|
|
3615
|
-
}
|
|
3616
|
-
async function forwardProviderResponses(providerConfig, payload, requestHeaders, options = {}) {
|
|
3617
|
-
consola.log(`<-- model: ${payload.model}`);
|
|
3618
|
-
const transportConfig = getResponsesTransportConfig();
|
|
3619
|
-
const headers = buildProviderUpstreamHeaders(providerConfig, requestHeaders);
|
|
3620
|
-
applyOpencodeSessionHeader(providerConfig, headers, payload.prompt_cache_key?.trim() || void 0);
|
|
3621
|
-
return await fetchResponsesWithLifecycle(`${providerConfig.baseUrl}/v1/responses`, {
|
|
3622
|
-
method: "POST",
|
|
3623
|
-
headers,
|
|
3624
|
-
body: JSON.stringify(payload)
|
|
3625
|
-
}, {
|
|
3626
|
-
headersTimeoutMs: transportConfig.headersTimeoutMs,
|
|
3627
|
-
signal: options.signal,
|
|
3628
|
-
streamInactivityTimeoutMs: transportConfig.streamInactivityTimeoutMs
|
|
3629
|
-
});
|
|
3630
|
-
}
|
|
3631
|
-
const PROVIDER_MODELS_TIMEOUT_MS = 15e3;
|
|
3632
|
-
async function forwardProviderModels(providerConfig, requestHeaders) {
|
|
3633
|
-
return await fetch(`${providerConfig.baseUrl}/v1/models`, {
|
|
3634
|
-
method: "GET",
|
|
3635
|
-
headers: buildProviderUpstreamHeaders(providerConfig, requestHeaders),
|
|
3636
|
-
signal: AbortSignal.timeout(PROVIDER_MODELS_TIMEOUT_MS)
|
|
3637
|
-
});
|
|
3638
|
-
}
|
|
3639
|
-
/** Align with Codex images: long-running generation/edits need a generous cap. */
|
|
3640
|
-
const PROVIDER_IMAGES_TIMEOUT_MS = 900 * 1e3;
|
|
3641
|
-
const providerImagesDispatcher = createTimeoutDispatcher(PROVIDER_IMAGES_TIMEOUT_MS);
|
|
3642
|
-
function resolveProviderRequestUrl(providerConfig, requestUrl, path) {
|
|
3643
|
-
const upstreamUrl = new URL(`${providerConfig.baseUrl}${path}`);
|
|
3644
|
-
upstreamUrl.search = new URL(requestUrl, "http://localhost").search;
|
|
3645
|
-
return upstreamUrl.toString();
|
|
3646
|
-
}
|
|
3647
|
-
async function forwardProviderAlphaSearch(providerConfig, request) {
|
|
3648
|
-
const headers = buildProviderUpstreamHeaders(providerConfig, request.headers);
|
|
3649
|
-
const body = await request.arrayBuffer();
|
|
3650
|
-
return await fetch(resolveProviderRequestUrl(providerConfig, request.url, "/v1/alpha/search"), {
|
|
3651
|
-
method: "POST",
|
|
3652
|
-
headers,
|
|
3653
|
-
body
|
|
3654
|
-
});
|
|
3655
|
-
}
|
|
3656
|
-
async function forwardProviderImages(providerConfig, request, operation) {
|
|
3657
|
-
const headers = buildProviderUpstreamHeaders(providerConfig, request.headers);
|
|
3658
|
-
const contentType = request.headers.get("content-type");
|
|
3659
|
-
if (contentType) headers["content-type"] = contentType;
|
|
3660
|
-
else if (operation === "edits") delete headers["content-type"];
|
|
3661
|
-
const init = {
|
|
3662
|
-
method: "POST",
|
|
3663
|
-
headers,
|
|
3664
|
-
body: request.body,
|
|
3665
|
-
duplex: "half",
|
|
3666
|
-
signal: AbortSignal.timeout(PROVIDER_IMAGES_TIMEOUT_MS)
|
|
3667
|
-
};
|
|
3668
|
-
const upstreamUrl = resolveProviderRequestUrl(providerConfig, request.url, `/v1/images/${operation}`);
|
|
3669
|
-
if (typeof Bun !== "undefined") return await fetch(upstreamUrl, init);
|
|
3670
|
-
return await fetch$1(upstreamUrl, {
|
|
3671
|
-
...init,
|
|
3672
|
-
dispatcher: providerImagesDispatcher
|
|
3673
|
-
});
|
|
3674
|
-
}
|
|
3675
|
-
//#endregion
|
|
3676
3476
|
//#region src/routes/alpha-search/alpha-search-responses.ts
|
|
3677
|
-
const logger$
|
|
3477
|
+
const logger$17 = createHandlerLogger("alpha-search-responses-handler");
|
|
3678
3478
|
const SESSION_TTL_MS = 3600 * 1e3;
|
|
3679
3479
|
const MAX_SESSIONS = 128;
|
|
3680
3480
|
const MAX_URL_REFERENCES = 256;
|
|
@@ -4032,14 +3832,14 @@ function createRemoteResponsesPayload(request, model, instruction) {
|
|
|
4032
3832
|
};
|
|
4033
3833
|
}
|
|
4034
3834
|
async function requestProviderSearch(c, providerConfig, payload, model, sessionId) {
|
|
4035
|
-
debugJson(logger$
|
|
3835
|
+
debugJson(logger$17, "Alpha search provider Responses request:", {
|
|
4036
3836
|
payload,
|
|
4037
3837
|
provider: providerConfig.name
|
|
4038
3838
|
});
|
|
4039
3839
|
const upstreamResponse = await forwardProviderResponses(providerConfig, payload, c.req.raw.headers, { signal: c.req.raw.signal });
|
|
4040
3840
|
if (!upstreamResponse.ok) throw new HTTPError(`Failed to create ${providerConfig.name} responses for alpha search`, upstreamResponse);
|
|
4041
3841
|
const result = await upstreamResponse.json();
|
|
4042
|
-
debugJson(logger$
|
|
3842
|
+
debugJson(logger$17, "Alpha search provider Responses result:", {
|
|
4043
3843
|
provider: providerConfig.name,
|
|
4044
3844
|
result
|
|
4045
3845
|
});
|
|
@@ -4055,7 +3855,7 @@ async function requestProviderSearch(c, providerConfig, payload, model, sessionI
|
|
|
4055
3855
|
return result;
|
|
4056
3856
|
}
|
|
4057
3857
|
async function requestCopilotSearch(payload, model, requestId, sessionId, signal) {
|
|
4058
|
-
debugJson(logger$
|
|
3858
|
+
debugJson(logger$17, "Alpha search Copilot Responses request:", payload);
|
|
4059
3859
|
const result = await alphaSearchResponsesDependencies.createResponses(payload, {
|
|
4060
3860
|
vision: false,
|
|
4061
3861
|
initiator: "agent",
|
|
@@ -4064,7 +3864,7 @@ async function requestCopilotSearch(payload, model, requestId, sessionId, signal
|
|
|
4064
3864
|
sessionId,
|
|
4065
3865
|
signal
|
|
4066
3866
|
});
|
|
4067
|
-
debugJson(logger$
|
|
3867
|
+
debugJson(logger$17, "Alpha search Copilot Responses result:", result);
|
|
4068
3868
|
alphaSearchResponsesDependencies.createUsageRecorder(model, sessionId)({
|
|
4069
3869
|
...normalizeResponsesUsage(result.usage),
|
|
4070
3870
|
total_nano_aiu: normalizeOptionalToken(result.copilot_usage?.total_nano_aiu)
|
|
@@ -4179,7 +3979,7 @@ async function forwardCodexAlphaSearch(request) {
|
|
|
4179
3979
|
}
|
|
4180
3980
|
//#endregion
|
|
4181
3981
|
//#region src/routes/alpha-search/route.ts
|
|
4182
|
-
const logger$
|
|
3982
|
+
const logger$16 = createHandlerLogger("alpha-search-handler");
|
|
4183
3983
|
const alphaSearchRoutes = new Hono();
|
|
4184
3984
|
const alphaSearchRouteDependencies = {
|
|
4185
3985
|
findEndpointModel,
|
|
@@ -4199,9 +3999,9 @@ function parseDebugBody(body) {
|
|
|
4199
3999
|
}
|
|
4200
4000
|
}
|
|
4201
4001
|
async function forwardCodexAlphaSearchRequest(request) {
|
|
4202
|
-
await debugJsonAsync(logger$
|
|
4002
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
|
|
4203
4003
|
const upstreamResponse = await alphaSearchRouteDependencies.forwardCodexAlphaSearch(request);
|
|
4204
|
-
await debugJsonAsync(logger$
|
|
4004
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.response", async () => ({
|
|
4205
4005
|
body: parseDebugBody(await upstreamResponse.clone().text()),
|
|
4206
4006
|
statusCode: upstreamResponse.status
|
|
4207
4007
|
}));
|
|
@@ -4312,7 +4112,7 @@ alphaSearchRoutes.post("/", async (c) => {
|
|
|
4312
4112
|
try {
|
|
4313
4113
|
return await handleAlphaSearchRequest(c);
|
|
4314
4114
|
} catch (error) {
|
|
4315
|
-
logger$
|
|
4115
|
+
logger$16.error("alpha_search.error", { error });
|
|
4316
4116
|
return await forwardError(c, error);
|
|
4317
4117
|
}
|
|
4318
4118
|
});
|
|
@@ -4485,7 +4285,7 @@ const normalizeProviderResponsesReasoningEffort = (payload, providerConfig) => {
|
|
|
4485
4285
|
};
|
|
4486
4286
|
//#endregion
|
|
4487
4287
|
//#region src/routes/provider/chat-completions/handler.ts
|
|
4488
|
-
const logger$
|
|
4288
|
+
const logger$15 = createHandlerLogger("provider-chat-completions-handler");
|
|
4489
4289
|
async function handleProviderChatCompletionsForProvider(c, options) {
|
|
4490
4290
|
const { payload, provider } = options;
|
|
4491
4291
|
const providerConfig = await resolveProviderConfig(provider);
|
|
@@ -4499,13 +4299,13 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
4499
4299
|
applyProviderStreamOptions(payload);
|
|
4500
4300
|
applyDashScopePreserveThinkingDefault(payload, providerConfig);
|
|
4501
4301
|
applyProviderContextCache(payload, modelConfig, providerConfig);
|
|
4502
|
-
debugJson(logger$
|
|
4302
|
+
debugJson(logger$15, "provider.chat_completions.request", {
|
|
4503
4303
|
payload,
|
|
4504
4304
|
provider
|
|
4505
4305
|
});
|
|
4506
|
-
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers);
|
|
4306
|
+
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers, { signal: c.req.raw.signal });
|
|
4507
4307
|
if (!upstreamResponse.ok) {
|
|
4508
|
-
logger$
|
|
4308
|
+
logger$15.error("Failed to create provider chat completions", {
|
|
4509
4309
|
provider,
|
|
4510
4310
|
statusCode: upstreamResponse.status
|
|
4511
4311
|
});
|
|
@@ -4519,7 +4319,7 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
4519
4319
|
});
|
|
4520
4320
|
const responseBody = await upstreamResponse.clone().json();
|
|
4521
4321
|
recordUsage(normalizeOpenAIUsage(responseBody.usage));
|
|
4522
|
-
debugJson(logger$
|
|
4322
|
+
debugJson(logger$15, "provider.chat_completions.response", responseBody);
|
|
4523
4323
|
return createProviderProxyResponse(upstreamResponse);
|
|
4524
4324
|
}
|
|
4525
4325
|
const applyProviderStreamOptions = (payload) => {
|
|
@@ -4541,12 +4341,12 @@ const createProviderChatCompletionsUsageRecorder = (payload, provider, modelConf
|
|
|
4541
4341
|
providerName: provider
|
|
4542
4342
|
});
|
|
4543
4343
|
const streamProviderChatCompletions = (c, upstreamResponse, options) => {
|
|
4544
|
-
logger$
|
|
4344
|
+
logger$15.debug("provider.chat_completions.streaming", { provider: options.provider });
|
|
4545
4345
|
return streamSSE(c, async (stream) => {
|
|
4546
4346
|
let usage = {};
|
|
4547
4347
|
try {
|
|
4548
4348
|
for await (const chunk of events(upstreamResponse)) {
|
|
4549
|
-
debugJson(logger$
|
|
4349
|
+
debugJson(logger$15, "provider.chat_completions.stream_chunk", chunk);
|
|
4550
4350
|
if (chunk.data && chunk.data !== "[DONE]") {
|
|
4551
4351
|
const parsedChunk = parseChatCompletionChunkData(chunk.data);
|
|
4552
4352
|
if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
|
|
@@ -4600,7 +4400,7 @@ const createChatCompletions = async (payload, options) => {
|
|
|
4600
4400
|
};
|
|
4601
4401
|
//#endregion
|
|
4602
4402
|
//#region src/routes/chat-completions/handler.ts
|
|
4603
|
-
const logger$
|
|
4403
|
+
const logger$14 = createHandlerLogger("chat-completions-handler");
|
|
4604
4404
|
async function handleCompletion$1(c) {
|
|
4605
4405
|
let payload = await c.req.json();
|
|
4606
4406
|
const requestedModel = payload.model;
|
|
@@ -4614,7 +4414,7 @@ async function handleCompletion$1(c) {
|
|
|
4614
4414
|
provider: providerModelAlias.provider
|
|
4615
4415
|
});
|
|
4616
4416
|
}
|
|
4617
|
-
debugJson(logger$
|
|
4417
|
+
debugJson(logger$14, "Request payload:", payload);
|
|
4618
4418
|
const selectedModel = findEndpointModel(payload.model);
|
|
4619
4419
|
payload.model = selectedModel?.id ?? payload.model;
|
|
4620
4420
|
if (isNullish(payload.max_tokens) && isNullish(payload.max_completion_tokens)) {
|
|
@@ -4622,16 +4422,16 @@ async function handleCompletion$1(c) {
|
|
|
4622
4422
|
...payload,
|
|
4623
4423
|
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
|
4624
4424
|
};
|
|
4625
|
-
debugJson(logger$
|
|
4425
|
+
debugJson(logger$14, "Set max_tokens to:", payload.max_tokens);
|
|
4626
4426
|
}
|
|
4627
4427
|
if (payload.model.includes("gpt")) {
|
|
4628
4428
|
if (isNullish(payload.max_completion_tokens)) payload.max_completion_tokens = payload.max_tokens;
|
|
4629
4429
|
delete payload.max_tokens;
|
|
4630
4430
|
}
|
|
4631
4431
|
const requestId = generateRequestIdFromPayload(payload);
|
|
4632
|
-
logger$
|
|
4432
|
+
logger$14.debug("Generated request ID:", requestId);
|
|
4633
4433
|
const sessionId = getUUID(requestId);
|
|
4634
|
-
logger$
|
|
4434
|
+
logger$14.debug("Extracted session ID:", sessionId);
|
|
4635
4435
|
const recordUsage = createCopilotTokenUsageRecorder({
|
|
4636
4436
|
endpoint: "chat_completions",
|
|
4637
4437
|
fallbackSessionId: sessionId,
|
|
@@ -4642,18 +4442,18 @@ async function handleCompletion$1(c) {
|
|
|
4642
4442
|
sessionId
|
|
4643
4443
|
});
|
|
4644
4444
|
if (isNonStreaming$1(response)) {
|
|
4645
|
-
debugJson(logger$
|
|
4445
|
+
debugJson(logger$14, "Non-streaming response:", response);
|
|
4646
4446
|
recordUsage({
|
|
4647
4447
|
...normalizeOpenAIUsage(response.usage),
|
|
4648
4448
|
total_nano_aiu: normalizeOptionalToken(response.copilot_usage?.total_nano_aiu)
|
|
4649
4449
|
});
|
|
4650
4450
|
return c.json(response);
|
|
4651
4451
|
}
|
|
4652
|
-
logger$
|
|
4452
|
+
logger$14.debug("Streaming response");
|
|
4653
4453
|
return streamSSE(c, async (stream) => {
|
|
4654
4454
|
let usage = {};
|
|
4655
4455
|
for await (const chunk of response) {
|
|
4656
|
-
debugJson(logger$
|
|
4456
|
+
debugJson(logger$14, "Streaming chunk:", chunk);
|
|
4657
4457
|
const parsedChunk = parseChatCompletionChunk(chunk);
|
|
4658
4458
|
if (parsedChunk?.usage || parsedChunk?.copilot_usage) usage = {
|
|
4659
4459
|
...normalizeOpenAIUsage(parsedChunk.usage),
|
|
@@ -4742,31 +4542,48 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
4742
4542
|
}
|
|
4743
4543
|
});
|
|
4744
4544
|
//#endregion
|
|
4745
|
-
//#region src/
|
|
4746
|
-
|
|
4747
|
-
const
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4545
|
+
//#region src/routes/images/body-limit.ts
|
|
4546
|
+
function createImagesBodyTooLargeError(maxBytes) {
|
|
4547
|
+
const message = `Image request body exceeds the ${maxBytes}-byte limit`;
|
|
4548
|
+
return new HTTPError(message, new Response(message, {
|
|
4549
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
4550
|
+
status: 413
|
|
4551
|
+
}));
|
|
4752
4552
|
}
|
|
4753
|
-
async function
|
|
4754
|
-
const
|
|
4755
|
-
if (
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4553
|
+
async function readRequestBodyWithLimit(request, maxBytes) {
|
|
4554
|
+
const contentLength = request.headers.get("content-length");
|
|
4555
|
+
if (contentLength !== null) {
|
|
4556
|
+
const declaredBytes = Number(contentLength);
|
|
4557
|
+
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) throw createImagesBodyTooLargeError(maxBytes);
|
|
4558
|
+
}
|
|
4559
|
+
if (!request.body) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
4560
|
+
const reader = request.body.getReader();
|
|
4561
|
+
const chunks = [];
|
|
4562
|
+
let totalBytes = 0;
|
|
4563
|
+
try {
|
|
4564
|
+
while (true) {
|
|
4565
|
+
const { done, value } = await reader.read();
|
|
4566
|
+
if (done) break;
|
|
4567
|
+
if (!value || value.byteLength === 0) continue;
|
|
4568
|
+
totalBytes += value.byteLength;
|
|
4569
|
+
if (totalBytes > maxBytes) {
|
|
4570
|
+
try {
|
|
4571
|
+
await reader.cancel();
|
|
4572
|
+
} catch {}
|
|
4573
|
+
throw createImagesBodyTooLargeError(maxBytes);
|
|
4574
|
+
}
|
|
4575
|
+
chunks.push(value);
|
|
4576
|
+
}
|
|
4577
|
+
} finally {
|
|
4578
|
+
reader.releaseLock();
|
|
4579
|
+
}
|
|
4580
|
+
const body = new Uint8Array(totalBytes);
|
|
4581
|
+
let offset = 0;
|
|
4582
|
+
for (const chunk of chunks) {
|
|
4583
|
+
body.set(chunk, offset);
|
|
4584
|
+
offset += chunk.byteLength;
|
|
4585
|
+
}
|
|
4586
|
+
return body.buffer;
|
|
4770
4587
|
}
|
|
4771
4588
|
//#endregion
|
|
4772
4589
|
//#region src/routes/images/forward-provider-images.ts
|
|
@@ -4795,13 +4612,38 @@ async function forwardProviderImagesWithLogging(providerConfig, request, operati
|
|
|
4795
4612
|
return createProviderProxyResponse(upstreamResponse);
|
|
4796
4613
|
}
|
|
4797
4614
|
//#endregion
|
|
4798
|
-
//#region src/
|
|
4799
|
-
const
|
|
4800
|
-
const
|
|
4801
|
-
|
|
4615
|
+
//#region src/services/codex/images.ts
|
|
4616
|
+
const CODEX_IMAGES_TIMEOUT_MS = 900 * 1e3;
|
|
4617
|
+
const codexImagesDispatcher = createTimeoutDispatcher(CODEX_IMAGES_TIMEOUT_MS);
|
|
4618
|
+
function resolveCodexImagesUrl(requestUrl, operation) {
|
|
4619
|
+
const upstreamUrl = new URL(`${CODEX_API_BASE_URL}/codex/images/${operation}`);
|
|
4620
|
+
upstreamUrl.search = new URL(requestUrl, "http://localhost").search;
|
|
4621
|
+
return upstreamUrl.toString();
|
|
4622
|
+
}
|
|
4623
|
+
async function forwardCodexImages(request, operation) {
|
|
4624
|
+
const headers = buildCodexRequestHeaders(request.headers);
|
|
4625
|
+
if (!headers.has("accept")) headers.set("accept", "application/json");
|
|
4626
|
+
if (operation === "generations" && !headers.has("content-type")) headers.set("content-type", "application/json");
|
|
4627
|
+
const init = {
|
|
4628
|
+
method: "POST",
|
|
4629
|
+
headers,
|
|
4630
|
+
body: request.body,
|
|
4631
|
+
duplex: "half",
|
|
4632
|
+
signal: AbortSignal.timeout(CODEX_IMAGES_TIMEOUT_MS)
|
|
4633
|
+
};
|
|
4634
|
+
const upstreamUrl = resolveCodexImagesUrl(request.url, operation);
|
|
4635
|
+
if (typeof Bun !== "undefined") return await fetch(upstreamUrl, init);
|
|
4636
|
+
return await fetch$1(upstreamUrl, {
|
|
4637
|
+
...init,
|
|
4638
|
+
dispatcher: codexImagesDispatcher
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
//#endregion
|
|
4642
|
+
//#region src/routes/images/shared.ts
|
|
4643
|
+
const logger$13 = createHandlerLogger("images-handler");
|
|
4802
4644
|
const imageRouteDependencies = {
|
|
4645
|
+
maxRequestBodyBytes: 64 * 1024 * 1024,
|
|
4803
4646
|
debugJsonAsync,
|
|
4804
|
-
maxRequestBodyBytes: MAX_IMAGES_REQUEST_BODY_BYTES,
|
|
4805
4647
|
resolveMappedModel,
|
|
4806
4648
|
resolveProviderConfig
|
|
4807
4649
|
};
|
|
@@ -4811,114 +4653,33 @@ function getContentMetadata(headers) {
|
|
|
4811
4653
|
contentLength: headers.get("content-length")
|
|
4812
4654
|
};
|
|
4813
4655
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
model
|
|
4821
|
-
}),
|
|
4822
|
-
headers,
|
|
4823
|
-
method: request.method,
|
|
4824
|
-
signal: request.signal
|
|
4825
|
-
});
|
|
4656
|
+
/**
|
|
4657
|
+
* Snapshots request headers before the body is consumed: Bun drops
|
|
4658
|
+
* auto-generated body headers (like the multipart boundary) afterwards.
|
|
4659
|
+
*/
|
|
4660
|
+
function snapshotRequestHeaders(request) {
|
|
4661
|
+
return new Headers(request.headers);
|
|
4826
4662
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4663
|
+
/**
|
|
4664
|
+
* Rebuilds a request from primitives instead of cloning: under Node runtimes
|
|
4665
|
+
* srvx wraps incoming requests in a proxy class whose prototype chain
|
|
4666
|
+
* satisfies `instanceof Request` without the native internals, so
|
|
4667
|
+
* `new Request(request)` throws.
|
|
4668
|
+
*/
|
|
4669
|
+
function createForwardRequest(request, requestHeaders, body) {
|
|
4670
|
+
const init = {
|
|
4829
4671
|
body,
|
|
4672
|
+
duplex: "half",
|
|
4830
4673
|
headers: requestHeaders,
|
|
4831
4674
|
method: request.method,
|
|
4832
4675
|
signal: request.signal
|
|
4833
|
-
}
|
|
4834
|
-
|
|
4835
|
-
function createMultipartImagesRequest(request, requestHeaders, formData, model) {
|
|
4836
|
-
formData.set("model", model);
|
|
4837
|
-
const headers = new Headers(requestHeaders);
|
|
4838
|
-
headers.delete("content-length");
|
|
4839
|
-
headers.delete("content-type");
|
|
4840
|
-
return new Request(request.url, {
|
|
4841
|
-
body: formData,
|
|
4842
|
-
headers,
|
|
4843
|
-
method: request.method,
|
|
4844
|
-
signal: request.signal
|
|
4845
|
-
});
|
|
4846
|
-
}
|
|
4847
|
-
function createImagesBodyTooLargeError(maxBytes) {
|
|
4848
|
-
const message = `Image request body exceeds the ${maxBytes}-byte limit`;
|
|
4849
|
-
return new HTTPError(message, new Response(message, {
|
|
4850
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
4851
|
-
status: 413
|
|
4852
|
-
}));
|
|
4853
|
-
}
|
|
4854
|
-
async function readRequestBodyWithLimit(request, maxBytes) {
|
|
4855
|
-
const contentLength = request.headers.get("content-length");
|
|
4856
|
-
if (contentLength !== null) {
|
|
4857
|
-
const declaredBytes = Number(contentLength);
|
|
4858
|
-
if (Number.isFinite(declaredBytes) && declaredBytes > maxBytes) throw createImagesBodyTooLargeError(maxBytes);
|
|
4859
|
-
}
|
|
4860
|
-
if (!request.body) return /* @__PURE__ */ new ArrayBuffer(0);
|
|
4861
|
-
const reader = request.body.getReader();
|
|
4862
|
-
const chunks = [];
|
|
4863
|
-
let totalBytes = 0;
|
|
4864
|
-
try {
|
|
4865
|
-
while (true) {
|
|
4866
|
-
const { done, value } = await reader.read();
|
|
4867
|
-
if (done) break;
|
|
4868
|
-
if (!value || value.byteLength === 0) continue;
|
|
4869
|
-
totalBytes += value.byteLength;
|
|
4870
|
-
if (totalBytes > maxBytes) {
|
|
4871
|
-
try {
|
|
4872
|
-
await reader.cancel();
|
|
4873
|
-
} catch {}
|
|
4874
|
-
throw createImagesBodyTooLargeError(maxBytes);
|
|
4875
|
-
}
|
|
4876
|
-
chunks.push(value);
|
|
4877
|
-
}
|
|
4878
|
-
} finally {
|
|
4879
|
-
reader.releaseLock();
|
|
4880
|
-
}
|
|
4881
|
-
const body = new Uint8Array(totalBytes);
|
|
4882
|
-
let offset = 0;
|
|
4883
|
-
for (const chunk of chunks) {
|
|
4884
|
-
body.set(chunk, offset);
|
|
4885
|
-
offset += chunk.byteLength;
|
|
4886
|
-
}
|
|
4887
|
-
return body.buffer;
|
|
4888
|
-
}
|
|
4889
|
-
async function parseImagesRequest(request) {
|
|
4890
|
-
const requestHeaders = new Headers(request.headers);
|
|
4891
|
-
const contentType = requestHeaders.get("content-type");
|
|
4892
|
-
const body = await readRequestBodyWithLimit(request, imageRouteDependencies.maxRequestBodyBytes);
|
|
4893
|
-
const originalRequest = createBufferedImagesRequest(request, requestHeaders, body);
|
|
4894
|
-
if (contentType?.split(";", 1)[0]?.trim().toLowerCase() === "multipart/form-data" && contentType) try {
|
|
4895
|
-
const formData = await bufferToFormData(body, contentType);
|
|
4896
|
-
const model = formData.get("model");
|
|
4897
|
-
if (typeof model !== "string") return originalRequest;
|
|
4898
|
-
return {
|
|
4899
|
-
createRequest: (mappedModel) => createMultipartImagesRequest(request, requestHeaders, formData, mappedModel),
|
|
4900
|
-
model,
|
|
4901
|
-
originalRequest
|
|
4902
|
-
};
|
|
4903
|
-
} catch {
|
|
4904
|
-
return originalRequest;
|
|
4905
|
-
}
|
|
4906
|
-
try {
|
|
4907
|
-
const payload = JSON.parse(new TextDecoder().decode(body));
|
|
4908
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
|
|
4909
|
-
return {
|
|
4910
|
-
createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
|
|
4911
|
-
model: payload.model,
|
|
4912
|
-
originalRequest
|
|
4913
|
-
};
|
|
4914
|
-
} catch {
|
|
4915
|
-
return originalRequest;
|
|
4916
|
-
}
|
|
4676
|
+
};
|
|
4677
|
+
return new Request(request.url, init);
|
|
4917
4678
|
}
|
|
4918
4679
|
/**
|
|
4919
|
-
* Handles Codex images proxying. Pass
|
|
4680
|
+
* Handles Codex images proxying. Pass a resolvedProviderConfig when the
|
|
4920
4681
|
* caller already resolved the codex provider to avoid a second resolve, and
|
|
4921
|
-
* pass
|
|
4682
|
+
* pass a request when model mapping rebuilt the request body.
|
|
4922
4683
|
*/
|
|
4923
4684
|
async function handleCodexImages(c, operation, resolvedProviderConfig, request = c.req.raw) {
|
|
4924
4685
|
try {
|
|
@@ -4926,48 +4687,386 @@ async function handleCodexImages(c, operation, resolvedProviderConfig, request =
|
|
|
4926
4687
|
message: "Provider 'codex' not found or disabled",
|
|
4927
4688
|
type: "invalid_request_error"
|
|
4928
4689
|
} }, 404);
|
|
4929
|
-
if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$
|
|
4930
|
-
else debugJson(logger$
|
|
4690
|
+
if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$13, "images.generations.codex.request", async () => ({ body: await request.clone().text() }));
|
|
4691
|
+
else debugJson(logger$13, "images.edits.codex.request", getContentMetadata(request.headers));
|
|
4931
4692
|
const upstreamResponse = await forwardCodexImages(request, operation);
|
|
4932
|
-
debugJson(logger$
|
|
4693
|
+
debugJson(logger$13, `images.${operation}.codex.response`, {
|
|
4933
4694
|
...getContentMetadata(upstreamResponse.headers),
|
|
4934
4695
|
statusCode: upstreamResponse.status
|
|
4935
4696
|
});
|
|
4936
4697
|
return createProviderProxyResponse(upstreamResponse);
|
|
4937
4698
|
} catch (error) {
|
|
4938
|
-
logger$
|
|
4699
|
+
logger$13.error(`images.${operation}.codex.error`, { error });
|
|
4939
4700
|
return await forwardError(c, error);
|
|
4940
4701
|
}
|
|
4941
4702
|
}
|
|
4942
|
-
|
|
4703
|
+
/**
|
|
4704
|
+
* Routes a parsed images request: applies the model mapping, forwards to a
|
|
4705
|
+
* mapped provider when configured, and otherwise falls back to Codex.
|
|
4706
|
+
*/
|
|
4707
|
+
async function routeImagesRequest(c, operation, parsed) {
|
|
4708
|
+
const requestedModel = parsed.model;
|
|
4709
|
+
const mappedModel = imageRouteDependencies.resolveMappedModel(requestedModel);
|
|
4710
|
+
if (mappedModel !== requestedModel) consola.debug(`Resolved model mapping: ${requestedModel} -> ${mappedModel}`);
|
|
4711
|
+
const providerModelAlias = parseProviderModelAlias(mappedModel);
|
|
4712
|
+
if (providerModelAlias) {
|
|
4713
|
+
const providerConfig = await imageRouteDependencies.resolveProviderConfig(providerModelAlias.provider);
|
|
4714
|
+
if (providerConfig) {
|
|
4715
|
+
const request = parsed.createRequest(providerModelAlias.model);
|
|
4716
|
+
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig, request);
|
|
4717
|
+
return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
|
|
4718
|
+
logger: logger$13,
|
|
4719
|
+
provider: providerModelAlias.provider
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
consola.debug(`Provider '${providerModelAlias.provider}' not found or disabled; forwarding the original model to Codex`);
|
|
4723
|
+
return await handleCodexImages(c, operation, void 0, parsed.originalRequest ?? parsed.createRequest(requestedModel));
|
|
4724
|
+
}
|
|
4725
|
+
if (parsed.originalRequest && mappedModel === requestedModel) return await handleCodexImages(c, operation, void 0, parsed.originalRequest);
|
|
4726
|
+
return await handleCodexImages(c, operation, void 0, parsed.createRequest(mappedModel));
|
|
4727
|
+
}
|
|
4728
|
+
//#endregion
|
|
4729
|
+
//#region src/routes/images/temp-form-data.ts
|
|
4730
|
+
const logger$12 = createHandlerLogger("images-temp-form-data");
|
|
4731
|
+
const multipartStagingDependencies = { createWriteStream };
|
|
4732
|
+
/**
|
|
4733
|
+
* Matches the images forwarding timeouts: an upstream upload may take that
|
|
4734
|
+
* long, so staged files must survive at least the whole forwarding window.
|
|
4735
|
+
*/
|
|
4736
|
+
const STAGED_FILES_TTL_MS = 900 * 1e3;
|
|
4737
|
+
const DEFAULT_MULTIPART_STAGING_LIMITS = {
|
|
4738
|
+
maxBodySizeBytes: 128 * 1024 * 1024,
|
|
4739
|
+
maxFieldSizeBytes: 10 * 1024 * 1024,
|
|
4740
|
+
maxFields: 64,
|
|
4741
|
+
maxFileSizeBytes: 64 * 1024 * 1024,
|
|
4742
|
+
maxFiles: 16,
|
|
4743
|
+
maxParts: 80
|
|
4744
|
+
};
|
|
4745
|
+
var InvalidMultipartBodyError = class extends Error {
|
|
4746
|
+
constructor(options) {
|
|
4747
|
+
super("Invalid multipart form data body", options);
|
|
4748
|
+
this.name = "InvalidMultipartBodyError";
|
|
4749
|
+
}
|
|
4750
|
+
};
|
|
4751
|
+
var MultipartBodyTooLargeError = class extends Error {
|
|
4752
|
+
constructor() {
|
|
4753
|
+
super("Multipart form data body exceeds the configured upload limits");
|
|
4754
|
+
this.name = "MultipartBodyTooLargeError";
|
|
4755
|
+
}
|
|
4756
|
+
};
|
|
4757
|
+
function sanitizeFileName(name) {
|
|
4758
|
+
return name.split(/[\\/]/).pop()?.replaceAll("\0", "") || "file";
|
|
4759
|
+
}
|
|
4760
|
+
async function createDiskBackedBlob(filePath, type) {
|
|
4761
|
+
if (typeof Bun !== "undefined") return Bun.file(filePath, { type });
|
|
4762
|
+
const { openAsBlob } = await import("node:fs");
|
|
4763
|
+
return await openAsBlob(filePath, { type });
|
|
4764
|
+
}
|
|
4765
|
+
function toError(error) {
|
|
4766
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
4767
|
+
}
|
|
4768
|
+
function parseMultipartBody(body, contentType, directory, entries, limits) {
|
|
4769
|
+
return new Promise((resolve, reject) => {
|
|
4770
|
+
let busboy;
|
|
4771
|
+
try {
|
|
4772
|
+
busboy = new Busboy({
|
|
4773
|
+
headers: { "content-type": contentType },
|
|
4774
|
+
limits: {
|
|
4775
|
+
fieldSize: limits.maxFieldSizeBytes,
|
|
4776
|
+
fields: limits.maxFields,
|
|
4777
|
+
fileSize: limits.maxFileSizeBytes,
|
|
4778
|
+
files: limits.maxFiles,
|
|
4779
|
+
parts: limits.maxParts
|
|
4780
|
+
}
|
|
4781
|
+
});
|
|
4782
|
+
} catch (error) {
|
|
4783
|
+
reject(new InvalidMultipartBodyError({ cause: toError(error) }));
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
const pendingWrites = [];
|
|
4787
|
+
const activeFileStreams = /* @__PURE__ */ new Set();
|
|
4788
|
+
const source = body === null ? Readable.from([]) : Readable.fromWeb(body);
|
|
4789
|
+
let bodySizeBytes = 0;
|
|
4790
|
+
const bodyLimiter = new Transform({ transform(chunk, _encoding, callback) {
|
|
4791
|
+
bodySizeBytes += chunk.byteLength;
|
|
4792
|
+
if (bodySizeBytes > limits.maxBodySizeBytes) {
|
|
4793
|
+
callback(new MultipartBodyTooLargeError());
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
callback(null, chunk);
|
|
4797
|
+
} });
|
|
4798
|
+
let fileIndex = 0;
|
|
4799
|
+
let failure = null;
|
|
4800
|
+
let inputPipeline = null;
|
|
4801
|
+
let settled = false;
|
|
4802
|
+
const settleStreams = () => Promise.allSettled([...inputPipeline ? [inputPipeline] : [], ...pendingWrites]);
|
|
4803
|
+
const fail = (error) => {
|
|
4804
|
+
if (failure || settled) return;
|
|
4805
|
+
const failureError = toError(error);
|
|
4806
|
+
failure = failureError;
|
|
4807
|
+
source.destroy(failureError);
|
|
4808
|
+
bodyLimiter.destroy(failureError);
|
|
4809
|
+
busboy.destroy(failureError);
|
|
4810
|
+
for (const stream of activeFileStreams) stream.destroy(failureError);
|
|
4811
|
+
settleStreams().then(() => {
|
|
4812
|
+
if (settled) return;
|
|
4813
|
+
settled = true;
|
|
4814
|
+
reject(failureError);
|
|
4815
|
+
});
|
|
4816
|
+
};
|
|
4817
|
+
const failLimit = () => fail(new MultipartBodyTooLargeError());
|
|
4818
|
+
busboy.on("field", (name, value, nameTruncated, valueTruncated) => {
|
|
4819
|
+
if (nameTruncated || valueTruncated) {
|
|
4820
|
+
failLimit();
|
|
4821
|
+
return;
|
|
4822
|
+
}
|
|
4823
|
+
entries.push({
|
|
4824
|
+
kind: "field",
|
|
4825
|
+
name,
|
|
4826
|
+
value
|
|
4827
|
+
});
|
|
4828
|
+
});
|
|
4829
|
+
busboy.on("file", (name, stream, fileName, _transferEncoding, mimeType) => {
|
|
4830
|
+
if (failure) {
|
|
4831
|
+
stream.resume();
|
|
4832
|
+
return;
|
|
4833
|
+
}
|
|
4834
|
+
const filePath = join(directory, `${fileIndex}.upload`);
|
|
4835
|
+
fileIndex += 1;
|
|
4836
|
+
entries.push({
|
|
4837
|
+
fileName: sanitizeFileName(fileName),
|
|
4838
|
+
filePath,
|
|
4839
|
+
kind: "file",
|
|
4840
|
+
mimeType,
|
|
4841
|
+
name
|
|
4842
|
+
});
|
|
4843
|
+
activeFileStreams.add(stream);
|
|
4844
|
+
stream.once("close", () => activeFileStreams.delete(stream));
|
|
4845
|
+
stream.once("limit", failLimit);
|
|
4846
|
+
const write = pipeline(stream, multipartStagingDependencies.createWriteStream(filePath));
|
|
4847
|
+
pendingWrites.push(write);
|
|
4848
|
+
write.catch(fail);
|
|
4849
|
+
});
|
|
4850
|
+
busboy.on("fieldsLimit", failLimit);
|
|
4851
|
+
busboy.on("filesLimit", failLimit);
|
|
4852
|
+
busboy.on("partsLimit", failLimit);
|
|
4853
|
+
inputPipeline = pipeline(source, bodyLimiter, busboy);
|
|
4854
|
+
inputPipeline.then(() => {
|
|
4855
|
+
Promise.allSettled(pendingWrites).then((results) => {
|
|
4856
|
+
if (failure || settled) return;
|
|
4857
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
4858
|
+
if (failed) fail(failed.reason);
|
|
4859
|
+
else {
|
|
4860
|
+
settled = true;
|
|
4861
|
+
resolve();
|
|
4862
|
+
}
|
|
4863
|
+
});
|
|
4864
|
+
}, (error) => {
|
|
4865
|
+
fail(error instanceof MultipartBodyTooLargeError ? error : new InvalidMultipartBodyError({ cause: toError(error) }));
|
|
4866
|
+
});
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
function resolveStagingLimits(overrides) {
|
|
4870
|
+
const limits = {
|
|
4871
|
+
...DEFAULT_MULTIPART_STAGING_LIMITS,
|
|
4872
|
+
...overrides
|
|
4873
|
+
};
|
|
4874
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive safe integer`);
|
|
4875
|
+
return limits;
|
|
4876
|
+
}
|
|
4877
|
+
/**
|
|
4878
|
+
* Streams a multipart request body through the parser, writing every file
|
|
4879
|
+
* part to a temporary directory as it arrives and rebuilding the form data
|
|
4880
|
+
* with disk-backed blobs, so forwarding streams payloads from disk instead
|
|
4881
|
+
* of pinning the whole upload in memory.
|
|
4882
|
+
*/
|
|
4883
|
+
async function stageMultipartBodyToDisk(body, contentType, limitOverrides = {}) {
|
|
4884
|
+
const limits = resolveStagingLimits(limitOverrides);
|
|
4885
|
+
const directory = await mkdtemp(join(tmpdir(), "copilot-api-images-"));
|
|
4886
|
+
const entries = [];
|
|
4943
4887
|
try {
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4888
|
+
await parseMultipartBody(body, contentType, directory, entries, limits);
|
|
4889
|
+
} catch (error) {
|
|
4890
|
+
await rm(directory, {
|
|
4891
|
+
force: true,
|
|
4892
|
+
recursive: true
|
|
4893
|
+
});
|
|
4894
|
+
throw error;
|
|
4895
|
+
}
|
|
4896
|
+
const stagedFormData = new FormData();
|
|
4897
|
+
try {
|
|
4898
|
+
for (const entry of entries) if (entry.kind === "field") stagedFormData.append(entry.name, entry.value);
|
|
4899
|
+
else stagedFormData.append(entry.name, await createDiskBackedBlob(entry.filePath, entry.mimeType), entry.fileName);
|
|
4900
|
+
} catch (error) {
|
|
4901
|
+
await rm(directory, {
|
|
4902
|
+
force: true,
|
|
4903
|
+
recursive: true
|
|
4904
|
+
});
|
|
4905
|
+
throw error;
|
|
4906
|
+
}
|
|
4907
|
+
let cleanupPromise = null;
|
|
4908
|
+
let cleanupTimer = null;
|
|
4909
|
+
let unregisterProcessCleanup = null;
|
|
4910
|
+
const cleanup = () => {
|
|
4911
|
+
cleanupPromise ??= (async () => {
|
|
4912
|
+
if (cleanupTimer) {
|
|
4913
|
+
clearTimeout(cleanupTimer);
|
|
4914
|
+
cleanupTimer = null;
|
|
4915
|
+
}
|
|
4916
|
+
try {
|
|
4917
|
+
await rm(directory, {
|
|
4918
|
+
force: true,
|
|
4919
|
+
recursive: true
|
|
4958
4920
|
});
|
|
4921
|
+
} catch (error) {
|
|
4922
|
+
logger$12.warn("Failed to remove staged images temp directory", {
|
|
4923
|
+
directory,
|
|
4924
|
+
error
|
|
4925
|
+
});
|
|
4926
|
+
} finally {
|
|
4927
|
+
unregisterProcessCleanup?.();
|
|
4928
|
+
unregisterProcessCleanup = null;
|
|
4959
4929
|
}
|
|
4960
|
-
|
|
4961
|
-
|
|
4930
|
+
})();
|
|
4931
|
+
return cleanupPromise;
|
|
4932
|
+
};
|
|
4933
|
+
unregisterProcessCleanup = registerProcessCleanup(() => {
|
|
4934
|
+
if (cleanupTimer) {
|
|
4935
|
+
clearTimeout(cleanupTimer);
|
|
4936
|
+
cleanupTimer = null;
|
|
4937
|
+
}
|
|
4938
|
+
try {
|
|
4939
|
+
rmSync(directory, {
|
|
4940
|
+
force: true,
|
|
4941
|
+
recursive: true
|
|
4942
|
+
});
|
|
4943
|
+
} catch (error) {
|
|
4944
|
+
logger$12.warn("Failed to remove staged images temp directory on exit", {
|
|
4945
|
+
directory,
|
|
4946
|
+
error
|
|
4947
|
+
});
|
|
4948
|
+
} finally {
|
|
4949
|
+
unregisterProcessCleanup?.();
|
|
4950
|
+
unregisterProcessCleanup = null;
|
|
4951
|
+
}
|
|
4952
|
+
});
|
|
4953
|
+
return {
|
|
4954
|
+
directory,
|
|
4955
|
+
formData: stagedFormData,
|
|
4956
|
+
cleanup,
|
|
4957
|
+
scheduleCleanup: () => {
|
|
4958
|
+
if (cleanupPromise || cleanupTimer) return;
|
|
4959
|
+
cleanupTimer = setTimeout(() => void cleanup(), STAGED_FILES_TTL_MS);
|
|
4960
|
+
cleanupTimer.unref();
|
|
4961
|
+
}
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
//#endregion
|
|
4965
|
+
//#region src/routes/images/edits-handler.ts
|
|
4966
|
+
const imageEditsRouteDependencies = { stageMultipartBodyToDisk };
|
|
4967
|
+
function createStagedFormDataRequest(request, requestHeaders, formData) {
|
|
4968
|
+
const headers = new Headers(requestHeaders);
|
|
4969
|
+
headers.delete("content-length");
|
|
4970
|
+
headers.delete("content-type");
|
|
4971
|
+
return createForwardRequest(request, headers, formData);
|
|
4972
|
+
}
|
|
4973
|
+
function createMultipartImagesRequest(request, requestHeaders, formData, model) {
|
|
4974
|
+
formData.set("model", model);
|
|
4975
|
+
return createStagedFormDataRequest(request, requestHeaders, formData);
|
|
4976
|
+
}
|
|
4977
|
+
/**
|
|
4978
|
+
* Streams an edits multipart body to disk, staging uploaded files so
|
|
4979
|
+
* forwarding streams them from the filesystem instead of pinning them in
|
|
4980
|
+
* memory. Non-multipart bodies come back as an untouched request stream;
|
|
4981
|
+
* malformed multipart bodies throw a typed error because the consumed stream
|
|
4982
|
+
* can no longer be forwarded.
|
|
4983
|
+
*/
|
|
4984
|
+
async function parseEditsRequest(request) {
|
|
4985
|
+
const requestHeaders = snapshotRequestHeaders(request);
|
|
4986
|
+
const contentType = requestHeaders.get("content-type");
|
|
4987
|
+
if (contentType?.split(";", 1)[0]?.trim().toLowerCase() !== "multipart/form-data" || !contentType) return createForwardRequest(request, requestHeaders, await readRequestBodyWithLimit(request, imageRouteDependencies.maxRequestBodyBytes));
|
|
4988
|
+
const staged = await imageEditsRouteDependencies.stageMultipartBodyToDisk(request.body, contentType, { maxBodySizeBytes: imageRouteDependencies.maxRequestBodyBytes });
|
|
4989
|
+
const model = staged.formData.get("model");
|
|
4990
|
+
return {
|
|
4991
|
+
model: typeof model === "string" ? model : void 0,
|
|
4992
|
+
requestHeaders,
|
|
4993
|
+
staged
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
async function handleImagesEdits(c) {
|
|
4997
|
+
try {
|
|
4998
|
+
const parsed = await parseEditsRequest(c.req.raw);
|
|
4999
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed);
|
|
5000
|
+
const { model, requestHeaders, staged } = parsed;
|
|
5001
|
+
try {
|
|
5002
|
+
const response = model === void 0 ? await handleCodexImages(c, "edits", void 0, createStagedFormDataRequest(c.req.raw, requestHeaders, staged.formData)) : await routeImagesRequest(c, "edits", {
|
|
5003
|
+
createRequest: (mappedModel) => createMultipartImagesRequest(c.req.raw, requestHeaders, staged.formData, mappedModel),
|
|
5004
|
+
model
|
|
5005
|
+
});
|
|
5006
|
+
staged.scheduleCleanup();
|
|
5007
|
+
return response;
|
|
5008
|
+
} catch (error) {
|
|
5009
|
+
await staged.cleanup();
|
|
5010
|
+
throw error;
|
|
4962
5011
|
}
|
|
4963
|
-
return await handleCodexImages(c, operation, void 0, mappedModel === requestedModel ? parsedRequest.originalRequest : parsedRequest.createRequest(mappedModel));
|
|
4964
5012
|
} catch (error) {
|
|
4965
|
-
|
|
5013
|
+
if (error instanceof InvalidMultipartBodyError || error instanceof MultipartBodyTooLargeError) return c.json({ error: {
|
|
5014
|
+
message: error.message,
|
|
5015
|
+
type: "invalid_request_error"
|
|
5016
|
+
} }, error instanceof MultipartBodyTooLargeError ? 413 : 400);
|
|
5017
|
+
logger$13.error("images.edits.error", { error });
|
|
4966
5018
|
return await forwardError(c, error);
|
|
4967
5019
|
}
|
|
4968
5020
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
5021
|
+
//#endregion
|
|
5022
|
+
//#region src/routes/images/generations-handler.ts
|
|
5023
|
+
function createJsonImagesRequest(request, requestHeaders, payload, model) {
|
|
5024
|
+
const headers = new Headers(requestHeaders);
|
|
5025
|
+
headers.delete("content-length");
|
|
5026
|
+
return createForwardRequest(request, headers, new TextEncoder().encode(JSON.stringify({
|
|
5027
|
+
...payload,
|
|
5028
|
+
model
|
|
5029
|
+
})));
|
|
5030
|
+
}
|
|
5031
|
+
/**
|
|
5032
|
+
* Parses a generations request body as JSON. Generation payloads are small
|
|
5033
|
+
* JSON documents, so one byte copy covers both routing on the model field
|
|
5034
|
+
* and forwarding an unchanged body to Codex. Rebuilt bodies stay binary so
|
|
5035
|
+
* Node does not synthesize a text/plain content type.
|
|
5036
|
+
*/
|
|
5037
|
+
async function parseGenerationsRequest(request) {
|
|
5038
|
+
const requestHeaders = snapshotRequestHeaders(request);
|
|
5039
|
+
const body = new Uint8Array(await readRequestBodyWithLimit(request, imageRouteDependencies.maxRequestBodyBytes));
|
|
5040
|
+
const originalRequest = createForwardRequest(request, requestHeaders, body);
|
|
5041
|
+
let payload;
|
|
5042
|
+
try {
|
|
5043
|
+
const bodyText = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
5044
|
+
payload = JSON.parse(bodyText);
|
|
5045
|
+
} catch {
|
|
5046
|
+
return originalRequest;
|
|
5047
|
+
}
|
|
5048
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
|
|
5049
|
+
return {
|
|
5050
|
+
createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
|
|
5051
|
+
model: payload.model,
|
|
5052
|
+
originalRequest
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
async function handleImagesGenerations(c) {
|
|
5056
|
+
try {
|
|
5057
|
+
const parsed = await parseGenerationsRequest(c.req.raw);
|
|
5058
|
+
if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed);
|
|
5059
|
+
return await routeImagesRequest(c, "generations", parsed);
|
|
5060
|
+
} catch (error) {
|
|
5061
|
+
logger$13.error("images.generations.error", { error });
|
|
5062
|
+
return await forwardError(c, error);
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
//#endregion
|
|
5066
|
+
//#region src/routes/images/route.ts
|
|
5067
|
+
const imageRoutes = new Hono();
|
|
5068
|
+
imageRoutes.post("/generations", handleImagesGenerations);
|
|
5069
|
+
imageRoutes.post("/edits", handleImagesEdits);
|
|
4971
5070
|
//#endregion
|
|
4972
5071
|
//#region src/lib/tokenizer.ts
|
|
4973
5072
|
const ENCODING_MAP = {
|
|
@@ -6117,6 +6216,7 @@ async function handleCountTokens(c) {
|
|
|
6117
6216
|
//#endregion
|
|
6118
6217
|
//#region src/lib/codex-rate-limit.ts
|
|
6119
6218
|
const codexRateLimitScopes = ["primary", "secondary"];
|
|
6219
|
+
const quotaWarningUsedPercent = 90;
|
|
6120
6220
|
const formatCodexRateLimitResetAt = (resetAt) => {
|
|
6121
6221
|
const date = /* @__PURE__ */ new Date(resetAt * 1e3);
|
|
6122
6222
|
return Number.isNaN(date.getTime()) ? String(resetAt) : date.toLocaleString();
|
|
@@ -6131,16 +6231,21 @@ const logCodexRateLimitsEvent = (event) => {
|
|
|
6131
6231
|
const rateLimitRecord = rateLimits;
|
|
6132
6232
|
const allowed = typeof rateLimitRecord.allowed === "boolean" ? rateLimitRecord.allowed : null;
|
|
6133
6233
|
const limitReached = typeof rateLimitRecord.limit_reached === "boolean" ? rateLimitRecord.limit_reached : null;
|
|
6234
|
+
const blocked = allowed === false || limitReached === true;
|
|
6235
|
+
let hasWindow = false;
|
|
6134
6236
|
for (const scope of codexRateLimitScopes) {
|
|
6135
6237
|
const window = rateLimitRecord[scope];
|
|
6136
6238
|
if (!isCodexRateLimitWindow(window)) continue;
|
|
6239
|
+
hasWindow = true;
|
|
6137
6240
|
const summary = [];
|
|
6138
6241
|
if (allowed !== null) summary.push(`allowed=${allowed}`);
|
|
6139
6242
|
if (limitReached !== null) summary.push(`limit_reached=${limitReached}`);
|
|
6140
6243
|
summary.push(`used=${window.used_percent}%`, `reset_at=${formatCodexRateLimitResetAt(window.reset_at)}`);
|
|
6141
|
-
const
|
|
6142
|
-
|
|
6244
|
+
const message = `${planType ? `Codex ${scope} quota (${planType})` : `Codex ${scope} quota`}: ${summary.join(", ")}`;
|
|
6245
|
+
if (blocked || window.used_percent >= quotaWarningUsedPercent) consola.warn(message);
|
|
6246
|
+
else consola.debug(message);
|
|
6143
6247
|
}
|
|
6248
|
+
if (blocked && !hasWindow) consola.warn(`Codex quota restricted: allowed=${allowed}, limit_reached=${limitReached}`);
|
|
6144
6249
|
};
|
|
6145
6250
|
const isCodexRateLimitWindow = (value) => {
|
|
6146
6251
|
if (!value || typeof value !== "object") return false;
|
|
@@ -9212,7 +9317,7 @@ const handleOpenAICompatibleProviderMessages = async (c, options) => {
|
|
|
9212
9317
|
payload: openAIPayload,
|
|
9213
9318
|
provider
|
|
9214
9319
|
});
|
|
9215
|
-
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, openAIPayload, c.req.raw.headers);
|
|
9320
|
+
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, openAIPayload, c.req.raw.headers, { signal: c.req.raw.signal });
|
|
9216
9321
|
if (!upstreamResponse.ok) {
|
|
9217
9322
|
logger$10.error("Failed to create openai-compatible responses", upstreamResponse);
|
|
9218
9323
|
throw new HTTPError("Failed to create openai-compatible responses", upstreamResponse);
|
|
@@ -11418,17 +11523,6 @@ function isDeepSeekModelId(modelId) {
|
|
|
11418
11523
|
function shouldInjectMessagesToolCallTips(userAgent, targetModel) {
|
|
11419
11524
|
return isCodexUserAgent(userAgent) && !isDeepSeekModelId(targetModel);
|
|
11420
11525
|
}
|
|
11421
|
-
async function logCodexModelsResponse(response) {
|
|
11422
|
-
try {
|
|
11423
|
-
const models = await response.clone().json();
|
|
11424
|
-
debugJson(logger$8, "models.codex.response", {
|
|
11425
|
-
statusCode: response.status,
|
|
11426
|
-
models
|
|
11427
|
-
});
|
|
11428
|
-
} catch (error) {
|
|
11429
|
-
logger$8.warn("models.codex.response_log_error", { error });
|
|
11430
|
-
}
|
|
11431
|
-
}
|
|
11432
11526
|
/**
|
|
11433
11527
|
* Proxies a models request to the fixed Codex upstream models endpoint.
|
|
11434
11528
|
* Returns a 404 JSON response when the codex provider is unavailable.
|
|
@@ -11440,9 +11534,7 @@ async function handleCodexModelsProxy(c, resolvedProviderConfig) {
|
|
|
11440
11534
|
message: "Provider 'codex' not found or disabled",
|
|
11441
11535
|
type: "invalid_request_error"
|
|
11442
11536
|
} }, 404);
|
|
11443
|
-
|
|
11444
|
-
await logCodexModelsResponse(upstreamResponse);
|
|
11445
|
-
return createProviderProxyResponse(upstreamResponse);
|
|
11537
|
+
return createProviderProxyResponse(await forwardCodexModels(c.req.url, c.req.raw.headers));
|
|
11446
11538
|
}
|
|
11447
11539
|
async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
11448
11540
|
const [upstreamCatalog, candidates] = await Promise.all([tryGetCodexCatalog(c), Promise.resolve(candidatesRequest).catch((error) => {
|
|
@@ -11482,12 +11574,6 @@ async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
|
11482
11574
|
...upstreamCatalog ?? {},
|
|
11483
11575
|
models
|
|
11484
11576
|
};
|
|
11485
|
-
debugJson(logger$8, "models.codex.merged_response", {
|
|
11486
|
-
upstreamCount: upstreamModels.length,
|
|
11487
|
-
codexProviderAliasCount: codexProviderAliases.length,
|
|
11488
|
-
syntheticCount: syntheticModels.length,
|
|
11489
|
-
models: response
|
|
11490
|
-
});
|
|
11491
11577
|
return c.json(response);
|
|
11492
11578
|
}
|
|
11493
11579
|
function createCatalogAlias(model, slug, providerName) {
|
|
@@ -12034,6 +12120,74 @@ providerModelRoutes.get("/", async (c) => {
|
|
|
12034
12120
|
}
|
|
12035
12121
|
});
|
|
12036
12122
|
//#endregion
|
|
12123
|
+
//#region src/lib/zstd-request.ts
|
|
12124
|
+
const ZSTD_CONTENT_ENCODING = "zstd";
|
|
12125
|
+
const INVALID_BODY_STATUS = 400;
|
|
12126
|
+
let nodeZlibPromise = null;
|
|
12127
|
+
const zstdDecompressionMiddleware = async (c, next) => {
|
|
12128
|
+
if (c.req.header("content-encoding")?.trim().toLowerCase() !== ZSTD_CONTENT_ENCODING) return next();
|
|
12129
|
+
try {
|
|
12130
|
+
const decompressedBody = await decompressZstd(new Uint8Array(await c.req.raw.arrayBuffer()));
|
|
12131
|
+
const headers = new Headers(c.req.raw.headers);
|
|
12132
|
+
headers.delete("content-encoding");
|
|
12133
|
+
headers.delete("content-length");
|
|
12134
|
+
c.req.raw = new Request(c.req.raw.url, {
|
|
12135
|
+
body: decompressedBody,
|
|
12136
|
+
headers,
|
|
12137
|
+
method: c.req.raw.method,
|
|
12138
|
+
signal: c.req.raw.signal
|
|
12139
|
+
});
|
|
12140
|
+
c.req.bodyCache = {};
|
|
12141
|
+
} catch {
|
|
12142
|
+
return c.json({ error: {
|
|
12143
|
+
message: "Failed to decompress zstd request body.",
|
|
12144
|
+
type: "invalid_request_error"
|
|
12145
|
+
} }, INVALID_BODY_STATUS);
|
|
12146
|
+
}
|
|
12147
|
+
return next();
|
|
12148
|
+
};
|
|
12149
|
+
const decompressZstd = async (input) => {
|
|
12150
|
+
const bun = getBunRuntime();
|
|
12151
|
+
if (bun?.zstdDecompress) return toUint8Array(await bun.zstdDecompress(input));
|
|
12152
|
+
const nodeZlib = await getNodeZlib();
|
|
12153
|
+
if (nodeZlib?.zstdDecompress) return new Promise((resolve, reject) => {
|
|
12154
|
+
nodeZlib.zstdDecompress?.(input, (error, result) => {
|
|
12155
|
+
if (error) {
|
|
12156
|
+
reject(error);
|
|
12157
|
+
return;
|
|
12158
|
+
}
|
|
12159
|
+
resolve(toUint8Array(result));
|
|
12160
|
+
});
|
|
12161
|
+
});
|
|
12162
|
+
return decompress(input);
|
|
12163
|
+
};
|
|
12164
|
+
const getBunRuntime = () => globalThis.Bun;
|
|
12165
|
+
const getNodeZlib = async () => {
|
|
12166
|
+
nodeZlibPromise ??= import("node:zlib").then((module) => module).catch(() => null);
|
|
12167
|
+
return nodeZlibPromise;
|
|
12168
|
+
};
|
|
12169
|
+
const toUint8Array = (data) => {
|
|
12170
|
+
if (data instanceof Uint8Array) return data;
|
|
12171
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
12172
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
12173
|
+
};
|
|
12174
|
+
//#endregion
|
|
12175
|
+
//#region src/services/qoder/responses-compat.ts
|
|
12176
|
+
const NATIVE_SEARCH = new Set(["web_search", "web_search_preview"]);
|
|
12177
|
+
function qoderResponsesPayload(payload) {
|
|
12178
|
+
const tools = payload.tools ?? [];
|
|
12179
|
+
if (!tools.some((tool) => typeof tool.type === "string" && NATIVE_SEARCH.has(tool.type))) return payload;
|
|
12180
|
+
const choice = payload.tool_choice;
|
|
12181
|
+
if (typeof choice === "object" && choice !== null && NATIVE_SEARCH.has(choice.type)) throw new ResponsesMessagesTranslationError("Qoder does not support provider-native web search; use a client search tool");
|
|
12182
|
+
const supported = tools.filter((tool) => !(typeof tool.type === "string" && NATIVE_SEARCH.has(tool.type)));
|
|
12183
|
+
if (choice === "required" && !supported.length) throw new ResponsesMessagesTranslationError("Qoder cannot satisfy a required native web search tool");
|
|
12184
|
+
return {
|
|
12185
|
+
...payload,
|
|
12186
|
+
tools: supported,
|
|
12187
|
+
instructions: [payload.instructions, "Provider-native web search is unavailable on this route. Do not claim to have searched the web unless an available client tool actually returns search results."].filter(Boolean).join("\n\n")
|
|
12188
|
+
};
|
|
12189
|
+
}
|
|
12190
|
+
//#endregion
|
|
12037
12191
|
//#region src/routes/responses/custom-tool-input-stream-decoder.ts
|
|
12038
12192
|
const CUSTOM_TOOL_INPUT_PREFIX_TOKENS = [
|
|
12039
12193
|
"{",
|
|
@@ -12919,7 +13073,8 @@ function isAnthropicResponse(value) {
|
|
|
12919
13073
|
const logger$2 = createHandlerLogger("provider-responses-handler");
|
|
12920
13074
|
const providerResponsesHandlerDependencies = { resolveProviderConfig };
|
|
12921
13075
|
async function handleProviderResponsesForProvider(c, options) {
|
|
12922
|
-
const {
|
|
13076
|
+
const { provider } = options;
|
|
13077
|
+
let { payload } = options;
|
|
12923
13078
|
debugJson(logger$2, "Responses request payload:", {
|
|
12924
13079
|
payload,
|
|
12925
13080
|
provider
|
|
@@ -12929,6 +13084,7 @@ async function handleProviderResponsesForProvider(c, options) {
|
|
|
12929
13084
|
message: `Provider '${provider}' does not support the /v1/responses endpoint`,
|
|
12930
13085
|
type: "invalid_request_error"
|
|
12931
13086
|
} }, 400);
|
|
13087
|
+
if (isQoderSdkProvider(providerConfig)) payload = qoderResponsesPayload(payload);
|
|
12932
13088
|
const effectiveType = resolveEffectiveProviderType(providerConfig, payload.model);
|
|
12933
13089
|
const normalizedReasoningEffort = normalizeProviderResponsesReasoningEffort(payload, providerConfig);
|
|
12934
13090
|
if (normalizedReasoningEffort) logger$2.debug(`Normalized reasoning effort from ${normalizedReasoningEffort.from} to ${normalizedReasoningEffort.to} based on the provider model configuration`);
|
|
@@ -12945,7 +13101,8 @@ async function handleProviderResponsesForProvider(c, options) {
|
|
|
12945
13101
|
type: "invalid_request_error"
|
|
12946
13102
|
} }, 400);
|
|
12947
13103
|
filterReasoningForTransport(payload, false);
|
|
12948
|
-
|
|
13104
|
+
const model = providerConfig.name === "codex" ? getModels().data.find((model) => model.id === payload.model) : void 0;
|
|
13105
|
+
if (applyResponsesApiContextManagement(payload, model?.capabilities.limits.max_prompt_tokens, {
|
|
12949
13106
|
compactThresholdRatio: .8,
|
|
12950
13107
|
source: "responses"
|
|
12951
13108
|
})) compactInputByLatestCompaction(payload);
|
|
@@ -13073,6 +13230,7 @@ const getResponsesEvents = (response, signal) => createResponsesSafeStream(creat
|
|
|
13073
13230
|
//#endregion
|
|
13074
13231
|
//#region src/routes/provider/responses/route.ts
|
|
13075
13232
|
const providerResponsesRoutes = new Hono();
|
|
13233
|
+
providerResponsesRoutes.use(zstdDecompressionMiddleware);
|
|
13076
13234
|
providerResponsesRoutes.post("/", async (c) => {
|
|
13077
13235
|
try {
|
|
13078
13236
|
const provider = c.req.param("provider") ?? "";
|
|
@@ -13330,6 +13488,7 @@ const getTrimmedHeader = (c, name) => {
|
|
|
13330
13488
|
//#endregion
|
|
13331
13489
|
//#region src/routes/responses/route.ts
|
|
13332
13490
|
const responsesRoutes = new Hono();
|
|
13491
|
+
responsesRoutes.use(zstdDecompressionMiddleware);
|
|
13333
13492
|
responsesRoutes.post("/", async (c) => {
|
|
13334
13493
|
try {
|
|
13335
13494
|
return await handleResponses(c);
|
|
@@ -13380,6 +13539,14 @@ tokenUsageRoute.get("/events", async (c) => {
|
|
|
13380
13539
|
return c.json(eventsPage);
|
|
13381
13540
|
});
|
|
13382
13541
|
//#endregion
|
|
13542
|
+
//#region src/routes/qoder/route.ts
|
|
13543
|
+
const qoderRoutes = new Hono();
|
|
13544
|
+
qoderRoutes.get("/usage", async (c) => {
|
|
13545
|
+
const provider = getProviderConfig("qoder");
|
|
13546
|
+
if (!provider || !isQoderSdkProvider(provider)) return c.json({ error: { message: "Builtin Qoder provider is not configured or is disabled" } }, 404);
|
|
13547
|
+
return forwardQoder(provider, "/usage", { signal: c.req.raw.signal });
|
|
13548
|
+
});
|
|
13549
|
+
//#endregion
|
|
13383
13550
|
//#region src/routes/usage/route.ts
|
|
13384
13551
|
const usageRoute = new Hono();
|
|
13385
13552
|
usageRoute.get("/", async (c) => {
|
|
@@ -13393,54 +13560,69 @@ usageRoute.get("/", async (c) => {
|
|
|
13393
13560
|
});
|
|
13394
13561
|
//#endregion
|
|
13395
13562
|
//#region src/server.ts
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
server.use(
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
server.
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
server.
|
|
13426
|
-
server.
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
server.
|
|
13431
|
-
server.route("/
|
|
13432
|
-
server.route("/
|
|
13433
|
-
server.route("/
|
|
13434
|
-
server.route("/
|
|
13435
|
-
server.route("/
|
|
13436
|
-
server.route("
|
|
13437
|
-
server.route("
|
|
13438
|
-
server.route("
|
|
13439
|
-
server.route("
|
|
13440
|
-
server.route("
|
|
13441
|
-
server.route("
|
|
13442
|
-
server.route("
|
|
13443
|
-
server.route("
|
|
13444
|
-
server.route("
|
|
13563
|
+
function resolveSameOriginCorsOrigin(origin, context) {
|
|
13564
|
+
if (!origin) return null;
|
|
13565
|
+
try {
|
|
13566
|
+
return origin === new URL(context.req.url).origin ? origin : null;
|
|
13567
|
+
} catch {
|
|
13568
|
+
return null;
|
|
13569
|
+
}
|
|
13570
|
+
}
|
|
13571
|
+
function createServer(options = {}) {
|
|
13572
|
+
const server = new Hono();
|
|
13573
|
+
const networkExposed = options.networkExposed ?? false;
|
|
13574
|
+
server.use(traceIdMiddleware);
|
|
13575
|
+
server.use(logger());
|
|
13576
|
+
server.use(networkExposed ? cors({ origin: resolveSameOriginCorsOrigin }) : cors());
|
|
13577
|
+
server.use("*", createAuthMiddleware({
|
|
13578
|
+
getApiKeys: options.getApiKeys,
|
|
13579
|
+
allowUnauthenticatedPaths: [
|
|
13580
|
+
"/",
|
|
13581
|
+
"/usage-viewer",
|
|
13582
|
+
"/usage-viewer/"
|
|
13583
|
+
],
|
|
13584
|
+
shouldSkipPath: (path) => path.startsWith("/admin/"),
|
|
13585
|
+
allowWhenNoApiKeys: !networkExposed
|
|
13586
|
+
}));
|
|
13587
|
+
server.use("/admin/*", createAuthMiddleware({
|
|
13588
|
+
getApiKeys: getConfiguredAdminApiKeys,
|
|
13589
|
+
allowUnauthenticatedPaths: [],
|
|
13590
|
+
allowWhenNoApiKeys: false
|
|
13591
|
+
}));
|
|
13592
|
+
server.get("/", (c) => c.text("Server running"));
|
|
13593
|
+
server.get("/usage-viewer", (c) => {
|
|
13594
|
+
const usageViewerFileUrl = new URL("../pages/index.html", import.meta.url);
|
|
13595
|
+
return c.html(readFileSync(usageViewerFileUrl, "utf8"));
|
|
13596
|
+
});
|
|
13597
|
+
server.get("/usage-viewer/", (c) => c.redirect("/usage-viewer", 301));
|
|
13598
|
+
server.route("/chat/completions", completionRoutes);
|
|
13599
|
+
server.route("/admin/config", configRoutes);
|
|
13600
|
+
server.route("/models", modelRoutes);
|
|
13601
|
+
server.route("/embeddings", embeddingRoutes);
|
|
13602
|
+
server.route("/usage", usageRoute);
|
|
13603
|
+
server.route("/qoder", qoderRoutes);
|
|
13604
|
+
server.route("/token-usage", tokenUsageRoute);
|
|
13605
|
+
server.route("/responses", responsesRoutes);
|
|
13606
|
+
server.route("/alpha/search", alphaSearchRoutes);
|
|
13607
|
+
server.route("/images", imageRoutes);
|
|
13608
|
+
server.route("/v1/chat/completions", completionRoutes);
|
|
13609
|
+
server.route("/v1/models", modelRoutes);
|
|
13610
|
+
server.route("/v1/embeddings", embeddingRoutes);
|
|
13611
|
+
server.route("/v1/responses", responsesRoutes);
|
|
13612
|
+
server.route("/v1/alpha/search", alphaSearchRoutes);
|
|
13613
|
+
server.route("/v1/images", imageRoutes);
|
|
13614
|
+
server.route("/v1/messages", messageRoutes);
|
|
13615
|
+
server.route("/:provider/v1/messages", providerMessageRoutes);
|
|
13616
|
+
server.route("/:provider/v1/models", providerModelRoutes);
|
|
13617
|
+
server.route("/:provider/v1/responses", providerResponsesRoutes);
|
|
13618
|
+
server.route("/:provider/v1/alpha/search", providerAlphaSearchRoutes);
|
|
13619
|
+
server.route("/:provider/v1/images", providerImageRoutes);
|
|
13620
|
+
server.route("/:provider/models", providerModelRoutes);
|
|
13621
|
+
server.route("/:provider/responses", providerResponsesRoutes);
|
|
13622
|
+
server.route("/:provider/alpha/search", providerAlphaSearchRoutes);
|
|
13623
|
+
server.route("/:provider/images", providerImageRoutes);
|
|
13624
|
+
return server;
|
|
13625
|
+
}
|
|
13626
|
+
createServer();
|
|
13445
13627
|
//#endregion
|
|
13446
|
-
export {
|
|
13628
|
+
export { createServer };
|