@jeffreycao/copilot-api 2.4.1 → 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 +3 -3
- package/README.zh-CN.md +3 -3
- package/dist/main.js +1 -1
- package/dist/{server-HBeQNO83.js → server-BvDQf9Nd.js} +524 -137
- package/dist/server-BvDQf9Nd.js.map +1 -0
- package/dist/{start-Bxq46GxM.js → start-BGY-GA8y.js} +2 -2
- package/dist/{start-Bxq46GxM.js.map → start-BGY-GA8y.js.map} +1 -1
- package/package.json +2 -1
- package/dist/server-HBeQNO83.js.map +0 -1
|
@@ -4,18 +4,22 @@ import { a as isDeferredToolName, c as parseMcpToolSearchSentinel, d as shouldEn
|
|
|
4
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;
|
|
@@ -2474,7 +2426,7 @@ async function forwardProviderImages(providerConfig, request, operation) {
|
|
|
2474
2426
|
}
|
|
2475
2427
|
//#endregion
|
|
2476
2428
|
//#region src/routes/alpha-search/alpha-search-responses.ts
|
|
2477
|
-
const logger$
|
|
2429
|
+
const logger$17 = createHandlerLogger("alpha-search-responses-handler");
|
|
2478
2430
|
const SESSION_TTL_MS = 3600 * 1e3;
|
|
2479
2431
|
const MAX_SESSIONS = 128;
|
|
2480
2432
|
const MAX_URL_REFERENCES = 256;
|
|
@@ -2832,14 +2784,14 @@ function createRemoteResponsesPayload(request, model, instruction) {
|
|
|
2832
2784
|
};
|
|
2833
2785
|
}
|
|
2834
2786
|
async function requestProviderSearch(c, providerConfig, payload, model, sessionId) {
|
|
2835
|
-
debugJson(logger$
|
|
2787
|
+
debugJson(logger$17, "Alpha search provider Responses request:", {
|
|
2836
2788
|
payload,
|
|
2837
2789
|
provider: providerConfig.name
|
|
2838
2790
|
});
|
|
2839
2791
|
const upstreamResponse = await forwardProviderResponses(providerConfig, payload, c.req.raw.headers, { signal: c.req.raw.signal });
|
|
2840
2792
|
if (!upstreamResponse.ok) throw new HTTPError(`Failed to create ${providerConfig.name} responses for alpha search`, upstreamResponse);
|
|
2841
2793
|
const result = await upstreamResponse.json();
|
|
2842
|
-
debugJson(logger$
|
|
2794
|
+
debugJson(logger$17, "Alpha search provider Responses result:", {
|
|
2843
2795
|
provider: providerConfig.name,
|
|
2844
2796
|
result
|
|
2845
2797
|
});
|
|
@@ -2855,7 +2807,7 @@ async function requestProviderSearch(c, providerConfig, payload, model, sessionI
|
|
|
2855
2807
|
return result;
|
|
2856
2808
|
}
|
|
2857
2809
|
async function requestCopilotSearch(payload, model, requestId, sessionId, signal) {
|
|
2858
|
-
debugJson(logger$
|
|
2810
|
+
debugJson(logger$17, "Alpha search Copilot Responses request:", payload);
|
|
2859
2811
|
const result = await alphaSearchResponsesDependencies.createResponses(payload, {
|
|
2860
2812
|
vision: false,
|
|
2861
2813
|
initiator: "agent",
|
|
@@ -2864,7 +2816,7 @@ async function requestCopilotSearch(payload, model, requestId, sessionId, signal
|
|
|
2864
2816
|
sessionId,
|
|
2865
2817
|
signal
|
|
2866
2818
|
});
|
|
2867
|
-
debugJson(logger$
|
|
2819
|
+
debugJson(logger$17, "Alpha search Copilot Responses result:", result);
|
|
2868
2820
|
alphaSearchResponsesDependencies.createUsageRecorder(model, sessionId)({
|
|
2869
2821
|
...normalizeResponsesUsage(result.usage),
|
|
2870
2822
|
total_nano_aiu: normalizeOptionalToken(result.copilot_usage?.total_nano_aiu)
|
|
@@ -2979,7 +2931,7 @@ async function forwardCodexAlphaSearch(request) {
|
|
|
2979
2931
|
}
|
|
2980
2932
|
//#endregion
|
|
2981
2933
|
//#region src/routes/alpha-search/route.ts
|
|
2982
|
-
const logger$
|
|
2934
|
+
const logger$16 = createHandlerLogger("alpha-search-handler");
|
|
2983
2935
|
const alphaSearchRoutes = new Hono();
|
|
2984
2936
|
const alphaSearchRouteDependencies = {
|
|
2985
2937
|
findEndpointModel,
|
|
@@ -2999,9 +2951,9 @@ function parseDebugBody(body) {
|
|
|
2999
2951
|
}
|
|
3000
2952
|
}
|
|
3001
2953
|
async function forwardCodexAlphaSearchRequest(request) {
|
|
3002
|
-
await debugJsonAsync(logger$
|
|
2954
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
|
|
3003
2955
|
const upstreamResponse = await alphaSearchRouteDependencies.forwardCodexAlphaSearch(request);
|
|
3004
|
-
await debugJsonAsync(logger$
|
|
2956
|
+
await debugJsonAsync(logger$16, "alpha_search.codex.response", async () => ({
|
|
3005
2957
|
body: parseDebugBody(await upstreamResponse.clone().text()),
|
|
3006
2958
|
statusCode: upstreamResponse.status
|
|
3007
2959
|
}));
|
|
@@ -3112,7 +3064,7 @@ alphaSearchRoutes.post("/", async (c) => {
|
|
|
3112
3064
|
try {
|
|
3113
3065
|
return await handleAlphaSearchRequest(c);
|
|
3114
3066
|
} catch (error) {
|
|
3115
|
-
logger$
|
|
3067
|
+
logger$16.error("alpha_search.error", { error });
|
|
3116
3068
|
return await forwardError(c, error);
|
|
3117
3069
|
}
|
|
3118
3070
|
});
|
|
@@ -3240,7 +3192,7 @@ const normalizeProviderResponsesReasoningEffort = (payload, providerConfig) => {
|
|
|
3240
3192
|
};
|
|
3241
3193
|
//#endregion
|
|
3242
3194
|
//#region src/routes/provider/chat-completions/handler.ts
|
|
3243
|
-
const logger$
|
|
3195
|
+
const logger$15 = createHandlerLogger("provider-chat-completions-handler");
|
|
3244
3196
|
async function handleProviderChatCompletionsForProvider(c, options) {
|
|
3245
3197
|
const { payload, provider } = options;
|
|
3246
3198
|
const providerConfig = await resolveProviderConfig(provider);
|
|
@@ -3254,13 +3206,13 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
3254
3206
|
applyProviderStreamOptions(payload);
|
|
3255
3207
|
applyDashScopePreserveThinkingDefault(payload, providerConfig);
|
|
3256
3208
|
applyProviderContextCache(payload, modelConfig, providerConfig);
|
|
3257
|
-
debugJson(logger$
|
|
3209
|
+
debugJson(logger$15, "provider.chat_completions.request", {
|
|
3258
3210
|
payload,
|
|
3259
3211
|
provider
|
|
3260
3212
|
});
|
|
3261
3213
|
const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers);
|
|
3262
3214
|
if (!upstreamResponse.ok) {
|
|
3263
|
-
logger$
|
|
3215
|
+
logger$15.error("Failed to create provider chat completions", {
|
|
3264
3216
|
provider,
|
|
3265
3217
|
statusCode: upstreamResponse.status
|
|
3266
3218
|
});
|
|
@@ -3274,7 +3226,7 @@ async function handleProviderChatCompletionsForProvider(c, options) {
|
|
|
3274
3226
|
});
|
|
3275
3227
|
const responseBody = await upstreamResponse.clone().json();
|
|
3276
3228
|
recordUsage(normalizeOpenAIUsage(responseBody.usage));
|
|
3277
|
-
debugJson(logger$
|
|
3229
|
+
debugJson(logger$15, "provider.chat_completions.response", responseBody);
|
|
3278
3230
|
return createProviderProxyResponse(upstreamResponse);
|
|
3279
3231
|
}
|
|
3280
3232
|
const applyProviderStreamOptions = (payload) => {
|
|
@@ -3296,12 +3248,12 @@ const createProviderChatCompletionsUsageRecorder = (payload, provider, modelConf
|
|
|
3296
3248
|
providerName: provider
|
|
3297
3249
|
});
|
|
3298
3250
|
const streamProviderChatCompletions = (c, upstreamResponse, options) => {
|
|
3299
|
-
logger$
|
|
3251
|
+
logger$15.debug("provider.chat_completions.streaming", { provider: options.provider });
|
|
3300
3252
|
return streamSSE(c, async (stream) => {
|
|
3301
3253
|
let usage = {};
|
|
3302
3254
|
try {
|
|
3303
3255
|
for await (const chunk of events(upstreamResponse)) {
|
|
3304
|
-
debugJson(logger$
|
|
3256
|
+
debugJson(logger$15, "provider.chat_completions.stream_chunk", chunk);
|
|
3305
3257
|
if (chunk.data && chunk.data !== "[DONE]") {
|
|
3306
3258
|
const parsedChunk = parseChatCompletionChunkData(chunk.data);
|
|
3307
3259
|
if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
|
|
@@ -3355,7 +3307,7 @@ const createChatCompletions = async (payload, options) => {
|
|
|
3355
3307
|
};
|
|
3356
3308
|
//#endregion
|
|
3357
3309
|
//#region src/routes/chat-completions/handler.ts
|
|
3358
|
-
const logger$
|
|
3310
|
+
const logger$14 = createHandlerLogger("chat-completions-handler");
|
|
3359
3311
|
async function handleCompletion$1(c) {
|
|
3360
3312
|
let payload = await c.req.json();
|
|
3361
3313
|
const requestedModel = payload.model;
|
|
@@ -3369,7 +3321,7 @@ async function handleCompletion$1(c) {
|
|
|
3369
3321
|
provider: providerModelAlias.provider
|
|
3370
3322
|
});
|
|
3371
3323
|
}
|
|
3372
|
-
debugJson(logger$
|
|
3324
|
+
debugJson(logger$14, "Request payload:", payload);
|
|
3373
3325
|
const selectedModel = findEndpointModel(payload.model);
|
|
3374
3326
|
payload.model = selectedModel?.id ?? payload.model;
|
|
3375
3327
|
if (isNullish(payload.max_tokens) && isNullish(payload.max_completion_tokens)) {
|
|
@@ -3377,16 +3329,16 @@ async function handleCompletion$1(c) {
|
|
|
3377
3329
|
...payload,
|
|
3378
3330
|
max_tokens: selectedModel?.capabilities.limits.max_output_tokens
|
|
3379
3331
|
};
|
|
3380
|
-
debugJson(logger$
|
|
3332
|
+
debugJson(logger$14, "Set max_tokens to:", payload.max_tokens);
|
|
3381
3333
|
}
|
|
3382
3334
|
if (payload.model.includes("gpt")) {
|
|
3383
3335
|
if (isNullish(payload.max_completion_tokens)) payload.max_completion_tokens = payload.max_tokens;
|
|
3384
3336
|
delete payload.max_tokens;
|
|
3385
3337
|
}
|
|
3386
3338
|
const requestId = generateRequestIdFromPayload(payload);
|
|
3387
|
-
logger$
|
|
3339
|
+
logger$14.debug("Generated request ID:", requestId);
|
|
3388
3340
|
const sessionId = getUUID(requestId);
|
|
3389
|
-
logger$
|
|
3341
|
+
logger$14.debug("Extracted session ID:", sessionId);
|
|
3390
3342
|
const recordUsage = createCopilotTokenUsageRecorder({
|
|
3391
3343
|
endpoint: "chat_completions",
|
|
3392
3344
|
fallbackSessionId: sessionId,
|
|
@@ -3397,18 +3349,18 @@ async function handleCompletion$1(c) {
|
|
|
3397
3349
|
sessionId
|
|
3398
3350
|
});
|
|
3399
3351
|
if (isNonStreaming$1(response)) {
|
|
3400
|
-
debugJson(logger$
|
|
3352
|
+
debugJson(logger$14, "Non-streaming response:", response);
|
|
3401
3353
|
recordUsage({
|
|
3402
3354
|
...normalizeOpenAIUsage(response.usage),
|
|
3403
3355
|
total_nano_aiu: normalizeOptionalToken(response.copilot_usage?.total_nano_aiu)
|
|
3404
3356
|
});
|
|
3405
3357
|
return c.json(response);
|
|
3406
3358
|
}
|
|
3407
|
-
logger$
|
|
3359
|
+
logger$14.debug("Streaming response");
|
|
3408
3360
|
return streamSSE(c, async (stream) => {
|
|
3409
3361
|
let usage = {};
|
|
3410
3362
|
for await (const chunk of response) {
|
|
3411
|
-
debugJson(logger$
|
|
3363
|
+
debugJson(logger$14, "Streaming chunk:", chunk);
|
|
3412
3364
|
const parsedChunk = parseChatCompletionChunk(chunk);
|
|
3413
3365
|
if (parsedChunk?.usage || parsedChunk?.copilot_usage) usage = {
|
|
3414
3366
|
...normalizeOpenAIUsage(parsedChunk.usage),
|
|
@@ -3497,6 +3449,32 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
3497
3449
|
}
|
|
3498
3450
|
});
|
|
3499
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
|
|
3500
3478
|
//#region src/services/codex/images.ts
|
|
3501
3479
|
const CODEX_IMAGES_TIMEOUT_MS = 900 * 1e3;
|
|
3502
3480
|
const codexImagesDispatcher = createTimeoutDispatcher(CODEX_IMAGES_TIMEOUT_MS);
|
|
@@ -3524,41 +3502,433 @@ async function forwardCodexImages(request, operation) {
|
|
|
3524
3502
|
});
|
|
3525
3503
|
}
|
|
3526
3504
|
//#endregion
|
|
3527
|
-
//#region src/routes/images/
|
|
3528
|
-
const logger$
|
|
3529
|
-
const
|
|
3530
|
-
|
|
3531
|
-
|
|
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) {
|
|
3532
3513
|
return {
|
|
3533
3514
|
contentType: headers.get("content-type"),
|
|
3534
3515
|
contentLength: headers.get("content-length")
|
|
3535
3516
|
};
|
|
3536
3517
|
}
|
|
3537
3518
|
/**
|
|
3538
|
-
*
|
|
3539
|
-
*
|
|
3519
|
+
* Snapshots request headers before the body is consumed: Bun drops
|
|
3520
|
+
* auto-generated body headers (like the multipart boundary) afterwards.
|
|
3540
3521
|
*/
|
|
3541
|
-
|
|
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.
|
|
3530
|
+
*/
|
|
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) {
|
|
3542
3547
|
try {
|
|
3543
|
-
if (!(resolvedProviderConfig ?? await resolveProviderConfig("codex"))) return c.json({ error: {
|
|
3548
|
+
if (!(resolvedProviderConfig ?? await imageRouteDependencies.resolveProviderConfig("codex"))) return c.json({ error: {
|
|
3544
3549
|
message: "Provider 'codex' not found or disabled",
|
|
3545
3550
|
type: "invalid_request_error"
|
|
3546
3551
|
} }, 404);
|
|
3547
|
-
if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$
|
|
3548
|
-
else debugJson(logger$
|
|
3549
|
-
const upstreamResponse = await forwardCodexImages(
|
|
3550
|
-
debugJson(logger$
|
|
3551
|
-
...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),
|
|
3552
3557
|
statusCode: upstreamResponse.status
|
|
3553
3558
|
});
|
|
3554
3559
|
return createProviderProxyResponse(upstreamResponse);
|
|
3555
3560
|
} catch (error) {
|
|
3556
|
-
logger$
|
|
3561
|
+
logger$13.error(`images.${operation}.codex.error`, { error });
|
|
3562
|
+
return await forwardError(c, error);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
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 });
|
|
3557
3924
|
return await forwardError(c, error);
|
|
3558
3925
|
}
|
|
3559
3926
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3927
|
+
//#endregion
|
|
3928
|
+
//#region src/routes/images/route.ts
|
|
3929
|
+
const imageRoutes = new Hono();
|
|
3930
|
+
imageRoutes.post("/generations", handleImagesGenerations);
|
|
3931
|
+
imageRoutes.post("/edits", handleImagesEdits);
|
|
3562
3932
|
//#endregion
|
|
3563
3933
|
//#region src/lib/tokenizer.ts
|
|
3564
3934
|
const ENCODING_MAP = {
|
|
@@ -10032,17 +10402,6 @@ function isDeepSeekModelId(modelId) {
|
|
|
10032
10402
|
function shouldInjectMessagesToolCallTips(userAgent, targetModel) {
|
|
10033
10403
|
return isCodexUserAgent(userAgent) && !isDeepSeekModelId(targetModel);
|
|
10034
10404
|
}
|
|
10035
|
-
async function logCodexModelsResponse(response) {
|
|
10036
|
-
try {
|
|
10037
|
-
const models = await response.clone().json();
|
|
10038
|
-
debugJson(logger$8, "models.codex.response", {
|
|
10039
|
-
statusCode: response.status,
|
|
10040
|
-
models
|
|
10041
|
-
});
|
|
10042
|
-
} catch (error) {
|
|
10043
|
-
logger$8.warn("models.codex.response_log_error", { error });
|
|
10044
|
-
}
|
|
10045
|
-
}
|
|
10046
10405
|
/**
|
|
10047
10406
|
* Proxies a models request to the fixed Codex upstream models endpoint.
|
|
10048
10407
|
* Returns a 404 JSON response when the codex provider is unavailable.
|
|
@@ -10054,9 +10413,7 @@ async function handleCodexModelsProxy(c, resolvedProviderConfig) {
|
|
|
10054
10413
|
message: "Provider 'codex' not found or disabled",
|
|
10055
10414
|
type: "invalid_request_error"
|
|
10056
10415
|
} }, 404);
|
|
10057
|
-
|
|
10058
|
-
await logCodexModelsResponse(upstreamResponse);
|
|
10059
|
-
return createProviderProxyResponse(upstreamResponse);
|
|
10416
|
+
return createProviderProxyResponse(await forwardCodexModels(c.req.url, c.req.raw.headers));
|
|
10060
10417
|
}
|
|
10061
10418
|
async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
10062
10419
|
const [upstreamCatalog, candidates] = await Promise.all([tryGetCodexCatalog(c), Promise.resolve(candidatesRequest).catch((error) => {
|
|
@@ -10095,12 +10452,6 @@ async function handleMergedCodexModels(c, candidatesRequest, options = {}) {
|
|
|
10095
10452
|
...upstreamCatalog ?? {},
|
|
10096
10453
|
models
|
|
10097
10454
|
};
|
|
10098
|
-
debugJson(logger$8, "models.codex.merged_response", {
|
|
10099
|
-
upstreamCount: upstreamModels.length,
|
|
10100
|
-
codexProviderAliasCount: codexProviderAliases.length,
|
|
10101
|
-
syntheticCount: syntheticModels.length,
|
|
10102
|
-
models: response
|
|
10103
|
-
});
|
|
10104
10455
|
return c.json(response);
|
|
10105
10456
|
}
|
|
10106
10457
|
function createCatalogAlias(model, slug, providerName) {
|
|
@@ -10543,12 +10894,6 @@ providerAlphaSearchRoutes.post("/", async (c) => {
|
|
|
10543
10894
|
//#region src/routes/provider/images/route.ts
|
|
10544
10895
|
const logger$5 = createHandlerLogger("provider-images-handler");
|
|
10545
10896
|
const providerImageRoutes = new Hono();
|
|
10546
|
-
function getContentMetadata(headers) {
|
|
10547
|
-
return {
|
|
10548
|
-
contentType: headers.get("content-type"),
|
|
10549
|
-
contentLength: headers.get("content-length")
|
|
10550
|
-
};
|
|
10551
|
-
}
|
|
10552
10897
|
async function handleProviderImages(c, operation) {
|
|
10553
10898
|
const provider = c.req.param("provider") ?? "";
|
|
10554
10899
|
try {
|
|
@@ -10558,21 +10903,10 @@ async function handleProviderImages(c, operation) {
|
|
|
10558
10903
|
type: "invalid_request_error"
|
|
10559
10904
|
} }, 404);
|
|
10560
10905
|
if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig);
|
|
10561
|
-
|
|
10562
|
-
|
|
10906
|
+
return await forwardProviderImagesWithLogging(providerConfig, c.req.raw, operation, {
|
|
10907
|
+
logger: logger$5,
|
|
10563
10908
|
provider
|
|
10564
|
-
}));
|
|
10565
|
-
else debugJson(logger$5, "provider.images.edits.request", {
|
|
10566
|
-
...getContentMetadata(c.req.raw.headers),
|
|
10567
|
-
provider
|
|
10568
|
-
});
|
|
10569
|
-
const upstreamResponse = await forwardProviderImages(providerConfig, c.req.raw, operation);
|
|
10570
|
-
debugJson(logger$5, `provider.images.${operation}.response`, {
|
|
10571
|
-
...getContentMetadata(upstreamResponse.headers),
|
|
10572
|
-
provider,
|
|
10573
|
-
statusCode: upstreamResponse.status
|
|
10574
10909
|
});
|
|
10575
|
-
return createProviderProxyResponse(upstreamResponse);
|
|
10576
10910
|
} catch (error) {
|
|
10577
10911
|
logger$5.error(`provider.images.${operation}.error`, {
|
|
10578
10912
|
provider,
|
|
@@ -10636,6 +10970,58 @@ providerModelRoutes.get("/", async (c) => {
|
|
|
10636
10970
|
}
|
|
10637
10971
|
});
|
|
10638
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
|
|
10639
11025
|
//#region src/routes/responses/custom-tool-input-stream-decoder.ts
|
|
10640
11026
|
const CUSTOM_TOOL_INPUT_PREFIX_TOKENS = [
|
|
10641
11027
|
"{",
|
|
@@ -11675,6 +12061,7 @@ const getResponsesEvents = (response, signal) => createResponsesSafeStream(creat
|
|
|
11675
12061
|
//#endregion
|
|
11676
12062
|
//#region src/routes/provider/responses/route.ts
|
|
11677
12063
|
const providerResponsesRoutes = new Hono();
|
|
12064
|
+
providerResponsesRoutes.use(zstdDecompressionMiddleware);
|
|
11678
12065
|
providerResponsesRoutes.post("/", async (c) => {
|
|
11679
12066
|
try {
|
|
11680
12067
|
const provider = c.req.param("provider") ?? "";
|
|
@@ -11926,6 +12313,7 @@ const getTrimmedHeader = (c, name) => {
|
|
|
11926
12313
|
//#endregion
|
|
11927
12314
|
//#region src/routes/responses/route.ts
|
|
11928
12315
|
const responsesRoutes = new Hono();
|
|
12316
|
+
responsesRoutes.use(zstdDecompressionMiddleware);
|
|
11929
12317
|
responsesRoutes.post("/", async (c) => {
|
|
11930
12318
|
try {
|
|
11931
12319
|
return await handleResponses(c);
|
|
@@ -12006,7 +12394,6 @@ server.use("/admin/*", createAuthMiddleware({
|
|
|
12006
12394
|
allowUnauthenticatedPaths: [],
|
|
12007
12395
|
allowWhenNoApiKeys: false
|
|
12008
12396
|
}));
|
|
12009
|
-
server.use(zstdDecompressionMiddleware);
|
|
12010
12397
|
server.get("/", (c) => c.text("Server running"));
|
|
12011
12398
|
server.get("/usage-viewer", (c) => {
|
|
12012
12399
|
const usageViewerFileUrl = new URL("../pages/index.html", import.meta.url);
|
|
@@ -12041,4 +12428,4 @@ server.route("/:provider/images", providerImageRoutes);
|
|
|
12041
12428
|
//#endregion
|
|
12042
12429
|
export { server };
|
|
12043
12430
|
|
|
12044
|
-
//# sourceMappingURL=server-
|
|
12431
|
+
//# sourceMappingURL=server-BvDQf9Nd.js.map
|