openai-ws-opencode 0.1.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/SECURITY.md +11 -0
  4. package/bin/setup.js +239 -0
  5. package/dist/auth/oauth.d.ts +55 -0
  6. package/dist/auth/oauth.d.ts.map +1 -0
  7. package/dist/auth/oauth.js +205 -0
  8. package/dist/auth/oauth.js.map +1 -0
  9. package/dist/auth/tokens.d.ts +19 -0
  10. package/dist/auth/tokens.d.ts.map +1 -0
  11. package/dist/auth/tokens.js +59 -0
  12. package/dist/auth/tokens.js.map +1 -0
  13. package/dist/constants.d.ts +17 -0
  14. package/dist/constants.d.ts.map +1 -0
  15. package/dist/constants.js +17 -0
  16. package/dist/constants.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/models/defaults.d.ts +16 -0
  22. package/dist/models/defaults.d.ts.map +1 -0
  23. package/dist/models/defaults.js +82 -0
  24. package/dist/models/defaults.js.map +1 -0
  25. package/dist/models/resolve.d.ts +112 -0
  26. package/dist/models/resolve.d.ts.map +1 -0
  27. package/dist/models/resolve.js +159 -0
  28. package/dist/models/resolve.js.map +1 -0
  29. package/dist/plugin.d.ts +4 -0
  30. package/dist/plugin.d.ts.map +1 -0
  31. package/dist/plugin.js +142 -0
  32. package/dist/plugin.js.map +1 -0
  33. package/dist/testing.d.ts +7 -0
  34. package/dist/testing.d.ts.map +1 -0
  35. package/dist/testing.js +7 -0
  36. package/dist/testing.js.map +1 -0
  37. package/dist/transport/body.d.ts +3 -0
  38. package/dist/transport/body.d.ts.map +1 -0
  39. package/dist/transport/body.js +22 -0
  40. package/dist/transport/body.js.map +1 -0
  41. package/dist/transport/bridge.d.ts +3 -0
  42. package/dist/transport/bridge.d.ts.map +1 -0
  43. package/dist/transport/bridge.js +78 -0
  44. package/dist/transport/bridge.js.map +1 -0
  45. package/dist/transport/headers.d.ts +32 -0
  46. package/dist/transport/headers.d.ts.map +1 -0
  47. package/dist/transport/headers.js +55 -0
  48. package/dist/transport/headers.js.map +1 -0
  49. package/dist/transport/pool.d.ts +56 -0
  50. package/dist/transport/pool.d.ts.map +1 -0
  51. package/dist/transport/pool.js +259 -0
  52. package/dist/transport/pool.js.map +1 -0
  53. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jared Boynton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # openai-ws-opencode
