@jeffreycao/copilot-api 2.4.0 → 2.4.2

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