@agi-cli/sdk 0.1.111 → 0.1.112
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 +1 -1
- package/src/auth/src/index.ts +9 -0
- package/src/auth/src/openai-oauth.ts +283 -0
- package/src/core/src/providers/resolver.ts +21 -1
- package/src/index.ts +14 -0
- package/src/providers/src/catalog.ts +354 -13
- package/src/providers/src/index.ts +6 -0
- package/src/providers/src/openai-oauth-client.ts +206 -0
- package/src/providers/src/utils.ts +39 -0
- package/src/types/src/auth.ts +2 -0
package/package.json
CHANGED
package/src/auth/src/index.ts
CHANGED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
|
|
5
|
+
const OPENAI_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
|
|
6
|
+
const OPENAI_ISSUER = 'https://auth.openai.com';
|
|
7
|
+
const OPENAI_CALLBACK_PORT = 1455;
|
|
8
|
+
|
|
9
|
+
function generatePKCE() {
|
|
10
|
+
const verifier = randomBytes(32)
|
|
11
|
+
.toString('base64')
|
|
12
|
+
.replace(/\+/g, '-')
|
|
13
|
+
.replace(/\//g, '_')
|
|
14
|
+
.replace(/=/g, '');
|
|
15
|
+
|
|
16
|
+
const challenge = createHash('sha256')
|
|
17
|
+
.update(verifier)
|
|
18
|
+
.digest('base64')
|
|
19
|
+
.replace(/\+/g, '-')
|
|
20
|
+
.replace(/\//g, '_')
|
|
21
|
+
.replace(/=/g, '');
|
|
22
|
+
|
|
23
|
+
return { verifier, challenge };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generateState() {
|
|
27
|
+
return randomBytes(32)
|
|
28
|
+
.toString('base64')
|
|
29
|
+
.replace(/\+/g, '-')
|
|
30
|
+
.replace(/\//g, '_')
|
|
31
|
+
.replace(/=/g, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function openBrowser(url: string) {
|
|
35
|
+
const platform = process.platform;
|
|
36
|
+
let command: string;
|
|
37
|
+
|
|
38
|
+
switch (platform) {
|
|
39
|
+
case 'darwin':
|
|
40
|
+
command = `open "${url}"`;
|
|
41
|
+
break;
|
|
42
|
+
case 'win32':
|
|
43
|
+
command = `start "${url}"`;
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
command = `xdg-open "${url}"`;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Promise<void>((resolve, reject) => {
|
|
51
|
+
const child = spawn(command, [], { shell: true });
|
|
52
|
+
child.on('error', reject);
|
|
53
|
+
child.on('exit', (code) => {
|
|
54
|
+
if (code === 0) resolve();
|
|
55
|
+
else reject(new Error(`Failed to open browser (exit code ${code})`));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type OpenAIOAuthResult = {
|
|
61
|
+
url: string;
|
|
62
|
+
verifier: string;
|
|
63
|
+
waitForCallback: () => Promise<string>;
|
|
64
|
+
close: () => void;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export async function authorizeOpenAI(): Promise<OpenAIOAuthResult> {
|
|
68
|
+
const pkce = generatePKCE();
|
|
69
|
+
const state = generateState();
|
|
70
|
+
const redirectUri = `http://localhost:${OPENAI_CALLBACK_PORT}/auth/callback`;
|
|
71
|
+
|
|
72
|
+
const params = new URLSearchParams({
|
|
73
|
+
response_type: 'code',
|
|
74
|
+
client_id: OPENAI_CLIENT_ID,
|
|
75
|
+
redirect_uri: redirectUri,
|
|
76
|
+
scope: 'openid profile email offline_access',
|
|
77
|
+
code_challenge: pkce.challenge,
|
|
78
|
+
code_challenge_method: 'S256',
|
|
79
|
+
id_token_add_organizations: 'true',
|
|
80
|
+
codex_cli_simplified_flow: 'true',
|
|
81
|
+
state: state,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const authUrl = `${OPENAI_ISSUER}/oauth/authorize?${params.toString()}`;
|
|
85
|
+
|
|
86
|
+
let resolveCallback: (code: string) => void;
|
|
87
|
+
let rejectCallback: (error: Error) => void;
|
|
88
|
+
const callbackPromise = new Promise<string>((resolve, reject) => {
|
|
89
|
+
resolveCallback = resolve;
|
|
90
|
+
rejectCallback = reject;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const server = createServer((req, res) => {
|
|
94
|
+
const reqUrl = new URL(
|
|
95
|
+
req.url || '/',
|
|
96
|
+
`http://localhost:${OPENAI_CALLBACK_PORT}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (reqUrl.pathname === '/auth/callback') {
|
|
100
|
+
const code = reqUrl.searchParams.get('code');
|
|
101
|
+
const returnedState = reqUrl.searchParams.get('state');
|
|
102
|
+
const error = reqUrl.searchParams.get('error');
|
|
103
|
+
|
|
104
|
+
if (error) {
|
|
105
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
106
|
+
res.end(
|
|
107
|
+
`<html><body><h1>Authentication Failed</h1><p>${error}</p></body></html>`,
|
|
108
|
+
);
|
|
109
|
+
rejectCallback(new Error(`OAuth error: ${error}`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (returnedState !== state) {
|
|
114
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
115
|
+
res.end(
|
|
116
|
+
'<html><body><h1>Invalid State</h1><p>State mismatch. Please try again.</p></body></html>',
|
|
117
|
+
);
|
|
118
|
+
rejectCallback(new Error('State mismatch'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (code) {
|
|
123
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
124
|
+
res.end(`
|
|
125
|
+
<html>
|
|
126
|
+
<head><title>AGI - Authentication Successful</title></head>
|
|
127
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
128
|
+
<h1>✅ Authentication Successful</h1>
|
|
129
|
+
<p>You can close this window and return to the terminal.</p>
|
|
130
|
+
</body>
|
|
131
|
+
</html>
|
|
132
|
+
`);
|
|
133
|
+
resolveCallback(code);
|
|
134
|
+
} else {
|
|
135
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
136
|
+
res.end('<html><body><h1>Missing Code</h1></body></html>');
|
|
137
|
+
rejectCallback(new Error('No authorization code received'));
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
res.writeHead(404);
|
|
141
|
+
res.end('Not found');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await new Promise<void>((resolve, reject) => {
|
|
146
|
+
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
147
|
+
if (err.code === 'EADDRINUSE') {
|
|
148
|
+
reject(
|
|
149
|
+
new Error(
|
|
150
|
+
`Port ${OPENAI_CALLBACK_PORT} is already in use. Make sure no other OAuth flow is running (including the official Codex CLI).`,
|
|
151
|
+
),
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
reject(err);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
server.listen(OPENAI_CALLBACK_PORT, '127.0.0.1', () => resolve());
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
url: authUrl,
|
|
162
|
+
verifier: pkce.verifier,
|
|
163
|
+
waitForCallback: () => callbackPromise,
|
|
164
|
+
close: () => {
|
|
165
|
+
server.close();
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function exchangeOpenAI(code: string, verifier: string) {
|
|
171
|
+
const redirectUri = `http://localhost:${OPENAI_CALLBACK_PORT}/auth/callback`;
|
|
172
|
+
|
|
173
|
+
const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
|
|
174
|
+
method: 'POST',
|
|
175
|
+
headers: {
|
|
176
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
177
|
+
},
|
|
178
|
+
body: new URLSearchParams({
|
|
179
|
+
grant_type: 'authorization_code',
|
|
180
|
+
code,
|
|
181
|
+
redirect_uri: redirectUri,
|
|
182
|
+
client_id: OPENAI_CLIENT_ID,
|
|
183
|
+
code_verifier: verifier,
|
|
184
|
+
}).toString(),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
const error = await response.text();
|
|
189
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const json = (await response.json()) as {
|
|
193
|
+
id_token: string;
|
|
194
|
+
access_token: string;
|
|
195
|
+
refresh_token: string;
|
|
196
|
+
expires_in?: number;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let accountId: string | undefined;
|
|
200
|
+
try {
|
|
201
|
+
const payload = JSON.parse(
|
|
202
|
+
Buffer.from(json.access_token.split('.')[1], 'base64').toString(),
|
|
203
|
+
);
|
|
204
|
+
accountId = payload['https://api.openai.com/auth']?.chatgpt_account_id;
|
|
205
|
+
} catch {}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
idToken: json.id_token,
|
|
209
|
+
access: json.access_token,
|
|
210
|
+
refresh: json.refresh_token,
|
|
211
|
+
expires: Date.now() + (json.expires_in || 3600) * 1000,
|
|
212
|
+
accountId,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function refreshOpenAIToken(refreshToken: string) {
|
|
217
|
+
const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
},
|
|
222
|
+
body: JSON.stringify({
|
|
223
|
+
client_id: OPENAI_CLIENT_ID,
|
|
224
|
+
grant_type: 'refresh_token',
|
|
225
|
+
refresh_token: refreshToken,
|
|
226
|
+
scope: 'openid profile email',
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw new Error('Failed to refresh OpenAI token');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const json = (await response.json()) as {
|
|
235
|
+
id_token?: string;
|
|
236
|
+
access_token?: string;
|
|
237
|
+
refresh_token?: string;
|
|
238
|
+
expires_in?: number;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
idToken: json.id_token,
|
|
243
|
+
access: json.access_token || '',
|
|
244
|
+
refresh: json.refresh_token || refreshToken,
|
|
245
|
+
expires: Date.now() + (json.expires_in || 3600) * 1000,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function openOpenAIAuthUrl(url: string) {
|
|
250
|
+
try {
|
|
251
|
+
await openBrowser(url);
|
|
252
|
+
return true;
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function obtainOpenAIApiKey(idToken: string): Promise<string> {
|
|
259
|
+
const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
263
|
+
},
|
|
264
|
+
body: new URLSearchParams({
|
|
265
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
266
|
+
client_id: OPENAI_CLIENT_ID,
|
|
267
|
+
requested_token: 'openai-api-key',
|
|
268
|
+
subject_token: idToken,
|
|
269
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
|
|
270
|
+
}).toString(),
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const error = await response.text();
|
|
275
|
+
throw new Error(`API key exchange failed: ${error}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const json = (await response.json()) as {
|
|
279
|
+
access_token: string;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return json.access_token;
|
|
283
|
+
}
|
|
@@ -3,7 +3,12 @@ import { anthropic, createAnthropic } from '@ai-sdk/anthropic';
|
|
|
3
3
|
import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
4
4
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
5
5
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
catalog,
|
|
8
|
+
createSolforgeModel,
|
|
9
|
+
createOpenAIOAuthModel,
|
|
10
|
+
} from '../../../providers/src/index.ts';
|
|
11
|
+
import type { OAuth } from '../../../types/src/index.ts';
|
|
7
12
|
|
|
8
13
|
export type ProviderName =
|
|
9
14
|
| 'openai'
|
|
@@ -19,6 +24,8 @@ export type ModelConfig = {
|
|
|
19
24
|
apiKey?: string;
|
|
20
25
|
customFetch?: typeof fetch;
|
|
21
26
|
baseURL?: string;
|
|
27
|
+
oauth?: OAuth;
|
|
28
|
+
projectRoot?: string;
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
export async function resolveModel(
|
|
@@ -27,6 +34,19 @@ export async function resolveModel(
|
|
|
27
34
|
config: ModelConfig = {},
|
|
28
35
|
) {
|
|
29
36
|
if (provider === 'openai') {
|
|
37
|
+
if (config.oauth) {
|
|
38
|
+
return createOpenAIOAuthModel(model, {
|
|
39
|
+
oauth: config.oauth,
|
|
40
|
+
projectRoot: config.projectRoot,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (config.customFetch) {
|
|
44
|
+
const instance = createOpenAI({
|
|
45
|
+
apiKey: config.apiKey || 'oauth-token',
|
|
46
|
+
fetch: config.customFetch,
|
|
47
|
+
});
|
|
48
|
+
return instance(model);
|
|
49
|
+
}
|
|
30
50
|
if (config.apiKey) {
|
|
31
51
|
const instance = createOpenAI({ apiKey: config.apiKey });
|
|
32
52
|
return instance(model);
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export {
|
|
|
40
40
|
providerIds,
|
|
41
41
|
defaultModelFor,
|
|
42
42
|
hasModel,
|
|
43
|
+
getFastModel,
|
|
43
44
|
} from './providers/src/index.ts';
|
|
44
45
|
export {
|
|
45
46
|
isProviderAuthorized,
|
|
@@ -60,6 +61,11 @@ export type {
|
|
|
60
61
|
SolforgeAuth,
|
|
61
62
|
SolforgeProviderOptions,
|
|
62
63
|
} from './providers/src/index.ts';
|
|
64
|
+
export {
|
|
65
|
+
createOpenAIOAuthFetch,
|
|
66
|
+
createOpenAIOAuthModel,
|
|
67
|
+
} from './providers/src/index.ts';
|
|
68
|
+
export type { OpenAIOAuthConfig } from './providers/src/index.ts';
|
|
63
69
|
|
|
64
70
|
// =======================
|
|
65
71
|
// Authentication (from internal auth module)
|
|
@@ -75,6 +81,14 @@ export {
|
|
|
75
81
|
openAuthUrl,
|
|
76
82
|
createApiKey,
|
|
77
83
|
} from './auth/src/index.ts';
|
|
84
|
+
export {
|
|
85
|
+
authorizeOpenAI,
|
|
86
|
+
exchangeOpenAI,
|
|
87
|
+
refreshOpenAIToken,
|
|
88
|
+
openOpenAIAuthUrl,
|
|
89
|
+
obtainOpenAIApiKey,
|
|
90
|
+
} from './auth/src/index.ts';
|
|
91
|
+
export type { OpenAIOAuthResult } from './auth/src/index.ts';
|
|
78
92
|
|
|
79
93
|
// =======================
|
|
80
94
|
// Configuration (from internal config module)
|
|
@@ -559,7 +559,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
559
559
|
label: 'GPT-5.1 Codex mini',
|
|
560
560
|
modalities: {
|
|
561
561
|
input: ['text', 'image'],
|
|
562
|
-
output: ['text'
|
|
562
|
+
output: ['text'],
|
|
563
563
|
},
|
|
564
564
|
toolCall: true,
|
|
565
565
|
reasoning: true,
|
|
@@ -629,6 +629,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
629
629
|
output: 16384,
|
|
630
630
|
},
|
|
631
631
|
},
|
|
632
|
+
{
|
|
633
|
+
id: 'gpt-5.2-codex',
|
|
634
|
+
label: 'GPT-5.2 Codex',
|
|
635
|
+
modalities: {
|
|
636
|
+
input: ['text', 'image'],
|
|
637
|
+
output: ['text'],
|
|
638
|
+
},
|
|
639
|
+
toolCall: true,
|
|
640
|
+
reasoning: true,
|
|
641
|
+
attachment: true,
|
|
642
|
+
temperature: false,
|
|
643
|
+
knowledge: '2025-08-31',
|
|
644
|
+
releaseDate: '2025-12-11',
|
|
645
|
+
lastUpdated: '2025-12-11',
|
|
646
|
+
openWeights: false,
|
|
647
|
+
cost: {
|
|
648
|
+
input: 1.75,
|
|
649
|
+
output: 14,
|
|
650
|
+
cacheRead: 0.175,
|
|
651
|
+
},
|
|
652
|
+
limit: {
|
|
653
|
+
context: 400000,
|
|
654
|
+
output: 128000,
|
|
655
|
+
},
|
|
656
|
+
},
|
|
632
657
|
{
|
|
633
658
|
id: 'gpt-5.2-pro',
|
|
634
659
|
label: 'GPT-5.2 Pro',
|
|
@@ -4328,6 +4353,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
4328
4353
|
output: 16384,
|
|
4329
4354
|
},
|
|
4330
4355
|
},
|
|
4356
|
+
{
|
|
4357
|
+
id: 'openai/gpt-5.2-codex',
|
|
4358
|
+
label: 'GPT-5.2-Codex',
|
|
4359
|
+
modalities: {
|
|
4360
|
+
input: ['text', 'image'],
|
|
4361
|
+
output: ['text'],
|
|
4362
|
+
},
|
|
4363
|
+
toolCall: true,
|
|
4364
|
+
reasoning: true,
|
|
4365
|
+
attachment: true,
|
|
4366
|
+
temperature: true,
|
|
4367
|
+
knowledge: '2025-08-31',
|
|
4368
|
+
releaseDate: '2026-01-14',
|
|
4369
|
+
lastUpdated: '2026-01-14',
|
|
4370
|
+
openWeights: false,
|
|
4371
|
+
cost: {
|
|
4372
|
+
input: 1.75,
|
|
4373
|
+
output: 14,
|
|
4374
|
+
cacheRead: 0.175,
|
|
4375
|
+
},
|
|
4376
|
+
limit: {
|
|
4377
|
+
context: 400000,
|
|
4378
|
+
output: 128000,
|
|
4379
|
+
},
|
|
4380
|
+
},
|
|
4331
4381
|
{
|
|
4332
4382
|
id: 'openai/gpt-5.2-pro',
|
|
4333
4383
|
label: 'GPT-5.2 Pro',
|
|
@@ -4877,6 +4927,30 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
4877
4927
|
output: 66536,
|
|
4878
4928
|
},
|
|
4879
4929
|
},
|
|
4930
|
+
{
|
|
4931
|
+
id: 'qwen/qwen3-coder-30b-a3b-instruct',
|
|
4932
|
+
label: 'Qwen3 Coder 30B A3B Instruct',
|
|
4933
|
+
modalities: {
|
|
4934
|
+
input: ['text'],
|
|
4935
|
+
output: ['text'],
|
|
4936
|
+
},
|
|
4937
|
+
toolCall: true,
|
|
4938
|
+
reasoning: false,
|
|
4939
|
+
attachment: false,
|
|
4940
|
+
temperature: true,
|
|
4941
|
+
knowledge: '2025-04',
|
|
4942
|
+
releaseDate: '2025-07-31',
|
|
4943
|
+
lastUpdated: '2025-07-31',
|
|
4944
|
+
openWeights: true,
|
|
4945
|
+
cost: {
|
|
4946
|
+
input: 0.07,
|
|
4947
|
+
output: 0.27,
|
|
4948
|
+
},
|
|
4949
|
+
limit: {
|
|
4950
|
+
context: 160000,
|
|
4951
|
+
output: 65536,
|
|
4952
|
+
},
|
|
4953
|
+
},
|
|
4880
4954
|
{
|
|
4881
4955
|
id: 'qwen/qwen3-coder-flash',
|
|
4882
4956
|
label: 'Qwen3 Coder Flash',
|
|
@@ -5698,8 +5772,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
5698
5772
|
attachment: true,
|
|
5699
5773
|
temperature: true,
|
|
5700
5774
|
knowledge: '2025-03-31',
|
|
5701
|
-
releaseDate: '2025-11-
|
|
5702
|
-
lastUpdated: '2025-11-
|
|
5775
|
+
releaseDate: '2025-11-24',
|
|
5776
|
+
lastUpdated: '2025-11-24',
|
|
5703
5777
|
openWeights: false,
|
|
5704
5778
|
cost: {
|
|
5705
5779
|
input: 5,
|
|
@@ -5916,8 +5990,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
5916
5990
|
attachment: true,
|
|
5917
5991
|
temperature: false,
|
|
5918
5992
|
knowledge: '2024-09-30',
|
|
5919
|
-
releaseDate: '2025-
|
|
5920
|
-
lastUpdated: '2025-
|
|
5993
|
+
releaseDate: '2025-09-15',
|
|
5994
|
+
lastUpdated: '2025-09-15',
|
|
5921
5995
|
openWeights: false,
|
|
5922
5996
|
cost: {
|
|
5923
5997
|
input: 1.07,
|
|
@@ -5972,8 +6046,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
5972
6046
|
attachment: true,
|
|
5973
6047
|
temperature: false,
|
|
5974
6048
|
knowledge: '2024-09-30',
|
|
5975
|
-
releaseDate: '2025-11-
|
|
5976
|
-
lastUpdated: '2025-11-
|
|
6049
|
+
releaseDate: '2025-11-13',
|
|
6050
|
+
lastUpdated: '2025-11-13',
|
|
5977
6051
|
openWeights: false,
|
|
5978
6052
|
cost: {
|
|
5979
6053
|
input: 1.07,
|
|
@@ -6000,8 +6074,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6000
6074
|
attachment: true,
|
|
6001
6075
|
temperature: false,
|
|
6002
6076
|
knowledge: '2024-09-30',
|
|
6003
|
-
releaseDate: '2025-11-
|
|
6004
|
-
lastUpdated: '2025-11-
|
|
6077
|
+
releaseDate: '2025-11-13',
|
|
6078
|
+
lastUpdated: '2025-11-13',
|
|
6005
6079
|
openWeights: false,
|
|
6006
6080
|
cost: {
|
|
6007
6081
|
input: 1.07,
|
|
@@ -6028,8 +6102,8 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6028
6102
|
attachment: true,
|
|
6029
6103
|
temperature: false,
|
|
6030
6104
|
knowledge: '2024-09-30',
|
|
6031
|
-
releaseDate: '2025-11-
|
|
6032
|
-
lastUpdated: '2025-11-
|
|
6105
|
+
releaseDate: '2025-11-13',
|
|
6106
|
+
lastUpdated: '2025-11-13',
|
|
6033
6107
|
openWeights: false,
|
|
6034
6108
|
cost: {
|
|
6035
6109
|
input: 1.25,
|
|
@@ -6044,6 +6118,34 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6044
6118
|
npm: '@ai-sdk/openai',
|
|
6045
6119
|
},
|
|
6046
6120
|
},
|
|
6121
|
+
{
|
|
6122
|
+
id: 'gpt-5.1-codex-mini',
|
|
6123
|
+
label: 'GPT-5.1 Codex Mini',
|
|
6124
|
+
modalities: {
|
|
6125
|
+
input: ['text', 'image'],
|
|
6126
|
+
output: ['text'],
|
|
6127
|
+
},
|
|
6128
|
+
toolCall: true,
|
|
6129
|
+
reasoning: true,
|
|
6130
|
+
attachment: true,
|
|
6131
|
+
temperature: false,
|
|
6132
|
+
knowledge: '2024-09-30',
|
|
6133
|
+
releaseDate: '2025-11-13',
|
|
6134
|
+
lastUpdated: '2025-11-13',
|
|
6135
|
+
openWeights: false,
|
|
6136
|
+
cost: {
|
|
6137
|
+
input: 0.25,
|
|
6138
|
+
output: 2,
|
|
6139
|
+
cacheRead: 0.025,
|
|
6140
|
+
},
|
|
6141
|
+
limit: {
|
|
6142
|
+
context: 400000,
|
|
6143
|
+
output: 128000,
|
|
6144
|
+
},
|
|
6145
|
+
provider: {
|
|
6146
|
+
npm: '@ai-sdk/openai',
|
|
6147
|
+
},
|
|
6148
|
+
},
|
|
6047
6149
|
{
|
|
6048
6150
|
id: 'gpt-5.2',
|
|
6049
6151
|
label: 'GPT-5.2',
|
|
@@ -6056,8 +6158,36 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6056
6158
|
attachment: true,
|
|
6057
6159
|
temperature: false,
|
|
6058
6160
|
knowledge: '2025-08-31',
|
|
6059
|
-
releaseDate: '2025-11
|
|
6060
|
-
lastUpdated: '2025-11
|
|
6161
|
+
releaseDate: '2025-12-11',
|
|
6162
|
+
lastUpdated: '2025-12-11',
|
|
6163
|
+
openWeights: false,
|
|
6164
|
+
cost: {
|
|
6165
|
+
input: 1.75,
|
|
6166
|
+
output: 14,
|
|
6167
|
+
cacheRead: 0.175,
|
|
6168
|
+
},
|
|
6169
|
+
limit: {
|
|
6170
|
+
context: 400000,
|
|
6171
|
+
output: 128000,
|
|
6172
|
+
},
|
|
6173
|
+
provider: {
|
|
6174
|
+
npm: '@ai-sdk/openai',
|
|
6175
|
+
},
|
|
6176
|
+
},
|
|
6177
|
+
{
|
|
6178
|
+
id: 'gpt-5.2-codex',
|
|
6179
|
+
label: 'GPT-5.2 Codex',
|
|
6180
|
+
modalities: {
|
|
6181
|
+
input: ['text', 'image'],
|
|
6182
|
+
output: ['text'],
|
|
6183
|
+
},
|
|
6184
|
+
toolCall: true,
|
|
6185
|
+
reasoning: true,
|
|
6186
|
+
attachment: true,
|
|
6187
|
+
temperature: false,
|
|
6188
|
+
knowledge: '2025-08-31',
|
|
6189
|
+
releaseDate: '2026-01-14',
|
|
6190
|
+
lastUpdated: '2026-01-14',
|
|
6061
6191
|
openWeights: false,
|
|
6062
6192
|
cost: {
|
|
6063
6193
|
input: 1.75,
|
|
@@ -6146,6 +6276,34 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6146
6276
|
output: 262144,
|
|
6147
6277
|
},
|
|
6148
6278
|
},
|
|
6279
|
+
{
|
|
6280
|
+
id: 'minimax-m2.1-free',
|
|
6281
|
+
label: 'MiniMax M2.1',
|
|
6282
|
+
modalities: {
|
|
6283
|
+
input: ['text'],
|
|
6284
|
+
output: ['text'],
|
|
6285
|
+
},
|
|
6286
|
+
toolCall: true,
|
|
6287
|
+
reasoning: true,
|
|
6288
|
+
attachment: false,
|
|
6289
|
+
temperature: true,
|
|
6290
|
+
knowledge: '2025-01',
|
|
6291
|
+
releaseDate: '2025-12-23',
|
|
6292
|
+
lastUpdated: '2025-12-23',
|
|
6293
|
+
openWeights: true,
|
|
6294
|
+
cost: {
|
|
6295
|
+
input: 0,
|
|
6296
|
+
output: 0,
|
|
6297
|
+
cacheRead: 0,
|
|
6298
|
+
},
|
|
6299
|
+
limit: {
|
|
6300
|
+
context: 204800,
|
|
6301
|
+
output: 131072,
|
|
6302
|
+
},
|
|
6303
|
+
provider: {
|
|
6304
|
+
npm: '@ai-sdk/anthropic',
|
|
6305
|
+
},
|
|
6306
|
+
},
|
|
6149
6307
|
{
|
|
6150
6308
|
id: 'qwen3-coder',
|
|
6151
6309
|
label: 'Qwen3 Coder',
|
|
@@ -6360,4 +6518,187 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
|
|
|
6360
6518
|
api: 'https://api.z.ai/api/paas/v4',
|
|
6361
6519
|
doc: 'https://docs.z.ai/guides/overview/pricing',
|
|
6362
6520
|
},
|
|
6521
|
+
'zai-coding': {
|
|
6522
|
+
id: 'zai-coding',
|
|
6523
|
+
models: [
|
|
6524
|
+
{
|
|
6525
|
+
id: 'glm-4.5',
|
|
6526
|
+
label: 'GLM-4.5',
|
|
6527
|
+
modalities: {
|
|
6528
|
+
input: ['text'],
|
|
6529
|
+
output: ['text'],
|
|
6530
|
+
},
|
|
6531
|
+
toolCall: true,
|
|
6532
|
+
reasoning: true,
|
|
6533
|
+
attachment: false,
|
|
6534
|
+
temperature: true,
|
|
6535
|
+
knowledge: '2025-04',
|
|
6536
|
+
releaseDate: '2025-07-28',
|
|
6537
|
+
lastUpdated: '2025-07-28',
|
|
6538
|
+
openWeights: true,
|
|
6539
|
+
cost: {
|
|
6540
|
+
input: 0,
|
|
6541
|
+
output: 0,
|
|
6542
|
+
cacheRead: 0,
|
|
6543
|
+
},
|
|
6544
|
+
limit: {
|
|
6545
|
+
context: 131072,
|
|
6546
|
+
output: 98304,
|
|
6547
|
+
},
|
|
6548
|
+
},
|
|
6549
|
+
{
|
|
6550
|
+
id: 'glm-4.5-air',
|
|
6551
|
+
label: 'GLM-4.5-Air',
|
|
6552
|
+
modalities: {
|
|
6553
|
+
input: ['text'],
|
|
6554
|
+
output: ['text'],
|
|
6555
|
+
},
|
|
6556
|
+
toolCall: true,
|
|
6557
|
+
reasoning: true,
|
|
6558
|
+
attachment: false,
|
|
6559
|
+
temperature: true,
|
|
6560
|
+
knowledge: '2025-04',
|
|
6561
|
+
releaseDate: '2025-07-28',
|
|
6562
|
+
lastUpdated: '2025-07-28',
|
|
6563
|
+
openWeights: true,
|
|
6564
|
+
cost: {
|
|
6565
|
+
input: 0,
|
|
6566
|
+
output: 0,
|
|
6567
|
+
cacheRead: 0,
|
|
6568
|
+
},
|
|
6569
|
+
limit: {
|
|
6570
|
+
context: 131072,
|
|
6571
|
+
output: 98304,
|
|
6572
|
+
},
|
|
6573
|
+
},
|
|
6574
|
+
{
|
|
6575
|
+
id: 'glm-4.5-flash',
|
|
6576
|
+
label: 'GLM-4.5-Flash',
|
|
6577
|
+
modalities: {
|
|
6578
|
+
input: ['text'],
|
|
6579
|
+
output: ['text'],
|
|
6580
|
+
},
|
|
6581
|
+
toolCall: true,
|
|
6582
|
+
reasoning: true,
|
|
6583
|
+
attachment: false,
|
|
6584
|
+
temperature: true,
|
|
6585
|
+
knowledge: '2025-04',
|
|
6586
|
+
releaseDate: '2025-07-28',
|
|
6587
|
+
lastUpdated: '2025-07-28',
|
|
6588
|
+
openWeights: true,
|
|
6589
|
+
cost: {
|
|
6590
|
+
input: 0,
|
|
6591
|
+
output: 0,
|
|
6592
|
+
cacheRead: 0,
|
|
6593
|
+
},
|
|
6594
|
+
limit: {
|
|
6595
|
+
context: 131072,
|
|
6596
|
+
output: 98304,
|
|
6597
|
+
},
|
|
6598
|
+
},
|
|
6599
|
+
{
|
|
6600
|
+
id: 'glm-4.5v',
|
|
6601
|
+
label: 'GLM-4.5V',
|
|
6602
|
+
modalities: {
|
|
6603
|
+
input: ['text', 'image', 'video'],
|
|
6604
|
+
output: ['text'],
|
|
6605
|
+
},
|
|
6606
|
+
toolCall: true,
|
|
6607
|
+
reasoning: true,
|
|
6608
|
+
attachment: true,
|
|
6609
|
+
temperature: true,
|
|
6610
|
+
knowledge: '2025-04',
|
|
6611
|
+
releaseDate: '2025-08-11',
|
|
6612
|
+
lastUpdated: '2025-08-11',
|
|
6613
|
+
openWeights: true,
|
|
6614
|
+
cost: {
|
|
6615
|
+
input: 0,
|
|
6616
|
+
output: 0,
|
|
6617
|
+
},
|
|
6618
|
+
limit: {
|
|
6619
|
+
context: 64000,
|
|
6620
|
+
output: 16384,
|
|
6621
|
+
},
|
|
6622
|
+
},
|
|
6623
|
+
{
|
|
6624
|
+
id: 'glm-4.6',
|
|
6625
|
+
label: 'GLM-4.6',
|
|
6626
|
+
modalities: {
|
|
6627
|
+
input: ['text'],
|
|
6628
|
+
output: ['text'],
|
|
6629
|
+
},
|
|
6630
|
+
toolCall: true,
|
|
6631
|
+
reasoning: true,
|
|
6632
|
+
attachment: false,
|
|
6633
|
+
temperature: true,
|
|
6634
|
+
knowledge: '2025-04',
|
|
6635
|
+
releaseDate: '2025-09-30',
|
|
6636
|
+
lastUpdated: '2025-09-30',
|
|
6637
|
+
openWeights: true,
|
|
6638
|
+
cost: {
|
|
6639
|
+
input: 0,
|
|
6640
|
+
output: 0,
|
|
6641
|
+
cacheRead: 0,
|
|
6642
|
+
},
|
|
6643
|
+
limit: {
|
|
6644
|
+
context: 204800,
|
|
6645
|
+
output: 131072,
|
|
6646
|
+
},
|
|
6647
|
+
},
|
|
6648
|
+
{
|
|
6649
|
+
id: 'glm-4.6v',
|
|
6650
|
+
label: 'GLM-4.6V',
|
|
6651
|
+
modalities: {
|
|
6652
|
+
input: ['text', 'image', 'video'],
|
|
6653
|
+
output: ['text'],
|
|
6654
|
+
},
|
|
6655
|
+
toolCall: true,
|
|
6656
|
+
reasoning: true,
|
|
6657
|
+
attachment: true,
|
|
6658
|
+
temperature: true,
|
|
6659
|
+
knowledge: '2025-04',
|
|
6660
|
+
releaseDate: '2025-12-08',
|
|
6661
|
+
lastUpdated: '2025-12-08',
|
|
6662
|
+
openWeights: true,
|
|
6663
|
+
cost: {
|
|
6664
|
+
input: 0,
|
|
6665
|
+
output: 0,
|
|
6666
|
+
},
|
|
6667
|
+
limit: {
|
|
6668
|
+
context: 128000,
|
|
6669
|
+
output: 32768,
|
|
6670
|
+
},
|
|
6671
|
+
},
|
|
6672
|
+
{
|
|
6673
|
+
id: 'glm-4.7',
|
|
6674
|
+
label: 'GLM-4.7',
|
|
6675
|
+
modalities: {
|
|
6676
|
+
input: ['text'],
|
|
6677
|
+
output: ['text'],
|
|
6678
|
+
},
|
|
6679
|
+
toolCall: true,
|
|
6680
|
+
reasoning: true,
|
|
6681
|
+
attachment: false,
|
|
6682
|
+
temperature: true,
|
|
6683
|
+
knowledge: '2025-04',
|
|
6684
|
+
releaseDate: '2025-12-22',
|
|
6685
|
+
lastUpdated: '2025-12-22',
|
|
6686
|
+
openWeights: true,
|
|
6687
|
+
cost: {
|
|
6688
|
+
input: 0,
|
|
6689
|
+
output: 0,
|
|
6690
|
+
cacheRead: 0,
|
|
6691
|
+
},
|
|
6692
|
+
limit: {
|
|
6693
|
+
context: 204800,
|
|
6694
|
+
output: 131072,
|
|
6695
|
+
},
|
|
6696
|
+
},
|
|
6697
|
+
],
|
|
6698
|
+
label: 'Z.AI Coding Plan',
|
|
6699
|
+
env: ['ZHIPU_API_KEY'],
|
|
6700
|
+
npm: '@ai-sdk/openai-compatible',
|
|
6701
|
+
api: 'https://api.z.ai/api/coding/paas/v4',
|
|
6702
|
+
doc: 'https://docs.z.ai/devpack/overview',
|
|
6703
|
+
},
|
|
6363
6704
|
} as const satisfies Partial<Record<ProviderId, ProviderCatalogEntry>>;
|
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
providerIds,
|
|
12
12
|
defaultModelFor,
|
|
13
13
|
hasModel,
|
|
14
|
+
getFastModel,
|
|
14
15
|
} from './utils.ts';
|
|
15
16
|
export { validateProviderModel } from './validate.ts';
|
|
16
17
|
export { estimateModelCostUsd } from './pricing.ts';
|
|
@@ -23,3 +24,8 @@ export type {
|
|
|
23
24
|
SolforgeAuth,
|
|
24
25
|
SolforgeProviderOptions,
|
|
25
26
|
} from './solforge-client.ts';
|
|
27
|
+
export {
|
|
28
|
+
createOpenAIOAuthFetch,
|
|
29
|
+
createOpenAIOAuthModel,
|
|
30
|
+
} from './openai-oauth-client.ts';
|
|
31
|
+
export type { OpenAIOAuthConfig } from './openai-oauth-client.ts';
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import type { OAuth } from '../../types/src/index.ts';
|
|
3
|
+
import { refreshOpenAIToken } from '../../auth/src/openai-oauth.ts';
|
|
4
|
+
import { setAuth, getAuth } from '../../auth/src/index.ts';
|
|
5
|
+
|
|
6
|
+
const CHATGPT_BACKEND_URL = 'https://chatgpt.com/backend-api';
|
|
7
|
+
const OPENAI_API_URL = 'https://api.openai.com/v1';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_INSTRUCTIONS = `You are a helpful coding assistant. Be concise and direct.`;
|
|
10
|
+
|
|
11
|
+
export type OpenAIOAuthConfig = {
|
|
12
|
+
oauth: OAuth;
|
|
13
|
+
projectRoot?: string;
|
|
14
|
+
instructions?: string;
|
|
15
|
+
reasoningEffort?: 'none' | 'low' | 'medium' | 'high' | 'xhigh';
|
|
16
|
+
reasoningSummary?: 'auto' | 'detailed';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function ensureValidToken(
|
|
20
|
+
oauth: OAuth,
|
|
21
|
+
projectRoot?: string,
|
|
22
|
+
): Promise<{ access: string; accountId?: string }> {
|
|
23
|
+
const bufferMs = 5 * 60 * 1000;
|
|
24
|
+
if (oauth.expires > Date.now() + bufferMs) {
|
|
25
|
+
return { access: oauth.access, accountId: oauth.accountId };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const newTokens = await refreshOpenAIToken(oauth.refresh);
|
|
30
|
+
const updatedOAuth: OAuth = {
|
|
31
|
+
type: 'oauth',
|
|
32
|
+
access: newTokens.access,
|
|
33
|
+
refresh: newTokens.refresh,
|
|
34
|
+
expires: newTokens.expires,
|
|
35
|
+
accountId: oauth.accountId,
|
|
36
|
+
idToken: newTokens.idToken,
|
|
37
|
+
};
|
|
38
|
+
await setAuth('openai', updatedOAuth, projectRoot, 'global');
|
|
39
|
+
return { access: newTokens.access, accountId: oauth.accountId };
|
|
40
|
+
} catch {
|
|
41
|
+
return { access: oauth.access, accountId: oauth.accountId };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripIdsFromInput(input: unknown): unknown {
|
|
46
|
+
if (Array.isArray(input)) {
|
|
47
|
+
return input
|
|
48
|
+
.filter((item) => {
|
|
49
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
50
|
+
if (item.type === 'item_reference') return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
})
|
|
54
|
+
.map((item) => {
|
|
55
|
+
if (item && typeof item === 'object') {
|
|
56
|
+
const result: Record<string, unknown> = {};
|
|
57
|
+
for (const [key, value] of Object.entries(item)) {
|
|
58
|
+
if (key === 'id') continue;
|
|
59
|
+
result[key] = stripIdsFromInput(value);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
return item;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (input && typeof input === 'object') {
|
|
67
|
+
const result: Record<string, unknown> = {};
|
|
68
|
+
for (const [key, value] of Object.entries(input)) {
|
|
69
|
+
if (key === 'id') continue;
|
|
70
|
+
result[key] = stripIdsFromInput(value);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function rewriteUrl(url: string): string {
|
|
78
|
+
if (url.includes('/responses')) {
|
|
79
|
+
return url
|
|
80
|
+
.replace(OPENAI_API_URL, CHATGPT_BACKEND_URL)
|
|
81
|
+
.replace('/responses', '/codex/responses');
|
|
82
|
+
}
|
|
83
|
+
return url.replace(OPENAI_API_URL, CHATGPT_BACKEND_URL);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
87
|
+
let currentOAuth = config.oauth;
|
|
88
|
+
const instructions = config.instructions || DEFAULT_INSTRUCTIONS;
|
|
89
|
+
|
|
90
|
+
const customFetch = async (
|
|
91
|
+
input: Parameters<typeof fetch>[0],
|
|
92
|
+
init?: Parameters<typeof fetch>[1],
|
|
93
|
+
): Promise<Response> => {
|
|
94
|
+
const { access: accessToken, accountId } = await ensureValidToken(
|
|
95
|
+
currentOAuth,
|
|
96
|
+
config.projectRoot,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const originalUrl =
|
|
100
|
+
typeof input === 'string'
|
|
101
|
+
? input
|
|
102
|
+
: input instanceof URL
|
|
103
|
+
? input.href
|
|
104
|
+
: input.url;
|
|
105
|
+
const targetUrl = rewriteUrl(originalUrl);
|
|
106
|
+
|
|
107
|
+
let body = init?.body;
|
|
108
|
+
if (body && typeof body === 'string') {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(body);
|
|
111
|
+
|
|
112
|
+
parsed.store = false;
|
|
113
|
+
parsed.stream = true;
|
|
114
|
+
parsed.instructions = instructions;
|
|
115
|
+
|
|
116
|
+
if (parsed.input) {
|
|
117
|
+
parsed.input = stripIdsFromInput(parsed.input);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!parsed.include) {
|
|
121
|
+
parsed.include = ['reasoning.encrypted_content'];
|
|
122
|
+
} else if (
|
|
123
|
+
Array.isArray(parsed.include) &&
|
|
124
|
+
!parsed.include.includes('reasoning.encrypted_content')
|
|
125
|
+
) {
|
|
126
|
+
parsed.include.push('reasoning.encrypted_content');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!parsed.reasoning) {
|
|
130
|
+
const providerOpts = parsed.providerOptions?.openai || {};
|
|
131
|
+
parsed.reasoning = {
|
|
132
|
+
effort:
|
|
133
|
+
providerOpts.reasoningEffort ||
|
|
134
|
+
config.reasoningEffort ||
|
|
135
|
+
'medium',
|
|
136
|
+
summary:
|
|
137
|
+
providerOpts.reasoningSummary ||
|
|
138
|
+
config.reasoningSummary ||
|
|
139
|
+
'auto',
|
|
140
|
+
};
|
|
141
|
+
} else {
|
|
142
|
+
const providerOpts = parsed.providerOptions?.openai || {};
|
|
143
|
+
if (!parsed.reasoning.effort) {
|
|
144
|
+
parsed.reasoning.effort =
|
|
145
|
+
providerOpts.reasoningEffort ||
|
|
146
|
+
config.reasoningEffort ||
|
|
147
|
+
'medium';
|
|
148
|
+
}
|
|
149
|
+
if (!parsed.reasoning.summary) {
|
|
150
|
+
parsed.reasoning.summary =
|
|
151
|
+
providerOpts.reasoningSummary ||
|
|
152
|
+
config.reasoningSummary ||
|
|
153
|
+
'auto';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
delete parsed.max_output_tokens;
|
|
158
|
+
delete parsed.max_completion_tokens;
|
|
159
|
+
|
|
160
|
+
body = JSON.stringify(parsed);
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const headers = new Headers(init?.headers);
|
|
165
|
+
headers.delete('x-api-key');
|
|
166
|
+
headers.set('Authorization', `Bearer ${accessToken}`);
|
|
167
|
+
headers.set('OpenAI-Beta', 'responses=experimental');
|
|
168
|
+
headers.set('originator', 'codex_cli_rs');
|
|
169
|
+
headers.set('accept', 'text/event-stream');
|
|
170
|
+
if (accountId) {
|
|
171
|
+
headers.set('chatgpt-account-id', accountId);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const response = await fetch(targetUrl, {
|
|
175
|
+
...init,
|
|
176
|
+
body,
|
|
177
|
+
headers,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (response.status === 401) {
|
|
181
|
+
const refreshed = await getAuth('openai', config.projectRoot);
|
|
182
|
+
if (refreshed?.type === 'oauth') {
|
|
183
|
+
currentOAuth = refreshed;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return response;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return customFetch as typeof fetch;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function createOpenAIOAuthModel(
|
|
194
|
+
model: string,
|
|
195
|
+
config: OpenAIOAuthConfig,
|
|
196
|
+
) {
|
|
197
|
+
const customFetch = createOpenAIOAuthFetch(config);
|
|
198
|
+
|
|
199
|
+
const provider = createOpenAI({
|
|
200
|
+
apiKey: 'chatgpt-oauth',
|
|
201
|
+
baseURL: CHATGPT_BACKEND_URL,
|
|
202
|
+
fetch: customFetch,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return provider(model);
|
|
206
|
+
}
|
|
@@ -22,3 +22,42 @@ export function hasModel(
|
|
|
22
22
|
if (!model) return false;
|
|
23
23
|
return listModels(provider).includes(model);
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
const PREFERRED_FAST_MODELS: Partial<Record<ProviderId, string[]>> = {
|
|
27
|
+
openai: ['gpt-4o-mini', 'gpt-4.1-nano', 'gpt-4.1-mini'],
|
|
28
|
+
anthropic: [
|
|
29
|
+
'claude-3-5-haiku-latest',
|
|
30
|
+
'claude-3-5-haiku-20241022',
|
|
31
|
+
'claude-haiku-4-5',
|
|
32
|
+
],
|
|
33
|
+
google: [
|
|
34
|
+
'gemini-2.0-flash-lite',
|
|
35
|
+
'gemini-2.0-flash',
|
|
36
|
+
'gemini-2.5-flash-lite',
|
|
37
|
+
],
|
|
38
|
+
openrouter: [
|
|
39
|
+
'openai/gpt-4o-mini',
|
|
40
|
+
'google/gemini-2.0-flash-001',
|
|
41
|
+
'anthropic/claude-3.5-haiku',
|
|
42
|
+
],
|
|
43
|
+
opencode: ['gpt-5-nano', 'claude-3-5-haiku', 'gemini-3-flash'],
|
|
44
|
+
zai: ['glm-4.5-flash', 'glm-4.5-air'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function getFastModel(provider: ProviderId): string | undefined {
|
|
48
|
+
const providerModels = catalog[provider]?.models ?? [];
|
|
49
|
+
if (!providerModels.length) return undefined;
|
|
50
|
+
|
|
51
|
+
const preferred = PREFERRED_FAST_MODELS[provider] ?? [];
|
|
52
|
+
for (const modelId of preferred) {
|
|
53
|
+
if (providerModels.some((m) => m.id === modelId)) {
|
|
54
|
+
return modelId;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sorted = [...providerModels]
|
|
59
|
+
.filter((m) => m.cost?.input !== undefined && m.toolCall !== false)
|
|
60
|
+
.sort((a, b) => (a.cost?.input ?? Infinity) - (b.cost?.input ?? Infinity));
|
|
61
|
+
|
|
62
|
+
return sorted[0]?.id ?? providerModels[0]?.id;
|
|
63
|
+
}
|