@ottocode/sdk 0.1.288 → 0.1.290
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
package/src/auth/src/index.ts
CHANGED
|
@@ -74,11 +74,16 @@ export {
|
|
|
74
74
|
export {
|
|
75
75
|
authorizeOpenAI,
|
|
76
76
|
exchangeOpenAI,
|
|
77
|
+
exchangeOpenAIDeviceCode,
|
|
77
78
|
refreshOpenAIToken,
|
|
78
79
|
openOpenAIAuthUrl,
|
|
79
80
|
obtainOpenAIApiKey,
|
|
81
|
+
pollOpenAIDeviceCodeOnce,
|
|
82
|
+
requestOpenAIDeviceCode,
|
|
80
83
|
authorizeOpenAIWeb,
|
|
81
84
|
exchangeOpenAIWeb,
|
|
85
|
+
type OpenAIDeviceCodeResponse,
|
|
86
|
+
type OpenAIDevicePollResult,
|
|
82
87
|
type OpenAIOAuthResult,
|
|
83
88
|
} from './openai-oauth.ts';
|
|
84
89
|
|
|
@@ -5,6 +5,9 @@ import { createServer } from 'node:http';
|
|
|
5
5
|
const OPENAI_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
6
6
|
const OPENAI_ISSUER = 'https://auth.openai.com';
|
|
7
7
|
const OPENAI_CALLBACK_PORT = 1455;
|
|
8
|
+
const OPENAI_DEVICE_CALLBACK_URI = `${OPENAI_ISSUER}/deviceauth/callback`;
|
|
9
|
+
const OPENAI_DEVICE_AUTH_URL = `${OPENAI_ISSUER}/api/accounts/deviceauth`;
|
|
10
|
+
const OPENAI_DEVICE_VERIFICATION_URI = `${OPENAI_ISSUER}/codex/device`;
|
|
8
11
|
|
|
9
12
|
function generatePKCE() {
|
|
10
13
|
const verifier = randomBytes(32)
|
|
@@ -64,6 +67,180 @@ export type OpenAIOAuthResult = {
|
|
|
64
67
|
close: () => void;
|
|
65
68
|
};
|
|
66
69
|
|
|
70
|
+
export type OpenAIDeviceCodeResponse = {
|
|
71
|
+
verificationUri: string;
|
|
72
|
+
userCode: string;
|
|
73
|
+
deviceAuthId: string;
|
|
74
|
+
interval: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type OpenAIDevicePollResult =
|
|
78
|
+
| {
|
|
79
|
+
status: 'complete';
|
|
80
|
+
code: string;
|
|
81
|
+
codeVerifier: string;
|
|
82
|
+
codeChallenge: string;
|
|
83
|
+
}
|
|
84
|
+
| { status: 'pending' }
|
|
85
|
+
| { status: 'error'; error: string };
|
|
86
|
+
|
|
87
|
+
function parseOpenAIDeviceInterval(interval: unknown): number {
|
|
88
|
+
if (
|
|
89
|
+
typeof interval === 'number' &&
|
|
90
|
+
Number.isFinite(interval) &&
|
|
91
|
+
interval > 0
|
|
92
|
+
) {
|
|
93
|
+
return interval;
|
|
94
|
+
}
|
|
95
|
+
if (typeof interval === 'string') {
|
|
96
|
+
const parsed = Number.parseInt(interval, 10);
|
|
97
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
98
|
+
}
|
|
99
|
+
return 5;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseOpenAITokenPayload(accessToken: string): string | undefined {
|
|
103
|
+
try {
|
|
104
|
+
const payload = JSON.parse(
|
|
105
|
+
Buffer.from(accessToken.split('.')[1], 'base64').toString(),
|
|
106
|
+
);
|
|
107
|
+
return payload['https://api.openai.com/auth']?.chatgpt_account_id;
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function requestOpenAIDeviceCode(): Promise<OpenAIDeviceCodeResponse> {
|
|
112
|
+
const response = await fetch(`${OPENAI_DEVICE_AUTH_URL}/usercode`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
},
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
client_id: OPENAI_CLIENT_ID,
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
const error = await response.text().catch(() => '');
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Failed to initiate OpenAI device authorization${error ? `: ${error}` : ''}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const data = (await response.json()) as {
|
|
130
|
+
device_auth_id?: string;
|
|
131
|
+
user_code?: string;
|
|
132
|
+
usercode?: string;
|
|
133
|
+
interval?: number | string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const deviceAuthId = data.device_auth_id;
|
|
137
|
+
const userCode = data.user_code ?? data.usercode;
|
|
138
|
+
if (!deviceAuthId || !userCode) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
'OpenAI device authorization response was missing code data',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
verificationUri: OPENAI_DEVICE_VERIFICATION_URI,
|
|
146
|
+
userCode,
|
|
147
|
+
deviceAuthId,
|
|
148
|
+
interval: parseOpenAIDeviceInterval(data.interval),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function pollOpenAIDeviceCodeOnce(
|
|
153
|
+
deviceAuthId: string,
|
|
154
|
+
userCode: string,
|
|
155
|
+
): Promise<OpenAIDevicePollResult> {
|
|
156
|
+
const response = await fetch(`${OPENAI_DEVICE_AUTH_URL}/token`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
'Content-Type': 'application/json',
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
device_auth_id: deviceAuthId,
|
|
163
|
+
user_code: userCode,
|
|
164
|
+
}),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (response.status === 403 || response.status === 404) {
|
|
168
|
+
return { status: 'pending' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
const error = await response.text().catch(() => '');
|
|
173
|
+
return {
|
|
174
|
+
status: 'error',
|
|
175
|
+
error: `OpenAI device authorization failed${error ? `: ${error}` : ''}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const data = (await response.json()) as {
|
|
180
|
+
authorization_code?: string;
|
|
181
|
+
code_verifier?: string;
|
|
182
|
+
code_challenge?: string;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (!data.authorization_code || !data.code_verifier || !data.code_challenge) {
|
|
186
|
+
return {
|
|
187
|
+
status: 'error',
|
|
188
|
+
error: 'OpenAI device authorization response was missing token data',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
status: 'complete',
|
|
194
|
+
code: data.authorization_code,
|
|
195
|
+
codeVerifier: data.code_verifier,
|
|
196
|
+
codeChallenge: data.code_challenge,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function exchangeOpenAIDeviceCode(code: string, verifier: string) {
|
|
201
|
+
return exchangeOpenAIWithRedirect(code, verifier, OPENAI_DEVICE_CALLBACK_URI);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function exchangeOpenAIWithRedirect(
|
|
205
|
+
code: string,
|
|
206
|
+
verifier: string,
|
|
207
|
+
redirectUri: string,
|
|
208
|
+
) {
|
|
209
|
+
const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
213
|
+
},
|
|
214
|
+
body: new URLSearchParams({
|
|
215
|
+
grant_type: 'authorization_code',
|
|
216
|
+
code,
|
|
217
|
+
redirect_uri: redirectUri,
|
|
218
|
+
client_id: OPENAI_CLIENT_ID,
|
|
219
|
+
code_verifier: verifier,
|
|
220
|
+
}).toString(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (!response.ok) {
|
|
224
|
+
const error = await response.text();
|
|
225
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const json = (await response.json()) as {
|
|
229
|
+
id_token: string;
|
|
230
|
+
access_token: string;
|
|
231
|
+
refresh_token: string;
|
|
232
|
+
expires_in?: number;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
idToken: json.id_token,
|
|
237
|
+
access: json.access_token,
|
|
238
|
+
refresh: json.refresh_token,
|
|
239
|
+
expires: Date.now() + (json.expires_in || 3600) * 1000,
|
|
240
|
+
accountId: parseOpenAITokenPayload(json.access_token),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
67
244
|
export async function authorizeOpenAI(): Promise<OpenAIOAuthResult> {
|
|
68
245
|
const pkce = generatePKCE();
|
|
69
246
|
const state = generateState();
|
|
@@ -222,48 +399,7 @@ export async function authorizeOpenAI(): Promise<OpenAIOAuthResult> {
|
|
|
222
399
|
|
|
223
400
|
export async function exchangeOpenAI(code: string, verifier: string) {
|
|
224
401
|
const redirectUri = `http://localhost:${OPENAI_CALLBACK_PORT}/auth/callback`;
|
|
225
|
-
|
|
226
|
-
const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
|
|
227
|
-
method: 'POST',
|
|
228
|
-
headers: {
|
|
229
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
230
|
-
},
|
|
231
|
-
body: new URLSearchParams({
|
|
232
|
-
grant_type: 'authorization_code',
|
|
233
|
-
code,
|
|
234
|
-
redirect_uri: redirectUri,
|
|
235
|
-
client_id: OPENAI_CLIENT_ID,
|
|
236
|
-
code_verifier: verifier,
|
|
237
|
-
}).toString(),
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
if (!response.ok) {
|
|
241
|
-
const error = await response.text();
|
|
242
|
-
throw new Error(`Token exchange failed: ${error}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const json = (await response.json()) as {
|
|
246
|
-
id_token: string;
|
|
247
|
-
access_token: string;
|
|
248
|
-
refresh_token: string;
|
|
249
|
-
expires_in?: number;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
let accountId: string | undefined;
|
|
253
|
-
try {
|
|
254
|
-
const payload = JSON.parse(
|
|
255
|
-
Buffer.from(json.access_token.split('.')[1], 'base64').toString(),
|
|
256
|
-
);
|
|
257
|
-
accountId = payload['https://api.openai.com/auth']?.chatgpt_account_id;
|
|
258
|
-
} catch {}
|
|
259
|
-
|
|
260
|
-
return {
|
|
261
|
-
idToken: json.id_token,
|
|
262
|
-
access: json.access_token,
|
|
263
|
-
refresh: json.refresh_token,
|
|
264
|
-
expires: Date.now() + (json.expires_in || 3600) * 1000,
|
|
265
|
-
accountId,
|
|
266
|
-
};
|
|
402
|
+
return exchangeOpenAIWithRedirect(code, verifier, redirectUri);
|
|
267
403
|
}
|
|
268
404
|
|
|
269
405
|
export async function refreshOpenAIToken(refreshToken: string) {
|
|
@@ -367,45 +503,5 @@ export async function exchangeOpenAIWeb(
|
|
|
367
503
|
verifier: string,
|
|
368
504
|
redirectUri: string,
|
|
369
505
|
) {
|
|
370
|
-
|
|
371
|
-
method: 'POST',
|
|
372
|
-
headers: {
|
|
373
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
374
|
-
},
|
|
375
|
-
body: new URLSearchParams({
|
|
376
|
-
grant_type: 'authorization_code',
|
|
377
|
-
code,
|
|
378
|
-
redirect_uri: redirectUri,
|
|
379
|
-
client_id: OPENAI_CLIENT_ID,
|
|
380
|
-
code_verifier: verifier,
|
|
381
|
-
}).toString(),
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
if (!response.ok) {
|
|
385
|
-
const error = await response.text();
|
|
386
|
-
throw new Error(`Token exchange failed: ${error}`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const json = (await response.json()) as {
|
|
390
|
-
id_token: string;
|
|
391
|
-
access_token: string;
|
|
392
|
-
refresh_token: string;
|
|
393
|
-
expires_in?: number;
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
let accountId: string | undefined;
|
|
397
|
-
try {
|
|
398
|
-
const payload = JSON.parse(
|
|
399
|
-
Buffer.from(json.access_token.split('.')[1], 'base64').toString(),
|
|
400
|
-
);
|
|
401
|
-
accountId = payload['https://api.openai.com/auth']?.chatgpt_account_id;
|
|
402
|
-
} catch {}
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
idToken: json.id_token,
|
|
406
|
-
access: json.access_token,
|
|
407
|
-
refresh: json.refresh_token,
|
|
408
|
-
expires: Date.now() + (json.expires_in || 3600) * 1000,
|
|
409
|
-
accountId,
|
|
410
|
-
};
|
|
506
|
+
return exchangeOpenAIWithRedirect(code, verifier, redirectUri);
|
|
411
507
|
}
|
package/src/config/src/index.ts
CHANGED
|
@@ -38,6 +38,12 @@ const DEFAULTS: {
|
|
|
38
38
|
guidedMode: false,
|
|
39
39
|
reasoningText: true,
|
|
40
40
|
reasoningLevel: 'high',
|
|
41
|
+
theme: 'dark',
|
|
42
|
+
vimMode: false,
|
|
43
|
+
compactThread: true,
|
|
44
|
+
fontFamily: 'IBM Plex Mono',
|
|
45
|
+
smartEdges: true,
|
|
46
|
+
releaseToSend: false,
|
|
41
47
|
fullWidthContent: false,
|
|
42
48
|
autoCompactThresholdTokens: null,
|
|
43
49
|
},
|
|
@@ -84,6 +84,12 @@ export async function writeDefaults(
|
|
|
84
84
|
reasoningText: boolean;
|
|
85
85
|
reasoningLevel: 'minimal' | 'low' | 'medium' | 'high' | 'max' | 'xhigh';
|
|
86
86
|
theme: string;
|
|
87
|
+
vimMode: boolean;
|
|
88
|
+
compactThread: boolean;
|
|
89
|
+
fontFamily: string;
|
|
90
|
+
smartEdges: boolean;
|
|
91
|
+
releaseToSend: boolean;
|
|
92
|
+
fullWidthContent: boolean;
|
|
87
93
|
autoCompactThresholdTokens: number | null;
|
|
88
94
|
}>,
|
|
89
95
|
projectRoot?: string,
|
package/src/index.ts
CHANGED
|
@@ -184,13 +184,20 @@ export {
|
|
|
184
184
|
export {
|
|
185
185
|
authorizeOpenAI,
|
|
186
186
|
exchangeOpenAI,
|
|
187
|
+
exchangeOpenAIDeviceCode,
|
|
187
188
|
refreshOpenAIToken,
|
|
188
189
|
openOpenAIAuthUrl,
|
|
189
190
|
obtainOpenAIApiKey,
|
|
191
|
+
pollOpenAIDeviceCodeOnce,
|
|
192
|
+
requestOpenAIDeviceCode,
|
|
190
193
|
authorizeOpenAIWeb,
|
|
191
194
|
exchangeOpenAIWeb,
|
|
192
195
|
} from './auth/src/index.ts';
|
|
193
|
-
export type {
|
|
196
|
+
export type {
|
|
197
|
+
OpenAIDeviceCodeResponse,
|
|
198
|
+
OpenAIDevicePollResult,
|
|
199
|
+
OpenAIOAuthResult,
|
|
200
|
+
} from './auth/src/index.ts';
|
|
194
201
|
export {
|
|
195
202
|
authorizeXai,
|
|
196
203
|
exchangeXai,
|
|
@@ -40,7 +40,7 @@ const PREFERRED_FAST_MODELS: Partial<Record<ProviderId, string[]>> = {
|
|
|
40
40
|
google: ['gemini-2.0-flash-lite'],
|
|
41
41
|
openrouter: ['anthropic/claude-3.5-haiku'],
|
|
42
42
|
opencode: ['claude-3-5-haiku'],
|
|
43
|
-
ottorouter: ['
|
|
43
|
+
ottorouter: ['glm-5-turbo'],
|
|
44
44
|
xai: ['grok-code-fast-1', 'grok-4-fast'],
|
|
45
45
|
zai: ['glm-4.5-flash'],
|
|
46
46
|
copilot: ['gpt-4.1-mini'],
|
package/src/types/src/config.ts
CHANGED
|
@@ -31,6 +31,11 @@ export type DefaultConfig = {
|
|
|
31
31
|
reasoningText?: boolean;
|
|
32
32
|
reasoningLevel?: ReasoningLevel;
|
|
33
33
|
theme?: string;
|
|
34
|
+
vimMode?: boolean;
|
|
35
|
+
compactThread?: boolean;
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
smartEdges?: boolean;
|
|
38
|
+
releaseToSend?: boolean;
|
|
34
39
|
fullWidthContent?: boolean;
|
|
35
40
|
autoCompactThresholdTokens?: number | null;
|
|
36
41
|
};
|