openai-oauth 0.0.1 → 1.0.0
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 +27 -11
- package/dist/{chunk-VZQM4BY4.js → chunk-WDJBHN6N.js} +212 -37
- package/dist/cli.js +267 -38
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
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
|
|
27
|
-
|
|
|
28
|
-
| Host binding
|
|
29
|
-
| Port
|
|
30
|
-
| Model allowlist
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
| OAuth
|
|
34
|
-
|
|
|
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` (
|
|
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,
|
|
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
|
-
|
|
22526
|
-
|
|
22527
|
-
|
|
22528
|
-
|
|
22529
|
-
|
|
22530
|
-
|
|
22531
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,187 @@
|
|
|
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-
|
|
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.0",
|
|
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
|
+
typecheck: "tsc --noEmit",
|
|
37
|
+
test: "vitest run",
|
|
38
|
+
"test:live": "LIVE_CODEX_E2E=1 vitest run test/live.e2e.test.ts"
|
|
39
|
+
},
|
|
40
|
+
dependencies: {
|
|
41
|
+
ai: "6.0.0-beta.138",
|
|
42
|
+
yargs: "^17.7.2"
|
|
43
|
+
},
|
|
44
|
+
devDependencies: {
|
|
45
|
+
"@ai-sdk/openai": "3.0.0-beta.87",
|
|
46
|
+
"@types/node": "20.17.24",
|
|
47
|
+
"@types/yargs": "^17.0.33",
|
|
48
|
+
"openai-oauth-core": "workspace:*",
|
|
49
|
+
"openai-oauth-provider": "workspace:*",
|
|
50
|
+
typescript: "latest",
|
|
51
|
+
vitest: "latest",
|
|
52
|
+
zod: "^3.25.76"
|
|
53
|
+
},
|
|
54
|
+
author: "EvanZhouDev",
|
|
55
|
+
license: "AGPL-3.0-only",
|
|
56
|
+
bugs: {
|
|
57
|
+
url: "https://github.com/EvanZhouDev/openai-oauth/issues"
|
|
58
|
+
},
|
|
59
|
+
homepage: "https://github.com/EvanZhouDev/openai-oauth#readme"
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/cli-logging.ts
|
|
63
|
+
var ansi = {
|
|
64
|
+
dim: "\x1B[2m",
|
|
65
|
+
reset: "\x1B[0m",
|
|
66
|
+
underline: "\x1B[4m"
|
|
67
|
+
};
|
|
68
|
+
var formatWarningMessage = (warning, provider, model) => {
|
|
69
|
+
const prefix = `openai-oauth Warning (${provider} / ${model}):`;
|
|
70
|
+
switch (warning.type) {
|
|
71
|
+
case "unsupported": {
|
|
72
|
+
let message = `${prefix} The feature "${warning.feature}" is not supported.`;
|
|
73
|
+
if (warning.details) {
|
|
74
|
+
message += ` ${warning.details}`;
|
|
75
|
+
}
|
|
76
|
+
return message;
|
|
77
|
+
}
|
|
78
|
+
case "compatibility": {
|
|
79
|
+
let message = `${prefix} The feature "${warning.feature}" is used in a compatibility mode.`;
|
|
80
|
+
if (warning.details) {
|
|
81
|
+
message += ` ${warning.details}`;
|
|
82
|
+
}
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
case "other":
|
|
86
|
+
return `${prefix} ${warning.message ?? "Unknown warning."}`;
|
|
87
|
+
default:
|
|
88
|
+
return `${prefix} ${JSON.stringify(warning, null, 2)}`;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var withAnsi = (text, code, options) => {
|
|
92
|
+
if (!options?.useColor) {
|
|
93
|
+
return text;
|
|
94
|
+
}
|
|
95
|
+
return `${code}${text}${ansi.reset}`;
|
|
96
|
+
};
|
|
97
|
+
var underline = (text, options) => withAnsi(text, ansi.underline, options);
|
|
98
|
+
var dim = (text, options) => withAnsi(text, ansi.dim, options);
|
|
99
|
+
var toStartupMessage = (baseUrl, availableModels, options) => [
|
|
100
|
+
`OpenAI-compatible endpoint ready at ${underline(baseUrl, options)}`,
|
|
101
|
+
dim("Use this as your OpenAI base URL. No API key is required.", options),
|
|
102
|
+
"",
|
|
103
|
+
`Available Models: ${availableModels.join(", ")}`
|
|
104
|
+
].join("\n");
|
|
105
|
+
var installCliWarningLogger = () => {
|
|
106
|
+
let hasLoggedWarningSystemMessage = false;
|
|
107
|
+
globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
|
|
108
|
+
if (warnings.length === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!hasLoggedWarningSystemMessage) {
|
|
112
|
+
hasLoggedWarningSystemMessage = true;
|
|
113
|
+
console.info("");
|
|
114
|
+
console.info(
|
|
115
|
+
"openai-oauth Warning System: To turn off warning logging, set the AI_SDK_LOG_WARNINGS global to false."
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
for (const warning of warnings) {
|
|
119
|
+
console.warn(formatWarningMessage(warning, provider, model));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/update-check.ts
|
|
125
|
+
var REGISTRY_URL = "https://registry.npmjs.org/openai-oauth/latest";
|
|
126
|
+
var normalizeVersion = (value) => {
|
|
127
|
+
if (typeof value !== "string") {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
const match = value.trim().match(/^\d+\.\d+\.\d+$/);
|
|
131
|
+
return match?.[0];
|
|
132
|
+
};
|
|
133
|
+
var compareSemver = (left, right) => {
|
|
134
|
+
const leftParts = left.split(".").map(Number);
|
|
135
|
+
const rightParts = right.split(".").map(Number);
|
|
136
|
+
for (let index = 0; index < 3; index += 1) {
|
|
137
|
+
const leftPart = leftParts[index] ?? 0;
|
|
138
|
+
const rightPart = rightParts[index] ?? 0;
|
|
139
|
+
if (leftPart < rightPart) {
|
|
140
|
+
return -1;
|
|
141
|
+
}
|
|
142
|
+
if (leftPart > rightPart) {
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return 0;
|
|
147
|
+
};
|
|
148
|
+
var fetchLatestVersion = async (fetchImpl) => {
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetchImpl(REGISTRY_URL, {
|
|
151
|
+
headers: {
|
|
152
|
+
accept: "application/json"
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
return void 0;
|
|
157
|
+
}
|
|
158
|
+
const parsed = await response.json();
|
|
159
|
+
return typeof parsed.version === "string" ? normalizeVersion(parsed.version) : void 0;
|
|
160
|
+
} catch {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
var checkForOpenAIOAuthUpdates = async (currentVersion, dependencies = {}) => {
|
|
165
|
+
const normalizedCurrentVersion = normalizeVersion(currentVersion);
|
|
166
|
+
if (normalizedCurrentVersion == null) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const latestVersion = await fetchLatestVersion(
|
|
170
|
+
dependencies.fetchImpl ?? globalThis.fetch
|
|
171
|
+
);
|
|
172
|
+
if (latestVersion == null) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (compareSemver(normalizedCurrentVersion, latestVersion) >= 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
dependencies.onWarning?.(
|
|
179
|
+
`A newer version of openai-oauth is available: ${normalizedCurrentVersion} -> ${latestVersion}.
|
|
180
|
+
Run \`npx openai-oauth@latest\` to use the newest version.`
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/cli-app.ts
|
|
12
185
|
var parseModels = (value) => {
|
|
13
186
|
if (typeof value !== "string") {
|
|
14
187
|
return void 0;
|
|
@@ -16,34 +189,66 @@ var parseModels = (value) => {
|
|
|
16
189
|
const models = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
17
190
|
return models.length > 0 ? models : void 0;
|
|
18
191
|
};
|
|
192
|
+
var helpLines = [
|
|
193
|
+
"Free OpenAI API access with your ChatGPT account.",
|
|
194
|
+
"",
|
|
195
|
+
"Usage",
|
|
196
|
+
" npx openai-oauth@latest [options]",
|
|
197
|
+
"",
|
|
198
|
+
"Options",
|
|
199
|
+
" --host <host> Host interface to bind to.",
|
|
200
|
+
" --port <port> Port to listen on. Default: 10531",
|
|
201
|
+
" --models <ids> Comma-separated model ids to expose from /v1/models.",
|
|
202
|
+
" --codex-version <version> Codex API version to use for model discovery.",
|
|
203
|
+
" --base-url <url> Override the upstream Codex base URL.",
|
|
204
|
+
" --oauth-client-id <id> Override the OAuth client id used for refresh.",
|
|
205
|
+
" --oauth-token-url <url> Override the OAuth token URL used for refresh.",
|
|
206
|
+
" --oauth-file <path> Path to the local auth.json file.",
|
|
207
|
+
"",
|
|
208
|
+
"Flags",
|
|
209
|
+
" --help Show help",
|
|
210
|
+
` --version Show version (${package_default.version})`,
|
|
211
|
+
"",
|
|
212
|
+
"Notes",
|
|
213
|
+
" If no auth file is found, run: npx @openai/codex login",
|
|
214
|
+
" By default, available models are discovered from your account."
|
|
215
|
+
];
|
|
216
|
+
var createCliParser = (argv) => yargs(argv).scriptName("openai-oauth").strict().help(false).version(false).option("host", {
|
|
217
|
+
type: "string",
|
|
218
|
+
describe: "Host interface to bind to."
|
|
219
|
+
}).option("port", {
|
|
220
|
+
type: "number",
|
|
221
|
+
describe: "Port to listen on. Default: 10531"
|
|
222
|
+
}).option("models", {
|
|
223
|
+
type: "string",
|
|
224
|
+
describe: "Comma-separated model ids to expose from /v1/models.",
|
|
225
|
+
coerce: parseModels
|
|
226
|
+
}).option("codex-version", {
|
|
227
|
+
type: "string",
|
|
228
|
+
describe: "Codex API version to use for model discovery."
|
|
229
|
+
}).option("base-url", {
|
|
230
|
+
type: "string",
|
|
231
|
+
describe: "Override the upstream Codex base URL."
|
|
232
|
+
}).option("oauth-client-id", {
|
|
233
|
+
type: "string",
|
|
234
|
+
describe: "Override the OAuth client id used for refresh."
|
|
235
|
+
}).option("oauth-token-url", {
|
|
236
|
+
type: "string",
|
|
237
|
+
describe: "Override the OAuth token URL used for refresh."
|
|
238
|
+
}).option("oauth-file", {
|
|
239
|
+
type: "string",
|
|
240
|
+
describe: "Path to the local auth.json file."
|
|
241
|
+
});
|
|
242
|
+
var isHelpFlag = (argv) => argv.includes("--help") || argv.includes("-h");
|
|
243
|
+
var isVersionFlag = (argv) => argv.includes("--version");
|
|
244
|
+
var toHelpMessage = () => helpLines.join("\n");
|
|
19
245
|
var parseCliArgs = (argv) => {
|
|
20
|
-
const parsed =
|
|
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();
|
|
246
|
+
const parsed = createCliParser(argv).parseSync();
|
|
43
247
|
return {
|
|
44
248
|
host: parsed.host,
|
|
45
249
|
port: parsed.port,
|
|
46
250
|
models: parsed.models,
|
|
251
|
+
codexVersion: parsed.codexVersion,
|
|
47
252
|
baseURL: parsed.baseUrl,
|
|
48
253
|
clientId: parsed.oauthClientId,
|
|
49
254
|
tokenUrl: parsed.oauthTokenUrl,
|
|
@@ -51,9 +256,10 @@ var parseCliArgs = (argv) => {
|
|
|
51
256
|
};
|
|
52
257
|
};
|
|
53
258
|
var toServerOptions = (args) => ({
|
|
54
|
-
host: args.host
|
|
55
|
-
port: args.port ??
|
|
259
|
+
host: args.host,
|
|
260
|
+
port: args.port ?? DEFAULT_PORT,
|
|
56
261
|
models: args.models,
|
|
262
|
+
codexVersion: args.codexVersion,
|
|
57
263
|
baseURL: args.baseURL,
|
|
58
264
|
clientId: args.clientId,
|
|
59
265
|
tokenUrl: args.tokenUrl,
|
|
@@ -82,28 +288,51 @@ var toMissingAuthFileMessage = (authFilePath) => {
|
|
|
82
288
|
"Run `npx @openai/codex login` and try again."
|
|
83
289
|
].join("\n");
|
|
84
290
|
};
|
|
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
291
|
var runCli = async (argv = hideBin(process.argv)) => {
|
|
292
|
+
if (isHelpFlag(argv)) {
|
|
293
|
+
console.log(toHelpMessage());
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (isVersionFlag(argv)) {
|
|
297
|
+
console.log(package_default.version);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
installCliWarningLogger();
|
|
93
301
|
const args = parseCliArgs(argv);
|
|
94
302
|
const options = toServerOptions(args);
|
|
95
303
|
const existingAuthFile = await findExistingAuthFile(options.authFilePath);
|
|
96
304
|
if (!existingAuthFile) {
|
|
97
305
|
throw new Error(toMissingAuthFileMessage(options.authFilePath));
|
|
98
306
|
}
|
|
307
|
+
const client = createCodexOAuthClient({
|
|
308
|
+
...options,
|
|
309
|
+
responsesState: false
|
|
310
|
+
});
|
|
311
|
+
const availableModels = await resolveOpenAIOAuthModels(
|
|
312
|
+
client,
|
|
313
|
+
options.models,
|
|
314
|
+
{
|
|
315
|
+
codexVersion: options.codexVersion,
|
|
316
|
+
onWarning: (message) => {
|
|
317
|
+
console.error(message);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
);
|
|
99
321
|
const server = await startOpenAIOAuthServer(options);
|
|
100
322
|
console.log(
|
|
101
|
-
toStartupMessage(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
323
|
+
toStartupMessage(
|
|
324
|
+
`http://${server.host}:${server.port}/v1`,
|
|
325
|
+
availableModels,
|
|
326
|
+
{
|
|
327
|
+
useColor: process.stdout.isTTY
|
|
328
|
+
}
|
|
329
|
+
)
|
|
106
330
|
);
|
|
331
|
+
void checkForOpenAIOAuthUpdates(package_default.version, {
|
|
332
|
+
onWarning: (message) => {
|
|
333
|
+
console.error(message);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
107
336
|
const shutdown = async () => {
|
|
108
337
|
await server.close();
|
|
109
338
|
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