@jeffreycao/copilot-api 1.14.6 → 1.14.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -668,9 +668,15 @@ These endpoints mimic the OpenAI API structure.
668
668
 
669
669
  ### Codex Backend Proxy Endpoints
670
670
 
671
- | Endpoint | Method | Description |
672
- | -------------------- | ------ | ----------- |
673
- | `POST /alpha/search` | `POST` | Transparently forwards the JSON body and query parameters to the Codex Alpha Search upstream. The gateway replaces client authorization and account headers with the active Codex login, forwards compatible headers such as `accept`, `content-type`, `originator`, `user-agent`, and `cookie`, and returns the upstream status, headers, and body unchanged. |
671
+ These endpoints require an active Codex login. Each endpoint is available both without a version prefix and under `/v1`.
672
+
673
+ | Endpoint | Method | Description |
674
+ | --- | --- | --- |
675
+ | `POST /alpha/search`<br>`POST /v1/alpha/search` | `POST` | Transparently forwards the JSON body and query parameters to the Codex Alpha Search upstream. |
676
+ | `POST /images/generations`<br>`POST /v1/images/generations` | `POST` | Forwards a JSON image generation request to the Codex Images upstream. When the request omits `Content-Type`, the gateway defaults it to `application/json`. |
677
+ | `POST /images/edits`<br>`POST /v1/images/edits` | `POST` | Forwards an image edit request to the Codex Images upstream. Send this request as `multipart/form-data` and let the HTTP client generate the `boundary`; the gateway preserves the incoming content type and streams the upload body. |
678
+
679
+ For every endpoint above, the gateway replaces client authorization and account headers with the active Codex login, preserves query parameters and compatible request headers, and returns the upstream status, headers, and body.
674
680
 
675
681
  ### Anthropic Compatible Endpoints
676
682
 
package/README.zh-CN.md CHANGED
@@ -674,9 +674,15 @@ curl http://localhost:4141/admin/config/model-mappings \
674
674
 
675
675
  ### Codex 后端代理端点
676
676
 
677
+ 这些端点要求已有可用的 Codex 登录态。每个端点同时提供无版本前缀和 `/v1` 两种路径。
678
+
677
679
  | 端点 | 方法 | 说明 |
678
680
  | --- | --- | --- |
679
- | `POST /alpha/search` | `POST` | 将 JSON 请求体和查询参数透明转发到 Codex Alpha Search 上游。网关会使用当前 Codex 登录态覆盖客户端的 authorization 和 account header,透传 `accept`、`content-type`、`originator`、`user-agent`、`cookie` 等兼容 header,并原样返回上游状态码、响应头和响应体。 |
681
+ | `POST /alpha/search`<br>`POST /v1/alpha/search` | `POST` | 将 JSON 请求体和查询参数透明转发到 Codex Alpha Search 上游。 |
682
+ | `POST /images/generations`<br>`POST /v1/images/generations` | `POST` | 将 JSON 图片生成请求转发到 Codex Images 上游。请求未携带 `Content-Type` 时,网关默认补充 `application/json`。 |
683
+ | `POST /images/edits`<br>`POST /v1/images/edits` | `POST` | 将图片编辑请求转发到 Codex Images 上游。请使用 `multipart/form-data`,并让 HTTP 客户端自动生成 `boundary`;网关会保留传入的 content type,并以流式方式转发上传请求体。 |
684
+
685
+ 对于以上所有端点,网关都会使用当前 Codex 登录态覆盖客户端的 authorization 和 account header,保留查询参数及兼容的请求头,并返回上游状态码、响应头和响应体。
680
686
 
681
687
  ### Anthropic 兼容端点
682
688
 
package/dist/main.js CHANGED
@@ -25,7 +25,7 @@ bindElectronFetch();
25
25
  const { auth } = await import("./auth-DvJ8r_tN.js");
26
26
  const { debug } = await import("./debug-Bb5hamXX.js");
27
27
  const { mcp } = await import("./mcp-BseuqgHR.js");
