@jeffreycao/copilot-api 2.7.28 → 2.7.29

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.
@@ -6,10 +6,9 @@ import { n as isProviderAgentModelVisible, t as getProviderAgentModels } from ".
6
6
  import { n as stopModelsRefreshLoop, t as setupCopilotRuntime } from "./copilot-runtime-Vxyt2nub.js";
7
7
  import consola from "consola";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
- import fs, { createWriteStream, readFileSync, rmSync } from "node:fs";
10
- import path, { join } from "node:path";
11
- import fs$1, { mkdtemp, rm } from "node:fs/promises";
12
- import { tmpdir } from "node:os";
9
+ import fs, { readFileSync } from "node:fs";
10
+ import path from "node:path";
11
+ import fs$1 from "node:fs/promises";
13
12
  import { fetch as fetch$1, getGlobalDispatcher } from "undici";
14
13
  import { events } from "fetch-event-stream";
15
14
  import { z } from "zod";
@@ -18,9 +17,6 @@ import { cors } from "hono/cors";
18
17
  import { logger } from "hono/logger";
19
18
  import util from "node:util";
20
19
  import { streamSSE } from "hono/streaming";
21
- import { Busboy } from "@fastify/busboy";
22
- import { Readable, Transform } from "node:stream";
23
- import { pipeline } from "node:stream/promises";
24
20
  import { decompress } from "fzstd";
25
21
  //#region src/lib/trace.ts
26
22
  const traceIdMiddleware = async (c, next) => {
@@ -2193,7 +2189,7 @@ async function forwardProviderImages(providerConfig, request, operation) {
2193
2189
  }
2194
2190
  //#endregion
2195
2191
  //#region src/routes/alpha-search/alpha-search-responses.ts
2196
- const logger$20 = createHandlerLogger("alpha-search-responses-handler");
2192
+ const logger$19 = createHandlerLogger("alpha-search-responses-handler");
2197
2193
  const SESSION_TTL_MS = 36e5;
2198
2194
  const MAX_SESSIONS = 128;
2199
2195
  const MAX_URL_REFERENCES = 256;
@@ -2551,14 +2547,14 @@ function createRemoteResponsesPayload(request, model, instruction) {
2551
2547
  };
2552
2548
  }
