@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.288",
3
+ "version": "0.1.290",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -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
- const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
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
  }
@@ -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 { OpenAIOAuthResult } from './auth/src/index.ts';
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: ['kimi-k2.5'],
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'],
@@ -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
  };