28
- const { start } = await import("./start-DNPIXvm4.js");
28
+ const { start } = await import("./start-juT2yVlW.js");
29
29
  await runMain(defineCommand({
30
30
  meta: {
31
31
  name: "copilot-api",
@@ -7,6 +7,7 @@ import fs, { readFileSync } from "node:fs";
7
7
  import fs$1 from "node:fs/promises";
8
8
  import path from "node:path";
9
9
  import { events } from "fetch-event-stream";
10
+ import { fetch as fetch$1, getGlobalDispatcher } from "undici";
10
11
  import { z } from "zod";
11
12
  import { Hono } from "hono";
12
13
  import { cors } from "hono/cors";
@@ -299,6 +300,10 @@ const debugLazy = (logger, factory) => {
299
300
  const debugJson = (logger, label, value) => {
300
301
  debugLazy(logger, () => [label, JSON.stringify(value)]);
301
302
  };
303
+ const debugJsonAsync = async (logger, label, factory) => {
304
+ if (!state.verbose) return;
305
+ logger.debug(label, JSON.stringify(await factory()));
306
+ };
302
307
  const debugJsonTail = (logger, label, { value, tailLength = 400 }) => {
303
308
  debugLazy(logger, () => [label, JSON.stringify(value).slice(-tailLength)]);
304
309
  };
@@ -441,7 +446,7 @@ async function forwardProviderModels(providerConfig, requestHeaders) {
441
446
  }
442
447
  //#endregion
443
448
  //#region src/routes/alpha-search/route.ts
444
- const logger$10 = createHandlerLogger("alpha-search-handler");
449
+ const logger$11 = createHandlerLogger("alpha-search-handler");
445
450
  const alphaSearchRoutes = new Hono();
446
451
  alphaSearchRoutes.post("/", async (c) => {
447
452
  try {
@@ -449,15 +454,15 @@ alphaSearchRoutes.post("/", async (c) => {
449
454
  message: "Provider 'codex' not found or disabled",
450
455
  type: "invalid_request_error"
451
456
  } }, 404);
452
- debugJson(logger$10, "alpha_search.codex.request", { body: await c.req.raw.clone().text() });
457
+ await debugJsonAsync(logger$11, "alpha_search.codex.request", async () => ({ body: await c.req.raw.clone().text() }));
453
458
  const upstreamResponse = await forwardCodexAlphaSearch(c.req.raw);
454
- debugJson(logger$10, "alpha_search.codex.response", {
459
+ await debugJsonAsync(logger$11, "alpha_search.codex.response", async () => ({
455
460
  body: await upstreamResponse.clone().text(),
456
461
  statusCode: upstreamResponse.status
457
- });
462
+ }));
458
463
  return createProviderProxyResponse(upstreamResponse);
459
464
  } catch (error) {
460
- logger$10.error("alpha_search.codex.error", { error });
465
+ logger$11.error("alpha_search.codex.error", { error });
461
466
  return await forwardError(c, error);
462
467
  }
463
468
  });
@@ -1589,7 +1594,7 @@ const setContextCacheControl = (part) => {
1589
1594
  };
1590
1595
  //#endregion
1591
1596
  //#region src/routes/provider/chat-completions/handler.ts
1592
- const logger$9 = createHandlerLogger("provider-chat-completions-handler");
1597
+ const logger$10 = createHandlerLogger("provider-chat-completions-handler");
1593
1598
  async function handleProviderChatCompletionsForProvider(c, options) {
1594
1599
  const { payload, provider } = options;
1595
1600
  const providerConfig = await resolveProviderConfig(provider);
@@ -1603,13 +1608,13 @@ async function handleProviderChatCompletionsForProvider(c, options) {
1603
1608
  applyProviderStreamOptions(payload);
1604
1609
  applyDashScopePreserveThinkingDefault(payload, providerConfig);
1605
1610
  applyProviderContextCache(payload, modelConfig, providerConfig);
1606
- debugJson(logger$9, "provider.chat_completions.request", {
1611
+ debugJson(logger$10, "provider.chat_completions.request", {
1607
1612
  payload,
1608
1613
  provider
1609
1614
  });
1610
1615
  const upstreamResponse = await forwardProviderChatCompletions(providerConfig, payload, c.req.raw.headers);
1611
1616
  if (!upstreamResponse.ok) {
1612
- logger$9.error("Failed to create provider chat completions", {
1617
+ logger$10.error("Failed to create provider chat completions", {
1613
1618
  provider,
1614
1619
  statusCode: upstreamResponse.status
1615
1620
  });
@@ -1623,7 +1628,7 @@ async function handleProviderChatCompletionsForProvider(c, options) {
1623
1628
  });
1624
1629
  const responseBody = await upstreamResponse.clone().json();
1625
1630
  recordUsage(normalizeOpenAIUsage(responseBody.usage));
1626
- debugJson(logger$9, "provider.chat_completions.response", responseBody);
1631
+ debugJson(logger$10, "provider.chat_completions.response", responseBody);
1627
1632
  return createProviderProxyResponse(upstreamResponse);
1628
1633
  }
1629
1634
  const applyProviderModelDefaults = (payload, modelConfig) => {
@@ -1653,12 +1658,12 @@ const createProviderChatCompletionsUsageRecorder = (payload, provider, modelConf
1653
1658
  providerName: provider
1654
1659
  });
1655
1660
  const streamProviderChatCompletions = (c, upstreamResponse, options) => {
1656
- logger$9.debug("provider.chat_completions.streaming", { provider: options.provider });
1661
+ logger$10.debug("provider.chat_completions.streaming", { provider: options.provider });
1657
1662
  return streamSSE(c, async (stream) => {
1658
1663
  let usage = {};
1659
1664
  try {
1660
1665
  for await (const chunk of events(upstreamResponse)) {
1661
- debugJson(logger$9, "provider.chat_completions.stream_chunk", chunk);
1666
+ debugJson(logger$10, "provider.chat_completions.stream_chunk", chunk);
1662
1667
  if (chunk.data && chunk.data !== "[DONE]") {
1663
1668
  const parsedChunk = parseChatCompletionChunkData(chunk.data);
1664
1669
  if (parsedChunk?.usage) usage = normalizeOpenAIUsage(parsedChunk.usage);
@@ -1785,7 +1790,7 @@ const createChatCompletions = async (payload, options) => {
1785
1790
  };
1786
1791
  //#endregion
1787
1792
  //#region src/routes/chat-completions/handler.ts
1788
- const logger$8 = createHandlerLogger("chat-completions-handler");
1793
+ const logger$9 = createHandlerLogger("chat-completions-handler");
1789
1794
  async function handleCompletion$1(c) {
1790
1795
  let payload = await c.req.json();
1791
1796
  const requestedModel = payload.model;
@@ -1799,23 +1804,23 @@ async function handleCompletion$1(c) {
1799
1804
  provider: providerModelAlias.provider
1800
1805
  });
1801
1806
  }
1802
- debugJson(logger$8, "Request payload:", payload);
1807
+ debugJson(logger$9, "Request payload:", payload);
1803
1808
  const selectedModel = state.models?.data.find((model) => model.id === payload.model);
1804
1809
  if (isNullish(payload.max_tokens) && isNullish(payload.max_completion_tokens)) {
1805
1810
  payload = {
1806
1811
  ...payload,
1807
1812
  max_tokens: selectedModel?.capabilities.limits.max_output_tokens
1808
1813
  };
1809
- debugJson(logger$8, "Set max_tokens to:", payload.max_tokens);
1814
+ debugJson(logger$9, "Set max_tokens to:", payload.max_tokens);
1810
1815
  }
1811
1816
  if (payload.model.includes("gpt")) {
1812
1817
  if (isNullish(payload.max_completion_tokens)) payload.max_completion_tokens = payload.max_tokens;
1813
1818
  delete payload.max_tokens;
1814
1819
  }
1815
1820
  const requestId = generateRequestIdFromPayload(payload);
1816
- logger$8.debug("Generated request ID:", requestId);
1821
+ logger$9.debug("Generated request ID:", requestId);
1817
1822
  const sessionId = getUUID(requestId);
1818
- logger$8.debug("Extracted session ID:", sessionId);
1823
+ logger$9.debug("Extracted session ID:", sessionId);
1819
1824
  const recordUsage = createCopilotTokenUsageRecorder({
1820
1825
  endpoint: "chat_completions",
1821
1826
  fallbackSessionId: sessionId,
@@ -1826,18 +1831,18 @@ async function handleCompletion$1(c) {
1826
1831
  sessionId
1827
1832
  });
1828
1833
  if (isNonStreaming$1(response)) {
1829
- debugJson(logger$8, "Non-streaming response:", response);
1834
+ debugJson(logger$9, "Non-streaming response:", response);
1830
1835
  recordUsage({
1831
1836
  ...normalizeOpenAIUsage(response.usage),
1832
1837
  total_nano_aiu: normalizeOptionalToken(response.copilot_usage?.total_nano_aiu)
1833
1838
  });
1834
1839
  return c.json(response);
1835
1840
  }
1836
- logger$8.debug("Streaming response");
1841
+ logger$9.debug("Streaming response");
1837
1842
  return streamSSE(c, async (stream) => {
1838
1843
  let usage = {};
1839
1844
  for await (const chunk of response) {
1840
- debugJson(logger$8, "Streaming chunk:", chunk);
1845
+ debugJson(logger$9, "Streaming chunk:", chunk);
1841
1846
  const parsedChunk = parseChatCompletionChunk(chunk);
1842
1847
  if (parsedChunk?.usage || parsedChunk?.copilot_usage) usage = {
1843
1848
  ...normalizeOpenAIUsage(parsedChunk.usage),
@@ -1926,6 +1931,71 @@ embeddingRoutes.post("/", async (c) => {
1926
1931
  }
1927
1932
  });
1928
1933
  //#endregion
1934
+ //#region src/services/codex/images.ts
1935
+ const CODEX_IMAGES_TIMEOUT_MS = 900 * 1e3;
1936
+ const codexImagesDispatcher = { dispatch(options, handler) {
1937
+ return getGlobalDispatcher().dispatch({
1938
+ ...options,
1939
+ bodyTimeout: CODEX_IMAGES_TIMEOUT_MS,
1940
+ headersTimeout: CODEX_IMAGES_TIMEOUT_MS
1941
+ }, handler);
1942
+ } };
1943
+ function resolveCodexImagesUrl(requestUrl, operation) {
1944
+ const upstreamUrl = new URL(`${CODEX_API_BASE_URL}/codex/images/${operation}`);
1945
+ upstreamUrl.search = new URL(requestUrl, "http://localhost").search;
1946
+ return upstreamUrl.toString();
1947
+ }
1948
+ async function forwardCodexImages(request, operation) {
1949
+ const headers = buildCodexRequestHeaders(request.headers);
1950
+ if (!headers.has("accept")) headers.set("accept", "application/json");
1951
+ if (operation === "generations" && !headers.has("content-type")) headers.set("content-type", "application/json");
1952
+ const init = {
1953
+ method: "POST",
1954
+ headers,
1955
+ body: request.body,
1956
+ duplex: "half",
1957
+ signal: AbortSignal.timeout(CODEX_IMAGES_TIMEOUT_MS)
1958
+ };
1959
+ const upstreamUrl = resolveCodexImagesUrl(request.url, operation);
1960
+ if (typeof Bun !== "undefined") return await fetch(upstreamUrl, init);
1961
+ return await fetch$1(upstreamUrl, {
1962
+ ...init,
1963
+ dispatcher: codexImagesDispatcher
1964
+ });
1965
+ }
1966
+ //#endregion
1967
+ //#region src/routes/images/route.ts
1968
+ const logger$8 = createHandlerLogger("images-handler");
1969
+ const imageRoutes = new Hono();
1970
+ const imageRouteDependencies = { debugJsonAsync };
1971
+ function getContentMetadata(headers) {
1972
+ return {
1973
+ contentType: headers.get("content-type"),
1974
+ contentLength: headers.get("content-length")
1975
+ };
1976
+ }
1977
+ async function handleCodexImages(c, operation) {
1978
+ try {
1979
+ if (!await resolveProviderConfig("codex")) return c.json({ error: {
1980
+ message: "Provider 'codex' not found or disabled",
1981
+ type: "invalid_request_error"
1982
+ } }, 404);
1983
+ if (operation === "generations") await imageRouteDependencies.debugJsonAsync(logger$8, "images.generations.codex.request", async () => ({ body: await c.req.raw.clone().text() }));
1984
+ else debugJson(logger$8, "images.edits.codex.request", getContentMetadata(c.req.raw.headers));
1985
+ const upstreamResponse = await forwardCodexImages(c.req.raw, operation);
1986
+ debugJson(logger$8, `images.${operation}.codex.response`, {
1987
+ ...getContentMetadata(upstreamResponse.headers),
1988
+ statusCode: upstreamResponse.status
1989
+ });
1990
+ return createProviderProxyResponse(upstreamResponse);
1991
+ } catch (error) {
1992
+ logger$8.error(`images.${operation}.codex.error`, { error });
1993
+ return await forwardError(c, error);
1994
+ }
1995
+ }
1996
+ imageRoutes.post("/generations", (c) => handleCodexImages(c, "generations"));
1997
+ imageRoutes.post("/edits", (c) => handleCodexImages(c, "edits"));
1998
+ //#endregion
1929
1999
  //#region src/lib/tokenizer.ts
1930
2000
  const ENCODING_MAP = {
1931
2001
  o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"),
@@ -6910,14 +6980,17 @@ server.route("/token-usage", tokenUsageRoute);
6910
6980
  server.route("/token", tokenRoute);
6911
6981
  server.route("/responses", responsesRoutes);
6912
6982
  server.route("/alpha/search", alphaSearchRoutes);
6983
+ server.route("/images", imageRoutes);
6913
6984
  server.route("/v1/chat/completions", completionRoutes);
6914
6985
  server.route("/v1/models", modelRoutes);
6915
6986
  server.route("/v1/embeddings", embeddingRoutes);
6916
6987
  server.route("/v1/responses", responsesRoutes);
6988
+ server.route("/v1/alpha/search", alphaSearchRoutes);
6989
+ server.route("/v1/images", imageRoutes);
6917
6990
  server.route("/v1/messages", messageRoutes);
6918
6991
  server.route("/:provider/v1/messages", providerMessageRoutes);
6919
6992
  server.route("/:provider/v1/models", providerModelRoutes);
6920
6993
  //#endregion
6921
6994
  export { server };
6922
6995
 
6923
- //# sourceMappingURL=server-uQjD_RpA.js.map
6996
+ //# sourceMappingURL=server-CF0okzl9.js.map