@ottocode/sdk 0.1.180 → 0.1.181

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.
@@ -3,35 +3,50 @@ import type { OAuth } from '../../types/src/index.ts';
3
3
  import { refreshOpenAIToken } from '../../auth/src/openai-oauth.ts';
4
4
  import { setAuth, getAuth } from '../../auth/src/index.ts';
5
5
 
6
- const CODEX_API_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';
6
+ const CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
7
7
 
8
8
  export type OpenAIOAuthConfig = {
9
9
  oauth: OAuth;
10
10
  projectRoot?: string;
11
11
  };
12
12
 
13
+ async function refreshAndPersist(
14
+ oauth: OAuth,
15
+ projectRoot?: string,
16
+ ): Promise<OAuth> {
17
+ const newTokens = await refreshOpenAIToken(oauth.refresh);
18
+ const updated: OAuth = {
19
+ type: 'oauth',
20
+ access: newTokens.access,
21
+ refresh: newTokens.refresh,
22
+ expires: newTokens.expires,
23
+ accountId: oauth.accountId,
24
+ idToken: newTokens.idToken,
25
+ };
26
+ await setAuth('openai', updated, projectRoot, 'global');
27
+ return updated;
28
+ }
29
+
13
30
  async function ensureValidToken(
14
31
  oauth: OAuth,
15
32
  projectRoot?: string,
16
- ): Promise<{ access: string; accountId?: string }> {
33
+ ): Promise<{ oauth: OAuth; access: string; accountId?: string }> {
17
34
  if (oauth.access && oauth.expires > Date.now()) {
18
- return { access: oauth.access, accountId: oauth.accountId };
35
+ return { oauth, access: oauth.access, accountId: oauth.accountId };
19
36
  }
20
37
 
21
38
  try {
22
- const newTokens = await refreshOpenAIToken(oauth.refresh);
23
- const updatedOAuth: OAuth = {
24
- type: 'oauth',
25
- access: newTokens.access,
26
- refresh: newTokens.refresh,
27
- expires: newTokens.expires,
28
- accountId: oauth.accountId,
29
- idToken: newTokens.idToken,
39
+ const updated = await refreshAndPersist(oauth, projectRoot);
40
+ return {
41
+ oauth: updated,
42
+ access: updated.access,
43
+ accountId: updated.accountId,
30
44
  };
31
- await setAuth('openai', updatedOAuth, projectRoot, 'global');
32
- return { access: newTokens.access, accountId: oauth.accountId };
33
45
  } catch {
34
- return { access: oauth.access, accountId: oauth.accountId };
46
+ console.error(
47
+ '[openai-oauth] Token refresh failed, falling back to expired token',
48
+ );
49
+ return { oauth, access: oauth.access, accountId: oauth.accountId };
35
50
  }
36
51
  }
37
52
 
@@ -41,11 +56,27 @@ function rewriteUrl(url: string): string {
41
56
  parsed.pathname.includes('/v1/responses') ||
42
57
  parsed.pathname.includes('/chat/completions')
43
58
  ) {
44
- return CODEX_API_ENDPOINT;
59
+ return `${CODEX_BASE_URL}${parsed.pathname.replace(/^.*\/v1/, '/v1')}`;
45
60
  }
46
61
  return url;
47
62
  }
48
63
 
64
+ function buildHeaders(
65
+ init: RequestInit | undefined,
66
+ accessToken: string,
67
+ accountId?: string,
68
+ ): Headers {
69
+ const headers = new Headers(init?.headers);
70
+ headers.delete('Authorization');
71
+ headers.delete('authorization');
72
+ headers.set('authorization', `Bearer ${accessToken}`);
73
+ headers.set('originator', 'otto');
74
+ if (accountId) {
75
+ headers.set('ChatGPT-Account-Id', accountId);
76
+ }
77
+ return headers;
78
+ }
79
+
49
80
  export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
50
81
  let currentOAuth = config.oauth;
51
82
 
@@ -53,10 +84,8 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
53
84
  input: Parameters<typeof fetch>[0],
54
85
  init?: Parameters<typeof fetch>[1],
55
86
  ): Promise<Response> => {
56
- const { access: accessToken, accountId } = await ensureValidToken(
57
- currentOAuth,
58
- config.projectRoot,
59
- );
87
+ const validated = await ensureValidToken(currentOAuth, config.projectRoot);
88
+ currentOAuth = validated.oauth;
60
89
 
61
90
  const originalUrl =
62
91
  typeof input === 'string'
@@ -66,24 +95,49 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
66
95
  : input.url;
67
96
  const targetUrl = rewriteUrl(originalUrl);
68
97
 
69
- const headers = new Headers(init?.headers);
70
- headers.delete('Authorization');
71
- headers.delete('authorization');
72
- headers.set('authorization', `Bearer ${accessToken}`);
73
- headers.set('originator', 'otto');
74
- if (accountId) {
75
- headers.set('ChatGPT-Account-Id', accountId);
76
- }
98
+ const headers = buildHeaders(init, validated.access, validated.accountId);
77
99
 
78
100
  const response = await fetch(targetUrl, {
79
101
  ...init,
80
102
  headers,
103
+ // biome-ignore lint/suspicious/noTsIgnore: Bun-specific fetch option
104
+ // @ts-ignore
105
+ timeout: false,
81
106
  });
82
107
 
83
108
  if (response.status === 401) {
84
- const refreshed = await getAuth('openai', config.projectRoot);
85
- if (refreshed?.type === 'oauth') {
86
- currentOAuth = refreshed;
109
+ try {
110
+ const refreshedFromDisk = await getAuth('openai', config.projectRoot);
111
+ if (
112
+ refreshedFromDisk?.type === 'oauth' &&
113
+ refreshedFromDisk.access !== validated.access
114
+ ) {
115
+ currentOAuth = refreshedFromDisk;
116
+ } else {
117
+ currentOAuth = await refreshAndPersist(
118
+ currentOAuth,
119
+ config.projectRoot,
120
+ );
121
+ }
122
+
123
+ const retryHeaders = buildHeaders(
124
+ init,
125
+ currentOAuth.access,
126
+ currentOAuth.accountId,
127
+ );
128
+
129
+ return fetch(targetUrl, {
130
+ ...init,
131
+ headers: retryHeaders,
132
+ // biome-ignore lint/suspicious/noTsIgnore: Bun-specific fetch option
133
+ // @ts-ignore
134
+ timeout: false,
135
+ });
136
+ } catch {
137
+ console.error(
138
+ '[openai-oauth] 401 retry failed, returning original 401 response',
139
+ );
140
+ return response;
87
141
  }
88
142
  }
89
143
 
@@ -101,6 +155,7 @@ export function createOpenAIOAuthModel(
101
155
 
102
156
  const provider = createOpenAI({
103
157
  apiKey: 'chatgpt-oauth',
158
+ baseURL: CODEX_BASE_URL,
104
159
  fetch: customFetch,
105
160
  });
106
161