@ottocode/sdk 0.1.221 → 0.1.222

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.221",
3
+ "version": "0.1.222",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -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: () => callbackPromise,
215
+ waitForCallback: callbackWithTimeout,
201
216
  close: () => {
217
+ if (timeoutId) clearTimeout(timeoutId);
202
218
  server.close();
203
219
  },
204
220
  };
@@ -704,8 +704,8 @@ async function applyAddOperation(
704
704
  oldStart: 0,
705
705
  oldLines: 0,
706
706
  newStart: 1,
707
- newLines: lines.length,
708
- additions: lines.length,
707
+ newLines: operation.lines.length,
708
+ additions: operation.lines.length,
709
709
  deletions: 0,
710
710
  };
711
711
 
@@ -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
- const newTokens = await refreshOpenAIToken(oauth.refresh);
20
- const updated: OAuth = {
21
- type: 'oauth',
22
- access: newTokens.access,
23
- refresh: newTokens.refresh,
24
- expires: newTokens.expires,
25
- accountId: oauth.accountId,
26
- idToken: newTokens.idToken,
27
- };
28
- await setAuth('openai', updated, projectRoot, 'global');
29
- return updated;
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 expired token',
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(init, validated.access, validated.accountId);
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, {