@ljoukov/llm 7.0.18 → 7.0.19

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
@@ -108,13 +108,22 @@ refresh-token rotation and serves short-lived access tokens.
108
108
  - `CHATGPT_AUTH_TOKEN_PROVIDER_URL` (example: `https://chatgpt-auth.<your-domain>`)
109
109
  - `CHATGPT_AUTH_API_KEY` (shared secret; sent as `Authorization: Bearer ...` and `x-chatgpt-auth: ...`)
110
110
  - `CHATGPT_AUTH_TOKEN_PROVIDER_STORE` (`kv` or `d1`, defaults to `kv`)
111
+ - `CHATGPT_CODEX_PROXY_URL` (optional Vercel proxy endpoint, for example `https://<project>.vercel.app/api/codex/responses`)
112
+ - `CHATGPT_CODEX_PROXY_API_KEY` (bearer token for `CHATGPT_CODEX_PROXY_URL`)
113
+ - `CHATGPT_CODEX_ENDPOINT` (optional direct endpoint override; defaults to `https://chatgpt.com/backend-api/codex/responses`)
111
114
  - `CHATGPT_RESPONSES_WEBSOCKET_MODE` (`auto` | `off` | `only`, default: `auto`)
112
115
 
113
116
  This repo includes a Cloudflare Workers token provider implementation in `workers/chatgpt-auth/`.
117
+ It also includes a minimal Vercel streaming proxy app in `vercel/codex-proxy/`.
114
118
 
115
119
  If `CHATGPT_AUTH_TOKEN_PROVIDER_URL` + `CHATGPT_AUTH_API_KEY` are set, `chatgpt-*` models will fetch tokens from the
116
120
  token provider and will not read the local Codex auth store.
117
121
 
122
+ If `CHATGPT_CODEX_PROXY_URL` + `CHATGPT_CODEX_PROXY_API_KEY` are set, `chatgpt-*` text requests are sent through that
123
+ proxy and the local process does not need access to the Codex auth store or token provider. The Vercel proxy fetches
124
+ short-lived ChatGPT access tokens from `workers/chatgpt-auth` and streams the upstream Codex response body back to the
125
+ caller.
126
+
118
127
  ### Responses transport
119
128
 
120
129
  For OpenAI and `chatgpt-*` model paths, this library now tries **Responses WebSocket transport first** and falls back
package/dist/index.cjs CHANGED
@@ -712,6 +712,10 @@ function resolveOpenAiImagePriceResolution(imageSize) {
712
712
  var import_node_os2 = __toESM(require("os"), 1);
713
713
  var import_node_util = require("util");
714
714
 
715
+ // src/utils/env.ts
716
+ var import_node_fs = __toESM(require("fs"), 1);
717
+ var import_node_path = __toESM(require("path"), 1);
718
+
715
719
  // src/utils/runtimeSingleton.ts
716
720
  var runtimeSingletonStoreKey = /* @__PURE__ */ Symbol.for("@ljoukov/llm.runtimeSingletonStore");
717
721
  function getRuntimeSingletonStore() {
@@ -740,16 +744,7 @@ function getRuntimeSingleton(key, create) {
740
744
  return createdValue;
741
745
  }
742
746
 
743
- // src/openai/chatgpt-auth.ts
744
- var import_node_buffer = require("buffer");
745
- var import_node_fs2 = __toESM(require("fs"), 1);
746
- var import_node_os = __toESM(require("os"), 1);
747
- var import_node_path2 = __toESM(require("path"), 1);
748
- var import_zod = require("zod");
749
-
750
747
  // src/utils/env.ts
751
- var import_node_fs = __toESM(require("fs"), 1);
752
- var import_node_path = __toESM(require("path"), 1);
753
748
  var envState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.envState"), () => ({
754
749
  envLoaded: false
755
750
  }));
@@ -811,6 +806,11 @@ function parseEnvLine(line) {
811
806
  }
812
807
 
813
808
  // src/openai/chatgpt-auth.ts