2553
2549
  async function requestProviderSearch(c, providerConfig, payload, model, sessionId) {
2554
- debugJson(logger$20, "Alpha search provider Responses request:", {
2550
+ debugJson(logger$19, "Alpha search provider Responses request:", {
2555
2551
  payload,
2556
2552
  provider: providerConfig.name
2557
2553
  });
2558
2554
  const upstreamResponse = await forwardProviderResponses(providerConfig, payload, c.req.raw.headers, { clientSignal: c.req.raw.signal });
2559
2555
  if (!upstreamResponse.ok) throw new HTTPError(`Failed to create ${providerConfig.name} responses for alpha search`, upstreamResponse);
2560
2556
  const result = await upstreamResponse.json();
2561
- debugJson(logger$20, "Alpha search provider Responses result:", {
2557
+ debugJson(logger$19, "Alpha search provider Responses result:", {
2562
2558
  provider: providerConfig.name,
2563
2559
  result
2564
2560
  });
@@ -2574,7 +2570,7 @@ async function requestProviderSearch(c, providerConfig, payload, model, sessionI
2574
2570
  return result;
2575
2571
  }
2576
2572
  async function requestCopilotSearch(payload, model, requestId, sessionId, clientSignal) {
2577
- debugJson(logger$20, "Alpha search Copilot Responses request:", payload);
2573
+ debugJson(logger$19, "Alpha search Copilot Responses request:", payload);
2578
2574
  const result = await alphaSearchResponsesDependencies.createResponses(payload, {
2579
2575
  vision: false,
2580
2576
  initiator: "agent",
@@ -2583,7 +2579,7 @@ async function requestCopilotSearch(payload, model, requestId, sessionId, client
2583
2579
  sessionId,
2584
2580
  clientSignal
2585
2581
  });
2586
- debugJson(logger$20, "Alpha search Copilot Responses result:", result);
2582
+ debugJson(logger$19, "Alpha search Copilot Responses result:", result);
2587
2583
  alphaSearchResponsesDependencies.createUsageRecorder(model, sessionId)({
2588
2584
  ...normalizeResponsesUsage(result.usage),
2589
2585
  total_nano_aiu: normalizeOptionalToken(result.copilot_usage?.total_nano_aiu)
@@ -2698,7 +2694,7 @@ async function forwardCodexAlphaSearch(request) {
2698
2694
  }
2699
2695
  //#endregion
2700
2696
  //#region src/routes/alpha-search/route.ts
2701
- const logger$19 = createHandlerLogger("alpha-search-handler");
2697
+ const logger$18 = createHandlerLogger("alpha-search-handler");
2702
2698
  const alphaSearchRoutes = new Hono();
2703
2699
  const alphaSearchRouteDependencies = {
2704
2700
  findEndpointModel,
@@ -2718,9 +2714,9 @@ function parseDebugBody(body) {
2718
2714
  }
2719
2715
  }
2720
2716
  async function forwardCodexAlphaSearchRequest(request) {
2721
- await debugJsonAsync(logger$19, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
2717
+ await debugJsonAsync(logger$18, "alpha_search.codex.request", async () => ({ body: parseDebugBody(await request.clone().text()) }));
2722
2718
  const upstreamResponse = await alphaSearchRouteDependencies.forwardCodexAlphaSearch(request);
2723
- await debugJsonAsync(logger$19, "alpha_search.codex.response", async () => ({
2719
+ await debugJsonAsync(logger$18, "alpha_search.codex.response", async () => ({
2724
2720
  body: parseDebugBody(await upstreamResponse.clone().text()),
2725
2721
  statusCode: upstreamResponse.status
2726
2722
  }));
@@ -2831,7 +2827,7 @@ alphaSearchRoutes.post("/", async (c) => {
2831
2827
  try {
2832
2828
  return await handleAlphaSearchRequest(c);
2833
2829
  } catch (error) {
2834
- logger$19.error("alpha_search.error", { error });
2830
+ logger$18.error("alpha_search.error", { error });
2835
2831
  return await forwardError(c, error);
2836
2832
  }
2837
2833
  });
@@ -2978,7 +2974,7 @@ const normalizeProviderResponsesReasoningEffort = (payload, providerConfig) => {
2978
2974
  };
2979
2975
  //#endregion
2980
2976
  //#region src/routes/provider/chat-completions/handler.ts
2981
- const logger$18 = createHandlerLogger("provider-chat-completions-handler");
2977
+ const logger$17 = createHandlerLogger("provider-chat-completions-handler");
2982
2978
  async function handleProviderChatCompletionsForProvider(c, options) {
2983
2979
  const { payload, provider } = options;
2984
2980
  const configuredProvider = await resolveProviderConfig(provider);
@@ -2993,13 +2989,13 @@ async function handleProviderChatCompletionsForProvider(c, options) {
2993
2989
  applyProviderStreamOptions(payload);
2994
2990
  applyDashScopePreserveThinkingDefault(payload, providerConfig);
2995
2991
  applyProviderContextCache(payload, modelConfig, providerConfig);
2996
- debugJson(logger$18, "provider.chat_completions.request", {
2992
+ debugJson(logger$17, "provider.chat_completions.request", {
2997
2993
  payload,
2998
2994
  provider
2999
2995
  });
3000
2996
  const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers, { clientSignal: c.req.raw.signal });
3001
2997
  if (!upstreamResponse.ok) {
3002
- logger$18.error("Failed to create provider chat completions", {
2998
+ logger$17.error("Failed to create provider chat completions", {
3003
2999
  provider,
3004
3000
  statusCode: upstreamResponse.status
3005
3001
  });
@@ -3013,7 +3009,7 @@ async function handleProviderChatCompletionsForProvider(c, options) {
3013
3009
  });
3014
3010
  const responseBody = await upstreamResponse.clone().json();
3015
3011
  recordUsage(normalizeOpenAIUsage(responseBody.usage));
3016
- debugJson(logger$18, "provider.chat_completions.response", responseBody);
3012
+ debugJson(logger$17, "provider.chat_completions.response", responseBody);
3017
3013
  return createProviderProxyResponse(upstreamResponse);
3018
3014
  }
3019
3015
  const applyProviderStreamOptions = (payload) => {
@@ -3035,12 +3031,12 @@ const createProviderChatCompletionsUsageRecorder = (payload, provider, modelConf
3035
3031
  providerName: provider
3036
3032
  });
3037
3033
  const streamProviderChatCompletions = (c, upstreamResponse, options) => {
3038
- logger$18.debug("provider.chat_completions.streaming", { provider: options.provider });
3034
+ logger$17.debug("provider.chat_completions.streaming", { provider: options.provider });
3039
3035
  return streamSSE(c, async (stream) => {
3040
3036
  let usage = {};
3041
3037
  try {
3042
3038
  for await (const chunk of events(upstreamResponse)) {
3043
- debugJson(logger$18, "provider.chat_completions.stream_chunk", chunk);
3039
+ debugJson(logger$17, "provider.chat_completions.stream_chunk", chunk);
3044
3040
  if (chunk.data && chunk.data !== "[DONE]") {
3045
3041
  const parsedChunk = parseChatCompletionChunkData(chunk.data);
3046
3042
  if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
@@ -3099,7 +3095,7 @@ const createChatCompletions = async (payload, options) => {
3099
3095
  };
3100
3096
  //#endregion
3101
3097
  //#region src/routes/chat-completions/handler.ts
3102
- const logger$17 = createHandlerLogger("chat-completions-handler");
3098
+ const logger$16 = createHandlerLogger("chat-completions-handler");
3103
3099
  async function handleCompletion$1(c) {
3104
3100
  let payload = await c.req.json();
3105
3101
  const requestedModel = payload.model;
@@ -3113,7 +3109,7 @@ async function handleCompletion$1(c) {
3113
3109
  provider: providerModelAlias.provider
3114
3110
  });
3115
3111
  }
3116
- debugJson(logger$17, "Request payload:", payload);
3112
+ debugJson(logger$16, "Request payload:", payload);
3117
3113
  const selectedModel = findEndpointModel(payload.model);
3118
3114
  payload.model = selectedModel?.id ?? payload.model;
3119
3115
  if (isNullish(payload.max_tokens) && isNullish(payload.max_completion_tokens)) {
@@ -3121,16 +3117,16 @@ async function handleCompletion$1(c) {
3121
3117
  ...payload,
3122
3118
  max_tokens: selectedModel?.capabilities.limits.max_output_tokens
3123
3119
  };
3124
- debugJson(logger$17, "Set max_tokens to:", payload.max_tokens);
3120
+ debugJson(logger$16, "Set max_tokens to:", payload.max_tokens);
3125
3121
  }
3126
3122
  if (payload.model.includes("gpt")) {
3127
3123
  if (isNullish(payload.max_completion_tokens)) payload.max_completion_tokens = payload.max_tokens;
3128
3124
  delete payload.max_tokens;
3129
3125
  }
3130
3126
  const requestId = generateRequestIdFromPayload(payload);
3131
- logger$17.debug("Generated request ID:", requestId);
3127
+ logger$16.debug("Generated request ID:", requestId);
3132
3128
  const sessionId = getUUID(requestId);
3133
- logger$17.debug("Extracted session ID:", sessionId);
3129
+ logger$16.debug("Extracted session ID:", sessionId);
3134
3130
  const recordUsage = createCopilotTokenUsageRecorder({
3135
3131
  endpoint: "chat_completions",
3136
3132
  fallbackSessionId: sessionId,
@@ -3142,18 +3138,18 @@ async function handleCompletion$1(c) {
3142
3138
  sessionId
3143
3139
  });
3144
3140
  if (isNonStreaming$1(response)) {
3145
- debugJson(logger$17, "Non-streaming response:", response);
3141
+ debugJson(logger$16, "Non-streaming response:", response);
3146
3142
  recordUsage({
3147
3143
  ...normalizeOpenAIUsage(response.usage),
3148
3144
  total_nano_aiu: normalizeOptionalToken(response.copilot_usage?.total_nano_aiu)
3149
3145
  });
3150
3146
  return c.json(response);
3151
3147
  }
3152
- logger$17.debug("Streaming response");
3148
+ logger$16.debug("Streaming response");
3153
3149
  return streamSSE(c, async (stream) => {
3154
3150
  let usage = {};
3155
3151
  for await (const chunk of response) {
3156
- debugJson(logger$17, "Streaming chunk:", chunk);
3152
+ debugJson(logger$16, "Streaming chunk:", chunk);
3157
3153
  const parsedChunk = parseChatCompletionChunk(chunk);
3158
3154
  if (parsedChunk?.usage || parsedChunk?.copilot_usage) usage = {
3159
3155
  ...normalizeOpenAIUsage(parsedChunk.usage),
@@ -3383,7 +3379,7 @@ async function forwardCodexImages(request, operation) {
3383
3379
  }
3384
3380
  //#endregion
3385
3381
  //#region src/routes/images/shared.ts
3386
- const logger$16 = createHandlerLogger("images-handler");
3382
+ const logger$15 = createHandlerLogger("images-handler");
3387
3383
  const imageRouteDependencies = {
3388
3384
  debugJsonAsync,
3389
3385
  getImageEditModel,
@@ -3391,6 +3387,13 @@ const imageRouteDependencies = {
3391
3387
  resolveMappedModel,
3392
3388
  resolveProviderConfig
3393
3389
  };
3390
+ function imageEditContentTypeErrorResponse(c) {
3391
+ if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === "application/json") return null;
3392
+ return c.json({ error: {
3393
+ message: "Image edits require application/json with images: [{ image_url }] or [{ file_id }]; multipart uploads are not supported",
3394
+ type: "invalid_request_error"
3395
+ } }, 415);
3396
+ }
3394
3397
  function getContentMetadata(headers) {
3395
3398
  return {
3396
3399
  contentType: headers.get("content-type"),
@@ -3398,8 +3401,7 @@ function getContentMetadata(headers) {
3398
3401
  };
3399
3402
  }
3400
3403
  /**
3401
- * Snapshots request headers before the body is consumed: Bun drops
3402
- * auto-generated body headers (like the multipart boundary) afterwards.
3404
+ * Snapshots generated request headers before consuming the body.
3403
3405
  */
3404
3406
  function snapshotRequestHeaders(request) {
3405
3407
  return new Headers(request.headers);
@@ -3420,6 +3422,37 @@ function createForwardRequest(request, requestHeaders, body) {
3420
3422
  };
3421
3423
  return new Request(request.url, init);
3422
3424
  }
3425
+ function createJsonImagesRequest(request, requestHeaders, payload, model) {
3426
+ const headers = new Headers(requestHeaders);
3427
+ headers.delete("content-length");
3428
+ return createForwardRequest(request, headers, new TextEncoder().encode(JSON.stringify({
3429
+ ...payload,
3430
+ model
3431
+ })));
3432
+ }
3433
+ /**
3434
+ * Parses an image request body as JSON, retaining the original bytes for
3435
+ * unchanged forwarding while allowing model mapping to rebuild the payload.
3436
+ * Rebuilt bodies stay binary so Node does not synthesize a text/plain content type.
3437
+ */
3438
+ async function parseJsonImagesRequest(request) {
3439
+ const requestHeaders = snapshotRequestHeaders(request);
3440
+ const body = new Uint8Array(await request.arrayBuffer());
3441
+ const originalRequest = createForwardRequest(request, requestHeaders, body);
3442
+ let payload;
3443
+ try {
3444
+ const bodyText = new TextDecoder("utf-8", { fatal: true }).decode(body);
3445
+ payload = JSON.parse(bodyText);
3446
+ } catch {
3447
+ return originalRequest;
3448
+ }
3449
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
3450
+ return {
3451
+ createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
3452
+ model: payload.model,
3453
+ originalRequest
3454
+ };
3455
+ }
3423
3456
  /**
3424
3457
  * Handles Codex images proxying. Pass a resolvedProviderConfig when the
3425
3458
  * caller already resolved the codex provider to avoid a second resolve, and
@@ -3431,16 +3464,16 @@ async function handleCodexImages(c, operation, resolvedProviderConfig, request =
3431
3464
  message: "Provider 'codex' not found or disabled",
3432
3465
  type: "invalid_request_error"
3433
3466
  } }, 404);
3434
- if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$16, "images.generations.codex.request", async () => ({ body: await request.clone().text() }));
3435
- else debugJson(logger$16, "images.edits.codex.request", getContentMetadata(request.headers));
3467
+ if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$15, "images.generations.codex.request", async () => ({ body: await request.clone().text() }));
3468
+ else debugJson(logger$15, "images.edits.codex.request", getContentMetadata(request.headers));
3436
3469
  const upstreamResponse = await forwardCodexImages(request, operation);
3437
- debugJson(logger$16, `images.${operation}.codex.response`, {
3470
+ debugJson(logger$15, `images.${operation}.codex.response`, {
3438
3471
  ...getContentMetadata(upstreamResponse.headers),
3439
3472
  statusCode: upstreamResponse.status
3440
3473
  });
3441
3474
  return createProviderProxyResponse(upstreamResponse);
3442
3475
  } catch (error) {
3443
- logger$16.error(`images.${operation}.codex.error`, { error });
3476
+ logger$15.error(`images.${operation}.codex.error`, { error });
3444
3477
  return await forwardError(c, error);
3445
3478
  }
3446
3479
  }
@@ -3459,7 +3492,7 @@ async function routeImagesRequest(c, operation, parsed) {
3459
3492
  const request = parsed.createRequest(providerModelAlias.model);
3460
3493
  if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig, request);
3461
3494
  return await forwardProviderImagesWithLogging(providerConfig, request, operation, {
3462
- logger: logger$16,
3495
+ logger: logger$15,
3463
3496
  provider: providerModelAlias.provider
3464
3497
  });
3465
3498
  }
@@ -3541,12 +3574,6 @@ const XAI_ASPECT_RATIOS = [
3541
3574
  const XAI_ASPECT_RATIO_LABELS = /* @__PURE__ */ new Set([...XAI_ASPECT_RATIOS.map((ratio) => ratio.label), "auto"]);
3542
3575
  const XAI_RESPONSE_FORMATS = /* @__PURE__ */ new Set(["url", "b64_json"]);
3543
3576
  const XAI_RESOLUTIONS = /* @__PURE__ */ new Set(["1k", "2k"]);
3544
- const IMAGE_FIELD_NAMES = /* @__PURE__ */ new Set([
3545
- "image",
3546
- "image[]",
3547
- "images",
3548
- "images[]"
3549
- ]);
3550
3577
  const MAX_EDIT_IMAGES = 5;
3551
3578
  const MAX_IMAGE_COUNT = 10;
3552
3579
  const HIGH_RESOLUTION_EDGE = 1536;
@@ -3681,32 +3708,6 @@ function readImageReference(value) {
3681
3708
  if (value.file_id !== void 0) throw new XaiImagesRequestError("file_id is not supported for xAI image edits; provide an image_url instead");
3682
3709
  throw new XaiImagesRequestError("image must include a url, image_url, or b64_json");
3683
3710
  }
3684
- function detectImageMime(bytes, fileName) {
3685
- if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) return "image/png";
3686
- if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216) return "image/jpeg";
3687
- if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "image/webp";
3688
- if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70) return "image/gif";
3689
- const extension = fileName?.split(".").pop()?.toLowerCase();
3690
- if (extension === "jpg" || extension === "jpeg") return "image/jpeg";
3691
- if (extension === "webp") return "image/webp";
3692
- if (extension === "gif") return "image/gif";
3693
- return "image/png";
3694
- }
3695
- async function blobToImageReference(blob) {
3696
- const bytes = new Uint8Array(await blob.arrayBuffer());
3697
- if (bytes.byteLength === 0) throw new XaiImagesRequestError("image is empty");
3698
- const fileName = blob instanceof File ? blob.name : void 0;
3699
- const declared = blob.type.trim().toLowerCase();
3700
- return {
3701
- type: "image_url",
3702
- url: `data:${declared.startsWith("image/") ? declared : detectImageMime(bytes, fileName)};base64,${Buffer.from(bytes).toString("base64")}`
3703
- };
3704
- }
3705
- async function formValueToImageReference(value) {
3706
- if (typeof value === "string") return readImageReference(value);
3707
- if (!(value instanceof Blob)) throw new XaiImagesRequestError("image must be a file or a URL");
3708
- return await blobToImageReference(value);
3709
- }
3710
3711
  function collectJsonImages(payload) {
3711
3712
  const values = [];
3712
3713
  if (isUnknownArray(payload.image)) values.push(...payload.image);
@@ -3731,24 +3732,6 @@ function buildEditPayload(model, prompt, images, source) {
3731
3732
  else payload.images = images;
3732
3733
  return payload;
3733
3734
  }
3734
- function formOptions(formData) {
3735
- const source = {};
3736
- for (const key of [
3737
- "n",
3738
- "quality",
3739
- "response_format",
3740
- "aspect_ratio",
3741
- "resolution",
3742
- "size",
3743
- "stream",
3744
- "mask"
3745
- ]) {
3746
- const value = formData.get(key);
3747
- if (typeof value === "string") source[key] = value;
3748
- }
3749
- if (formData.get("mask") !== null || formData.get("mask[]") !== null) source.mask = true;
3750
- return source;
3751
- }
3752
3735
  /**
3753
3736
  * Translates an OpenAI image generation body into an xAI Imagine generation
3754
3737
  * request. The configured model replaces the client model.
@@ -3769,19 +3752,6 @@ function translateXaiImageEditJson(payload, model) {
3769
3752
  if (!isRecord$3(payload)) throw new XaiImagesRequestError("Image edit body must be a JSON object");
3770
3753
  return buildEditPayload(model, readPrompt(payload.prompt), collectJsonImages(payload).map((image) => readImageReference(image)), payload);
3771
3754
  }
3772
- /**
3773
- * Translates an OpenAI multipart image edit into an xAI Imagine edit request.
3774
- * File parts become base64 data URLs. A mask cannot be represented and is
3775
- * rejected.
3776
- */
3777
- async function translateXaiImageEditForm(formData, model) {
3778
- const images = [];
3779
- for (const [name, value] of formData.entries()) {
3780
- if (!IMAGE_FIELD_NAMES.has(name)) continue;
3781
- images.push(await formValueToImageReference(value));
3782
- }
3783
- return buildEditPayload(model, readPrompt(formData.get("prompt")), images, formOptions(formData));
3784
- }
3785
3755
  const MAX_RESPONSE_IMAGE_BYTES = 33554432;
3786
3756
  function responseHasB64Json(value) {
3787
3757
  return typeof value === "string" && value.trim() !== "";
@@ -3953,12 +3923,12 @@ async function forwardXaiImages(providerConfig, request, operation, payload) {
3953
3923
  method: "POST",
3954
3924
  signal: request.signal
3955
3925
  }), operation, {
3956
- logger: logger$16,
3926
+ logger: logger$15,
3957
3927
  provider: "xai"
3958
3928
  }), void 0, {
3959
3929
  clientSignal: request.signal,
3960
3930
  onImageUrl: (url) => {
3961
- logger$16.info("images.xai.output.url", JSON.stringify({
3931
+ logger$15.info("images.xai.output.url", JSON.stringify({
3962
3932
  operation,
3963
3933
  url
3964
3934
  }));
@@ -3969,285 +3939,11 @@ async function forwardXaiImages(providerConfig, request, operation, payload) {
3969
3939
  async function handleXaiImageGeneration(c, model) {
3970
3940
  return await forwardTranslatedXaiImages(c, "generations", translateXaiImageGeneration(await readJsonBody(c.req.raw, "Image generation body must be JSON"), model));
3971
3941
  }
3972
- async function handleXaiImageEdit(c, model, parsed) {
3973
- if (parsed instanceof Request) return await forwardTranslatedXaiImages(c, "edits", translateXaiImageEditJson(await readJsonBody(parsed, "Image edit body must be JSON"), model));
3974
- const { staged } = parsed;
3975
- let payload;
3976
- try {
3977
- payload = await translateXaiImageEditForm(staged.formData, model);
3978
- } finally {
3979
- await staged.cleanup();
3980
- }
3981
- return await forwardTranslatedXaiImages(c, "edits", payload);
3982
- }
3983
- //#endregion
3984
- //#region src/routes/images/temp-form-data.ts
3985
- const logger$15 = createHandlerLogger("images-temp-form-data");
3986
- const multipartStagingDependencies = { createWriteStream };
3987
- /**
3988
- * Matches the images forwarding timeouts: an upstream upload may take that
3989
- * long, so staged files must survive at least the whole forwarding window.
3990
- */
3991
- const STAGED_FILES_TTL_MS = 9e5;
3992
- const DEFAULT_MULTIPART_STAGING_LIMITS = {
3993
- maxBodySizeBytes: 134217728,
3994
- maxFieldSizeBytes: 10485760,
3995
- maxFields: 64,
3996
- maxFileSizeBytes: 67108864,
3997
- maxFiles: 16,
3998
- maxParts: 80
3999
- };
4000
- var InvalidMultipartBodyError = class extends Error {
4001
- constructor(options) {
4002
- super("Invalid multipart form data body", options);
4003
- this.name = "InvalidMultipartBodyError";
4004
- }
4005
- };
4006
- var MultipartBodyTooLargeError = class extends Error {
4007
- constructor() {
4008
- super("Multipart form data body exceeds the configured upload limits");
4009
- this.name = "MultipartBodyTooLargeError";
4010
- }
4011
- };
4012
- function sanitizeFileName(name) {
4013
- return name.split(/[\\/]/).pop()?.replaceAll("\0", "") || "file";
4014
- }
4015
- async function createDiskBackedBlob(filePath, type) {
4016
- if (typeof Bun !== "undefined") return Bun.file(filePath, { type });
4017
- const { openAsBlob } = await import("node:fs");
4018
- return await openAsBlob(filePath, { type });
4019
- }
4020
- function toError(error) {
4021
- return error instanceof Error ? error : new Error(String(error));
4022
- }
4023
- function parseMultipartBody(body, contentType, directory, entries, limits) {
4024
- return new Promise((resolve, reject) => {
4025
- let busboy;
4026
- try {
4027
- busboy = new Busboy({
4028
- headers: { "content-type": contentType },
4029
- limits: {
4030
- fieldSize: limits.maxFieldSizeBytes,
4031
- fields: limits.maxFields,
4032
- fileSize: limits.maxFileSizeBytes,
4033
- files: limits.maxFiles,
4034
- parts: limits.maxParts
4035
- }
4036
- });
4037
- } catch (error) {
4038
- reject(new InvalidMultipartBodyError({ cause: toError(error) }));
4039
- return;
4040
- }
4041
- const pendingWrites = [];
4042
- const activeFileStreams = /* @__PURE__ */ new Set();
4043
- const source = body === null ? Readable.from([]) : Readable.fromWeb(body);
4044
- let bodySizeBytes = 0;
4045
- const bodyLimiter = new Transform({ transform(chunk, _encoding, callback) {
4046
- bodySizeBytes += chunk.byteLength;
4047
- if (bodySizeBytes > limits.maxBodySizeBytes) {
4048
- callback(new MultipartBodyTooLargeError());
4049
- return;
4050
- }
4051
- callback(null, chunk);
4052
- } });
4053
- let fileIndex = 0;
4054
- let failure = null;
4055
- let inputPipeline = null;
4056
- let settled = false;
4057
- const settleStreams = () => Promise.allSettled([...inputPipeline ? [inputPipeline] : [], ...pendingWrites]);
4058
- const fail = (error) => {
4059
- if (failure || settled) return;
4060
- const failureError = toError(error);
4061
- failure = failureError;
4062
- source.destroy(failureError);
4063
- bodyLimiter.destroy(failureError);
4064
- busboy.destroy(failureError);
4065
- for (const stream of activeFileStreams) stream.destroy(failureError);
4066
- settleStreams().then(() => {
4067
- if (settled) return;
4068
- settled = true;
4069
- reject(failureError);
4070
- });
4071
- };
4072
- const failLimit = () => fail(new MultipartBodyTooLargeError());
4073
- busboy.on("field", (name, value, nameTruncated, valueTruncated) => {
4074
- if (nameTruncated || valueTruncated) {
4075
- failLimit();
4076
- return;
4077
- }
4078
- entries.push({
4079
- kind: "field",
4080
- name,
4081
- value
4082
- });
4083
- });
4084
- busboy.on("file", (name, stream, fileName, _transferEncoding, mimeType) => {
4085
- if (failure) {
4086
- stream.resume();
4087
- return;
4088
- }
4089
- const filePath = join(directory, `${fileIndex}.upload`);
4090
- fileIndex += 1;
4091
- entries.push({
4092
- fileName: sanitizeFileName(fileName),
4093
- filePath,
4094
- kind: "file",
4095
- mimeType,
4096
- name
4097
- });
4098
- activeFileStreams.add(stream);
4099
- stream.once("close", () => activeFileStreams.delete(stream));
4100
- stream.once("limit", failLimit);
4101
- const write = pipeline(stream, multipartStagingDependencies.createWriteStream(filePath));
4102
- pendingWrites.push(write);
4103
- write.catch(fail);
4104
- });
4105
- busboy.on("fieldsLimit", failLimit);
4106
- busboy.on("filesLimit", failLimit);
4107
- busboy.on("partsLimit", failLimit);
4108
- inputPipeline = pipeline(source, bodyLimiter, busboy);
4109
- inputPipeline.then(() => {
4110
- Promise.allSettled(pendingWrites).then((results) => {
4111
- if (failure || settled) return;
4112
- const failed = results.find((result) => result.status === "rejected");
4113
- if (failed) fail(failed.reason);
4114
- else {
4115
- settled = true;
4116
- resolve();
4117
- }
4118
- });
4119
- }, (error) => {
4120
- fail(error instanceof MultipartBodyTooLargeError ? error : new InvalidMultipartBodyError({ cause: toError(error) }));
4121
- });
4122
- });
4123
- }
4124
- function resolveStagingLimits(overrides) {
4125
- const limits = {
4126
- ...DEFAULT_MULTIPART_STAGING_LIMITS,
4127
- ...overrides
4128
- };
4129
- for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive safe integer`);
4130
- return limits;
4131
- }
4132
- /**
4133
- * Streams a multipart request body through the parser, writing every file
4134
- * part to a temporary directory as it arrives and rebuilding the form data
4135
- * with disk-backed blobs, so forwarding streams payloads from disk instead
4136
- * of pinning the whole upload in memory.
4137
- */
4138
- async function stageMultipartBodyToDisk(body, contentType, limitOverrides = {}) {
4139
- const limits = resolveStagingLimits(limitOverrides);
4140
- const directory = await mkdtemp(join(tmpdir(), "copilot-api-images-"));
4141
- const entries = [];
4142
- try {
4143
- await parseMultipartBody(body, contentType, directory, entries, limits);
4144
- } catch (error) {
4145
- await rm(directory, {
4146
- force: true,
4147
- recursive: true
4148
- });
4149
- throw error;
4150
- }
4151
- const stagedFormData = new FormData();
4152
- try {
4153
- for (const entry of entries) if (entry.kind === "field") stagedFormData.append(entry.name, entry.value);
4154
- else stagedFormData.append(entry.name, await createDiskBackedBlob(entry.filePath, entry.mimeType), entry.fileName);
4155
- } catch (error) {
4156
- await rm(directory, {
4157
- force: true,
4158
- recursive: true
4159
- });
4160
- throw error;
4161
- }
4162
- let cleanupPromise = null;
4163
- let cleanupTimer = null;
4164
- let unregisterProcessCleanup = null;
4165
- const cleanup = () => {
4166
- cleanupPromise ??= (async () => {
4167
- if (cleanupTimer) {
4168
- clearTimeout(cleanupTimer);
4169
- cleanupTimer = null;
4170
- }
4171
- try {
4172
- await rm(directory, {
4173
- force: true,
4174
- recursive: true
4175
- });
4176
- } catch (error) {
4177
- logger$15.warn("Failed to remove staged images temp directory", {
4178
- directory,
4179
- error
4180
- });
4181
- } finally {
4182
- unregisterProcessCleanup?.();
4183
- unregisterProcessCleanup = null;
4184
- }
4185
- })();
4186
- return cleanupPromise;
4187
- };
4188
- unregisterProcessCleanup = registerProcessCleanup(() => {
4189
- if (cleanupTimer) {
4190
- clearTimeout(cleanupTimer);
4191
- cleanupTimer = null;
4192
- }
4193
- try {
4194
- rmSync(directory, {
4195
- force: true,
4196
- recursive: true
4197
- });
4198
- } catch (error) {
4199
- logger$15.warn("Failed to remove staged images temp directory on exit", {
4200
- directory,
4201
- error
4202
- });
4203
- } finally {
4204
- unregisterProcessCleanup?.();
4205
- unregisterProcessCleanup = null;
4206
- }
4207
- });
4208
- return {
4209
- directory,
4210
- formData: stagedFormData,
4211
- cleanup,
4212
- scheduleCleanup: () => {
4213
- if (cleanupPromise || cleanupTimer) return;
4214
- cleanupTimer = setTimeout(() => void cleanup(), STAGED_FILES_TTL_MS);
4215
- cleanupTimer.unref();
4216
- }
4217
- };
3942
+ async function handleXaiImageEdit(c, model) {
3943
+ return await forwardTranslatedXaiImages(c, "edits", translateXaiImageEditJson(await readJsonBody(c.req.raw, "Image edit body must be JSON"), model));
4218
3944
  }
4219
3945
  //#endregion
4220
3946
  //#region src/routes/images/edits-handler.ts
4221
- const imageEditsRouteDependencies = { stageMultipartBodyToDisk };
4222
- function createStagedFormDataRequest(request, requestHeaders, formData) {
4223
- const headers = new Headers(requestHeaders);
4224
- headers.delete("content-length");
4225
- headers.delete("content-type");
4226
- return createForwardRequest(request, headers, formData);
4227
- }
4228
- function createMultipartImagesRequest(request, requestHeaders, formData, model) {
4229
- formData.set("model", model);
4230
- return createStagedFormDataRequest(request, requestHeaders, formData);
4231
- }
4232
- /**
4233
- * Streams an edits multipart body to disk, staging uploaded files so
4234
- * forwarding streams them from the filesystem instead of pinning them in
4235
- * memory. Non-multipart bodies come back as an untouched request stream;
4236
- * malformed multipart bodies throw a typed error because the consumed stream
4237
- * can no longer be forwarded.
4238
- */
4239
- async function parseEditsRequest(request) {
4240
- const requestHeaders = snapshotRequestHeaders(request);
4241
- const contentType = requestHeaders.get("content-type");
4242
- if (contentType?.split(";", 1)[0]?.trim().toLowerCase() !== "multipart/form-data" || !contentType) return createForwardRequest(request, requestHeaders, request.body);
4243
- const staged = await imageEditsRouteDependencies.stageMultipartBodyToDisk(request.body, contentType);
4244
- const model = staged.formData.get("model");
4245
- return {
4246
- model: typeof model === "string" ? model : void 0,
4247
- requestHeaders,
4248
- staged
4249
- };
4250
- }
4251
3947
  async function handleImagesEdits(c) {
4252
3948
  try {
4253
3949
  const configuredModel = imageRouteDependencies.getImageEditModel();
@@ -4255,66 +3951,21 @@ async function handleImagesEdits(c) {
4255
3951
  message: configuredModel.message,
4256
3952
  type: "invalid_request_error"
4257
3953
  } }, 400);
4258
- const parsed = await parseEditsRequest(c.req.raw);
4259
- if (configuredModel.status === "xai") return await handleXaiImageEdit(c, configuredModel.model, parsed);
3954
+ const contentTypeError = imageEditContentTypeErrorResponse(c);
3955
+ if (contentTypeError) return contentTypeError;
3956
+ if (configuredModel.status === "xai") return await handleXaiImageEdit(c, configuredModel.model);
3957
+ const parsed = await parseJsonImagesRequest(c.req.raw);
4260
3958
  if (parsed instanceof Request) return await handleCodexImages(c, "edits", void 0, parsed);
4261
- const { model, requestHeaders, staged } = parsed;
4262
- try {
4263
- const response = model === void 0 ? await handleCodexImages(c, "edits", void 0, createStagedFormDataRequest(c.req.raw, requestHeaders, staged.formData)) : await routeImagesRequest(c, "edits", {
4264
- createRequest: (mappedModel) => createMultipartImagesRequest(c.req.raw, requestHeaders, staged.formData, mappedModel),
4265
- model
4266
- });
4267
- staged.scheduleCleanup();
4268
- return response;
4269
- } catch (error) {
4270
- await staged.cleanup();
4271
- throw error;
4272
- }
3959
+ return await routeImagesRequest(c, "edits", parsed);
4273
3960
  } catch (error) {
4274
- if (error instanceof InvalidMultipartBodyError || error instanceof MultipartBodyTooLargeError) return c.json({ error: {
4275
- message: error.message,
4276
- type: "invalid_request_error"
4277
- } }, error instanceof MultipartBodyTooLargeError ? 413 : 400);
4278
3961
  const invalidRequest = xaiImagesRequestErrorResponse(c, error);
4279
3962
  if (invalidRequest) return invalidRequest;
4280
- logger$16.error("images.edits.error", { error });
3963
+ logger$15.error("images.edits.error", { error });
4281
3964
  return await forwardError(c, error);
4282
3965
  }
4283
3966
  }
4284
3967
  //#endregion
4285
3968
  //#region src/routes/images/generations-handler.ts
4286
- function createJsonImagesRequest(request, requestHeaders, payload, model) {
4287
- const headers = new Headers(requestHeaders);
4288
- headers.delete("content-length");
4289
- return createForwardRequest(request, headers, new TextEncoder().encode(JSON.stringify({
4290
- ...payload,
4291
- model
4292
- })));
4293
- }
4294
- /**
4295
- * Parses a generations request body as JSON. Generation payloads are small
4296
- * JSON documents, so one byte copy covers both routing on the model field
4297
- * and forwarding an unchanged body to Codex. Rebuilt bodies stay binary so
4298
- * Node does not synthesize a text/plain content type.
4299
- */
4300
- async function parseGenerationsRequest(request) {
4301
- const requestHeaders = snapshotRequestHeaders(request);
4302
- const body = new Uint8Array(await request.arrayBuffer());
4303
- const originalRequest = createForwardRequest(request, requestHeaders, body);
4304
- let payload;
4305
- try {
4306
- const bodyText = new TextDecoder("utf-8", { fatal: true }).decode(body);
4307
- payload = JSON.parse(bodyText);
4308
- } catch {
4309
- return originalRequest;
4310
- }
4311
- if (payload === null || typeof payload !== "object" || Array.isArray(payload) || typeof payload.model !== "string") return originalRequest;
4312
- return {
4313
- createRequest: (mappedModel) => createJsonImagesRequest(request, requestHeaders, payload, mappedModel),
4314
- model: payload.model,
4315
- originalRequest
4316
- };
4317
- }
4318
3969
  async function handleImagesGenerations(c) {
4319
3970
  try {
4320
3971
  const configuredModel = imageRouteDependencies.getImageGenerationModel();
@@ -4323,13 +3974,13 @@ async function handleImagesGenerations(c) {
4323
3974
  type: "invalid_request_error"
4324
3975
  } }, 400);
4325
3976
  if (configuredModel.status === "xai") return await handleXaiImageGeneration(c, configuredModel.model);
4326
- const parsed = await parseGenerationsRequest(c.req.raw);
3977
+ const parsed = await parseJsonImagesRequest(c.req.raw);
4327
3978
  if (parsed instanceof Request) return await handleCodexImages(c, "generations", void 0, parsed);
4328
3979
  return await routeImagesRequest(c, "generations", parsed);
4329
3980
  } catch (error) {
4330
3981
  const invalidRequest = xaiImagesRequestErrorResponse(c, error);
4331
3982
  if (invalidRequest) return invalidRequest;
4332
- logger$16.error("images.generations.error", { error });
3983
+ logger$15.error("images.generations.error", { error });
4333
3984
  return await forwardError(c, error);
4334
3985
  }
4335
3986
  }
@@ -12619,7 +12270,13 @@ async function handleProviderImages(c, operation) {
12619
12270
  message: `Provider '${provider}' not found or disabled`,
12620
12271
  type: "invalid_request_error"
12621
12272
  } }, 404);
12622
- if (providerConfig.name === "codex") return await handleCodexImages(c, operation, providerConfig);
12273
+ if (providerConfig.name === "codex") {
12274
+ if (operation === "edits") {
12275
+ const contentTypeError = imageEditContentTypeErrorResponse(c);
12276
+ if (contentTypeError) return contentTypeError;
12277
+ }
12278
+ return await handleCodexImages(c, operation, providerConfig);
12279
+ }
12623
12280
  return await forwardProviderImagesWithLogging(providerConfig, c.req.raw, operation, {
12624
12281
  logger: logger$6,
12625
12282
  provider
@@ -14305,4 +13962,4 @@ createServer();
14305
13962
  //#endregion
14306
13963
  export { createServer };
14307
13964
 
14308
- //# sourceMappingURL=server-C1Y_Wrlf.js.map
13965
+ //# sourceMappingURL=server-BE1P5ljq.js.map