diff --git a/Cargo.lock b/Cargo.lock index 21c20018..d910f879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,6 +117,30 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-lc-rs" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.9" @@ -275,6 +299,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -351,6 +377,7 @@ dependencies = [ "http-body-util", "hyper-util", "jiff", + "jsonwebtoken", "once_cell", "predicates", "prost", @@ -378,6 +405,15 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.5" @@ -571,6 +607,12 @@ dependencies = [ "syn", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.16.0" @@ -665,6 +707,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.32" @@ -1143,6 +1191,16 @@ dependencies = [ "syn", ] +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.103" @@ -1154,6 +1212,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" +dependencies = [ + "aws-lc-rs", + "base64", + "getrandom 0.2.17", + "js-sys", + "serde", + "serde_json", + "signature", + "zeroize", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1379,6 +1453,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1735,7 +1815,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -1815,7 +1895,7 @@ checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2002,6 +2082,15 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -2511,6 +2600,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -2966,6 +3061,20 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index 27ee8e75..3bd38bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ ratatui = "0.29" crossterm = "0.28" jiff = { version = "0.2", default-features = false, features = ["std", "tz-system", "tzdb-zoneinfo"] } tiktoken-rs = { version = "0.12", default-features = false } +jsonwebtoken = { version = "11.1", default-features = false, features = ["aws_lc_rs"] } [dev-dependencies] tokio = { version = "1", features = ["test-util"] } diff --git a/README.md b/README.md index 30ab8260..d6727221 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ The opt-in Images API returns base64 image data and consumes the signed-in accou | Provider | Account | Model selection | | ------------ | ------------------------------ | ----------------------------------------------- | +| [Sign in with ChatGPT](docs/src/content/docs/providers/chatgpt-siwc.md) | Eligible ChatGPT account with plan permission | Account-specific official catalog; isolated `siwc serve` | | Codex | ChatGPT Plus or Pro | Registered `gpt-*` models, `-fast`/`-ultrafast` | | Kimi | kimi.com with Kimi Code access | `kimi-for-coding` and aliases | | Grok | grok.com | Registered Grok models | @@ -105,6 +106,12 @@ The opt-in Images API returns base64 image data and consumes the signed-in accou Run `claude-code-proxy models` for the current catalog or `claude-code-proxy models --full` for every dynamic Cursor alias. +For the official public Responses API, use the opt-in **Sign in with ChatGPT** +provider: `claude-code-proxy siwc auth login --consent`, then +`claude-code-proxy siwc models`. Follow the +[SIWC guide](docs/src/content/docs/providers/chatgpt-siwc.md) to start an isolated +local proxy and configure Claude Code with an available model. + > [!WARNING] > The proxy accepts local requests without client authentication. It binds to > `127.0.0.1` by default. Protect any non-loopback listener with a firewall or diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 93880081..d39a508a 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -44,6 +44,7 @@ export default defineConfig({ label: 'Providers', items: [ { label: 'Choosing a provider', slug: 'providers/choosing-a-provider' }, + { label: 'Sign in with ChatGPT', slug: 'providers/chatgpt-siwc' }, { label: 'Codex', slug: 'providers/codex' }, { label: 'Kimi', slug: 'providers/kimi' }, { label: 'Grok', slug: 'providers/grok' }, diff --git a/docs/src/content/docs/providers/chatgpt-siwc.md b/docs/src/content/docs/providers/chatgpt-siwc.md new file mode 100644 index 00000000..ca920edc --- /dev/null +++ b/docs/src/content/docs/providers/chatgpt-siwc.md @@ -0,0 +1,164 @@ +--- +title: Sign in with ChatGPT +description: Authorize ChatGPT plan usage through official OAuth and the public Responses API in an isolated local proxy. +--- + +The opt-in `siwc` provider uses OpenAI's official Sign in with ChatGPT flow for +eligible ChatGPT plan usage. It connects to `https://api.openai.com/v1/responses` +with the selected account's OAuth access token. This project is independent of +OpenAI and Anthropic. + +OpenAI documents this flow for open-source, locally hosted tools. Account, +workspace, model access, and usage limits remain under OpenAI's control. See +the [official overview](https://developers.openai.com/siwc/token-sharing-open-source). + +## Authorize and discover models + +```sh +claude-code-proxy siwc auth login --consent +claude-code-proxy siwc auth status +claude-code-proxy siwc models +``` + +Complete sign-in and plan permission in your browser. `plan_permission: true` +means the saved grant contains both inference scopes. Identity-only sign-in +does not permit inference; repeat login with `--consent` if needed. + +Choose an exact `id` from `siwc models`; there is no assumed default GPT model. +The catalog is fetched for your account rather than copied from the Codex +registry. See OpenAI's [models and inference guide](https://developers.openai.com/siwc/token-sharing-open-source/models-and-inference). + +Use `--profile