809
+ var import_node_buffer = require("buffer");
810
+ var import_node_fs2 = __toESM(require("fs"), 1);
811
+ var import_node_os = __toESM(require("os"), 1);
812
+ var import_node_path2 = __toESM(require("path"), 1);
813
+ var import_zod = require("zod");
814
814
  var CHATGPT_AUTH_TOKEN_PROVIDER_URL_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_URL";
815
815
  var CHATGPT_AUTH_TOKEN_PROVIDER_STORE_ENV = "CHATGPT_AUTH_TOKEN_PROVIDER_STORE";
816
816
  var CHATGPT_AUTH_API_KEY_ENV = "CHATGPT_AUTH_API_KEY";
@@ -1718,19 +1718,21 @@ function createAbortError(reason) {
1718
1718
 
1719
1719
  // src/openai/chatgpt-codex.ts
1720
1720
  var CHATGPT_CODEX_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
1721
+ var CHATGPT_CODEX_ENDPOINT_ENV = "CHATGPT_CODEX_ENDPOINT";
1722
+ var CHATGPT_CODEX_PROXY_URL_ENV = "CHATGPT_CODEX_PROXY_URL";
1723
+ var CHATGPT_CODEX_PROXY_API_KEY_ENV = "CHATGPT_CODEX_PROXY_API_KEY";
1721
1724
  var CHATGPT_RESPONSES_EXPERIMENTAL_HEADER = "responses=experimental";
1722
1725
  var chatGptCodexState = getRuntimeSingleton(/* @__PURE__ */ Symbol.for("@ljoukov/llm.chatGptCodexState"), () => ({
1723
1726
  cachedResponsesWebSocketMode: null,
1724
1727
  chatGptResponsesWebSocketDisabled: false
1725
1728
  }));
1726
1729
  async function streamChatGptCodexResponse(options) {
1727
- const { access, accountId } = await getChatGptAuthProfile();
1730
+ const endpointConfig = await resolveChatGptCodexEndpointConfig();
1728
1731
  const mode = resolveChatGptResponsesWebSocketMode();
1729
1732
  const fallbackStreamFactory = () => {
1730
1733
  const streamPromise = streamChatGptCodexResponseSse({
1731
1734
  request: options.request,
1732
- access,
1733
- accountId,
1735
+ endpointConfig,
1734
1736
  sessionId: options.sessionId,
1735
1737
  signal: options.signal
1736
1738
  });
@@ -1752,15 +1754,14 @@ async function streamChatGptCodexResponse(options) {
1752
1754
  return fallbackStreamFactory();
1753
1755
  }
1754
1756
  const websocketHeaders = buildChatGptCodexHeaders({
1755
- access,
1756
- accountId,
1757
+ endpointConfig,
1757
1758
  sessionId: options.sessionId,
1758
1759
  useWebSocket: true
1759
1760
  });
1760
1761
  return createAdaptiveResponsesStream({
1761
1762
  mode,
1762
1763
  createWebSocketStream: async () => await createResponsesWebSocketStream({
1763
- url: toWebSocketUrl(CHATGPT_CODEX_ENDPOINT),
1764
+ url: toWebSocketUrl(endpointConfig.url),
1764
1765
  headers: websocketHeaders,
1765
1766
  request: options.request,
1766
1767
  signal: options.signal
@@ -1773,14 +1774,13 @@ async function streamChatGptCodexResponse(options) {
1773
1774
  }
1774
1775
  async function streamChatGptCodexResponseSse(options) {
1775
1776
  const headers = buildChatGptCodexHeaders({
1776
- access: options.access,
1777
- accountId: options.accountId,
1777
+ endpointConfig: options.endpointConfig,
1778
1778
  sessionId: options.sessionId,
1779
1779
  useWebSocket: false
1780
1780
  });
1781
1781
  headers.Accept = "text/event-stream";
1782
1782
  headers["Content-Type"] = "application/json";
1783
- const response = await fetch(CHATGPT_CODEX_ENDPOINT, {
1783
+ const response = await fetch(options.endpointConfig.url, {
1784
1784
  method: "POST",
1785
1785
  headers,
1786
1786
  body: JSON.stringify(options.request),
@@ -1800,24 +1800,66 @@ function resolveChatGptResponsesWebSocketMode() {
1800
1800
  if (chatGptCodexState.cachedResponsesWebSocketMode) {
1801
1801
  return chatGptCodexState.cachedResponsesWebSocketMode;
1802
1802
  }
1803
+ const explicitMode = process.env.CHATGPT_RESPONSES_WEBSOCKET_MODE ?? process.env.OPENAI_RESPONSES_WEBSOCKET_MODE;
1804
+ const defaultMode = resolveChatGptCodexProxyConfig() ? "off" : "auto";
1803
1805
  chatGptCodexState.cachedResponsesWebSocketMode = resolveResponsesWebSocketMode(
1804
- process.env.CHATGPT_RESPONSES_WEBSOCKET_MODE ?? process.env.OPENAI_RESPONSES_WEBSOCKET_MODE,
1805
- "auto"
1806
+ explicitMode,
1807
+ defaultMode
1806
1808
  );
1807
1809
  return chatGptCodexState.cachedResponsesWebSocketMode;
1808
1810
  }
1811
+ async function resolveChatGptCodexEndpointConfig() {
1812
+ const proxy = resolveChatGptCodexProxyConfig();
1813
+ if (proxy) {
1814
+ return proxy;
1815
+ }
1816
+ const { access, accountId } = await getChatGptAuthProfile();
1817
+ return {
1818
+ kind: "direct",
1819
+ url: resolveChatGptCodexEndpoint(),
1820
+ access,
1821
+ accountId
1822
+ };
1823
+ }
1824
+ function resolveChatGptCodexEndpoint() {
1825
+ loadLocalEnv();
1826
+ return process.env[CHATGPT_CODEX_ENDPOINT_ENV]?.trim() || CHATGPT_CODEX_ENDPOINT;
1827
+ }
1828
+ function resolveChatGptCodexProxyConfig() {
1829
+ loadLocalEnv();
1830
+ const url = process.env[CHATGPT_CODEX_PROXY_URL_ENV]?.trim();
1831
+ if (!url) {
1832
+ return null;
1833
+ }
1834
+ const apiKey = process.env[CHATGPT_CODEX_PROXY_API_KEY_ENV]?.trim();
1835
+ if (!apiKey) {
1836
+ throw new Error(
1837
+ `${CHATGPT_CODEX_PROXY_API_KEY_ENV} must be provided when ${CHATGPT_CODEX_PROXY_URL_ENV} is set.`
1838
+ );
1839
+ }
1840
+ return {
1841
+ kind: "proxy",
1842
+ url,
1843
+ apiKey
1844
+ };
1845
+ }
1809
1846
  function buildChatGptCodexHeaders(options) {
1810
1847
  const openAiBeta = options.useWebSocket ? mergeOpenAiBetaHeader(
1811
1848
  CHATGPT_RESPONSES_EXPERIMENTAL_HEADER,
1812
1849
  OPENAI_BETA_RESPONSES_WEBSOCKETS_V2
1813
1850
  ) : CHATGPT_RESPONSES_EXPERIMENTAL_HEADER;
1814
1851
  const headers = {
1815
- Authorization: `Bearer ${options.access}`,
1816
- "chatgpt-account-id": options.accountId,
1817
1852
  "OpenAI-Beta": openAiBeta,
1818
1853
  originator: "llm",
1819
1854
  "User-Agent": buildUserAgent()
1820
1855
  };
1856
+ if (options.endpointConfig.kind === "proxy") {
1857
+ headers.Authorization = `Bearer ${options.endpointConfig.apiKey}`;
1858
+ headers["x-codex-proxy-auth"] = options.endpointConfig.apiKey;
1859
+ } else {
1860
+ headers.Authorization = `Bearer ${options.endpointConfig.access}`;
1861
+ headers["chatgpt-account-id"] = options.endpointConfig.accountId;
1862
+ }
1821
1863
  if (options.sessionId) {
1822
1864
  headers.session_id = options.sessionId;
1823
1865
  }