2
+
3
+ Standalone OpenCode provider plugin for routing OpenAI Responses API streaming requests over WebSocket transport.
4
+
5
+ ## What it provides
6
+
7
+ - Provider id: `openai-ws`
8
+ - API-key transport: `wss://api.openai.com/v1/responses`
9
+ - ChatGPT/Codex OAuth transport: `wss://chatgpt.com/backend-api/codex/responses`
10
+ - Curated GPT/Codex model table with OpenCode variants
11
+ - Best-effort model refresh from OpenCode's public `models.dev` catalog at auth-load time
12
+ - WebSocket-to-SSE bridge so OpenCode can keep using its normal streaming path
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ npm install -g openai-ws-opencode
18
+ openai-ws-opencode setup --global
19
+ opencode auth login openai-ws
20
+ ```
21
+
22
+ For project-local setup:
23
+
24
+ ```sh
25
+ openai-ws-opencode setup --project
26
+ opencode auth login openai-ws
27
+ ```
28
+
29
+ The setup command is idempotent. It adds the npm plugin entry and a fallback `provider.openai-ws` model registry to `opencode.json`, preserving existing user model overrides.
30
+
31
+ At runtime the plugin also attempts a short best-effort fetch from `https://models.dev/api.json` to pick up newly listed GPT/Codex models. It falls back to the bundled table if the catalog is unavailable. The OpenAI `/v1/models` endpoint is not used for catalog discovery because it requires authentication and returns only basic IDs/ownership metadata, not OpenCode capabilities, limits, or variants.
32
+
33
+ Set `OPENAI_WS_OPENCODE_SKIP_CATALOG=1` to skip the `models.dev` lookup and use only the bundled fallback table plus your config overrides.
34
+
35
+ ## Manual config
36
+
37
+ If you do not want to use the setup command, add this shape to your OpenCode config and copy model entries from `openai-ws-opencode setup --path <temp-file>` output:
38
+
39
+ ```jsonc
40
+ {
41
+ "$schema": "https://opencode.ai/config.json",
42
+ "plugin": ["openai-ws-opencode@latest"],
43
+ "provider": {
44
+ "openai-ws": {
45
+ "name": "OpenAI WebSocket",
46
+ "api": "https://api.openai.com/v1",
47
+ "npm": "@ai-sdk/openai",
48
+ "models": {
49
+ "gpt-5.3-codex": {
50
+ "name": "GPT 5.3 Codex (WebSocket)",
51
+ "reasoning": true,
52
+ "temperature": false,
53
+ "attachment": true,
54
+ "tool_call": true,
55
+ "limit": { "context": 400000, "input": 272000, "output": 128000 },
56
+ "modalities": { "input": ["text", "image"], "output": ["text"] },
57
+ "options": {},
58
+ "provider": { "npm": "@ai-sdk/openai", "api": "https://api.openai.com/v1" }
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Auth modes
67
+
68
+ ### API key
69
+
70
+ Choose **OpenAI (WebSocket) - API Key** during `opencode auth login openai-ws`.
71
+
72
+ This path uses the public OpenAI Responses WebSocket endpoint and sends:
73
+
74
+ - `Authorization: Bearer <OPENAI_API_KEY>`
75
+ - `OpenAI-Beta: responses_websockets=2026-02-06`
76
+
77
+ ### ChatGPT/Codex OAuth
78
+
79
+ Choose the browser or headless ChatGPT Pro/Plus OAuth option during `opencode auth login openai-ws`.
80
+
81
+ This path uses the Codex/ChatGPT backend and sends:
82
+
83
+ - `Authorization: Bearer <access-token>`
84
+ - `ChatGPT-Account-Id` when available
85
+ - `originator: codex_cli_rs`
86
+ - `OpenAI-Beta: responses_websockets=2026-02-06`
87
+
88
+ Important: the OAuth/Codex WebSocket path is unofficial, reuses Codex OAuth behavior, targets a private ChatGPT backend, can break without notice, and may carry account or terms-of-service risk. This project is not affiliated with OpenAI or OpenCode.
89
+
90
+ ## Local development install
91
+
92
+ From this repo:
93
+
94
+ ```sh
95
+ bun install
96
+ bun run build
97
+ npm pack
98
+ ```
99
+
100
+ Then point OpenCode at the packed tarball through the plugin array:
101
+
102
+ ```jsonc
103
+ {
104
+ "plugin": ["openai-ws-opencode@file:/absolute/path/openai-ws-opencode-0.1.0.tgz"]
105
+ }
106
+ ```
107
+
108
+ Direct `file:///absolute/path/dist/index.js` imports are useful for debugging, but npm/tarball installs better match how OpenCode resolves dependencies.
109
+
110
+ ## Development
111
+
112
+ ```sh
113
+ bun install
114
+ bun run lint
115
+ bun run typecheck
116
+ bun test
117
+ bun run build
118
+ npm pack --dry-run
119
+ ```
120
+
121
+ The root package export intentionally exposes only the OpenCode plugin function because OpenCode calls every function exported by the root module. Test helpers are available from `openai-ws-opencode/testing`.
122
+
123
+ ## Troubleshooting
124
+
125
+ - **Provider is not visible:** run `openai-ws-opencode setup --global` or add `provider.openai-ws` manually.
126
+ - **Missing auth:** run `opencode auth login openai-ws`.
127
+ - **OAuth refresh failed:** re-run `opencode auth login openai-ws` and choose a ChatGPT OAuth method.
128
+ - **WebSocket 401/403:** verify the selected auth mode and account entitlement. OAuth/Codex behavior may change upstream.
129
+ - **Local `file://` install cannot find `ws`:** use the packed tarball install path or add dependencies to your OpenCode config directory.
package/SECURITY.md ADDED
@@ -0,0 +1,11 @@
1
+ # Security Policy
2
+
3
+ Report security issues privately to the repository owner instead of opening a public issue.
4
+
5
+ This plugin does not intentionally log API keys, OAuth access tokens, refresh tokens, authorization headers, or account IDs. Do not attach secrets, auth JSON files, screenshots containing tokens, or raw request headers to bug reports.
6
+
7
+ The ChatGPT/Codex OAuth transport uses unofficial backend behavior and may change without notice. Review your OpenAI/OpenCode account policies before using it. If the Codex OAuth client or backend behavior is revoked or materially changes, the mitigation path is to disable the OAuth/Codex auth method in a patch release while keeping API-key transport available.
8
+
9
+ The plugin performs a short best-effort request to `https://models.dev/api.json` to refresh public model metadata unless `OPENAI_WS_OPENCODE_SKIP_CATALOG=1` is set. No credentials are sent to that catalog endpoint.
10
+
11
+ Before release, maintainers should run validators, inspect `git diff`, run a secrets scan, and inspect `npm pack --dry-run` output for accidental credential or local-config inclusion.
package/bin/setup.js ADDED
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+
3
+ // bin/setup.ts
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { applyEdits, format, modify, parse } from "jsonc-parser";
8
+
9
+ // src/constants.ts
10
+ var OPENAI_API_BASE = "https://api.openai.com/v1";
11
+ var CODEX_API_BASE = "https://chatgpt.com/backend-api/codex";
12
+ var CODEX_API_ENDPOINT = `${CODEX_API_BASE}/responses`;
13
+
14
+ // src/models/defaults.ts
15
+ function makeVariants(efforts, includeSummary = true) {
16
+ return Object.fromEntries(efforts.map((effort) => [
17
+ effort,
18
+ {
19
+ reasoningEffort: effort,
20
+ ...includeSummary ? { reasoningSummary: "auto" } : {},
21
+ include: ["reasoning.encrypted_content"]
22
+ }
23
+ ]));
24
+ }
25
+ var OPENAI_WS_MODELS = {
26
+ "gpt-5.5": {
27
+ name: "GPT 5.5 (WebSocket)",
28
+ reasoning: true,
29
+ temperature: false,
30
+ limit: { context: 1050000, input: 922000, output: 128000 },
31
+ variants: makeVariants(["none", "minimal", "low", "medium", "high"]),
32
+ release_date: "2026-04-23"
33
+ },
34
+ "gpt-5.5-pro": {
35
+ name: "GPT 5.5 Pro (WebSocket)",
36
+ reasoning: true,
37
+ temperature: false,
38
+ limit: { context: 1050000, input: 922000, output: 128000 },
39
+ variants: makeVariants(["high"], false),
40
+ family: "gpt-pro",
41
+ release_date: "2026-04-23"
42
+ },
43
+ "gpt-5.4": {
44
+ name: "GPT 5.4 (WebSocket)",
45
+ reasoning: true,
46
+ temperature: true,
47
+ limit: { context: 1050000, output: 128000 },
48
+ variants: makeVariants(["none", "minimal", "low", "medium", "high"]),
49
+ release_date: "2026-03-05"
50
+ },
51
+ "gpt-5.3-codex": {
52
+ name: "GPT 5.3 Codex (WebSocket)",
53
+ reasoning: true,
54
+ temperature: false,
55
+ limit: { context: 400000, input: 272000, output: 128000 },
56
+ variants: makeVariants(["low", "medium", "high"]),
57
+ family: "gpt-codex",
58
+ release_date: "2026-02-05"
59
+ },
60
+ "gpt-5.3-codex-spark": {
61
+ name: "GPT 5.3 Codex Spark (WebSocket)",
62
+ reasoning: true,
63
+ temperature: false,
64
+ limit: { context: 128000, output: 128000 },
65
+ variants: makeVariants(["low", "medium", "high"], false),
66
+ family: "gpt-codex",
67
+ release_date: "2026-03-10"
68
+ },
69
+ "gpt-5.2": {
70
+ name: "GPT 5.2 (WebSocket)",
71
+ reasoning: true,
72
+ temperature: true,
73
+ limit: { context: 272000, output: 128000 },
74
+ variants: makeVariants(["none", "low", "medium", "high"]),
75
+ release_date: "2025-12-10"
76
+ },
77
+ "gpt-5.2-codex": {
78
+ name: "GPT 5.2 Codex (WebSocket)",
79
+ reasoning: true,
80
+ temperature: false,
81
+ limit: { context: 272000, output: 128000 },
82
+ variants: makeVariants(["low", "medium", "high"]),
83
+ family: "gpt-codex",
84
+ release_date: "2025-12-15"
85
+ },
86
+ "gpt-5.1-codex": {
87
+ name: "GPT 5.1 Codex (WebSocket)",
88
+ reasoning: true,
89
+ temperature: false,
90
+ limit: { context: 272000, output: 128000 },
91
+ variants: makeVariants(["low", "medium", "high"]),
92
+ family: "gpt-codex",
93
+ release_date: "2025-10-01"
94
+ }
95
+ };
96
+
97
+ // src/models/resolve.ts
98
+ function modelToOpenCodeConfig(model) {
99
+ return {
100
+ name: model.name,
101
+ ...model.family ? { family: model.family } : {},
102
+ ...model.release_date ? { release_date: model.release_date } : {},
103
+ attachment: true,
104
+ reasoning: model.reasoning,
105
+ temperature: model.temperature,
106
+ tool_call: true,
107
+ limit: model.limit,
108
+ modalities: {
109
+ input: ["text", "image"],
110
+ output: ["text"]
111
+ },
112
+ cost: { input: 0, output: 0, cache_read: 0, cache_write: 0 },
113
+ options: {},
114
+ provider: { npm: "@ai-sdk/openai", api: OPENAI_API_BASE },
115
+ variants: model.variants
116
+ };
117
+ }
118
+ function providerConfigModels(overrides = {}) {
119
+ const models = {};
120
+ for (const [id, model] of Object.entries(OPENAI_WS_MODELS)) {
121
+ models[id] = {
122
+ ...modelToOpenCodeConfig(model),
123
+ ...overrides[id] ?? {}
124
+ };
125
+ }
126
+ for (const [id, override] of Object.entries(overrides)) {
127
+ if (models[id])
128
+ continue;
129
+ models[id] = override;
130
+ }
131
+ return models;
132
+ }
133
+ function providerConfig(overrides = {}) {
134
+ return {
135
+ api: OPENAI_API_BASE,
136
+ name: "OpenAI WebSocket",
137
+ npm: "@ai-sdk/openai",
138
+ models: providerConfigModels(overrides)
139
+ };
140
+ }
141
+
142
+ // bin/setup.ts
143
+ var DEFAULT_PLUGIN_SPEC = "openai-ws-opencode@latest";
144
+ var PACKAGE_NAME = "openai-ws-opencode";
145
+ var SCHEMA = "https://opencode.ai/config.json";
146
+ function globalConfigPath() {
147
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
148
+ return path.join(configHome, "opencode", "opencode.json");
149
+ }
150
+ function projectConfigPath(cwd) {
151
+ return path.join(cwd, "opencode.json");
152
+ }
153
+ function targetPath(options) {
154
+ if (options.configPath)
155
+ return path.resolve(options.configPath);
156
+ if (options.mode === "project")
157
+ return projectConfigPath(options.cwd ?? process.cwd());
158
+ return globalConfigPath();
159
+ }
160
+ function applyJsonPatch(text, jsonPath, value) {
161
+ return applyEdits(text, modify(text, jsonPath, value, {
162
+ formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
163
+ ` }
164
+ }));
165
+ }
166
+ function pluginPackageName(specifier) {
167
+ if (specifier.startsWith("file://"))
168
+ return path.basename(new URL(specifier).pathname).replace(/\.[cm]?[jt]s$/, "");
169
+ const lastAt = specifier.lastIndexOf("@");
170
+ return lastAt > 0 ? specifier.slice(0, lastAt) : specifier;
171
+ }
172
+ function patchConfigText(input, pluginSpec = DEFAULT_PLUGIN_SPEC) {
173
+ let text = input.trim() ? input : "{}";
174
+ const config = parse(text) ?? {};
175
+ if (!config.$schema)
176
+ text = applyJsonPatch(text, ["$schema"], SCHEMA);
177
+ const current = (parse(text) ?? {}).plugin;
178
+ const plugins = Array.isArray(current) ? [...current] : [];
179
+ if (!plugins.some((plugin) => typeof plugin === "string" && pluginPackageName(plugin) === PACKAGE_NAME)) {
180
+ plugins.push(pluginSpec);
181
+ }
182
+ text = applyJsonPatch(text, ["plugin"], plugins);
183
+ const next = parse(text) ?? {};
184
+ const existingModels = next.provider?.["openai-ws"]?.models ?? {};
185
+ text = applyJsonPatch(text, ["provider", "openai-ws"], providerConfig(existingModels));
186
+ return applyEdits(text, format(text, undefined, {
187
+ insertSpaces: true,
188
+ tabSize: 2,
189
+ eol: `
190
+ `
191
+ }));
192
+ }
193
+ async function setupOpenCodeConfig(options = {}) {
194
+ const file = targetPath(options);
195
+ let existing = "";
196
+ try {
197
+ existing = await fs.readFile(file, "utf8");
198
+ } catch (error) {
199
+ if (error?.code !== "ENOENT")
200
+ throw error;
201
+ }
202
+ const updated = patchConfigText(existing, options.pluginSpec ?? DEFAULT_PLUGIN_SPEC);
203
+ await fs.mkdir(path.dirname(file), { recursive: true });
204
+ await fs.writeFile(file, updated.endsWith(`
205
+ `) ? updated : `${updated}
206
+ `, "utf8");
207
+ return file;
208
+ }
209
+ function parseArgs(argv) {
210
+ const options = { mode: "global" };
211
+ for (let index = 0;index < argv.length; index++) {
212
+ const arg = argv[index];
213
+ if (arg === "--project")
214
+ options.mode = "project";
215
+ else if (arg === "--global")
216
+ options.mode = "global";
217
+ else if (arg === "--path")
218
+ options.configPath = argv[++index];
219
+ else if (arg === "--plugin")
220
+ options.pluginSpec = argv[++index];
221
+ else if (arg === "--help" || arg === "-h") {
222
+ console.log("Usage: openai-ws-opencode setup [--global|--project] [--path <opencode.json>] [--plugin <specifier>]");
223
+ process.exit(0);
224
+ }
225
+ }
226
+ return options;
227
+ }
228
+ if (import.meta.url === `file://${process.argv[1]}`) {
229
+ setupOpenCodeConfig(parseArgs(process.argv.slice(2))).then((file) => {
230
+ console.log(`Updated OpenCode config: ${file}`);
231
+ }).catch((error) => {
232
+ console.error(error instanceof Error ? error.message : String(error));
233
+ process.exit(1);
234
+ });
235
+ }
236
+ export {
237
+ setupOpenCodeConfig,
238
+ patchConfigText
239
+ };
@@ -0,0 +1,55 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ declare function handleCallback(req: IncomingMessage, res: ServerResponse): Promise<void>;
3
+ export declare function stopOAuthServer(): void;
4
+ export declare function createBrowserAuthorization(): Promise<{
5
+ url: string;
6
+ instructions: string;
7
+ method: "auto";
8
+ callback(): Promise<{
9
+ type: "success";
10
+ refresh: string;
11
+ access: string;
12
+ expires: number;
13
+ accountId: string | undefined;
14
+ } | {
15
+ type: "failed";
16
+ refresh?: undefined;
17
+ access?: undefined;
18
+ expires?: undefined;
19
+ accountId?: undefined;
20
+ }>;
21
+ }>;
22
+ export declare function createDeviceAuthorization(): Promise<{
23
+ url: string;
24
+ instructions: string;
25
+ method: "auto";
26
+ callback(): Promise<{
27
+ type: "failed";
28
+ refresh?: undefined;
29
+ access?: undefined;
30
+ expires?: undefined;
31
+ accountId?: undefined;
32
+ } | {
33
+ type: "success";
34
+ refresh: string;
35
+ access: string;
36
+ expires: number;
37
+ accountId: string | undefined;
38
+ }>;
39
+ }>;
40
+ export declare const oauthMethods: ({
41
+ label: string;
42
+ type: "oauth";
43
+ authorize: typeof createBrowserAuthorization;
44
+ } | {
45
+ label: string;
46
+ type: "api";
47
+ authorize?: undefined;
48
+ })[];
49
+ export declare const oauthTesting: {
50
+ handleCallback: typeof handleCallback;
51
+ getPendingState: () => string | undefined;
52
+ reset: typeof stopOAuthServer;
53
+ };
54
+ export {};
55
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAAA,OAAa,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AAkE3E,iBAAe,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,iBAwCtE;AAeD,wBAAgB,eAAe,SAQ9B;AAED,wBAAsB,0BAA0B;;;;;;;;;;;;;;;;;GAmC/C;AAED,wBAAsB,yBAAyB;;;;;;;;;;;;;;;;;GA+C9C;AAED,eAAO,MAAM,YAAY;;;;;;;;IAexB,CAAA;AAED,eAAO,MAAM,YAAY;;;;CAIxB,CAAA"}
@@ -0,0 +1,205 @@
1
+ import http from "node:http";
2
+ import crypto from "node:crypto";
3
+ import { CLIENT_ID, CODEX_ORIGINATOR, ISSUER, OAUTH_PORT } from "../constants.js";
4
+ import { exchangeCodeForTokens, extractAccountId, tokenExpiry } from "./tokens.js";
5
+ let oauthServer;
6
+ let pendingOAuth;
7
+ const HTML_OK = "<!doctype html><html><body><h1>Authorization successful</h1><p>Return to OpenCode.</p></body></html>";
8
+ const HTML_ERROR = "<!doctype html><html><body><h1>Authorization failed</h1></body></html>";
9
+ function base64Url(buffer) {
10
+ return Buffer.isBuffer(buffer) ? buffer.toString("base64url") : Buffer.from(new Uint8Array(buffer)).toString("base64url");
11
+ }
12
+ function randomString(length) {
13
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
14
+ const bytes = crypto.randomBytes(length);
15
+ return Array.from(bytes, (byte) => chars[byte % chars.length]).join("");
16
+ }
17
+ async function generatePKCE() {
18
+ const verifier = randomString(43);
19
+ const challenge = base64Url(await crypto.webcrypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)));
20
+ return { verifier, challenge };
21
+ }
22
+ function generateState() {
23
+ return base64Url(crypto.randomBytes(32));
24
+ }
25
+ function buildAuthorizeUrl(redirectUri, pkce, state) {
26
+ const params = new URLSearchParams({
27
+ response_type: "code",
28
+ client_id: CLIENT_ID,
29
+ redirect_uri: redirectUri,
30
+ scope: "openid profile email offline_access",
31
+ code_challenge: pkce.challenge,
32
+ code_challenge_method: "S256",
33
+ id_token_add_organizations: "true",
34
+ codex_cli_simplified_flow: "true",
35
+ state,
36
+ originator: CODEX_ORIGINATOR,
37
+ });
38
+ return `${ISSUER}/oauth/authorize?${params.toString()}`;
39
+ }
40
+ function reply(res, status, body) {
41
+ res.writeHead(status, { "Content-Type": "text/html" });
42
+ res.end(body);
43
+ }
44
+ async function handleCallback(req, res) {
45
+ const url = new URL(req.url ?? "/", `http://localhost:${OAUTH_PORT}`);
46
+ if (url.pathname !== "/auth/callback") {
47
+ reply(res, 404, "Not found");
48
+ return;
49
+ }
50
+ const current = pendingOAuth;
51
+ if (!current) {
52
+ reply(res, 400, HTML_ERROR);
53
+ return;
54
+ }
55
+ const state = url.searchParams.get("state");
56
+ if (!state || state !== current.state) {
57
+ pendingOAuth = undefined;
58
+ clearTimeout(current.timeout);
59
+ current.reject(new Error("OAuth state mismatch"));
60
+ reply(res, 400, HTML_ERROR);
61
+ return;
62
+ }
63
+ const error = url.searchParams.get("error");
64
+ const code = url.searchParams.get("code");
65
+ pendingOAuth = undefined;
66
+ clearTimeout(current.timeout);
67
+ if (error || !code) {
68
+ current.reject(new Error(error || "Missing OAuth code"));
69
+ reply(res, 400, HTML_ERROR);
70
+ return;
71
+ }
72
+ try {
73
+ current.resolve(await exchangeCodeForTokens(code, current.redirectUri, current.pkce.verifier));
74
+ reply(res, 200, HTML_OK);
75
+ }
76
+ catch (cause) {
77
+ current.reject(cause instanceof Error ? cause : new Error("OAuth token exchange failed"));
78
+ reply(res, 400, HTML_ERROR);
79
+ }
80
+ }
81
+ async function startOAuthServer() {
82
+ if (!oauthServer) {
83
+ oauthServer = http.createServer((req, res) => {
84
+ void handleCallback(req, res);
85
+ });
86
+ await new Promise((resolve, reject) => {
87
+ oauthServer?.once("error", reject);
88
+ oauthServer?.listen(OAUTH_PORT, "127.0.0.1", () => resolve());
89
+ });
90
+ }
91
+ return `http://localhost:${OAUTH_PORT}/auth/callback`;
92
+ }
93
+ export function stopOAuthServer() {
94
+ oauthServer?.close();
95
+ oauthServer = undefined;
96
+ if (pendingOAuth) {
97
+ clearTimeout(pendingOAuth.timeout);
98
+ pendingOAuth.reject(new Error("OAuth server stopped"));
99
+ pendingOAuth = undefined;
100
+ }
101
+ }
102
+ export async function createBrowserAuthorization() {
103
+ const redirectUri = await startOAuthServer();
104
+ const pkce = await generatePKCE();
105
+ const state = generateState();
106
+ const callbackPromise = new Promise((resolve, reject) => {
107
+ const timeout = setTimeout(() => {
108
+ pendingOAuth = undefined;
109
+ reject(new Error("OAuth callback timeout"));
110
+ }, 5 * 60 * 1000);
111
+ if (typeof timeout === "object" && "unref" in timeout)
112
+ timeout.unref();
113
+ pendingOAuth = { pkce, state, redirectUri, resolve, reject, timeout };
114
+ });
115
+ return {
116
+ url: buildAuthorizeUrl(redirectUri, pkce, state),
117
+ instructions: "Complete authorization in your browser. This window will close automatically.",
118
+ method: "auto",
119
+ async callback() {
120
+ try {
121
+ const tokens = await callbackPromise;
122
+ if (!tokens.refresh_token)
123
+ throw new Error("OAuth refresh token missing");
124
+ stopOAuthServer();
125
+ return {
126
+ type: "success",
127
+ refresh: tokens.refresh_token,
128
+ access: tokens.access_token,
129
+ expires: tokenExpiry(tokens.expires_in),
130
+ accountId: extractAccountId(tokens),
131
+ };
132
+ }
133
+ catch {
134
+ stopOAuthServer();
135
+ return { type: "failed" };
136
+ }
137
+ },
138
+ };
139
+ }
140
+ export async function createDeviceAuthorization() {
141
+ const deviceResponse = await fetch(`${ISSUER}/api/accounts/deviceauth/usercode`, {
142
+ method: "POST",
143
+ headers: { "Content-Type": "application/json", "User-Agent": "openai-ws-opencode/0.1.0" },
144
+ body: JSON.stringify({ client_id: CLIENT_ID }),
145
+ });
146
+ if (!deviceResponse.ok)
147
+ throw new Error("Failed to initiate device authorization");
148
+ const deviceData = (await deviceResponse.json());
149
+ const intervalMs = Math.max(Number.parseInt(deviceData.interval ?? "5", 10) || 5, 1) * 1000;
150
+ return {
151
+ url: `${ISSUER}/codex/device`,
152
+ instructions: `Enter code: ${deviceData.user_code}`,
153
+ method: "auto",
154
+ async callback() {
155
+ for (;;) {
156
+ const response = await fetch(`${ISSUER}/api/accounts/deviceauth/token`, {
157
+ method: "POST",
158
+ headers: { "Content-Type": "application/json", "User-Agent": "openai-ws-opencode/0.1.0" },
159
+ body: JSON.stringify({
160
+ device_auth_id: deviceData.device_auth_id,
161
+ user_code: deviceData.user_code,
162
+ }),
163
+ });
164
+ if (response.ok) {
165
+ const data = (await response.json());
166
+ const tokens = await exchangeCodeForTokens(data.authorization_code, `${ISSUER}/deviceauth/callback`, data.code_verifier);
167
+ if (!tokens.refresh_token)
168
+ return { type: "failed" };
169
+ return {
170
+ type: "success",
171
+ refresh: tokens.refresh_token,
172
+ access: tokens.access_token,
173
+ expires: tokenExpiry(tokens.expires_in),
174
+ accountId: extractAccountId(tokens),
175
+ };
176
+ }
177
+ if (response.status !== 403 && response.status !== 404)
178
+ return { type: "failed" };
179
+ await new Promise((resolve) => setTimeout(resolve, intervalMs + 3000));
180
+ }
181
+ },
182
+ };
183
+ }
184
+ export const oauthMethods = [
185
+ {
186
+ label: "OpenAI (WebSocket) - ChatGPT Pro/Plus (browser)",
187
+ type: "oauth",
188
+ authorize: createBrowserAuthorization,
189
+ },
190
+ {
191
+ label: "OpenAI (WebSocket) - ChatGPT Pro/Plus (headless)",
192
+ type: "oauth",
193
+ authorize: createDeviceAuthorization,
194
+ },
195
+ {
196
+ label: "OpenAI (WebSocket) - API Key",
197
+ type: "api",
198
+ },
199
+ ];
200
+ export const oauthTesting = {
201
+ handleCallback,
202
+ getPendingState: () => pendingOAuth?.state,
203
+ reset: stopOAuthServer,
204
+ };
205
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}