openai-oauth 0.0.1 → 1.0.1

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
@@ -1,5 +1,7 @@
1
1
  # openai-oauth
2
2
 
3
+ [GitHub](https://github.com/EvanZhouDev/openai-oauth) | [Legal](#legal)
4
+
3
5
  OpenAI-compatible local endpoint backed by your ChatGPT account.
4
6
 
5
7
  ## Usage
@@ -13,6 +15,7 @@ When startup succeeds, the CLI prints:
13
15
  ```text
14
16
  OpenAI-compatible endpoint ready at http://127.0.0.1:10531/v1
15
17
  Use this as your OpenAI base URL. No API key is required.
18
+ Available Models: gpt-5.4, gpt-5.3-codex, ...
16
19
  ```
17
20
 
18
21
  If no auth file is available, it fails early and tells you to run:
@@ -23,15 +26,16 @@ npx @openai/codex login
23
26
 
24
27
  ## Configuration
25
28
 
26
- | Config | CLI | Default | Description |
27
- | --- | --- | --- | --- |
28
- | Host binding | `--host` | `127.0.0.1` | Host interface the local proxy binds to. |
29
- | Port | `--port` | `10531` | Port the local proxy binds to. |
30
- | Model allowlist | `--models` | `gpt-5.4`, `gpt-5.3-codex`, `gpt-5.3-codex-spark`, `gpt-5.2`, `gpt-5.1`, `gpt-5.1-codex`, `gpt-5.1-codex-max` | Comma-separated list of model ids exposed by `/v1/models`. |
31
- | Upstream base URL | `--base-url` | `https://chatgpt.com/backend-api/codex` | Override the upstream Codex base URL. |
32
- | OAuth client id | `--oauth-client-id` | `app_EMoamEEZ73f0CkXaXp7hrann` | Override the OAuth client id used for refresh. |
33
- | OAuth token URL | `--oauth-token-url` | `https://auth.openai.com/oauth/token` | Override the OAuth token URL used for refresh. |
34
- | Auth file path | `--oauth-file` | `--oauth-file` path if provided, otherwise `$CHATGPT_LOCAL_HOME/auth.json`, `$CODEX_HOME/auth.json`, `~/.chatgpt-local/auth.json`, `~/.codex/auth.json` | Override where the local OAuth auth file is discovered. |
29
+ | Config | CLI | Default | Description |
30
+ | ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
31
+ | Host binding | `--host` | `127.0.0.1` | Host interface the local proxy binds to. |
32
+ | Port | `--port` | `10531` | Port the local proxy binds to. |
33
+ | Model allowlist | `--models` | Account-specific Codex models discovered from ChatGPT | Comma-separated list of model ids exposed by `/v1/models`. When omitted, the CLI mirrors the models your account can actually use. |
34
+ | Codex API version | `--codex-version` | Local `codex --version`, then `@openai/codex` latest from npm, then `0.111.0` | Override the Codex API client version used for model discovery. |
35
+ | Upstream base URL | `--base-url` | `https://chatgpt.com/backend-api/codex` | Override the upstream Codex base URL. |
36
+ | OAuth client id | `--oauth-client-id` | `app_EMoamEEZ73f0CkXaXp7hrann` | Override the OAuth client id used for refresh. |
37
+ | OAuth token URL | `--oauth-token-url` | `https://auth.openai.com/oauth/token` | Override the OAuth token URL used for refresh. |
38
+ | Auth file path | `--oauth-file` | `--oauth-file` path if provided, otherwise `$CHATGPT_LOCAL_HOME/auth.json`, `$CODEX_HOME/auth.json`, `~/.chatgpt-local/auth.json`, `~/.codex/auth.json` | Override where the local OAuth auth file is discovered. |
35
39
 
36
40
  ## Features
37
41
 
@@ -40,7 +44,7 @@ What currently works:
40
44
  - Working Endpoints:
41
45
  - `/v1/responses`
42
46
  - `/v1/chat/completions`
43
- - `/v1/models` (with configurable model allowlist)
47
+ - `/v1/models` (account-aware by default, or overridden with `--models`)
44
48
  - Streaming Responses
45
49
  - Toolcalls
46
50
  - Reasoning Traces
@@ -57,4 +61,16 @@ What is intentionally not there yet:
57
61
 
58
62
  OpenAI's Codex CLI uses a special endpoint at `chatgpt.com/backend-api/codex/responses` to let you use special OpenAI rate limits tied to your ChatGPT account.
59
63
 
60
- By using the same Oauth tokens as Codex, we can effectively use OpenAI's API through Oauth instead of buying API credits.
64
+ By using the same Oauth tokens as Codex, we can effectively use OpenAI's API through Oauth instead of buying API credits.
65
+
66
+ ## Legal
67
+
68
+ This is an unofficial, community-maintained project and is not affiliated with, endorsed by, or sponsored by OpenAI, Inc.
69
+
70
+ It uses your local Codex/ChatGPT authentication cache (auth.json, e.g. `~/.codex/auth.json`) and should be treated like password-equivalent credentials.
71
+
72
+ Use only for personal, local experimentation on trusted machines; do not run as a hosted service, do not share access, and do not pool or redistribute tokens.
73
+
74
+ You are solely responsible for complying with OpenAI’s Terms, policies, and any applicable agreements; misuse may result in rate limits, suspension, or termination.
75
+
76
+ Provided “as is” with no warranties; you assume all risk for data exposure, costs, and account actions.
@@ -21790,19 +21790,6 @@ import { jsonSchema as jsonSchema2, tool as tool2 } from "ai";
21790
21790
 
21791
21791
  // src/shared.ts
21792
21792
  import { Buffer as Buffer2 } from "buffer";
21793
-
21794
- // src/types.ts
21795
- var defaultOpenAIOAuthModels = [
21796
- "gpt-5.4",
21797
- "gpt-5.3-codex",
21798
- "gpt-5.3-codex-spark",
21799
- "gpt-5.2",
21800
- "gpt-5.1",
21801
- "gpt-5.1-codex",
21802
- "gpt-5.1-codex-max"
21803
- ];
21804
-
21805
- // src/shared.ts
21806
21793
  var DEFAULT_HOST = "127.0.0.1";
21807
21794
  var DEFAULT_PORT = 10531;
21808
21795
  var jsonHeaders = {
@@ -21893,16 +21880,6 @@ var usesServerReplayState = (value) => {
21893
21880
  (item) => isRecord5(item) && item.type === "item_reference" && typeof item.id === "string"
21894
21881
  );
21895
21882
  };
21896
- var resolveModels = (models) => {
21897
- if (Array.isArray(models) && models.length > 0) {
21898
- return models;
21899
- }
21900
- const envModels = process.env.CODEX_OPENAI_MODEL_IDS;
21901
- if (typeof envModels === "string" && envModels.trim().length > 0) {
21902
- return envModels.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
21903
- }
21904
- return [...defaultOpenAIOAuthModels];
21905
- };
21906
21883
  var copyUpstreamResponse = (response) => {
21907
21884
  const headers = new Headers(response.headers);
21908
21885
  for (const [key, value] of Object.entries(corsHeaders)) {
@@ -22463,6 +22440,176 @@ var handleChatCompletionsRequest = async (request, provider, logger) => {
22463
22440
  }
22464
22441
  };
22465
22442
 
22443
+ // src/models.ts
22444
+ import { execFile } from "child_process";
22445
+ import { promisify } from "util";
22446
+ var MODELS_CACHE_TTL_MS = 5 * 60 * 1e3;
22447
+ var CODEX_VERSION_CACHE_TTL_MS = 60 * 60 * 1e3;
22448
+ var REGISTRY_URL = "https://registry.npmjs.org/@openai/codex/latest";
22449
+ var FALLBACK_CODEX_CLIENT_VERSION = "0.111.0";
22450
+ var execFileAsync = promisify(execFile);
22451
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22452
+ var uniqueStrings = (values) => {
22453
+ const seen = /* @__PURE__ */ new Set();
22454
+ const result = [];
22455
+ for (const value of values) {
22456
+ if (!seen.has(value)) {
22457
+ seen.add(value);
22458
+ result.push(value);
22459
+ }
22460
+ }
22461
+ return result;
22462
+ };
22463
+ var normalizeVersion = (value) => {
22464
+ if (typeof value !== "string") {
22465
+ return void 0;
22466
+ }
22467
+ const match = value.trim().match(/\b\d+\.\d+\.\d+\b/);
22468
+ return match?.[0];
22469
+ };
22470
+ var cachedCodexClientVersion;
22471
+ var codexClientVersionCacheExpiresAt = 0;
22472
+ var inflightCodexClientVersion;
22473
+ var resolveLocalCodexClientVersion = async (runCommand) => {
22474
+ try {
22475
+ const { stdout, stderr } = await runCommand("codex", ["--version"]);
22476
+ return normalizeVersion(stdout) ?? normalizeVersion(stderr);
22477
+ } catch {
22478
+ return void 0;
22479
+ }
22480
+ };
22481
+ var resolveRemoteCodexClientVersion = async (fetchImpl) => {
22482
+ try {
22483
+ const response = await fetchImpl(REGISTRY_URL, {
22484
+ headers: {
22485
+ accept: "application/json"
22486
+ }
22487
+ });
22488
+ if (!response.ok) {
22489
+ return void 0;
22490
+ }
22491
+ const parsed = await response.json();
22492
+ return typeof parsed.version === "string" ? normalizeVersion(parsed.version) : void 0;
22493
+ } catch {
22494
+ return void 0;
22495
+ }
22496
+ };
22497
+ var resolveCodexClientVersion = async (dependencies = {}) => {
22498
+ if (typeof dependencies.codexVersion === "string" && dependencies.codexVersion.trim().length > 0) {
22499
+ return dependencies.codexVersion.trim();
22500
+ }
22501
+ const now = Date.now();
22502
+ if (cachedCodexClientVersion && now < codexClientVersionCacheExpiresAt) {
22503
+ return cachedCodexClientVersion;
22504
+ }
22505
+ if (inflightCodexClientVersion) {
22506
+ return inflightCodexClientVersion;
22507
+ }
22508
+ const runCommand = dependencies.runCommand ?? execFileAsync;
22509
+ const fetchImpl = dependencies.fetchImpl ?? globalThis.fetch;
22510
+ inflightCodexClientVersion = (async () => {
22511
+ const localVersion = await resolveLocalCodexClientVersion(runCommand);
22512
+ if (localVersion) {
22513
+ cachedCodexClientVersion = localVersion;
22514
+ codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
22515
+ inflightCodexClientVersion = void 0;
22516
+ return localVersion;
22517
+ }
22518
+ const remoteVersion = await resolveRemoteCodexClientVersion(fetchImpl);
22519
+ if (remoteVersion) {
22520
+ cachedCodexClientVersion = remoteVersion;
22521
+ codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
22522
+ inflightCodexClientVersion = void 0;
22523
+ return remoteVersion;
22524
+ }
22525
+ cachedCodexClientVersion = FALLBACK_CODEX_CLIENT_VERSION;
22526
+ codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
22527
+ dependencies.onWarning?.(
22528
+ `Could not determine the Codex API version automatically. Falling back to ${FALLBACK_CODEX_CLIENT_VERSION}. Pass a version explicitly with --codex-version if you need to override it.`
22529
+ );
22530
+ inflightCodexClientVersion = void 0;
22531
+ return FALLBACK_CODEX_CLIENT_VERSION;
22532
+ })().catch((error40) => {
22533
+ inflightCodexClientVersion = void 0;
22534
+ throw error40;
22535
+ });
22536
+ return inflightCodexClientVersion;
22537
+ };
22538
+ var toUpstreamErrorMessage = (bodyText) => {
22539
+ if (typeof bodyText !== "string" || bodyText.length === 0) {
22540
+ return "Failed to load models from Codex.";
22541
+ }
22542
+ try {
22543
+ const parsed = JSON.parse(bodyText);
22544
+ if (typeof parsed.detail === "string" && parsed.detail.length > 0) {
22545
+ return parsed.detail;
22546
+ }
22547
+ if (isRecord6(parsed.error) && typeof parsed.error.message === "string") {
22548
+ return parsed.error.message;
22549
+ }
22550
+ } catch {
22551
+ }
22552
+ return bodyText;
22553
+ };
22554
+ var fetchAvailableModels = async (client, dependencies = {}) => {
22555
+ const clientVersion = await resolveCodexClientVersion(dependencies);
22556
+ const response = await client.request(
22557
+ `/models?client_version=${encodeURIComponent(clientVersion)}`
22558
+ );
22559
+ const bodyText = await response.text();
22560
+ if (!response.ok) {
22561
+ throw new Error(toUpstreamErrorMessage(bodyText));
22562
+ }
22563
+ let parsed;
22564
+ try {
22565
+ parsed = JSON.parse(bodyText);
22566
+ } catch {
22567
+ throw new Error("Codex returned an invalid models response.");
22568
+ }
22569
+ if (!Array.isArray(parsed.models)) {
22570
+ throw new Error("Codex returned a malformed models response.");
22571
+ }
22572
+ const models = uniqueStrings(
22573
+ parsed.models.map((model) => model.slug).filter(
22574
+ (slug) => typeof slug === "string" && slug.length > 0
22575
+ )
22576
+ );
22577
+ if (models.length === 0) {
22578
+ throw new Error("Codex returned an empty models list.");
22579
+ }
22580
+ return models;
22581
+ };
22582
+ var resolveOpenAIOAuthModels = async (client, configuredModels, dependencies = {}) => {
22583
+ if (Array.isArray(configuredModels) && configuredModels.length > 0) {
22584
+ return uniqueStrings(configuredModels);
22585
+ }
22586
+ return fetchAvailableModels(client, dependencies);
22587
+ };
22588
+ var createModelResolver = (client, configuredModels, dependencies = {}) => {
22589
+ let cachedModels;
22590
+ let cacheExpiresAt = 0;
22591
+ let inflight;
22592
+ return async () => {
22593
+ const now = Date.now();
22594
+ if (cachedModels && now < cacheExpiresAt) {
22595
+ return [...cachedModels];
22596
+ }
22597
+ if (inflight) {
22598
+ return [...await inflight];
22599
+ }
22600
+ inflight = resolveOpenAIOAuthModels(client, configuredModels, dependencies).then((models) => {
22601
+ cachedModels = models;
22602
+ cacheExpiresAt = Date.now() + MODELS_CACHE_TTL_MS;
22603
+ inflight = void 0;
22604
+ return models;
22605
+ }).catch((error40) => {
22606
+ inflight = void 0;
22607
+ throw error40;
22608
+ });
22609
+ return [...await inflight];
22610
+ };
22611
+ };
22612
+
22466
22613
  // src/responses.ts
22467
22614
  var handleResponsesRequest = async (request, settings, client) => {
22468
22615
  const body = await request.json();
@@ -22507,7 +22654,7 @@ var handleResponsesRequest = async (request, settings, client) => {
22507
22654
  };
22508
22655
 
22509
22656
  // src/server.ts
22510
- var handleRoutes = async (request, settings, provider, client, models, requestLogger) => {
22657
+ var handleRoutes = async (request, settings, provider, client, resolveModels, requestLogger) => {
22511
22658
  if (request.method === "OPTIONS") {
22512
22659
  return new Response(null, {
22513
22660
  status: 204,
@@ -22522,15 +22669,24 @@ var handleRoutes = async (request, settings, provider, client, models, requestLo
22522
22669
  });
22523
22670
  }
22524
22671
  if (request.method === "GET" && url2.pathname === "/v1/models") {
22525
- return toJsonResponse({
22526
- object: "list",
22527
- data: models.map((id) => ({
22528
- id,
22529
- object: "model",
22530
- created: 0,
22531
- owned_by: "codex-oauth"
22532
- }))
22533
- });
22672
+ try {
22673
+ const models = await resolveModels();
22674
+ return toJsonResponse({
22675
+ object: "list",
22676
+ data: models.map((id) => ({
22677
+ id,
22678
+ object: "model",
22679
+ created: 0,
22680
+ owned_by: "codex-oauth"
22681
+ }))
22682
+ });
22683
+ } catch (error40) {
22684
+ return toErrorResponse(
22685
+ error40 instanceof Error ? error40.message : "Failed to load models.",
22686
+ 502,
22687
+ "upstream_error"
22688
+ );
22689
+ }
22534
22690
  }
22535
22691
  if (request.method === "POST" && url2.pathname === "/v1/responses") {
22536
22692
  return handleResponsesRequest(request, settings, client);
@@ -22541,13 +22697,15 @@ var handleRoutes = async (request, settings, provider, client, models, requestLo
22541
22697
  return toErrorResponse("Route not found.", 404, "not_found_error");
22542
22698
  };
22543
22699
  var createOpenAIOAuthFetchHandler = (settings = {}) => {
22544
- const models = resolveModels(settings.models);
22545
22700
  const sharedSettings = {
22546
22701
  ...settings,
22547
22702
  responsesState: false
22548
22703
  };
22549
22704
  const client = createCodexOAuthClient(sharedSettings);
22550
22705
  const provider = createOpenAIOAuth(sharedSettings);
22706
+ const resolveModels = createModelResolver(client, settings.models, {
22707
+ codexVersion: settings.codexVersion
22708
+ });
22551
22709
  const requestLogger = createRequestLogger(settings);
22552
22710
  return async (request) => {
22553
22711
  try {
@@ -22556,7 +22714,7 @@ var createOpenAIOAuthFetchHandler = (settings = {}) => {
22556
22714
  settings,
22557
22715
  provider,
22558
22716
  client,
22559
- models,
22717
+ resolveModels,
22560
22718
  requestLogger
22561
22719
  );
22562
22720
  } catch (error40) {
@@ -22578,6 +22736,10 @@ var startOpenAIOAuthServer = async (settings = {}) => {
22578
22736
  const response = await handler(request);
22579
22737
  await writeWebResponse(res, response);
22580
22738
  } catch (error40) {
22739
+ if (res.headersSent || res.writableEnded) {
22740
+ res.destroy(error40 instanceof Error ? error40 : void 0);
22741
+ return;
22742
+ }
22581
22743
  const message = error40 instanceof Error ? error40.message : "Unexpected server error.";
22582
22744
  await writeWebResponse(res, toErrorResponse(message, 500, "server_error"));
22583
22745
  }
@@ -22607,10 +22769,23 @@ var startOpenAIOAuthServer = async (settings = {}) => {
22607
22769
  };
22608
22770
  };
22609
22771
 
22772
+ // src/types.ts
22773
+ var defaultOpenAIOAuthModels = [
22774
+ "gpt-5.4",
22775
+ "gpt-5.3-codex",
22776
+ "gpt-5.3-codex-spark",
22777
+ "gpt-5.2",
22778
+ "gpt-5.1",
22779
+ "gpt-5.1-codex",
22780
+ "gpt-5.1-codex-max"
22781
+ ];
22782
+
22610
22783
  export {
22611
22784
  resolveAuthFileCandidates,
22612
- defaultOpenAIOAuthModels,
22785
+ createCodexOAuthClient,
22613
22786
  DEFAULT_PORT,
22787
+ resolveOpenAIOAuthModels,
22614
22788
  createOpenAIOAuthFetchHandler,
22615
- startOpenAIOAuthServer
22789
+ startOpenAIOAuthServer,
22790
+ defaultOpenAIOAuthModels
22616
22791
  };
package/dist/cli.js CHANGED
@@ -1,14 +1,188 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_PORT,
4
+ createCodexOAuthClient,
4
5
  resolveAuthFileCandidates,
6
+ resolveOpenAIOAuthModels,
5
7
  startOpenAIOAuthServer
6
- } from "./chunk-VZQM4BY4.js";
8
+ } from "./chunk-WDJBHN6N.js";
7
9
 
8
10
  // src/cli-app.ts
9
11
  import { access } from "fs/promises";
10
12
  import yargs from "yargs";
11
13
  import { hideBin } from "yargs/helpers";
14
+
15
+ // package.json
16
+ var package_default = {
17
+ name: "openai-oauth",
18
+ version: "1.0.1",
19
+ description: "Free OpenAI API access with your ChatGPT account.",
20
+ type: "module",
21
+ bin: {
22
+ "openai-oauth": "./dist/cli.js"
23
+ },
24
+ exports: {
25
+ ".": {
26
+ types: "./dist/index.d.ts",
27
+ default: "./dist/index.js"
28
+ }
29
+ },
30
+ files: [
31
+ "dist"
32
+ ],
33
+ scripts: {
34
+ build: "tsup src/index.ts src/cli.ts --format esm --dts --outDir dist --clean",
35
+ dev: "bun ./src/cli.ts",
36
+ prepublishOnly: "bun run build",
37
+ typecheck: "tsc --noEmit",
38
+ test: "vitest run",
39
+ "test:live": "LIVE_CODEX_E2E=1 vitest run test/live.e2e.test.ts"
40
+ },
41
+ dependencies: {
42
+ ai: "6.0.116",
43
+ yargs: "^17.7.2"
44
+ },
45
+ devDependencies: {
46
+ "@ai-sdk/openai": "3.0.41",
47
+ "@types/node": "20.17.24",
48
+ "@types/yargs": "^17.0.33",
49
+ "openai-oauth-core": "workspace:*",
50
+ "openai-oauth-provider": "workspace:*",
51
+ typescript: "latest",
52
+ vitest: "latest",
53
+ zod: "^3.25.76"
54
+ },
55
+ author: "EvanZhouDev",
56
+ license: "AGPL-3.0-only",
57
+ bugs: {
58
+ url: "https://github.com/EvanZhouDev/openai-oauth/issues"
59
+ },
60
+ homepage: "https://github.com/EvanZhouDev/openai-oauth#readme"
61
+ };
62
+
63
+ // src/cli-logging.ts
64
+ var ansi = {
65
+ dim: "\x1B[2m",
66
+ reset: "\x1B[0m",
67
+ underline: "\x1B[4m"
68
+ };
69
+ var formatWarningMessage = (warning, provider, model) => {
70
+ const prefix = `openai-oauth Warning (${provider} / ${model}):`;
71
+ switch (warning.type) {
72
+ case "unsupported": {
73
+ let message = `${prefix} The feature "${warning.feature}" is not supported.`;
74
+ if (warning.details) {
75
+ message += ` ${warning.details}`;
76
+ }
77
+ return message;
78
+ }
79
+ case "compatibility": {
80
+ let message = `${prefix} The feature "${warning.feature}" is used in a compatibility mode.`;
81
+ if (warning.details) {
82
+ message += ` ${warning.details}`;
83
+ }
84
+ return message;
85
+ }
86
+ case "other":
87
+ return `${prefix} ${warning.message ?? "Unknown warning."}`;
88
+ default:
89
+ return `${prefix} ${JSON.stringify(warning, null, 2)}`;
90
+ }
91
+ };
92
+ var withAnsi = (text, code, options) => {
93
+ if (!options?.useColor) {
94
+ return text;
95
+ }
96
+ return `${code}${text}${ansi.reset}`;
97
+ };
98
+ var underline = (text, options) => withAnsi(text, ansi.underline, options);
99
+ var dim = (text, options) => withAnsi(text, ansi.dim, options);
100
+ var toStartupMessage = (baseUrl, availableModels, options) => [
101
+ `OpenAI-compatible endpoint ready at ${underline(baseUrl, options)}`,
102
+ dim("Use this as your OpenAI base URL. No API key is required.", options),
103
+ "",
104
+ `Available Models: ${availableModels.join(", ")}`
105
+ ].join("\n");
106
+ var installCliWarningLogger = () => {
107
+ let hasLoggedWarningSystemMessage = false;
108
+ globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
109
+ if (warnings.length === 0) {
110
+ return;
111
+ }
112
+ if (!hasLoggedWarningSystemMessage) {
113
+ hasLoggedWarningSystemMessage = true;
114
+ console.info("");
115
+ console.info(
116
+ "openai-oauth Warning System: To turn off warning logging, set the AI_SDK_LOG_WARNINGS global to false."
117
+ );
118
+ }
119
+ for (const warning of warnings) {
120
+ console.warn(formatWarningMessage(warning, provider, model));
121
+ }
122
+ };
123
+ };
124
+
125
+ // src/update-check.ts
126
+ var REGISTRY_URL = "https://registry.npmjs.org/openai-oauth/latest";
127
+ var normalizeVersion = (value) => {
128
+ if (typeof value !== "string") {
129
+ return void 0;
130
+ }
131
+ const match = value.trim().match(/^\d+\.\d+\.\d+$/);
132
+ return match?.[0];
133
+ };
134
+ var compareSemver = (left, right) => {
135
+ const leftParts = left.split(".").map(Number);
136
+ const rightParts = right.split(".").map(Number);
137
+ for (let index = 0; index < 3; index += 1) {
138
+ const leftPart = leftParts[index] ?? 0;
139
+ const rightPart = rightParts[index] ?? 0;
140
+ if (leftPart < rightPart) {
141
+ return -1;
142
+ }
143
+ if (leftPart > rightPart) {
144
+ return 1;
145
+ }
146
+ }
147
+ return 0;
148
+ };
149
+ var fetchLatestVersion = async (fetchImpl) => {
150
+ try {
151
+ const response = await fetchImpl(REGISTRY_URL, {
152
+ headers: {
153
+ accept: "application/json"
154
+ }
155
+ });
156
+ if (!response.ok) {
157
+ return void 0;
158
+ }
159
+ const parsed = await response.json();
160
+ return typeof parsed.version === "string" ? normalizeVersion(parsed.version) : void 0;
161
+ } catch {
162
+ return void 0;
163
+ }
164
+ };
165
+ var checkForOpenAIOAuthUpdates = async (currentVersion, dependencies = {}) => {
166
+ const normalizedCurrentVersion = normalizeVersion(currentVersion);
167
+ if (normalizedCurrentVersion == null) {
168
+ return;
169
+ }
170
+ const latestVersion = await fetchLatestVersion(
171
+ dependencies.fetchImpl ?? globalThis.fetch
172
+ );
173
+ if (latestVersion == null) {
174
+ return;
175
+ }
176
+ if (compareSemver(normalizedCurrentVersion, latestVersion) >= 0) {
177
+ return;
178
+ }
179
+ dependencies.onWarning?.(
180
+ `A newer version of openai-oauth is available: ${normalizedCurrentVersion} -> ${latestVersion}.
181
+ Run \`npx openai-oauth@latest\` to use the newest version.`
182
+ );
183
+ };
184
+
185
+ // src/cli-app.ts
12
186
  var parseModels = (value) => {
13
187
  if (typeof value !== "string") {
14
188
  return void 0;
@@ -16,34 +190,66 @@ var parseModels = (value) => {
16
190
  const models = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
17
191
  return models.length > 0 ? models : void 0;
18
192
  };
193
+ var helpLines = [
194
+ "Free OpenAI API access with your ChatGPT account.",
195
+ "",
196
+ "Usage",
197
+ " npx openai-oauth@latest [options]",
198
+ "",
199
+ "Options",
200
+ " --host <host> Host interface to bind to.",
201
+ " --port <port> Port to listen on. Default: 10531",
202
+ " --models <ids> Comma-separated model ids to expose from /v1/models.",
203
+ " --codex-version <version> Codex API version to use for model discovery.",
204
+ " --base-url <url> Override the upstream Codex base URL.",
205
+ " --oauth-client-id <id> Override the OAuth client id used for refresh.",
206
+ " --oauth-token-url <url> Override the OAuth token URL used for refresh.",
207
+ " --oauth-file <path> Path to the local auth.json file.",
208
+ "",
209
+ "Flags",
210
+ " --help Show help",
211
+ ` --version Show version (${package_default.version})`,
212
+ "",
213
+ "Notes",
214
+ " If no auth file is found, run: npx @openai/codex login",
215
+ " By default, available models are discovered from your account."
216
+ ];
217
+ var createCliParser = (argv) => yargs(argv).scriptName("openai-oauth").strict().help(false).version(false).option("host", {
218
+ type: "string",
219
+ describe: "Host interface to bind to."
220
+ }).option("port", {
221
+ type: "number",
222
+ describe: "Port to listen on. Default: 10531"
223
+ }).option("models", {
224
+ type: "string",
225
+ describe: "Comma-separated model ids to expose from /v1/models.",
226
+ coerce: parseModels
227
+ }).option("codex-version", {
228
+ type: "string",
229
+ describe: "Codex API version to use for model discovery."
230
+ }).option("base-url", {
231
+ type: "string",
232
+ describe: "Override the upstream Codex base URL."
233
+ }).option("oauth-client-id", {
234
+ type: "string",
235
+ describe: "Override the OAuth client id used for refresh."
236
+ }).option("oauth-token-url", {
237
+ type: "string",
238
+ describe: "Override the OAuth token URL used for refresh."
239
+ }).option("oauth-file", {
240
+ type: "string",
241
+ describe: "Path to the local auth.json file."
242
+ });
243
+ var isHelpFlag = (argv) => argv.includes("--help") || argv.includes("-h");
244
+ var isVersionFlag = (argv) => argv.includes("--version");
245
+ var toHelpMessage = () => helpLines.join("\n");
19
246
  var parseCliArgs = (argv) => {
20
- const parsed = yargs(argv).scriptName("openai-oauth").strict().help().option("host", {
21
- type: "string",
22
- describe: "Host interface to bind the local proxy to."
23
- }).option("port", {
24
- type: "number",
25
- describe: "Port to bind the local proxy to."
26
- }).option("models", {
27
- type: "string",
28
- describe: "Comma-separated list of models exposed by /v1/models.",
29
- coerce: parseModels
30
- }).option("base-url", {
31
- type: "string",
32
- describe: "Override the upstream Codex responses base URL."
33
- }).option("oauth-client-id", {
34
- type: "string",
35
- describe: "Override the OAuth client id used for token refresh."
36
- }).option("oauth-token-url", {
37
- type: "string",
38
- describe: "Override the OAuth token URL used for token refresh."
39
- }).option("oauth-file", {
40
- type: "string",
41
- describe: "Override the auth.json file path used for local OAuth tokens."
42
- }).parseSync();
247
+ const parsed = createCliParser(argv).parseSync();
43
248
  return {
44
249
  host: parsed.host,
45
250
  port: parsed.port,
46
251
  models: parsed.models,
252
+ codexVersion: parsed.codexVersion,
47
253
  baseURL: parsed.baseUrl,
48
254
  clientId: parsed.oauthClientId,
49
255
  tokenUrl: parsed.oauthTokenUrl,
@@ -51,9 +257,10 @@ var parseCliArgs = (argv) => {
51
257
  };
52
258
  };
53
259
  var toServerOptions = (args) => ({
54
- host: args.host ?? process.env.HOST,
55
- port: args.port ?? Number(process.env.PORT ?? String(DEFAULT_PORT)),
260
+ host: args.host,
261
+ port: args.port ?? DEFAULT_PORT,
56
262
  models: args.models,
263
+ codexVersion: args.codexVersion,
57
264
  baseURL: args.baseURL,
58
265
  clientId: args.clientId,
59
266
  tokenUrl: args.tokenUrl,
@@ -82,28 +289,51 @@ var toMissingAuthFileMessage = (authFilePath) => {
82
289
  "Run `npx @openai/codex login` and try again."
83
290
  ].join("\n");
84
291
  };
85
- var toStartupMessage = (options) => {
86
- const baseUrl = `http://${options.host ?? "127.0.0.1"}:${options.port ?? DEFAULT_PORT}/v1`;
87
- return [
88
- `OpenAI-compatible endpoint ready at ${baseUrl}`,
89
- "Use this as your OpenAI base URL. No API key is required."
90
- ].join("\n");
91
- };
92
292
  var runCli = async (argv = hideBin(process.argv)) => {
293
+ if (isHelpFlag(argv)) {
294
+ console.log(toHelpMessage());
295
+ return;
296
+ }
297
+ if (isVersionFlag(argv)) {
298
+ console.log(package_default.version);
299
+ return;
300
+ }
301
+ installCliWarningLogger();
93
302
  const args = parseCliArgs(argv);
94
303
  const options = toServerOptions(args);
95
304
  const existingAuthFile = await findExistingAuthFile(options.authFilePath);
96
305
  if (!existingAuthFile) {
97
306
  throw new Error(toMissingAuthFileMessage(options.authFilePath));
98
307
  }
308
+ const client = createCodexOAuthClient({
309
+ ...options,
310
+ responsesState: false
311
+ });
312
+ const availableModels = await resolveOpenAIOAuthModels(
313
+ client,
314
+ options.models,
315
+ {
316
+ codexVersion: options.codexVersion,
317
+ onWarning: (message) => {
318
+ console.error(message);
319
+ }
320
+ }
321
+ );
99
322
  const server = await startOpenAIOAuthServer(options);
100
323
  console.log(
101
- toStartupMessage({
102
- ...options,
103
- host: server.host,
104
- port: server.port
105
- })
324
+ toStartupMessage(
325
+ `http://${server.host}:${server.port}/v1`,
326
+ availableModels,
327
+ {
328
+ useColor: process.stdout.isTTY
329
+ }
330
+ )
106
331
  );
332
+ void checkForOpenAIOAuthUpdates(package_default.version, {
333
+ onWarning: (message) => {
334
+ console.error(message);
335
+ }
336
+ });
107
337
  const shutdown = async () => {
108
338
  await server.close();
109
339
  process.exit(0);
package/dist/index.d.ts CHANGED
@@ -45,6 +45,7 @@ declare class CodexResponsesState {
45
45
 
46
46
  type CodexOAuthSettings = Omit<AuthLoaderOptions, "fetch"> & {
47
47
  baseURL?: string;
48
+ codexVersion?: string;
48
49
  fetch?: FetchFunction;
49
50
  headers?: Record<string, string>;
50
51
  instructions?: string;
@@ -137,6 +138,7 @@ type OpenAIOAuthServerOptions = Omit<CodexOAuthSettings, "responsesState"> & {
137
138
  host?: string;
138
139
  port?: number;
139
140
  models?: string[];
141
+ codexVersion?: string;
140
142
  requestLogger?: (event: OpenAIOAuthServerLogEvent) => void;
141
143
  };
142
144
  type RunningOpenAIOAuthServer = {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  createOpenAIOAuthFetchHandler,
3
3
  defaultOpenAIOAuthModels,
4
4
  startOpenAIOAuthServer
5
- } from "./chunk-VZQM4BY4.js";
5
+ } from "./chunk-WDJBHN6N.js";
6
6
  export {
7
7
  createOpenAIOAuthFetchHandler,
8
8
  defaultOpenAIOAuthModels,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openai-oauth",
3
- "version": "0.0.1",
3
+ "version": "1.0.1",
4
4
  "description": "Free OpenAI API access with your ChatGPT account.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,16 +18,17 @@
18
18
  "scripts": {
19
19
  "build": "tsup src/index.ts src/cli.ts --format esm --dts --outDir dist --clean",
20
20
  "dev": "bun ./src/cli.ts",
21
+ "prepublishOnly": "bun run build",
21
22
  "typecheck": "tsc --noEmit",
22
23
  "test": "vitest run",
23
24
  "test:live": "LIVE_CODEX_E2E=1 vitest run test/live.e2e.test.ts"
24
25
  },
25
26
  "dependencies": {
26
- "ai": "6.0.0-beta.138",
27
+ "ai": "6.0.116",
27
28
  "yargs": "^17.7.2"
28
29
  },
29
30
  "devDependencies": {
30
- "@ai-sdk/openai": "3.0.0-beta.87",
31
+ "@ai-sdk/openai": "3.0.41",
31
32
  "@types/node": "20.17.24",
32
33
  "@types/yargs": "^17.0.33",
33
34
  "openai-oauth-core": "workspace:*",