@ottocode/sdk 0.1.221 → 0.1.223
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/package.json
CHANGED
|
@@ -194,11 +194,27 @@ export async function authorizeOpenAI(): Promise<OpenAIOAuthResult> {
|
|
|
194
194
|
server.listen(OPENAI_CALLBACK_PORT, '127.0.0.1', () => resolve());
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
const OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
198
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
199
|
+
|
|
200
|
+
const timeoutPromise = new Promise<string>((_resolve, reject) => {
|
|
201
|
+
timeoutId = setTimeout(() => {
|
|
202
|
+
server.close();
|
|
203
|
+
reject(new Error('OAuth callback timeout - authorization took too long'));
|
|
204
|
+
}, OAUTH_TIMEOUT_MS);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const callbackWithTimeout = () =>
|
|
208
|
+
Promise.race([callbackPromise, timeoutPromise]).finally(() => {
|
|
209
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
210
|
+
});
|
|
211
|
+
|
|
197
212
|
return {
|
|
198
213
|
url: authUrl,
|
|
199
214
|
verifier: pkce.verifier,
|
|
200
|
-
waitForCallback:
|
|
215
|
+
waitForCallback: callbackWithTimeout,
|
|
201
216
|
close: () => {
|
|
217
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
202
218
|
server.close();
|
|
203
219
|
},
|
|
204
220
|
};
|
|
@@ -7,33 +7,53 @@ import os from 'node:os';
|
|
|
7
7
|
const CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
|
|
8
8
|
const CODEX_RESPONSES_URL = 'https://chatgpt.com/backend-api/codex/responses';
|
|
9
9
|
|
|
10
|
+
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
11
|
+
const TOKEN_REFRESH_MAX_RETRIES = 2;
|
|
12
|
+
const TOKEN_REFRESH_RETRY_DELAY_MS = 1000;
|
|
13
|
+
|
|
10
14
|
export type OpenAIOAuthConfig = {
|
|
11
15
|
oauth: OAuth;
|
|
12
16
|
projectRoot?: string;
|
|
17
|
+
sessionId?: string;
|
|
13
18
|
};
|
|
14
19
|
|
|
20
|
+
function sleep(ms: number) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
async function refreshAndPersist(
|
|
16
25
|
oauth: OAuth,
|
|
17
26
|
projectRoot?: string,
|
|
18
27
|
): Promise<OAuth> {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
let lastError: Error | undefined;
|
|
29
|
+
for (let attempt = 0; attempt <= TOKEN_REFRESH_MAX_RETRIES; attempt++) {
|
|
30
|
+
try {
|
|
31
|
+
const newTokens = await refreshOpenAIToken(oauth.refresh);
|
|
32
|
+
const updated: OAuth = {
|
|
33
|
+
type: 'oauth',
|
|
34
|
+
access: newTokens.access,
|
|
35
|
+
refresh: newTokens.refresh,
|
|
36
|
+
expires: newTokens.expires,
|
|
37
|
+
accountId: oauth.accountId,
|
|
38
|
+
idToken: newTokens.idToken,
|
|
39
|
+
};
|
|
40
|
+
await setAuth('openai', updated, projectRoot, 'global');
|
|
41
|
+
return updated;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
44
|
+
if (attempt < TOKEN_REFRESH_MAX_RETRIES) {
|
|
45
|
+
await sleep(TOKEN_REFRESH_RETRY_DELAY_MS * (attempt + 1));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
throw lastError ?? new Error('Token refresh failed');
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
async function ensureValidToken(
|
|
33
53
|
oauth: OAuth,
|
|
34
54
|
projectRoot?: string,
|
|
35
55
|
): Promise<{ oauth: OAuth; access: string; accountId?: string }> {
|
|
36
|
-
if (oauth.access && oauth.expires > Date.now()) {
|
|
56
|
+
if (oauth.access && oauth.expires > Date.now() + TOKEN_EXPIRY_BUFFER_MS) {
|
|
37
57
|
return { oauth, access: oauth.access, accountId: oauth.accountId };
|
|
38
58
|
}
|
|
39
59
|
|
|
@@ -46,7 +66,7 @@ async function ensureValidToken(
|
|
|
46
66
|
};
|
|
47
67
|
} catch {
|
|
48
68
|
console.error(
|
|
49
|
-
'[openai-oauth] Token refresh failed, falling back to
|
|
69
|
+
'[openai-oauth] Token refresh failed after retries, falling back to current token',
|
|
50
70
|
);
|
|
51
71
|
return { oauth, access: oauth.access, accountId: oauth.accountId };
|
|
52
72
|
}
|
|
@@ -67,6 +87,7 @@ function buildHeaders(
|
|
|
67
87
|
init: RequestInit | undefined,
|
|
68
88
|
accessToken: string,
|
|
69
89
|
accountId?: string,
|
|
90
|
+
sessionId?: string,
|
|
70
91
|
): Headers {
|
|
71
92
|
const headers = new Headers(init?.headers);
|
|
72
93
|
headers.delete('Authorization');
|
|
@@ -80,6 +101,9 @@ function buildHeaders(
|
|
|
80
101
|
if (accountId) {
|
|
81
102
|
headers.set('ChatGPT-Account-Id', accountId);
|
|
82
103
|
}
|
|
104
|
+
if (sessionId) {
|
|
105
|
+
headers.set('session_id', sessionId);
|
|
106
|
+
}
|
|
83
107
|
return headers;
|
|
84
108
|
}
|
|
85
109
|
|
|
@@ -101,7 +125,12 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
|
101
125
|
: input.url;
|
|
102
126
|
const targetUrl = rewriteUrl(originalUrl);
|
|
103
127
|
|
|
104
|
-
const headers = buildHeaders(
|
|
128
|
+
const headers = buildHeaders(
|
|
129
|
+
init,
|
|
130
|
+
validated.access,
|
|
131
|
+
validated.accountId,
|
|
132
|
+
config.sessionId,
|
|
133
|
+
);
|
|
105
134
|
|
|
106
135
|
const response = await fetch(targetUrl, {
|
|
107
136
|
...init,
|
|
@@ -130,6 +159,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
|
130
159
|
init,
|
|
131
160
|
currentOAuth.access,
|
|
132
161
|
currentOAuth.accountId,
|
|
162
|
+
config.sessionId,
|
|
133
163
|
);
|
|
134
164
|
|
|
135
165
|
return fetch(targetUrl, {
|