openai-oauth 1.0.2 → 2.0.0-beta.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 +23 -57
- package/dist/chunk-XZLFK5NT.js +1441 -0
- package/dist/cli.js +197 -78
- package/dist/index.d.ts +13 -50
- package/dist/index.js +3 -1
- package/package.json +26 -6
- package/dist/chunk-2AENSHRT.js +0 -22829
|
@@ -0,0 +1,1441 @@
|
|
|
1
|
+
// src/login.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { createServer } from "http";
|
|
4
|
+
import { createConnection } from "net";
|
|
5
|
+
import {
|
|
6
|
+
createOpenAIOAuthRequest,
|
|
7
|
+
exchangeOpenAIOAuthCode,
|
|
8
|
+
saveAuthTokens
|
|
9
|
+
} from "@openai-oauth/core";
|
|
10
|
+
|
|
11
|
+
// src/callback-success.html?raw
|
|
12
|
+
var callback_success_default = `<!doctype html>
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8" />
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
17
|
+
<title>OpenAI OAuth Sign-In</title>
|
|
18
|
+
<link
|
|
19
|
+
rel="icon"
|
|
20
|
+
type="image/svg+xml"
|
|
21
|
+
href='data:image/svg+xml,%3Csvg width="1412" height="1412" viewBox="0 0 1412 1412" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cstyle%3E.mark%7Bfill:%23111111%7D@media(prefers-color-scheme:dark)%7B.mark%7Bfill:%23ffffff%7D%7D%3C/style%3E%3Cpath class="mark" d="M597.462 0.857699C673.717 -4.5771 755.535 16.0618 820.557 55.7878C839.621 67.4347 853.532 77.8804 870.903 91.7881C915.947 83.6597 962.083 83.5966 1007.15 91.601C1108.64 109.677 1198.75 167.436 1257.54 252.101C1315.95 336.355 1338.56 440.326 1320.41 541.218C1348.4 575.095 1369.47 609.408 1385.5 650.519C1422.8 746.143 1420.65 852.641 1379.53 946.681C1345.7 1023.88 1287.62 1087.95 1214.08 1129.17C1193.85 1140.47 1177.72 1147.33 1156.35 1155.65C1150.8 1167.38 1146.43 1180.08 1140.53 1192.52C1124.33 1226.23 1103.33 1257.42 1078.21 1285.12C1009.53 1360.77 913.675 1406.14 811.622 1411.3C797.103 1412.74 770.195 1411.71 755.535 1410.39C669.671 1402.64 607.216 1373.41 541.228 1320.14C498.371 1327.97 454.491 1328.42 411.479 1321.49C309.519 1305.16 218.308 1248.8 158.127 1164.91C95.5192 1077.93 74.597 975.652 91.7694 870.624C63.5815 837.852 41.1539 800.536 25.4414 760.272C-11.0983 663.999 -8.18036 557.204 33.5617 463.069C77.8418 363.472 155.004 295.015 255.809 256.181C260.004 248.164 265.705 231.947 270.502 221.837C286.044 188.764 306.234 158.081 330.46 130.717C398.638 53.6473 494.725 6.91416 597.462 0.857699ZM803.624 1297.49C879.882 1292.64 946.234 1261.95 997.071 1204.28C1015.09 1183.82 1029.9 1160.75 1041.02 1135.85C1047.58 1121.23 1051.8 1106.22 1056.86 1091.08C1067.19 1060.2 1088.78 1058.15 1115.61 1048.88C1125.47 1045.52 1135.09 1041.52 1144.43 1036.9C1243.91 988.43 1304.38 884.819 1297.64 774.394C1294 716.163 1271.8 660.631 1234.27 615.946C1223.26 602.711 1208.28 591.435 1203.34 574.896C1197.16 554.244 1207.83 529.807 1210.67 506.759C1221.87 416.705 1187.69 326.959 1119.41 267.154C1064.95 219.454 993.758 195.357 921.517 200.173C909.062 200.932 896.689 202.63 884.487 205.252C858.307 210.809 839.711 217.803 816.524 196.759C802.123 183.693 793.783 175.007 777.726 163.451C738.141 135.075 691.432 118.286 642.835 114.967C634.456 114.329 619.975 113.369 611.816 114.357C536.156 118.188 468.827 147.965 417.756 204.614C399.016 225.371 383.578 248.884 371.981 274.328C368.409 282.286 365.23 290.414 362.455 298.682C357.28 314.179 354.556 330.003 342.859 342.026C330.473 354.758 314.132 357.158 297.998 362.604C288.467 365.803 279.144 369.592 270.084 373.948C170.322 421.514 108.992 524.42 114.64 634.77C117.764 693.592 139.719 749.855 177.266 795.249C188.357 808.68 203.749 820.374 208.712 836.975C214.929 857.779 204.623 882.481 201.669 905.586C190.625 994.243 223.634 1082.71 290.065 1142.47C341.827 1188.92 413.829 1216.16 483.523 1212.31C496.702 1211.71 509.821 1210.17 522.783 1207.71C549.917 1202.44 573.428 1193.61 595.85 1215.61C654.397 1273.04 720.807 1298.82 803.624 1297.49Z"/%3E%3Cpath class="mark" fill-rule="evenodd" clip-rule="evenodd" d="M528.104 673.1C645.003 673.1 739.769 767.837 739.769 884.701C739.769 1001.56 645.003 1096.3 528.104 1096.3C411.204 1096.3 316.439 1001.56 316.439 884.701C316.439 767.837 411.204 673.1 528.104 673.1ZM526.618 785.954C472.886 785.954 429.327 829.499 429.327 883.216C429.327 936.932 472.886 980.478 526.618 980.478C580.351 980.478 623.91 936.932 623.91 883.216C623.91 829.499 580.351 785.954 526.618 785.954Z"/%3E%3Cpath class="mark" d="M974.454 335.733L1055.33 416.583L688.769 783.031L607.895 702.182L974.454 335.733Z"/%3E%3Cpath class="mark" d="M820.585 766.758L704.525 767.283L821.11 650.733L820.585 766.758Z"/%3E%3Cpath class="mark" d="M937.694 649.684L821.11 650.733L938.744 533.135L937.694 649.684Z"/%3E%3Cpath class="mark" d="M1054.8 532.611L938.744 533.135L1055.33 416.586L1054.8 532.611Z"/%3E%3Cpath class="mark" d="M1054.8 336.259L1055.33 416.583L974.454 335.733L1054.8 336.259Z"/%3E%3C/svg%3E'
|
|
22
|
+
/>
|
|
23
|
+
<style>
|
|
24
|
+
:root {
|
|
25
|
+
color-scheme: light;
|
|
26
|
+
--background: #ffffff;
|
|
27
|
+
--foreground: #000000;
|
|
28
|
+
--muted: #666666;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
* {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
background: var(--background);
|
|
38
|
+
color: var(--foreground);
|
|
39
|
+
font-family:
|
|
40
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
41
|
+
line-height: 1.5;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
main {
|
|
45
|
+
display: grid;
|
|
46
|
+
min-height: 100vh;
|
|
47
|
+
place-items: center;
|
|
48
|
+
padding: 32px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.card {
|
|
52
|
+
width: min(100%, 440px);
|
|
53
|
+
text-align: center;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.brand {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: 10px;
|
|
60
|
+
margin-bottom: 28px;
|
|
61
|
+
font-size: 20px;
|
|
62
|
+
font-weight: 560;
|
|
63
|
+
letter-spacing: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.logo {
|
|
67
|
+
display: block;
|
|
68
|
+
width: 22px;
|
|
69
|
+
height: 22px;
|
|
70
|
+
flex: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
h1 {
|
|
74
|
+
margin: 0;
|
|
75
|
+
font-size: 32px;
|
|
76
|
+
font-weight: 580;
|
|
77
|
+
letter-spacing: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.message {
|
|
81
|
+
margin: 12px auto 24px;
|
|
82
|
+
max-width: 360px;
|
|
83
|
+
color: var(--muted);
|
|
84
|
+
font-size: 16px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
a {
|
|
88
|
+
color: var(--foreground);
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
text-underline-offset: 3px;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
93
|
+
</head>
|
|
94
|
+
<body>
|
|
95
|
+
<main>
|
|
96
|
+
<section class="card" aria-labelledby="title">
|
|
97
|
+
<div class="brand" aria-label="OpenAI OAuth">
|
|
98
|
+
<svg
|
|
99
|
+
class="logo"
|
|
100
|
+
viewBox="0 0 1412 1412"
|
|
101
|
+
fill="none"
|
|
102
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
103
|
+
aria-hidden="true"
|
|
104
|
+
>
|
|
105
|
+
<path
|
|
106
|
+
d="M597.462 0.857699C673.717 -4.5771 755.535 16.0618 820.557 55.7878C839.621 67.4347 853.532 77.8804 870.903 91.7881C915.947 83.6597 962.083 83.5966 1007.15 91.601C1108.64 109.677 1198.75 167.436 1257.54 252.101C1315.95 336.355 1338.56 440.326 1320.41 541.218C1348.4 575.095 1369.47 609.408 1385.5 650.519C1422.8 746.143 1420.65 852.641 1379.53 946.681C1345.7 1023.88 1287.62 1087.95 1214.08 1129.17C1193.85 1140.47 1177.72 1147.33 1156.35 1155.65C1150.8 1167.38 1146.43 1180.08 1140.53 1192.52C1124.33 1226.23 1103.33 1257.42 1078.21 1285.12C1009.53 1360.77 913.675 1406.14 811.622 1411.3C797.103 1412.74 770.195 1411.71 755.535 1410.39C669.671 1402.64 607.216 1373.41 541.228 1320.14C498.371 1327.97 454.491 1328.42 411.479 1321.49C309.519 1305.16 218.308 1248.8 158.127 1164.91C95.5192 1077.93 74.597 975.652 91.7694 870.624C63.5815 837.852 41.1539 800.536 25.4414 760.272C-11.0983 663.999 -8.18036 557.204 33.5617 463.069C77.8418 363.472 155.004 295.015 255.809 256.181C260.004 248.164 265.705 231.947 270.502 221.837C286.044 188.764 306.234 158.081 330.46 130.717C398.638 53.6473 494.725 6.91416 597.462 0.857699ZM803.624 1297.49C879.882 1292.64 946.234 1261.95 997.071 1204.28C1015.09 1183.82 1029.9 1160.75 1041.02 1135.85C1047.58 1121.23 1051.8 1106.22 1056.86 1091.08C1067.19 1060.2 1088.78 1058.15 1115.61 1048.88C1125.47 1045.52 1135.09 1041.52 1144.43 1036.9C1243.91 988.43 1304.38 884.819 1297.64 774.394C1294 716.163 1271.8 660.631 1234.27 615.946C1223.26 602.711 1208.28 591.435 1203.34 574.896C1197.16 554.244 1207.83 529.807 1210.67 506.759C1221.87 416.705 1187.69 326.959 1119.41 267.154C1064.95 219.454 993.758 195.357 921.517 200.173C909.062 200.932 896.689 202.63 884.487 205.252C858.307 210.809 839.711 217.803 816.524 196.759C802.123 183.693 793.783 175.007 777.726 163.451C738.141 135.075 691.432 118.286 642.835 114.967C634.456 114.329 619.975 113.369 611.816 114.357C536.156 118.188 468.827 147.965 417.756 204.614C399.016 225.371 383.578 248.884 371.981 274.328C368.409 282.286 365.23 290.414 362.455 298.682C357.28 314.179 354.556 330.003 342.859 342.026C330.473 354.758 314.132 357.158 297.998 362.604C288.467 365.803 279.144 369.592 270.084 373.948C170.322 421.514 108.992 524.42 114.64 634.77C117.764 693.592 139.719 749.855 177.266 795.249C188.357 808.68 203.749 820.374 208.712 836.975C214.929 857.779 204.623 882.481 201.669 905.586C190.625 994.243 223.634 1082.71 290.065 1142.47C341.827 1188.92 413.829 1216.16 483.523 1212.31C496.702 1211.71 509.821 1210.17 522.783 1207.71C549.917 1202.44 573.428 1193.61 595.85 1215.61C654.397 1273.04 720.807 1298.82 803.624 1297.49Z"
|
|
107
|
+
fill="currentColor"
|
|
108
|
+
/>
|
|
109
|
+
<path
|
|
110
|
+
fill-rule="evenodd"
|
|
111
|
+
clip-rule="evenodd"
|
|
112
|
+
d="M528.104 673.1C645.003 673.1 739.769 767.837 739.769 884.701C739.769 1001.56 645.003 1096.3 528.104 1096.3C411.204 1096.3 316.439 1001.56 316.439 884.701C316.439 767.837 411.204 673.1 528.104 673.1ZM526.618 785.954C472.886 785.954 429.327 829.499 429.327 883.216C429.327 936.932 472.886 980.478 526.618 980.478C580.351 980.478 623.91 936.932 623.91 883.216C623.91 829.499 580.351 785.954 526.618 785.954Z"
|
|
113
|
+
fill="currentColor"
|
|
114
|
+
/>
|
|
115
|
+
<path
|
|
116
|
+
d="M974.454 335.733L1055.33 416.583L688.769 783.031L607.895 702.182L974.454 335.733Z"
|
|
117
|
+
fill="currentColor"
|
|
118
|
+
/>
|
|
119
|
+
<path
|
|
120
|
+
d="M820.585 766.758L704.525 767.283L821.11 650.733L820.585 766.758Z"
|
|
121
|
+
fill="currentColor"
|
|
122
|
+
/>
|
|
123
|
+
<path
|
|
124
|
+
d="M937.694 649.684L821.11 650.733L938.744 533.135L937.694 649.684Z"
|
|
125
|
+
fill="currentColor"
|
|
126
|
+
/>
|
|
127
|
+
<path
|
|
128
|
+
d="M1054.8 532.611L938.744 533.135L1055.33 416.586L1054.8 532.611Z"
|
|
129
|
+
fill="currentColor"
|
|
130
|
+
/>
|
|
131
|
+
<path
|
|
132
|
+
d="M1054.8 336.259L1055.33 416.583L974.454 335.733L1054.8 336.259Z"
|
|
133
|
+
fill="currentColor"
|
|
134
|
+
/>
|
|
135
|
+
</svg>
|
|
136
|
+
<span>OpenAI OAuth</span>
|
|
137
|
+
</div>
|
|
138
|
+
<h1 id="title">Sign-in complete</h1>
|
|
139
|
+
<p class="message">
|
|
140
|
+
Your ChatGPT credentials have been saved locally. You can close
|
|
141
|
+
this window and return to your terminal.
|
|
142
|
+
</p>
|
|
143
|
+
<a href="https://github.com/EvanZhouDev/openai-oauth">
|
|
144
|
+
Learn more about OpenAI OAuth
|
|
145
|
+
</a>
|
|
146
|
+
</section>
|
|
147
|
+
</main>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
// src/login.ts
|
|
153
|
+
var DEFAULT_LOGIN_REDIRECT_HOST = "localhost";
|
|
154
|
+
var DEFAULT_LOGIN_PORT = 1455;
|
|
155
|
+
var DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
156
|
+
var LOOPBACK_LISTEN_HOSTS = ["::1", "127.0.0.1"];
|
|
157
|
+
var isAddressInUseError = (error) => error instanceof Error && "code" in error && error.code === "EADDRINUSE";
|
|
158
|
+
var isAddressUnavailableError = (error) => error instanceof Error && "code" in error && ["EADDRNOTAVAIL", "EAFNOSUPPORT"].includes(
|
|
159
|
+
String(error.code)
|
|
160
|
+
);
|
|
161
|
+
var createLoginCancelledError = () => new Error("OpenAI OAuth login cancelled.");
|
|
162
|
+
var toCallbackHtml = (title, message) => `<!doctype html>
|
|
163
|
+
<html lang="en">
|
|
164
|
+
<head>
|
|
165
|
+
<meta charset="utf-8" />
|
|
166
|
+
<title>${title}</title>
|
|
167
|
+
<style>
|
|
168
|
+
body {
|
|
169
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
170
|
+
margin: 3rem;
|
|
171
|
+
line-height: 1.5;
|
|
172
|
+
}
|
|
173
|
+
</style>
|
|
174
|
+
</head>
|
|
175
|
+
<body>
|
|
176
|
+
<h1>${title}</h1>
|
|
177
|
+
<p>${message}</p>
|
|
178
|
+
</body>
|
|
179
|
+
</html>`;
|
|
180
|
+
var htmlHeaders = {
|
|
181
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
182
|
+
Connection: "close"
|
|
183
|
+
};
|
|
184
|
+
var textHeaders = {
|
|
185
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
186
|
+
Connection: "close"
|
|
187
|
+
};
|
|
188
|
+
var openUrl = (url) => {
|
|
189
|
+
const platform = process.platform;
|
|
190
|
+
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
191
|
+
const args = platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
192
|
+
const child = spawn(command, args, {
|
|
193
|
+
detached: true,
|
|
194
|
+
stdio: "ignore"
|
|
195
|
+
});
|
|
196
|
+
child.on("error", () => void 0);
|
|
197
|
+
child.unref();
|
|
198
|
+
};
|
|
199
|
+
var toCallbackPortInUseMessage = (redirectHost, port) => `OpenAI OAuth login needs http://${redirectHost}:${port}/auth/callback, but port ${port} is already in use. Stop the process using that port and try again.`;
|
|
200
|
+
var listen = (server, port, host) => new Promise((resolve, reject) => {
|
|
201
|
+
server.once("error", reject);
|
|
202
|
+
const onListening = () => {
|
|
203
|
+
server.off("error", reject);
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
server.listen(port, host, onListening);
|
|
207
|
+
});
|
|
208
|
+
var closeServers = async (servers) => {
|
|
209
|
+
await Promise.all(servers.map((server) => closeServer(server)));
|
|
210
|
+
};
|
|
211
|
+
var listenOnCallbackPort = async (handler, port, host) => {
|
|
212
|
+
if (host) {
|
|
213
|
+
const server = createServer(handler);
|
|
214
|
+
await listen(server, port, host);
|
|
215
|
+
return [server];
|
|
216
|
+
}
|
|
217
|
+
const servers = [];
|
|
218
|
+
for (const loopbackHost of LOOPBACK_LISTEN_HOSTS) {
|
|
219
|
+
const server = createServer(handler);
|
|
220
|
+
try {
|
|
221
|
+
await listen(server, port, loopbackHost);
|
|
222
|
+
servers.push(server);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
await closeServer(server);
|
|
225
|
+
if (isAddressUnavailableError(error)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
await closeServers(servers);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (servers.length === 0) {
|
|
233
|
+
throw new Error("No loopback address is available for OpenAI OAuth login.");
|
|
234
|
+
}
|
|
235
|
+
return servers;
|
|
236
|
+
};
|
|
237
|
+
var closeServer = (server) => new Promise((resolve, reject) => {
|
|
238
|
+
if (!server.listening) {
|
|
239
|
+
resolve();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
server.close((error) => {
|
|
243
|
+
if (error) {
|
|
244
|
+
reject(error);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
resolve();
|
|
248
|
+
});
|
|
249
|
+
server.closeIdleConnections?.();
|
|
250
|
+
});
|
|
251
|
+
var isCallbackHostReachable = (host, port, timeoutMs = 100) => new Promise((resolve) => {
|
|
252
|
+
let settled = false;
|
|
253
|
+
const socket = createConnection({ host, port });
|
|
254
|
+
const settle = (value) => {
|
|
255
|
+
if (settled) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
settled = true;
|
|
259
|
+
socket.destroy();
|
|
260
|
+
resolve(value);
|
|
261
|
+
};
|
|
262
|
+
socket.setTimeout(timeoutMs);
|
|
263
|
+
socket.once("connect", () => settle(true));
|
|
264
|
+
socket.once("error", () => settle(false));
|
|
265
|
+
socket.once("timeout", () => settle(false));
|
|
266
|
+
});
|
|
267
|
+
var runOpenAIOAuthLogin = async (options = {}) => {
|
|
268
|
+
if (options.signal?.aborted) {
|
|
269
|
+
throw createLoginCancelledError();
|
|
270
|
+
}
|
|
271
|
+
const redirectHost = options.redirectHost ?? DEFAULT_LOGIN_REDIRECT_HOST;
|
|
272
|
+
const listenHost = options.host;
|
|
273
|
+
const callbackPort = DEFAULT_LOGIN_PORT;
|
|
274
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS;
|
|
275
|
+
const onMessage = options.onMessage ?? console.log;
|
|
276
|
+
let expectedState = "";
|
|
277
|
+
let redirectUri = "";
|
|
278
|
+
let servers = [];
|
|
279
|
+
let settleCallback;
|
|
280
|
+
let rejectCallback;
|
|
281
|
+
let rejectAbort;
|
|
282
|
+
const callbackPromise = new Promise((resolve, reject) => {
|
|
283
|
+
settleCallback = resolve;
|
|
284
|
+
rejectCallback = reject;
|
|
285
|
+
});
|
|
286
|
+
const abortPromise = new Promise((_, reject) => {
|
|
287
|
+
rejectAbort = reject;
|
|
288
|
+
});
|
|
289
|
+
const abortLogin = () => {
|
|
290
|
+
rejectAbort?.(createLoginCancelledError());
|
|
291
|
+
};
|
|
292
|
+
options.signal?.addEventListener("abort", abortLogin, { once: true });
|
|
293
|
+
const handleCallbackRequest = (req, res) => {
|
|
294
|
+
const url = new URL(req.url ?? "/", redirectUri || `http://${redirectHost}`);
|
|
295
|
+
if (url.pathname === "/cancel") {
|
|
296
|
+
res.writeHead(200, textHeaders);
|
|
297
|
+
res.end("Cancelled");
|
|
298
|
+
rejectCallback?.(new Error("OpenAI OAuth login cancelled."));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (url.pathname !== "/auth/callback") {
|
|
302
|
+
res.writeHead(404, textHeaders);
|
|
303
|
+
res.end("Not found");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const error = url.searchParams.get("error");
|
|
307
|
+
if (error) {
|
|
308
|
+
res.writeHead(400, htmlHeaders);
|
|
309
|
+
res.end(
|
|
310
|
+
toCallbackHtml(
|
|
311
|
+
"OpenAI OAuth login failed",
|
|
312
|
+
"You can close this window and return to the terminal."
|
|
313
|
+
)
|
|
314
|
+
);
|
|
315
|
+
rejectCallback?.(new Error(`OpenAI OAuth login failed: ${error}`));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const state = url.searchParams.get("state");
|
|
319
|
+
const code = url.searchParams.get("code");
|
|
320
|
+
if (!code || state !== expectedState) {
|
|
321
|
+
res.writeHead(400, htmlHeaders);
|
|
322
|
+
res.end(
|
|
323
|
+
toCallbackHtml(
|
|
324
|
+
"OpenAI OAuth login failed",
|
|
325
|
+
"You can close this window and return to the terminal."
|
|
326
|
+
)
|
|
327
|
+
);
|
|
328
|
+
rejectCallback?.(new Error("OpenAI OAuth callback was invalid."));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
res.writeHead(200, htmlHeaders);
|
|
332
|
+
res.end(callback_success_default);
|
|
333
|
+
settleCallback?.({ code });
|
|
334
|
+
};
|
|
335
|
+
if (await isCallbackHostReachable(redirectHost, callbackPort)) {
|
|
336
|
+
throw new Error(toCallbackPortInUseMessage(redirectHost, callbackPort));
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
servers = await listenOnCallbackPort(
|
|
340
|
+
handleCallbackRequest,
|
|
341
|
+
callbackPort,
|
|
342
|
+
listenHost
|
|
343
|
+
);
|
|
344
|
+
const address = servers[0]?.address();
|
|
345
|
+
if (address?.port !== callbackPort) {
|
|
346
|
+
throw new Error("OpenAI OAuth login callback port could not be resolved.");
|
|
347
|
+
}
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (isAddressInUseError(error)) {
|
|
350
|
+
throw new Error(toCallbackPortInUseMessage(redirectHost, callbackPort));
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
redirectUri = `http://${redirectHost}:${callbackPort}/auth/callback`;
|
|
356
|
+
if (options.signal?.aborted) {
|
|
357
|
+
throw createLoginCancelledError();
|
|
358
|
+
}
|
|
359
|
+
const request = await createOpenAIOAuthRequest({
|
|
360
|
+
redirectUri,
|
|
361
|
+
clientId: options.clientId
|
|
362
|
+
});
|
|
363
|
+
expectedState = request.state;
|
|
364
|
+
onMessage(`OpenAI OAuth login URL: ${request.authorizationUrl}`);
|
|
365
|
+
if (options.openBrowser !== false) {
|
|
366
|
+
openUrl(request.authorizationUrl);
|
|
367
|
+
}
|
|
368
|
+
const timeout = new Promise((_, reject) => {
|
|
369
|
+
setTimeout(() => {
|
|
370
|
+
reject(new Error("OpenAI OAuth login timed out."));
|
|
371
|
+
}, timeoutMs).unref();
|
|
372
|
+
});
|
|
373
|
+
const callback = await Promise.race([
|
|
374
|
+
callbackPromise,
|
|
375
|
+
timeout,
|
|
376
|
+
abortPromise
|
|
377
|
+
]);
|
|
378
|
+
const token = await exchangeOpenAIOAuthCode({
|
|
379
|
+
code: callback.code,
|
|
380
|
+
codeVerifier: request.codeVerifier,
|
|
381
|
+
redirectUri,
|
|
382
|
+
clientId: options.clientId,
|
|
383
|
+
tokenUrl: options.tokenUrl,
|
|
384
|
+
fetch: options.fetch,
|
|
385
|
+
signal: options.signal
|
|
386
|
+
});
|
|
387
|
+
const saved = await saveAuthTokens({
|
|
388
|
+
token,
|
|
389
|
+
authFilePath: options.authFilePath
|
|
390
|
+
});
|
|
391
|
+
onMessage(`Credentials saved to ${saved.path}`);
|
|
392
|
+
return saved;
|
|
393
|
+
} finally {
|
|
394
|
+
options.signal?.removeEventListener("abort", abortLogin);
|
|
395
|
+
await closeServers(servers);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// src/server.ts
|
|
400
|
+
import { createServer as createServer2 } from "http";
|
|
401
|
+
import {
|
|
402
|
+
createOpenAIOAuth
|
|
403
|
+
} from "@openai-oauth/ai-sdk";
|
|
404
|
+
import { createCodexOAuthClient } from "@openai-oauth/core";
|
|
405
|
+
import { openaiCredentials } from "@openai-oauth/local";
|
|
406
|
+
|
|
407
|
+
// src/chat-completions.ts
|
|
408
|
+
import { generateText } from "ai";
|
|
409
|
+
|
|
410
|
+
// src/chat-messages.ts
|
|
411
|
+
import { jsonSchema, tool } from "ai";
|
|
412
|
+
|
|
413
|
+
// src/shared.ts
|
|
414
|
+
import { Buffer } from "buffer";
|
|
415
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
416
|
+
var DEFAULT_PORT = 10531;
|
|
417
|
+
var jsonHeaders = {
|
|
418
|
+
"content-type": "application/json; charset=utf-8"
|
|
419
|
+
};
|
|
420
|
+
var sseHeaders = {
|
|
421
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
422
|
+
"cache-control": "no-cache, no-transform",
|
|
423
|
+
connection: "keep-alive",
|
|
424
|
+
"x-accel-buffering": "no"
|
|
425
|
+
};
|
|
426
|
+
var corsHeaders = {
|
|
427
|
+
"access-control-allow-origin": "*",
|
|
428
|
+
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
429
|
+
"access-control-allow-headers": "authorization,content-type"
|
|
430
|
+
};
|
|
431
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
432
|
+
var isJsonValue = (value) => {
|
|
433
|
+
if (value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
if (Array.isArray(value)) {
|
|
437
|
+
return value.every((item) => isJsonValue(item));
|
|
438
|
+
}
|
|
439
|
+
if (isRecord(value)) {
|
|
440
|
+
return Object.values(value).every((item) => isJsonValue(item));
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
};
|
|
444
|
+
var toJsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
|
|
445
|
+
status,
|
|
446
|
+
headers: {
|
|
447
|
+
...jsonHeaders,
|
|
448
|
+
...corsHeaders
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
var toErrorResponse = (message, status = 400, type = "invalid_request_error") => toJsonResponse(
|
|
452
|
+
{
|
|
453
|
+
error: {
|
|
454
|
+
message,
|
|
455
|
+
type
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
status
|
|
459
|
+
);
|
|
460
|
+
var mapFinishReason = (finishReason) => {
|
|
461
|
+
switch (finishReason) {
|
|
462
|
+
case "stop":
|
|
463
|
+
return "stop";
|
|
464
|
+
case "length":
|
|
465
|
+
return "length";
|
|
466
|
+
case "tool-calls":
|
|
467
|
+
return "tool_calls";
|
|
468
|
+
case "content-filter":
|
|
469
|
+
return "content_filter";
|
|
470
|
+
default:
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
var toUsage = (usage) => ({
|
|
475
|
+
prompt_tokens: usage.inputTokens ?? 0,
|
|
476
|
+
completion_tokens: usage.outputTokens ?? 0,
|
|
477
|
+
total_tokens: usage.totalTokens ?? 0,
|
|
478
|
+
prompt_tokens_details: usage.cachedInputTokens == null ? void 0 : {
|
|
479
|
+
cached_tokens: usage.cachedInputTokens
|
|
480
|
+
},
|
|
481
|
+
completion_tokens_details: usage.reasoningTokens == null ? void 0 : {
|
|
482
|
+
reasoning_tokens: usage.reasoningTokens
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
var summarizeChatRequest = (request) => ({
|
|
486
|
+
bodyKeys: Object.keys(request).sort(),
|
|
487
|
+
messageCount: request.messages?.length ?? 0,
|
|
488
|
+
messageRoles: (request.messages ?? []).map((message) => message.role).filter((role) => typeof role === "string"),
|
|
489
|
+
model: request.model,
|
|
490
|
+
reasoningEffort: request.reasoning_effort,
|
|
491
|
+
stream: request.stream === true,
|
|
492
|
+
toolCount: request.tools?.length ?? 0
|
|
493
|
+
});
|
|
494
|
+
var copyUpstreamResponse = (response) => {
|
|
495
|
+
const headers = new Headers(response.headers);
|
|
496
|
+
for (const [key, value] of Object.entries(corsHeaders)) {
|
|
497
|
+
headers.set(key, value);
|
|
498
|
+
}
|
|
499
|
+
if (!headers.has("content-type")) {
|
|
500
|
+
headers.set("content-type", "application/json; charset=utf-8");
|
|
501
|
+
}
|
|
502
|
+
return new Response(response.body, {
|
|
503
|
+
status: response.status,
|
|
504
|
+
headers
|
|
505
|
+
});
|
|
506
|
+
};
|
|
507
|
+
var readNodeBody = async (request) => {
|
|
508
|
+
const chunks = [];
|
|
509
|
+
for await (const chunk of request) {
|
|
510
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
511
|
+
}
|
|
512
|
+
return Buffer.concat(chunks);
|
|
513
|
+
};
|
|
514
|
+
var toHeaders = (headers) => {
|
|
515
|
+
const nextHeaders = new Headers();
|
|
516
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
517
|
+
if (Array.isArray(value)) {
|
|
518
|
+
for (const item of value) {
|
|
519
|
+
nextHeaders.append(key, item);
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (typeof value === "string") {
|
|
524
|
+
nextHeaders.set(key, value);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return nextHeaders;
|
|
528
|
+
};
|
|
529
|
+
var toWebRequest = async (request, options) => {
|
|
530
|
+
const url = `http://${options.host}:${options.port}${request.url ?? "/"}`;
|
|
531
|
+
const body = request.method === "GET" || request.method === "HEAD" ? void 0 : await readNodeBody(request);
|
|
532
|
+
return new Request(url, {
|
|
533
|
+
method: request.method,
|
|
534
|
+
headers: toHeaders(request.headers),
|
|
535
|
+
body: body == null || body.byteLength === 0 ? void 0 : new Blob([Buffer.from(body)]),
|
|
536
|
+
duplex: "half"
|
|
537
|
+
});
|
|
538
|
+
};
|
|
539
|
+
var writeWebResponse = async (response, webResponse) => {
|
|
540
|
+
response.statusCode = webResponse.status;
|
|
541
|
+
webResponse.headers.forEach((value, key) => {
|
|
542
|
+
response.setHeader(key, value);
|
|
543
|
+
});
|
|
544
|
+
if (webResponse.body == null) {
|
|
545
|
+
response.end();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const reader = webResponse.body.getReader();
|
|
549
|
+
try {
|
|
550
|
+
while (true) {
|
|
551
|
+
const { done, value } = await reader.read();
|
|
552
|
+
if (done) {
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
response.write(Buffer.from(value));
|
|
556
|
+
}
|
|
557
|
+
} finally {
|
|
558
|
+
reader.releaseLock();
|
|
559
|
+
}
|
|
560
|
+
response.end();
|
|
561
|
+
};
|
|
562
|
+
var resolveAddress = (address, host) => ({
|
|
563
|
+
host: address.address === "::" || address.address === "0.0.0.0" ? host : address.address,
|
|
564
|
+
port: address.port
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// src/chat-messages.ts
|
|
568
|
+
var toJsonToolOutput = (value) => ({
|
|
569
|
+
type: "json",
|
|
570
|
+
value
|
|
571
|
+
});
|
|
572
|
+
var toTextToolOutput = (value) => ({
|
|
573
|
+
type: "text",
|
|
574
|
+
value
|
|
575
|
+
});
|
|
576
|
+
var coerceToolOutput = (content) => {
|
|
577
|
+
if (typeof content === "string") {
|
|
578
|
+
try {
|
|
579
|
+
const parsed = JSON.parse(content);
|
|
580
|
+
return toJsonToolOutput(parsed);
|
|
581
|
+
} catch {
|
|
582
|
+
return toTextToolOutput(content);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (isJsonValue(content)) {
|
|
586
|
+
return toJsonToolOutput(content);
|
|
587
|
+
}
|
|
588
|
+
return toTextToolOutput(String(content ?? ""));
|
|
589
|
+
};
|
|
590
|
+
var parseToolArguments = (value) => {
|
|
591
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
592
|
+
return {};
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
return JSON.parse(value);
|
|
596
|
+
} catch {
|
|
597
|
+
return value;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
var toTextParts = (content) => {
|
|
601
|
+
if (typeof content === "string") {
|
|
602
|
+
return content;
|
|
603
|
+
}
|
|
604
|
+
if (!Array.isArray(content)) {
|
|
605
|
+
return "";
|
|
606
|
+
}
|
|
607
|
+
return content.map((item) => {
|
|
608
|
+
if (!isRecord(item)) {
|
|
609
|
+
return "";
|
|
610
|
+
}
|
|
611
|
+
return item.type === "text" && typeof item.text === "string" ? item.text : "";
|
|
612
|
+
}).filter((item) => item.length > 0).join("");
|
|
613
|
+
};
|
|
614
|
+
var toUserContent = (content) => {
|
|
615
|
+
if (typeof content === "string") {
|
|
616
|
+
return content;
|
|
617
|
+
}
|
|
618
|
+
if (!Array.isArray(content)) {
|
|
619
|
+
return "";
|
|
620
|
+
}
|
|
621
|
+
const parts = [];
|
|
622
|
+
for (const item of content) {
|
|
623
|
+
if (!isRecord(item) || typeof item.type !== "string") {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
627
|
+
parts.push({ type: "text", text: item.text });
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (item.type === "image_url" && isRecord(item.image_url) && typeof item.image_url.url === "string") {
|
|
631
|
+
try {
|
|
632
|
+
parts.push({ type: "image", image: new URL(item.image_url.url) });
|
|
633
|
+
} catch {
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return parts.length > 0 ? parts : "";
|
|
638
|
+
};
|
|
639
|
+
var toModelMessages = (messages) => {
|
|
640
|
+
const modelMessages = [];
|
|
641
|
+
const toolNamesById = /* @__PURE__ */ new Map();
|
|
642
|
+
for (const message of messages) {
|
|
643
|
+
switch (message.role) {
|
|
644
|
+
case "system":
|
|
645
|
+
case "developer":
|
|
646
|
+
modelMessages.push({
|
|
647
|
+
role: "system",
|
|
648
|
+
content: toTextParts(message.content)
|
|
649
|
+
});
|
|
650
|
+
break;
|
|
651
|
+
case "user":
|
|
652
|
+
modelMessages.push({
|
|
653
|
+
role: "user",
|
|
654
|
+
content: toUserContent(message.content)
|
|
655
|
+
});
|
|
656
|
+
break;
|
|
657
|
+
case "assistant": {
|
|
658
|
+
const parts = [];
|
|
659
|
+
const text = toTextParts(message.content);
|
|
660
|
+
if (text.length > 0) {
|
|
661
|
+
parts.push({ type: "text", text });
|
|
662
|
+
}
|
|
663
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
664
|
+
const toolCallId = toolCall.id;
|
|
665
|
+
const toolName = toolCall.function?.name;
|
|
666
|
+
if (typeof toolCallId !== "string" || typeof toolName !== "string" || toolName.length === 0) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
toolNamesById.set(toolCallId, toolName);
|
|
670
|
+
parts.push({
|
|
671
|
+
type: "tool-call",
|
|
672
|
+
toolCallId,
|
|
673
|
+
toolName,
|
|
674
|
+
input: parseToolArguments(toolCall.function?.arguments)
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
modelMessages.push({
|
|
678
|
+
role: "assistant",
|
|
679
|
+
content: parts.length === 1 && parts[0]?.type === "text" ? parts[0].text : parts
|
|
680
|
+
});
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
case "tool":
|
|
684
|
+
if (typeof message.tool_call_id !== "string") {
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
modelMessages.push({
|
|
688
|
+
role: "tool",
|
|
689
|
+
content: [
|
|
690
|
+
{
|
|
691
|
+
type: "tool-result",
|
|
692
|
+
toolCallId: message.tool_call_id,
|
|
693
|
+
toolName: toolNamesById.get(message.tool_call_id) ?? "tool",
|
|
694
|
+
output: coerceToolOutput(message.content)
|
|
695
|
+
}
|
|
696
|
+
]
|
|
697
|
+
});
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return modelMessages;
|
|
702
|
+
};
|
|
703
|
+
var createToolSet = (tools) => {
|
|
704
|
+
if (!Array.isArray(tools)) {
|
|
705
|
+
return {};
|
|
706
|
+
}
|
|
707
|
+
const entries = [];
|
|
708
|
+
for (const definition of tools) {
|
|
709
|
+
const toolName = definition.function?.name;
|
|
710
|
+
if (definition.type !== "function" || typeof toolName !== "string" || toolName.length === 0) {
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
entries.push([
|
|
714
|
+
toolName,
|
|
715
|
+
tool({
|
|
716
|
+
description: definition.function?.description,
|
|
717
|
+
inputSchema: jsonSchema(
|
|
718
|
+
definition.function?.parameters ?? {
|
|
719
|
+
type: "object",
|
|
720
|
+
properties: {},
|
|
721
|
+
additionalProperties: true
|
|
722
|
+
}
|
|
723
|
+
)
|
|
724
|
+
})
|
|
725
|
+
]);
|
|
726
|
+
}
|
|
727
|
+
return Object.fromEntries(entries);
|
|
728
|
+
};
|
|
729
|
+
var toToolChoice = (toolChoice) => {
|
|
730
|
+
if (toolChoice == null || toolChoice === "auto" || toolChoice === "none" || toolChoice === "required") {
|
|
731
|
+
return toolChoice;
|
|
732
|
+
}
|
|
733
|
+
if (toolChoice.type === "function" && typeof toolChoice.function?.name === "string") {
|
|
734
|
+
return {
|
|
735
|
+
type: "tool",
|
|
736
|
+
toolName: toolChoice.function.name
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
return "auto";
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
// src/chat-stream.ts
|
|
743
|
+
import { streamText } from "ai";
|
|
744
|
+
|
|
745
|
+
// src/logging.ts
|
|
746
|
+
var createRequestLogger = (settings) => {
|
|
747
|
+
if (typeof settings.requestLogger === "function") {
|
|
748
|
+
return settings.requestLogger;
|
|
749
|
+
}
|
|
750
|
+
if (process.env.CODEX_OPENAI_SERVER_LOG_REQUESTS !== "1") {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
return (event) => {
|
|
754
|
+
console.log(
|
|
755
|
+
JSON.stringify({
|
|
756
|
+
source: "openai-oauth",
|
|
757
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
758
|
+
...event
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
var emitRequestLog = (logger, event) => {
|
|
764
|
+
try {
|
|
765
|
+
logger?.(event);
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// src/chat-stream.ts
|
|
771
|
+
var encodeSse = (data) => new TextEncoder().encode(`data: ${JSON.stringify(data)}
|
|
772
|
+
|
|
773
|
+
`);
|
|
774
|
+
var encodeDone = () => new TextEncoder().encode("data: [DONE]\n\n");
|
|
775
|
+
var logChatStreamResult = (logger, requestId, startedAt, finishReason, usage) => {
|
|
776
|
+
emitRequestLog(logger, {
|
|
777
|
+
type: "chat_response",
|
|
778
|
+
requestId,
|
|
779
|
+
path: "/v1/chat/completions",
|
|
780
|
+
status: 200,
|
|
781
|
+
stream: true,
|
|
782
|
+
durationMs: Date.now() - startedAt,
|
|
783
|
+
finishReason,
|
|
784
|
+
usage
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
var streamChatCompletions = async (request, provider, logContext) => {
|
|
788
|
+
const toolIndexes = /* @__PURE__ */ new Map();
|
|
789
|
+
const toolsWithDeltas = /* @__PURE__ */ new Set();
|
|
790
|
+
const created = Math.floor(Date.now() / 1e3);
|
|
791
|
+
const id = `chatcmpl_${crypto.randomUUID()}`;
|
|
792
|
+
const result = streamText({
|
|
793
|
+
model: provider(request.model ?? "gpt-5.2"),
|
|
794
|
+
messages: toModelMessages(request.messages ?? []),
|
|
795
|
+
tools: createToolSet(request.tools),
|
|
796
|
+
toolChoice: toToolChoice(request.tool_choice),
|
|
797
|
+
temperature: request.temperature,
|
|
798
|
+
topP: request.top_p,
|
|
799
|
+
stopSequences: typeof request.stop === "string" ? [request.stop] : Array.isArray(request.stop) ? request.stop : void 0,
|
|
800
|
+
maxOutputTokens: request.max_tokens,
|
|
801
|
+
providerOptions: {
|
|
802
|
+
openai: {
|
|
803
|
+
parallelToolCalls: request.parallel_tool_calls,
|
|
804
|
+
reasoningEffort: request.reasoning_effort
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
const stream = new ReadableStream({
|
|
809
|
+
async start(controller) {
|
|
810
|
+
controller.enqueue(
|
|
811
|
+
encodeSse({
|
|
812
|
+
id,
|
|
813
|
+
object: "chat.completion.chunk",
|
|
814
|
+
created,
|
|
815
|
+
model: request.model,
|
|
816
|
+
choices: [
|
|
817
|
+
{ index: 0, delta: { role: "assistant" }, finish_reason: null }
|
|
818
|
+
]
|
|
819
|
+
})
|
|
820
|
+
);
|
|
821
|
+
for await (const part of result.fullStream) {
|
|
822
|
+
switch (part.type) {
|
|
823
|
+
case "text-delta":
|
|
824
|
+
controller.enqueue(
|
|
825
|
+
encodeSse({
|
|
826
|
+
id,
|
|
827
|
+
object: "chat.completion.chunk",
|
|
828
|
+
created,
|
|
829
|
+
model: request.model,
|
|
830
|
+
choices: [
|
|
831
|
+
{
|
|
832
|
+
index: 0,
|
|
833
|
+
delta: { content: part.text },
|
|
834
|
+
finish_reason: null
|
|
835
|
+
}
|
|
836
|
+
]
|
|
837
|
+
})
|
|
838
|
+
);
|
|
839
|
+
break;
|
|
840
|
+
case "tool-input-start": {
|
|
841
|
+
const nextIndex = toolIndexes.size;
|
|
842
|
+
toolIndexes.set(part.id, nextIndex);
|
|
843
|
+
controller.enqueue(
|
|
844
|
+
encodeSse({
|
|
845
|
+
id,
|
|
846
|
+
object: "chat.completion.chunk",
|
|
847
|
+
created,
|
|
848
|
+
model: request.model,
|
|
849
|
+
choices: [
|
|
850
|
+
{
|
|
851
|
+
index: 0,
|
|
852
|
+
delta: {
|
|
853
|
+
tool_calls: [
|
|
854
|
+
{
|
|
855
|
+
index: nextIndex,
|
|
856
|
+
id: part.id,
|
|
857
|
+
type: "function",
|
|
858
|
+
function: { name: part.toolName, arguments: "" }
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
},
|
|
862
|
+
finish_reason: null
|
|
863
|
+
}
|
|
864
|
+
]
|
|
865
|
+
})
|
|
866
|
+
);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
case "tool-input-delta": {
|
|
870
|
+
const index = toolIndexes.get(part.id);
|
|
871
|
+
if (index == null) {
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
toolsWithDeltas.add(part.id);
|
|
875
|
+
controller.enqueue(
|
|
876
|
+
encodeSse({
|
|
877
|
+
id,
|
|
878
|
+
object: "chat.completion.chunk",
|
|
879
|
+
created,
|
|
880
|
+
model: request.model,
|
|
881
|
+
choices: [
|
|
882
|
+
{
|
|
883
|
+
index: 0,
|
|
884
|
+
delta: {
|
|
885
|
+
tool_calls: [
|
|
886
|
+
{ index, function: { arguments: part.delta } }
|
|
887
|
+
]
|
|
888
|
+
},
|
|
889
|
+
finish_reason: null
|
|
890
|
+
}
|
|
891
|
+
]
|
|
892
|
+
})
|
|
893
|
+
);
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case "tool-call": {
|
|
897
|
+
const index = toolIndexes.get(part.toolCallId);
|
|
898
|
+
if (index == null || toolsWithDeltas.has(part.toolCallId)) {
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
controller.enqueue(
|
|
902
|
+
encodeSse({
|
|
903
|
+
id,
|
|
904
|
+
object: "chat.completion.chunk",
|
|
905
|
+
created,
|
|
906
|
+
model: request.model,
|
|
907
|
+
choices: [
|
|
908
|
+
{
|
|
909
|
+
index: 0,
|
|
910
|
+
delta: {
|
|
911
|
+
tool_calls: [
|
|
912
|
+
{
|
|
913
|
+
index,
|
|
914
|
+
function: {
|
|
915
|
+
arguments: JSON.stringify(part.input)
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
]
|
|
919
|
+
},
|
|
920
|
+
finish_reason: null
|
|
921
|
+
}
|
|
922
|
+
]
|
|
923
|
+
})
|
|
924
|
+
);
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
case "finish":
|
|
928
|
+
logChatStreamResult(
|
|
929
|
+
logContext.logger,
|
|
930
|
+
logContext.requestId,
|
|
931
|
+
logContext.startedAt,
|
|
932
|
+
part.finishReason,
|
|
933
|
+
part.totalUsage
|
|
934
|
+
);
|
|
935
|
+
controller.enqueue(
|
|
936
|
+
encodeSse({
|
|
937
|
+
id,
|
|
938
|
+
object: "chat.completion.chunk",
|
|
939
|
+
created,
|
|
940
|
+
model: request.model,
|
|
941
|
+
choices: [
|
|
942
|
+
{
|
|
943
|
+
index: 0,
|
|
944
|
+
delta: {},
|
|
945
|
+
finish_reason: mapFinishReason(part.finishReason)
|
|
946
|
+
}
|
|
947
|
+
]
|
|
948
|
+
})
|
|
949
|
+
);
|
|
950
|
+
controller.enqueue(
|
|
951
|
+
encodeSse({
|
|
952
|
+
id,
|
|
953
|
+
object: "chat.completion.chunk",
|
|
954
|
+
created,
|
|
955
|
+
model: request.model,
|
|
956
|
+
choices: [],
|
|
957
|
+
usage: toUsage(part.totalUsage)
|
|
958
|
+
})
|
|
959
|
+
);
|
|
960
|
+
break;
|
|
961
|
+
case "error":
|
|
962
|
+
emitRequestLog(logContext.logger, {
|
|
963
|
+
type: "chat_error",
|
|
964
|
+
requestId: logContext.requestId,
|
|
965
|
+
path: "/v1/chat/completions",
|
|
966
|
+
durationMs: Date.now() - logContext.startedAt,
|
|
967
|
+
message: part.error instanceof Error ? part.error.message : "Streaming chat completion failed."
|
|
968
|
+
});
|
|
969
|
+
controller.error(
|
|
970
|
+
part.error instanceof Error ? part.error : new Error("Streaming chat completion failed.", {
|
|
971
|
+
cause: part.error
|
|
972
|
+
})
|
|
973
|
+
);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
controller.enqueue(encodeDone());
|
|
978
|
+
controller.close();
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
return new Response(stream, {
|
|
982
|
+
status: 200,
|
|
983
|
+
headers: {
|
|
984
|
+
...sseHeaders,
|
|
985
|
+
...corsHeaders
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// src/chat-completions.ts
|
|
991
|
+
var isChatRequest = (value) => isRecord(value) && (value.messages === void 0 || Array.isArray(value.messages));
|
|
992
|
+
var toChatCompletionResponse = (result, request) => {
|
|
993
|
+
const toolCalls = result.toolCalls.map((toolCall) => ({
|
|
994
|
+
id: toolCall.toolCallId,
|
|
995
|
+
type: "function",
|
|
996
|
+
function: {
|
|
997
|
+
name: toolCall.toolName,
|
|
998
|
+
arguments: JSON.stringify(toolCall.input)
|
|
999
|
+
}
|
|
1000
|
+
}));
|
|
1001
|
+
return toJsonResponse({
|
|
1002
|
+
id: `chatcmpl_${crypto.randomUUID()}`,
|
|
1003
|
+
object: "chat.completion",
|
|
1004
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1005
|
+
model: request.model,
|
|
1006
|
+
choices: [
|
|
1007
|
+
{
|
|
1008
|
+
index: 0,
|
|
1009
|
+
message: {
|
|
1010
|
+
role: "assistant",
|
|
1011
|
+
content: result.text.length > 0 ? result.text : null,
|
|
1012
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
|
1013
|
+
},
|
|
1014
|
+
finish_reason: mapFinishReason(result.finishReason)
|
|
1015
|
+
}
|
|
1016
|
+
],
|
|
1017
|
+
usage: toUsage(result.usage)
|
|
1018
|
+
});
|
|
1019
|
+
};
|
|
1020
|
+
var handleChatCompletionsRequest = async (request, provider, logger) => {
|
|
1021
|
+
const requestId = crypto.randomUUID();
|
|
1022
|
+
const startedAt = Date.now();
|
|
1023
|
+
const body = await request.json();
|
|
1024
|
+
if (!isChatRequest(body) || !Array.isArray(body.messages)) {
|
|
1025
|
+
emitRequestLog(logger, {
|
|
1026
|
+
type: "chat_error",
|
|
1027
|
+
requestId,
|
|
1028
|
+
path: "/v1/chat/completions",
|
|
1029
|
+
durationMs: Date.now() - startedAt,
|
|
1030
|
+
message: "`messages` must be an array."
|
|
1031
|
+
});
|
|
1032
|
+
return toErrorResponse("`messages` must be an array.");
|
|
1033
|
+
}
|
|
1034
|
+
emitRequestLog(logger, {
|
|
1035
|
+
type: "chat_request",
|
|
1036
|
+
requestId,
|
|
1037
|
+
path: "/v1/chat/completions",
|
|
1038
|
+
...summarizeChatRequest(body)
|
|
1039
|
+
});
|
|
1040
|
+
if (body.stream === true) {
|
|
1041
|
+
return streamChatCompletions(body, provider, {
|
|
1042
|
+
logger,
|
|
1043
|
+
requestId,
|
|
1044
|
+
startedAt
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const result = await generateText({
|
|
1049
|
+
model: provider(body.model ?? "gpt-5.2"),
|
|
1050
|
+
messages: toModelMessages(body.messages),
|
|
1051
|
+
tools: createToolSet(body.tools),
|
|
1052
|
+
toolChoice: toToolChoice(body.tool_choice),
|
|
1053
|
+
temperature: body.temperature,
|
|
1054
|
+
topP: body.top_p,
|
|
1055
|
+
stopSequences: typeof body.stop === "string" ? [body.stop] : Array.isArray(body.stop) ? body.stop : void 0,
|
|
1056
|
+
maxOutputTokens: body.max_tokens,
|
|
1057
|
+
providerOptions: {
|
|
1058
|
+
openai: {
|
|
1059
|
+
parallelToolCalls: body.parallel_tool_calls,
|
|
1060
|
+
reasoningEffort: body.reasoning_effort
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
emitRequestLog(logger, {
|
|
1065
|
+
type: "chat_response",
|
|
1066
|
+
requestId,
|
|
1067
|
+
path: "/v1/chat/completions",
|
|
1068
|
+
status: 200,
|
|
1069
|
+
stream: false,
|
|
1070
|
+
durationMs: Date.now() - startedAt,
|
|
1071
|
+
finishReason: result.finishReason,
|
|
1072
|
+
usage: result.usage
|
|
1073
|
+
});
|
|
1074
|
+
return toChatCompletionResponse(result, body);
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
emitRequestLog(logger, {
|
|
1077
|
+
type: "chat_error",
|
|
1078
|
+
requestId,
|
|
1079
|
+
path: "/v1/chat/completions",
|
|
1080
|
+
durationMs: Date.now() - startedAt,
|
|
1081
|
+
message: error instanceof Error ? error.message : "Unexpected server error."
|
|
1082
|
+
});
|
|
1083
|
+
throw error;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
// src/models.ts
|
|
1088
|
+
import { execFile } from "child_process";
|
|
1089
|
+
import { promisify } from "util";
|
|
1090
|
+
var MODELS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1091
|
+
var CODEX_VERSION_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
1092
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@openai/codex/latest";
|
|
1093
|
+
var FALLBACK_CODEX_CLIENT_VERSION = "0.111.0";
|
|
1094
|
+
var execFileAsync = promisify(execFile);
|
|
1095
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1096
|
+
var uniqueStrings = (values) => {
|
|
1097
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1098
|
+
const result = [];
|
|
1099
|
+
for (const value of values) {
|
|
1100
|
+
if (!seen.has(value)) {
|
|
1101
|
+
seen.add(value);
|
|
1102
|
+
result.push(value);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return result;
|
|
1106
|
+
};
|
|
1107
|
+
var normalizeVersion = (value) => {
|
|
1108
|
+
if (typeof value !== "string") {
|
|
1109
|
+
return void 0;
|
|
1110
|
+
}
|
|
1111
|
+
const match = value.trim().match(/\b\d+\.\d+\.\d+\b/);
|
|
1112
|
+
return match?.[0];
|
|
1113
|
+
};
|
|
1114
|
+
var cachedCodexClientVersion;
|
|
1115
|
+
var codexClientVersionCacheExpiresAt = 0;
|
|
1116
|
+
var inflightCodexClientVersion;
|
|
1117
|
+
var resolveLocalCodexClientVersion = async (runCommand) => {
|
|
1118
|
+
try {
|
|
1119
|
+
const { stdout, stderr } = await runCommand("codex", ["--version"]);
|
|
1120
|
+
return normalizeVersion(stdout) ?? normalizeVersion(stderr);
|
|
1121
|
+
} catch {
|
|
1122
|
+
return void 0;
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
var resolveRemoteCodexClientVersion = async (fetchImpl) => {
|
|
1126
|
+
try {
|
|
1127
|
+
const response = await fetchImpl(REGISTRY_URL, {
|
|
1128
|
+
headers: {
|
|
1129
|
+
accept: "application/json"
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
if (!response.ok) {
|
|
1133
|
+
return void 0;
|
|
1134
|
+
}
|
|
1135
|
+
const parsed = await response.json();
|
|
1136
|
+
return typeof parsed.version === "string" ? normalizeVersion(parsed.version) : void 0;
|
|
1137
|
+
} catch {
|
|
1138
|
+
return void 0;
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
var resolveCodexClientVersion = async (dependencies = {}) => {
|
|
1142
|
+
if (typeof dependencies.codexVersion === "string" && dependencies.codexVersion.trim().length > 0) {
|
|
1143
|
+
return dependencies.codexVersion.trim();
|
|
1144
|
+
}
|
|
1145
|
+
const now = Date.now();
|
|
1146
|
+
if (cachedCodexClientVersion && now < codexClientVersionCacheExpiresAt) {
|
|
1147
|
+
return cachedCodexClientVersion;
|
|
1148
|
+
}
|
|
1149
|
+
if (inflightCodexClientVersion) {
|
|
1150
|
+
return inflightCodexClientVersion;
|
|
1151
|
+
}
|
|
1152
|
+
const runCommand = dependencies.runCommand ?? execFileAsync;
|
|
1153
|
+
const fetchImpl = dependencies.fetchImpl ?? globalThis.fetch;
|
|
1154
|
+
inflightCodexClientVersion = (async () => {
|
|
1155
|
+
const localVersion = await resolveLocalCodexClientVersion(runCommand);
|
|
1156
|
+
if (localVersion) {
|
|
1157
|
+
cachedCodexClientVersion = localVersion;
|
|
1158
|
+
codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
|
|
1159
|
+
inflightCodexClientVersion = void 0;
|
|
1160
|
+
return localVersion;
|
|
1161
|
+
}
|
|
1162
|
+
const remoteVersion = await resolveRemoteCodexClientVersion(fetchImpl);
|
|
1163
|
+
if (remoteVersion) {
|
|
1164
|
+
cachedCodexClientVersion = remoteVersion;
|
|
1165
|
+
codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
|
|
1166
|
+
inflightCodexClientVersion = void 0;
|
|
1167
|
+
return remoteVersion;
|
|
1168
|
+
}
|
|
1169
|
+
cachedCodexClientVersion = FALLBACK_CODEX_CLIENT_VERSION;
|
|
1170
|
+
codexClientVersionCacheExpiresAt = Date.now() + CODEX_VERSION_CACHE_TTL_MS;
|
|
1171
|
+
dependencies.onWarning?.(
|
|
1172
|
+
`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.`
|
|
1173
|
+
);
|
|
1174
|
+
inflightCodexClientVersion = void 0;
|
|
1175
|
+
return FALLBACK_CODEX_CLIENT_VERSION;
|
|
1176
|
+
})().catch((error) => {
|
|
1177
|
+
inflightCodexClientVersion = void 0;
|
|
1178
|
+
throw error;
|
|
1179
|
+
});
|
|
1180
|
+
return inflightCodexClientVersion;
|
|
1181
|
+
};
|
|
1182
|
+
var toUpstreamErrorMessage = (bodyText) => {
|
|
1183
|
+
if (typeof bodyText !== "string" || bodyText.length === 0) {
|
|
1184
|
+
return "Failed to load models from Codex.";
|
|
1185
|
+
}
|
|
1186
|
+
try {
|
|
1187
|
+
const parsed = JSON.parse(bodyText);
|
|
1188
|
+
if (typeof parsed.detail === "string" && parsed.detail.length > 0) {
|
|
1189
|
+
return parsed.detail;
|
|
1190
|
+
}
|
|
1191
|
+
if (isRecord2(parsed.error) && typeof parsed.error.message === "string") {
|
|
1192
|
+
return parsed.error.message;
|
|
1193
|
+
}
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
return bodyText;
|
|
1197
|
+
};
|
|
1198
|
+
var fetchAvailableModels = async (client, dependencies = {}) => {
|
|
1199
|
+
const clientVersion = await resolveCodexClientVersion(dependencies);
|
|
1200
|
+
const response = await client.request(
|
|
1201
|
+
`/models?client_version=${encodeURIComponent(clientVersion)}`
|
|
1202
|
+
);
|
|
1203
|
+
const bodyText = await response.text();
|
|
1204
|
+
if (!response.ok) {
|
|
1205
|
+
throw new Error(toUpstreamErrorMessage(bodyText));
|
|
1206
|
+
}
|
|
1207
|
+
let parsed;
|
|
1208
|
+
try {
|
|
1209
|
+
parsed = JSON.parse(bodyText);
|
|
1210
|
+
} catch {
|
|
1211
|
+
throw new Error("Codex returned an invalid models response.");
|
|
1212
|
+
}
|
|
1213
|
+
if (!Array.isArray(parsed.models)) {
|
|
1214
|
+
throw new Error("Codex returned a malformed models response.");
|
|
1215
|
+
}
|
|
1216
|
+
const models = uniqueStrings(
|
|
1217
|
+
parsed.models.map((model) => model.slug).filter(
|
|
1218
|
+
(slug) => typeof slug === "string" && slug.length > 0
|
|
1219
|
+
)
|
|
1220
|
+
);
|
|
1221
|
+
if (models.length === 0) {
|
|
1222
|
+
throw new Error("Codex returned an empty models list.");
|
|
1223
|
+
}
|
|
1224
|
+
return models;
|
|
1225
|
+
};
|
|
1226
|
+
var resolveOpenAIOAuthModels = async (client, configuredModels, dependencies = {}) => {
|
|
1227
|
+
if (Array.isArray(configuredModels) && configuredModels.length > 0) {
|
|
1228
|
+
return uniqueStrings(configuredModels);
|
|
1229
|
+
}
|
|
1230
|
+
return fetchAvailableModels(client, dependencies);
|
|
1231
|
+
};
|
|
1232
|
+
var createModelResolver = (client, configuredModels, dependencies = {}) => {
|
|
1233
|
+
let cachedModels;
|
|
1234
|
+
let cacheExpiresAt = 0;
|
|
1235
|
+
let inflight;
|
|
1236
|
+
return async () => {
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
if (cachedModels && now < cacheExpiresAt) {
|
|
1239
|
+
return [...cachedModels];
|
|
1240
|
+
}
|
|
1241
|
+
if (inflight) {
|
|
1242
|
+
return [...await inflight];
|
|
1243
|
+
}
|
|
1244
|
+
inflight = resolveOpenAIOAuthModels(client, configuredModels, dependencies).then((models) => {
|
|
1245
|
+
cachedModels = models;
|
|
1246
|
+
cacheExpiresAt = Date.now() + MODELS_CACHE_TTL_MS;
|
|
1247
|
+
inflight = void 0;
|
|
1248
|
+
return models;
|
|
1249
|
+
}).catch((error) => {
|
|
1250
|
+
inflight = void 0;
|
|
1251
|
+
throw error;
|
|
1252
|
+
});
|
|
1253
|
+
return [...await inflight];
|
|
1254
|
+
};
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
// src/responses.ts
|
|
1258
|
+
import {
|
|
1259
|
+
collectCompletedResponseFromSse,
|
|
1260
|
+
normalizeCodexResponsesBody,
|
|
1261
|
+
usesServerReplayState
|
|
1262
|
+
} from "@openai-oauth/core";
|
|
1263
|
+
var handleResponsesRequest = async (request, settings, client) => {
|
|
1264
|
+
const body = await request.json();
|
|
1265
|
+
if (!isRecord(body)) {
|
|
1266
|
+
return toErrorResponse("Request body must be a JSON object.");
|
|
1267
|
+
}
|
|
1268
|
+
if (usesServerReplayState(body)) {
|
|
1269
|
+
return toErrorResponse(
|
|
1270
|
+
"Stateless Codex responses endpoint does not support `previous_response_id` or `item_reference`. Replay the full conversation history in `input` on each request."
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const wantsStream = body.stream === true;
|
|
1274
|
+
const upstream = await client.request("/responses", {
|
|
1275
|
+
method: "POST",
|
|
1276
|
+
headers: {
|
|
1277
|
+
"Content-Type": "application/json"
|
|
1278
|
+
},
|
|
1279
|
+
body: JSON.stringify(
|
|
1280
|
+
normalizeCodexResponsesBody(body, {
|
|
1281
|
+
forceStream: true,
|
|
1282
|
+
instructions: settings.instructions,
|
|
1283
|
+
storeResponses: settings.storeResponses
|
|
1284
|
+
})
|
|
1285
|
+
)
|
|
1286
|
+
});
|
|
1287
|
+
if (!upstream.ok) {
|
|
1288
|
+
return copyUpstreamResponse(upstream);
|
|
1289
|
+
}
|
|
1290
|
+
if (wantsStream) {
|
|
1291
|
+
return new Response(upstream.body, {
|
|
1292
|
+
status: upstream.status,
|
|
1293
|
+
headers: {
|
|
1294
|
+
...sseHeaders,
|
|
1295
|
+
...corsHeaders
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
const completed = await collectCompletedResponseFromSse(
|
|
1300
|
+
upstream.body ?? new ReadableStream()
|
|
1301
|
+
);
|
|
1302
|
+
return toJsonResponse(completed);
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
// src/server.ts
|
|
1306
|
+
var handleRoutes = async (request, settings, provider, client, resolveModels, requestLogger) => {
|
|
1307
|
+
if (request.method === "OPTIONS") {
|
|
1308
|
+
return new Response(null, {
|
|
1309
|
+
status: 204,
|
|
1310
|
+
headers: corsHeaders
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
const url = new URL(request.url);
|
|
1314
|
+
if (request.method === "GET" && url.pathname === "/health") {
|
|
1315
|
+
return toJsonResponse({
|
|
1316
|
+
ok: true,
|
|
1317
|
+
replay_state: "stateless"
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
if (request.method === "GET" && url.pathname === "/v1/models") {
|
|
1321
|
+
try {
|
|
1322
|
+
const models = await resolveModels();
|
|
1323
|
+
return toJsonResponse({
|
|
1324
|
+
object: "list",
|
|
1325
|
+
data: models.map((id) => ({
|
|
1326
|
+
id,
|
|
1327
|
+
object: "model",
|
|
1328
|
+
created: 0,
|
|
1329
|
+
owned_by: "codex-oauth"
|
|
1330
|
+
}))
|
|
1331
|
+
});
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return toErrorResponse(
|
|
1334
|
+
error instanceof Error ? error.message : "Failed to load models.",
|
|
1335
|
+
502,
|
|
1336
|
+
"upstream_error"
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (request.method === "POST" && url.pathname === "/v1/responses") {
|
|
1341
|
+
return handleResponsesRequest(request, settings, client);
|
|
1342
|
+
}
|
|
1343
|
+
if (request.method === "POST" && url.pathname === "/v1/chat/completions") {
|
|
1344
|
+
return handleChatCompletionsRequest(request, provider, requestLogger);
|
|
1345
|
+
}
|
|
1346
|
+
return toErrorResponse("Route not found.", 404, "not_found_error");
|
|
1347
|
+
};
|
|
1348
|
+
var createOpenAIOAuthFetchHandler = (settings = {}) => {
|
|
1349
|
+
const auth = openaiCredentials(settings);
|
|
1350
|
+
const sharedSettings = {
|
|
1351
|
+
...settings,
|
|
1352
|
+
auth: () => auth.getSession(),
|
|
1353
|
+
responsesState: false
|
|
1354
|
+
};
|
|
1355
|
+
const client = createCodexOAuthClient(sharedSettings);
|
|
1356
|
+
const provider = createOpenAIOAuth(auth);
|
|
1357
|
+
const resolveModels = createModelResolver(client, settings.models, {
|
|
1358
|
+
codexVersion: settings.codexVersion
|
|
1359
|
+
});
|
|
1360
|
+
const requestLogger = createRequestLogger(settings);
|
|
1361
|
+
return async (request) => {
|
|
1362
|
+
try {
|
|
1363
|
+
return await handleRoutes(
|
|
1364
|
+
request,
|
|
1365
|
+
settings,
|
|
1366
|
+
provider,
|
|
1367
|
+
client,
|
|
1368
|
+
resolveModels,
|
|
1369
|
+
requestLogger
|
|
1370
|
+
);
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
return toErrorResponse(
|
|
1373
|
+
error instanceof Error ? error.message : "Unexpected server error.",
|
|
1374
|
+
500,
|
|
1375
|
+
"server_error"
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
1380
|
+
var startOpenAIOAuthServer = async (settings = {}) => {
|
|
1381
|
+
const host = settings.host ?? DEFAULT_HOST;
|
|
1382
|
+
const port = settings.port ?? DEFAULT_PORT;
|
|
1383
|
+
const handler = createOpenAIOAuthFetchHandler(settings);
|
|
1384
|
+
const server = createServer2(async (req, res) => {
|
|
1385
|
+
try {
|
|
1386
|
+
const request = await toWebRequest(req, { host, port });
|
|
1387
|
+
const response = await handler(request);
|
|
1388
|
+
await writeWebResponse(res, response);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
if (res.headersSent || res.writableEnded) {
|
|
1391
|
+
res.destroy(error instanceof Error ? error : void 0);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
const message = error instanceof Error ? error.message : "Unexpected server error.";
|
|
1395
|
+
await writeWebResponse(res, toErrorResponse(message, 500, "server_error"));
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
await new Promise((resolve, reject) => {
|
|
1399
|
+
server.once("error", reject);
|
|
1400
|
+
server.listen(port, host, () => {
|
|
1401
|
+
server.off("error", reject);
|
|
1402
|
+
resolve();
|
|
1403
|
+
});
|
|
1404
|
+
});
|
|
1405
|
+
const address = resolveAddress(server.address(), host);
|
|
1406
|
+
return {
|
|
1407
|
+
server,
|
|
1408
|
+
host: address.host,
|
|
1409
|
+
port: address.port,
|
|
1410
|
+
url: `http://${address.host}:${address.port}/v1`,
|
|
1411
|
+
close: () => new Promise((resolve, reject) => {
|
|
1412
|
+
server.close((error) => {
|
|
1413
|
+
if (error) {
|
|
1414
|
+
reject(error);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
resolve();
|
|
1418
|
+
});
|
|
1419
|
+
})
|
|
1420
|
+
};
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
// src/types.ts
|
|
1424
|
+
var defaultOpenAIOAuthModels = [
|
|
1425
|
+
"gpt-5.4",
|
|
1426
|
+
"gpt-5.3-codex",
|
|
1427
|
+
"gpt-5.3-codex-spark",
|
|
1428
|
+
"gpt-5.2",
|
|
1429
|
+
"gpt-5.1",
|
|
1430
|
+
"gpt-5.1-codex",
|
|
1431
|
+
"gpt-5.1-codex-max"
|
|
1432
|
+
];
|
|
1433
|
+
|
|
1434
|
+
export {
|
|
1435
|
+
runOpenAIOAuthLogin,
|
|
1436
|
+
DEFAULT_PORT,
|
|
1437
|
+
resolveOpenAIOAuthModels,
|
|
1438
|
+
createOpenAIOAuthFetchHandler,
|
|
1439
|
+
startOpenAIOAuthServer,
|
|
1440
|
+
defaultOpenAIOAuthModels
|
|
1441
|
+
};
|