dsh-plugin-subscriptions 0.5.1 → 0.5.3
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/README.md +42 -1
- package/README.zh.md +42 -1
- package/lib/auth/device-flow.d.ts +0 -9
- package/lib/auth/device-flow.js +2 -1
- package/lib/auth/rpc.d.ts +44 -13
- package/lib/auth/rpc.js +127 -9
- package/lib/auth/store.d.ts +75 -17
- package/lib/auth/store.js +148 -27
- package/lib/client/SubscriptionsSection.d.ts +26 -3
- package/lib/client/SubscriptionsSection.js +263 -67
- package/lib/client/index.js +11 -0
- package/lib/client/locales.d.ts +82 -10
- package/lib/client/locales.js +82 -10
- package/lib/client.js +837 -223
- package/lib/client.js.map +1 -1
- package/lib/http.d.ts +114 -0
- package/lib/http.js +402 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +1938 -208
- package/lib/providers/accounts.d.ts +102 -0
- package/lib/providers/accounts.js +123 -0
- package/lib/providers/antigravity.d.ts +90 -0
- package/lib/providers/antigravity.js +392 -0
- package/lib/providers/claude.d.ts +22 -4
- package/lib/providers/claude.js +97 -16
- package/lib/providers/codex.d.ts +24 -3
- package/lib/providers/codex.js +121 -21
- package/lib/providers/common.d.ts +17 -0
- package/lib/providers/common.js +67 -3
- package/lib/providers/copilot.d.ts +23 -4
- package/lib/providers/copilot.js +99 -19
- package/lib/providers/grok.d.ts +24 -4
- package/lib/providers/grok.js +106 -19
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +148 -0
- package/lib/providers/pool-usage.d.ts +57 -0
- package/lib/providers/pool-usage.js +130 -0
- package/lib/providers/pool.d.ts +107 -0
- package/lib/providers/pool.js +371 -0
- package/lib/tools/image-generate.d.ts +3 -3
- package/lib/tools/image-generate.js +4 -2
- package/lib/tools/video-generate.d.ts +2 -2
- package/lib/tools/video-generate.js +4 -2
- package/lib/tools/x-search.d.ts +2 -2
- package/lib/tools/x-search.js +4 -2
- package/lib/translate/antigravity.d.ts +110 -0
- package/lib/translate/antigravity.js +303 -0
- package/package.json +14 -9
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Antigravity subscription provider. This is intentionally separate
|
|
3
|
+
* from Gemini CLI: it uses Antigravity OAuth scopes, project discovery, and
|
|
4
|
+
* the daily-cloudcode-pa v1internal request envelope.
|
|
5
|
+
*/
|
|
6
|
+
import { errorChain, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm';
|
|
7
|
+
import { resolveImages } from '../translate/resolved.js';
|
|
8
|
+
import { parseAntigravityResponse, streamAntigravity, toAntigravityRequest, } from '../translate/antigravity.js';
|
|
9
|
+
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverOrRetryAuth, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError, TokenManager, } from './common.js';
|
|
10
|
+
import { proxiedFetch } from '../http.js';
|
|
11
|
+
export const ANTIGRAVITY_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
12
|
+
export const ANTIGRAVITY_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
13
|
+
export const ANTIGRAVITY_USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
|
|
14
|
+
export const ANTIGRAVITY_DEFAULT_BASE_URL = 'https://daily-cloudcode-pa.googleapis.com';
|
|
15
|
+
export const ANTIGRAVITY_PROD_BASE_URL = 'https://cloudcode-pa.googleapis.com';
|
|
16
|
+
export const ANTIGRAVITY_DEFAULT_USER_AGENT = 'antigravity/1.104.0 dsh-plugin-subscriptions';
|
|
17
|
+
export const ANTIGRAVITY_PREEMPT_MS = 5 * 60_000;
|
|
18
|
+
const ANTIGRAVITY_CALLBACK_PATH = '/oauth-callback';
|
|
19
|
+
const ANTIGRAVITY_CONTEXT_WINDOW = 1_024_000;
|
|
20
|
+
const ANTIGRAVITY_DEFAULT_MAX_TOKENS = 65_536;
|
|
21
|
+
/** Antigravity, not Gemini CLI, OAuth scopes from the local reference clients. */
|
|
22
|
+
export const ANTIGRAVITY_SCOPES = [
|
|
23
|
+
'openid',
|
|
24
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
25
|
+
'https://www.googleapis.com/auth/userinfo.email',
|
|
26
|
+
'https://www.googleapis.com/auth/userinfo.profile',
|
|
27
|
+
'https://www.googleapis.com/auth/cclog',
|
|
28
|
+
'https://www.googleapis.com/auth/experimentsandconfigs',
|
|
29
|
+
];
|
|
30
|
+
/** Resolve and validate a user-supplied OAuth config without embedded credentials. */
|
|
31
|
+
export function resolveAntigravityOAuthConfig(config) {
|
|
32
|
+
const clientId = config?.clientId?.trim() || process.env.ANTIGRAVITY_CLIENT_ID?.trim() || '';
|
|
33
|
+
const clientSecret = config?.clientSecret?.trim() || process.env.ANTIGRAVITY_CLIENT_SECRET?.trim();
|
|
34
|
+
if (clientId.length === 0) {
|
|
35
|
+
throw new Error('Antigravity OAuth is not configured; set config.antigravity.clientId or ANTIGRAVITY_CLIENT_ID '
|
|
36
|
+
+ '(and clientSecret/ANTIGRAVITY_CLIENT_SECRET when required by the Google OAuth client)');
|
|
37
|
+
}
|
|
38
|
+
return { clientId, ...clientSecret === undefined || clientSecret.length === 0 ? {} : { clientSecret } };
|
|
39
|
+
}
|
|
40
|
+
/** Normalize the configured API origin and reject paths/credentials. */
|
|
41
|
+
export function antigravityBaseURL(value) {
|
|
42
|
+
const parsed = new URL(value?.trim() || ANTIGRAVITY_DEFAULT_BASE_URL);
|
|
43
|
+
if (parsed.protocol !== 'https:' || parsed.username.length > 0 || parsed.password.length > 0) {
|
|
44
|
+
throw new Error('config.antigravity.baseURL must be an HTTPS origin without credentials');
|
|
45
|
+
}
|
|
46
|
+
if (parsed.pathname !== '/' || parsed.search.length > 0 || parsed.hash.length > 0) {
|
|
47
|
+
throw new Error('config.antigravity.baseURL must not contain a path, query, or fragment');
|
|
48
|
+
}
|
|
49
|
+
return parsed.origin;
|
|
50
|
+
}
|
|
51
|
+
/** Google authorization-code + PKCE flow for Antigravity. */
|
|
52
|
+
export function antigravityFlow(oauth) {
|
|
53
|
+
return {
|
|
54
|
+
callbackPath: ANTIGRAVITY_CALLBACK_PATH,
|
|
55
|
+
listen: { host: 'localhost', ports: [51121, 0] },
|
|
56
|
+
timeoutMs: 5 * 60_000,
|
|
57
|
+
buildAuthorizeUrl({ redirectUri, state, pkce }) {
|
|
58
|
+
const params = new URLSearchParams({
|
|
59
|
+
access_type: 'offline',
|
|
60
|
+
client_id: oauth.clientId,
|
|
61
|
+
code_challenge: pkce.challenge,
|
|
62
|
+
code_challenge_method: 'S256',
|
|
63
|
+
include_granted_scopes: 'true',
|
|
64
|
+
prompt: 'consent',
|
|
65
|
+
redirect_uri: redirectUri,
|
|
66
|
+
response_type: 'code',
|
|
67
|
+
scope: ANTIGRAVITY_SCOPES.join(' '),
|
|
68
|
+
state,
|
|
69
|
+
});
|
|
70
|
+
return `${ANTIGRAVITY_AUTHORIZE_URL}?${params.toString()}`;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Shared Antigravity API headers. */
|
|
75
|
+
export function antigravityHeaders(accessToken, userAgent = ANTIGRAVITY_DEFAULT_USER_AGENT) {
|
|
76
|
+
return {
|
|
77
|
+
'authorization': `Bearer ${accessToken}`,
|
|
78
|
+
'content-type': 'application/json',
|
|
79
|
+
'user-agent': userAgent,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** POST a v1internal JSON method and classify non-2xx responses. */
|
|
83
|
+
async function callInternal(method, body, accessToken, runtime, fetchFn, signal) {
|
|
84
|
+
const response = await fetchFn(`${antigravityBaseURL(runtime.baseURL)}/v1internal:${method}`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: antigravityHeaders(accessToken, runtime.userAgent),
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
...signal === undefined ? {} : { signal },
|
|
89
|
+
});
|
|
90
|
+
if (!response.ok)
|
|
91
|
+
throw await httpLlmError(response, `Antigravity ${method}`);
|
|
92
|
+
return response.json();
|
|
93
|
+
}
|
|
94
|
+
function projectIdOf(value) {
|
|
95
|
+
if (typeof value === 'string' && value.length > 0)
|
|
96
|
+
return value;
|
|
97
|
+
if (typeof value === 'object' && value !== null) {
|
|
98
|
+
const id = value.id;
|
|
99
|
+
if (typeof id === 'string' && id.length > 0)
|
|
100
|
+
return id;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
/** Read (and, when enabled, initialize) the Antigravity project/account. */
|
|
105
|
+
export async function discoverAntigravityAccount(accessToken, runtime = {}, fetchFn = proxiedFetch) {
|
|
106
|
+
const metadata = { ideType: 'ANTIGRAVITY', platform: 'PLATFORM_UNSPECIFIED', pluginType: 'GEMINI' };
|
|
107
|
+
const load = await callInternal('loadCodeAssist', { metadata }, accessToken, runtime, fetchFn);
|
|
108
|
+
let projectId = projectIdOf(load.cloudaicompanionProject);
|
|
109
|
+
if (projectId === undefined && runtime.onboard !== false) {
|
|
110
|
+
const tierId = load.allowedTiers?.find(tier => tier.isDefault)?.id ?? 'LEGACY';
|
|
111
|
+
const onboardBody = { tierId, metadata };
|
|
112
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
113
|
+
const result = await callInternal('onboardUser', onboardBody, accessToken, runtime, fetchFn);
|
|
114
|
+
if (result.done === true) {
|
|
115
|
+
projectId = projectIdOf(result.response?.cloudaicompanionProject);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
await new Promise(resolve => setTimeout(resolve, 1_000));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (projectId === undefined) {
|
|
122
|
+
throw new Error('Antigravity account has no Cloud AI Companion project; open Antigravity and complete onboarding, then log in again');
|
|
123
|
+
}
|
|
124
|
+
let account;
|
|
125
|
+
try {
|
|
126
|
+
const profileResponse = await fetchFn(ANTIGRAVITY_USERINFO_URL, {
|
|
127
|
+
headers: { authorization: `Bearer ${accessToken}`, accept: 'application/json' },
|
|
128
|
+
});
|
|
129
|
+
if (profileResponse.ok) {
|
|
130
|
+
const profile = await profileResponse.json();
|
|
131
|
+
if (typeof profile.email === 'string' && profile.email.length > 0)
|
|
132
|
+
account = profile.email;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Identity is display-only; project discovery is the login boundary.
|
|
137
|
+
}
|
|
138
|
+
const plan = load.paidTier?.name ?? load.paidTier?.id ?? load.currentTier?.name ?? load.currentTier?.id;
|
|
139
|
+
return {
|
|
140
|
+
projectId,
|
|
141
|
+
...account === undefined ? {} : { account },
|
|
142
|
+
...typeof plan !== 'string' || plan.length === 0 ? {} : { plan },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function sessionFromTokens(tokens, account, fallback) {
|
|
146
|
+
if (typeof tokens.access_token !== 'string' || tokens.access_token.length === 0) {
|
|
147
|
+
throw new Error('Antigravity token endpoint returned no access token');
|
|
148
|
+
}
|
|
149
|
+
const refreshToken = tokens.refresh_token ?? fallback?.refreshToken;
|
|
150
|
+
if (refreshToken === undefined || refreshToken.length === 0) {
|
|
151
|
+
throw new Error('Antigravity token endpoint returned no refresh token; revoke the app grant and log in again');
|
|
152
|
+
}
|
|
153
|
+
if (typeof tokens.expires_in !== 'number' || tokens.expires_in <= 0) {
|
|
154
|
+
throw new Error('Antigravity token endpoint returned no usable expiry');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
accessToken: tokens.access_token,
|
|
158
|
+
refreshToken,
|
|
159
|
+
expiresAt: Date.now() + tokens.expires_in * 1000,
|
|
160
|
+
projectId: account.projectId,
|
|
161
|
+
...tokens.scope === undefined ? {} : { scopes: tokens.scope },
|
|
162
|
+
...account.account === undefined ? {} : { account: account.account },
|
|
163
|
+
...account.plan === undefined ? {} : { plan: account.plan },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** Exchange a Google OAuth authorization code and discover the Antigravity project. */
|
|
167
|
+
export async function exchangeAntigravityCode(code, verifier, redirectUri, oauth, runtime = {}, fetchFn = proxiedFetch) {
|
|
168
|
+
const body = new URLSearchParams({
|
|
169
|
+
grant_type: 'authorization_code',
|
|
170
|
+
code,
|
|
171
|
+
redirect_uri: redirectUri,
|
|
172
|
+
client_id: oauth.clientId,
|
|
173
|
+
code_verifier: verifier,
|
|
174
|
+
...oauth.clientSecret === undefined ? {} : { client_secret: oauth.clientSecret },
|
|
175
|
+
});
|
|
176
|
+
const response = await fetchFn(ANTIGRAVITY_TOKEN_URL, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
179
|
+
body: body.toString(),
|
|
180
|
+
});
|
|
181
|
+
if (!response.ok)
|
|
182
|
+
throw await oauthEndpointError(response, 'Antigravity');
|
|
183
|
+
const tokens = await response.json();
|
|
184
|
+
if (typeof tokens.access_token !== 'string')
|
|
185
|
+
throw new Error('Antigravity token endpoint returned no access token');
|
|
186
|
+
const account = await discoverAntigravityAccount(tokens.access_token, runtime, fetchFn);
|
|
187
|
+
return sessionFromTokens(tokens, account);
|
|
188
|
+
}
|
|
189
|
+
/** Refresh a stored Antigravity Google token, preserving project/account metadata. */
|
|
190
|
+
export async function refreshAntigravity(session, oauth, fetchFn = proxiedFetch) {
|
|
191
|
+
const body = new URLSearchParams({
|
|
192
|
+
grant_type: 'refresh_token',
|
|
193
|
+
refresh_token: session.refreshToken,
|
|
194
|
+
client_id: oauth.clientId,
|
|
195
|
+
...oauth.clientSecret === undefined ? {} : { client_secret: oauth.clientSecret },
|
|
196
|
+
});
|
|
197
|
+
const response = await fetchFn(ANTIGRAVITY_TOKEN_URL, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
200
|
+
body: body.toString(),
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok)
|
|
203
|
+
throw await oauthEndpointError(response, 'Antigravity');
|
|
204
|
+
return sessionFromTokens(await response.json(), {
|
|
205
|
+
projectId: session.projectId,
|
|
206
|
+
...session.account === undefined ? {} : { account: session.account },
|
|
207
|
+
...session.plan === undefined ? {} : { plan: session.plan },
|
|
208
|
+
}, session);
|
|
209
|
+
}
|
|
210
|
+
/** Refresh failures that require a fresh Google consent grant. */
|
|
211
|
+
export function isAntigravityPermanentRefreshError(error) {
|
|
212
|
+
return error instanceof OAuthEndpointError
|
|
213
|
+
&& (error.status === 400 || error.status === 401 || error.status === 403)
|
|
214
|
+
&& (error.oauthCode === 'invalid_grant' || error.status !== 400);
|
|
215
|
+
}
|
|
216
|
+
/** Fetch the authenticated account's live Antigravity model catalog. */
|
|
217
|
+
export async function fetchAntigravityModels(session, runtime = {}, fetchFn = proxiedFetch) {
|
|
218
|
+
const payload = await callInternal('fetchAvailableModels', { project: session.projectId }, session.accessToken, runtime, fetchFn);
|
|
219
|
+
if (typeof payload.models !== 'object' || payload.models === null) {
|
|
220
|
+
throw new Error('Antigravity models endpoint returned no models object');
|
|
221
|
+
}
|
|
222
|
+
const models = Object.entries(payload.models).map(([id, model]) => ({
|
|
223
|
+
id,
|
|
224
|
+
name: model.displayName ?? id.split('-').map(word => word.length === 0 ? word : word[0].toUpperCase() + word.slice(1)).join(' '),
|
|
225
|
+
...model.description === undefined ? {} : { description: model.description },
|
|
226
|
+
contextWindow: model.inputTokenLimit ?? model.maxInputTokens ?? ANTIGRAVITY_CONTEXT_WINDOW,
|
|
227
|
+
inputModalities: ['text', 'image'],
|
|
228
|
+
}));
|
|
229
|
+
if (models.length === 0)
|
|
230
|
+
throw new Error('Antigravity models endpoint returned an empty catalog');
|
|
231
|
+
return models;
|
|
232
|
+
}
|
|
233
|
+
function resetTime(value) {
|
|
234
|
+
if (typeof value !== 'string')
|
|
235
|
+
return undefined;
|
|
236
|
+
const parsed = Date.parse(value);
|
|
237
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
238
|
+
}
|
|
239
|
+
function usageWindow(kind, scope, quota) {
|
|
240
|
+
const remaining = quota?.remainingFraction;
|
|
241
|
+
if (typeof remaining !== 'number' || !Number.isFinite(remaining))
|
|
242
|
+
return undefined;
|
|
243
|
+
const resetsAt = resetTime(quota?.resetTime);
|
|
244
|
+
return {
|
|
245
|
+
kind,
|
|
246
|
+
scope,
|
|
247
|
+
usedPercent: Math.max(0, Math.min(100, (1 - remaining) * 100)),
|
|
248
|
+
...resetsAt === undefined ? {} : { resetsAt },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** Fetch plan and per-model quota windows when the upstream exposes them. */
|
|
252
|
+
export async function fetchAntigravityUsage(session, runtime = {}, fetchFn = proxiedFetch, signal) {
|
|
253
|
+
const metadata = { ideType: 'ANTIGRAVITY', platform: 'PLATFORM_UNSPECIFIED', pluginType: 'GEMINI' };
|
|
254
|
+
const [models, account] = await Promise.all([
|
|
255
|
+
callInternal('fetchAvailableModels', { project: session.projectId }, session.accessToken, runtime, fetchFn, signal),
|
|
256
|
+
callInternal('loadCodeAssist', { metadata }, session.accessToken, runtime, fetchFn, signal),
|
|
257
|
+
]);
|
|
258
|
+
const windows = [];
|
|
259
|
+
for (const [modelId, model] of Object.entries(models.models ?? {})) {
|
|
260
|
+
const ordinary = usageWindow('other', modelId, model.quotaInfo);
|
|
261
|
+
const weekly = usageWindow('weekly', modelId, model.weeklyQuotaInfo ?? model.weeklyQuota);
|
|
262
|
+
if (ordinary !== undefined)
|
|
263
|
+
windows.push(ordinary);
|
|
264
|
+
if (weekly !== undefined)
|
|
265
|
+
windows.push(weekly);
|
|
266
|
+
}
|
|
267
|
+
const plan = account.paidTier?.name ?? account.paidTier?.id
|
|
268
|
+
?? account.currentTier?.name ?? account.currentTier?.id ?? session.plan;
|
|
269
|
+
const credits = account.paidTier?.availableCredits?.[0]?.creditAmount;
|
|
270
|
+
const displayPlan = credits === undefined ? plan : `${plan ?? 'Antigravity'} · ${String(credits)} credits`;
|
|
271
|
+
return {
|
|
272
|
+
supported: true,
|
|
273
|
+
windows,
|
|
274
|
+
...displayPlan === undefined ? {} : { plan: displayPlan },
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/** URL for either v1internal generation transport. */
|
|
278
|
+
export function antigravityGenerateURL(baseURL, stream) {
|
|
279
|
+
return `${antigravityBaseURL(baseURL)}/v1internal:${stream ? 'streamGenerateContent?alt=sse' : 'generateContent'}`;
|
|
280
|
+
}
|
|
281
|
+
/** Forward one already-built payload to generateContent or streamGenerateContent. */
|
|
282
|
+
export async function requestAntigravityContent(session, payload, stream, runtime = {}, fetchFn = proxiedFetch, signal) {
|
|
283
|
+
return fetchFn(antigravityGenerateURL(runtime.baseURL, stream), {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
headers: {
|
|
286
|
+
...antigravityHeaders(session.accessToken, runtime.userAgent),
|
|
287
|
+
accept: stream ? 'text/event-stream' : 'application/json',
|
|
288
|
+
},
|
|
289
|
+
body: JSON.stringify(payload),
|
|
290
|
+
...signal === undefined ? {} : { signal },
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/** DSH provider adapter for the `antigravity` route. */
|
|
294
|
+
export class AntigravityAdapter extends LlmAdapter {
|
|
295
|
+
options;
|
|
296
|
+
catalog;
|
|
297
|
+
constructor(options) {
|
|
298
|
+
super();
|
|
299
|
+
this.options = options;
|
|
300
|
+
this.catalog = new ModelCatalogCache(options.catalogStore);
|
|
301
|
+
}
|
|
302
|
+
providerInfo(provider) {
|
|
303
|
+
return { id: provider, name: 'Google Antigravity' };
|
|
304
|
+
}
|
|
305
|
+
staticModels(provider) {
|
|
306
|
+
return this.options.models.map(model => ({
|
|
307
|
+
provider,
|
|
308
|
+
id: model.id,
|
|
309
|
+
name: model.name ?? model.id,
|
|
310
|
+
inputModalities: model.inputModalities ?? ['text', 'image'],
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
fetchCatalog() {
|
|
314
|
+
return this.options.tokens.session().then(session => fetchAntigravityModels(session, this.options.runtime, this.options.fetchFn));
|
|
315
|
+
}
|
|
316
|
+
async listModels(provider) {
|
|
317
|
+
if (await this.options.tokens.peek() === undefined)
|
|
318
|
+
return [];
|
|
319
|
+
if (!this.options.discovery)
|
|
320
|
+
return this.staticModels(provider);
|
|
321
|
+
try {
|
|
322
|
+
const models = await discoverOrRetryAuth(force => this.options.tokens.session(force), this.catalog, () => this.catalog.get(() => this.fetchCatalog()));
|
|
323
|
+
return models.map(model => ({
|
|
324
|
+
provider,
|
|
325
|
+
id: model.id,
|
|
326
|
+
name: model.name,
|
|
327
|
+
...model.description === undefined ? {} : { description: model.description },
|
|
328
|
+
inputModalities: model.inputModalities ?? ['text', 'image'],
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
if (isMissingOrInvalidCredential(error))
|
|
333
|
+
return [];
|
|
334
|
+
this.options.onWarn?.(`Antigravity model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
335
|
+
return this.staticModels(provider);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async discovered(model) {
|
|
339
|
+
if (!this.options.discovery)
|
|
340
|
+
return undefined;
|
|
341
|
+
const models = await this.catalog.resolve(() => this.fetchCatalog());
|
|
342
|
+
return models?.find(entry => entry.id === model);
|
|
343
|
+
}
|
|
344
|
+
async resolveModel(provider, model) {
|
|
345
|
+
const discovered = await this.discovered(model);
|
|
346
|
+
const configured = this.options.models.find(entry => entry.id === model);
|
|
347
|
+
return {
|
|
348
|
+
provider,
|
|
349
|
+
id: model,
|
|
350
|
+
name: discovered?.name ?? configured?.name ?? model,
|
|
351
|
+
...discovered?.description === undefined ? {} : { description: discovered.description },
|
|
352
|
+
inputModalities: discovered?.inputModalities ?? configured?.inputModalities ?? ['text', 'image'],
|
|
353
|
+
context: { contextWindow: discovered?.contextWindow ?? configured?.contextWindow ?? ANTIGRAVITY_CONTEXT_WINDOW },
|
|
354
|
+
defaultMaxTokens: configured?.maxTokens ?? ANTIGRAVITY_DEFAULT_MAX_TOKENS,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async *stream(options) {
|
|
358
|
+
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
359
|
+
try {
|
|
360
|
+
let session = await this.options.tokens.session();
|
|
361
|
+
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), watchdog.signal);
|
|
362
|
+
const payload = toAntigravityRequest(options, messages, session.projectId);
|
|
363
|
+
let response = await requestAntigravityContent(session, payload, true, this.options.runtime, this.options.fetchFn, watchdog.signal);
|
|
364
|
+
if (response.status === 401) {
|
|
365
|
+
this.catalog.invalidate();
|
|
366
|
+
session = await this.options.tokens.session(true);
|
|
367
|
+
response = await requestAntigravityContent(session, payload, true, this.options.runtime, this.options.fetchFn, watchdog.signal);
|
|
368
|
+
}
|
|
369
|
+
if (!response.ok)
|
|
370
|
+
throw await httpLlmError(response, 'Antigravity API');
|
|
371
|
+
if (response.body === null) {
|
|
372
|
+
throw new LlmError('Antigravity API returned no response body', EMPTY_RESPONSE_CODE);
|
|
373
|
+
}
|
|
374
|
+
yield* streamAntigravity(response.body, () => { watchdog.pulse(); });
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
throw mapFetchFailure('Antigravity API', error, watchdog, options.signal);
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
watchdog.stop();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Non-stream forwarding seam used by tests and future DSH complete calls. */
|
|
384
|
+
async generate(options) {
|
|
385
|
+
const session = await this.options.tokens.session();
|
|
386
|
+
const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), options.signal);
|
|
387
|
+
const response = await requestAntigravityContent(session, toAntigravityRequest(options, messages, session.projectId), false, this.options.runtime, this.options.fetchFn, options.signal);
|
|
388
|
+
if (!response.ok)
|
|
389
|
+
throw await httpLlmError(response, 'Antigravity API');
|
|
390
|
+
return parseAntigravityResponse(await response.json());
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -7,9 +7,10 @@ import { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
|
7
7
|
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
8
|
import type { FlowSpec } from '../auth/oauth-flow.js';
|
|
9
9
|
import type { ClaudeSession } from '../auth/store.js';
|
|
10
|
+
import type { PoolAdapter } from './pool.js';
|
|
10
11
|
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
11
12
|
import type { TranslatableMessage } from '../translate/resolved.js';
|
|
12
|
-
import {
|
|
13
|
+
import { AccountTokenManager } from './accounts.js';
|
|
13
14
|
import type { CatalogPersistence, DiscoveredModel, FetchFn, ModelEntry, ProviderUsage } from './common.js';
|
|
14
15
|
export declare const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
15
16
|
export declare const CLAUDE_AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
@@ -64,13 +65,15 @@ export declare const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usa
|
|
|
64
65
|
* @returns the mapped usage snapshot.
|
|
65
66
|
*/
|
|
66
67
|
export declare function fetchClaudeUsage(session: ClaudeSession, fetchFn?: FetchFn, signal?: AbortSignal): Promise<ProviderUsage>;
|
|
67
|
-
/** Fetch the live model catalog from the subscription endpoint. */
|
|
68
|
-
export declare function fetchClaudeModels(session: ClaudeSession, fetchFn?: FetchFn): Promise<DiscoveredModel[]>;
|
|
68
|
+
/** Fetch the live model catalog from the subscription endpoint. `signal` cancels the request. */
|
|
69
|
+
export declare function fetchClaudeModels(session: ClaudeSession, fetchFn?: FetchFn, signal?: AbortSignal): Promise<DiscoveredModel[]>;
|
|
69
70
|
/** Constructor dependencies for {@link ClaudeAdapter}. */
|
|
70
71
|
export interface ClaudeAdapterOptions {
|
|
71
72
|
models: readonly ModelEntry[];
|
|
72
73
|
streamIdleTimeoutMs: number;
|
|
73
|
-
tokens:
|
|
74
|
+
tokens: AccountTokenManager<ClaudeSession>;
|
|
75
|
+
/** Late-bound pool facade (wired after adapter construction); pools list under their first member's provider. */
|
|
76
|
+
pool?: () => PoolAdapter | undefined;
|
|
74
77
|
/** Whether to fetch the live catalog when logged in (false when config `models` overrides). */
|
|
75
78
|
discovery: boolean;
|
|
76
79
|
fetchFn?: FetchFn;
|
|
@@ -102,15 +105,30 @@ export declare function claudeRequestBody(options: GenerateOptions, messages: re
|
|
|
102
105
|
export declare class ClaudeAdapter extends LlmAdapter {
|
|
103
106
|
private readonly options;
|
|
104
107
|
private readonly catalog;
|
|
108
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
109
|
+
private readonly accountCatalogs;
|
|
110
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
111
|
+
private catalogOwner;
|
|
105
112
|
constructor(options: ClaudeAdapterOptions);
|
|
106
113
|
private fetchCatalog;
|
|
114
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
115
|
+
clearAccountCatalog(account?: string): void;
|
|
116
|
+
/** Persisted cache for the default account; a throwaway cache for any other. */
|
|
117
|
+
private catalogFor;
|
|
107
118
|
private discovered;
|
|
108
119
|
private staticModels;
|
|
109
120
|
providerInfo(provider: string): LlmProviderInfo;
|
|
110
121
|
providerRetryPolicy(provider: string): import("@deepseek-ai/dsh-llm").ResolvedRetryPolicy | undefined;
|
|
111
122
|
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
123
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
124
|
+
listOwnModels(provider: string, account?: string, signal?: AbortSignal): Promise<readonly LlmModelInfo[]>;
|
|
112
125
|
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
126
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
127
|
+
resolveOwnModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
113
128
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
129
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
130
|
+
streamAccount(options: GenerateOptions, account: string): AsyncIterable<StreamChunk>;
|
|
131
|
+
private streamCore;
|
|
114
132
|
/**
|
|
115
133
|
* `display: 'summarized'` is set explicitly on both shapes: `adaptive`-type
|
|
116
134
|
* models default to `display: 'omitted'`, which returns thinking blocks with
|
package/lib/providers/claude.js
CHANGED
|
@@ -7,7 +7,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
7
7
|
import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, resolveRetryPolicy } from '@deepseek-ai/dsh-llm';
|
|
8
8
|
import { resolveImages } from '../translate/resolved.js';
|
|
9
9
|
import { markMessageCache, streamAnthropic, toAnthropicMessages, toAnthropicSystem, toAnthropicTools, } from '../translate/anthropic.js';
|
|
10
|
-
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverOrRetryAuth, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError,
|
|
10
|
+
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError, } from './common.js';
|
|
11
|
+
import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
|
|
12
|
+
import { proxiedFetch } from '../http.js';
|
|
11
13
|
export const CLAUDE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
12
14
|
export const CLAUDE_AUTHORIZE_URL = 'https://claude.ai/oauth/authorize';
|
|
13
15
|
export const CLAUDE_TOKEN_URL = 'https://claude.ai/v1/oauth/token';
|
|
@@ -79,7 +81,7 @@ export const claudeFlow = {
|
|
|
79
81
|
/** Best-effort account profile; login must not fail when this does. */
|
|
80
82
|
async function fetchClaudeProfile(accessToken) {
|
|
81
83
|
try {
|
|
82
|
-
const response = await
|
|
84
|
+
const response = await proxiedFetch(CLAUDE_PROFILE_URL, {
|
|
83
85
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
84
86
|
});
|
|
85
87
|
if (!response.ok)
|
|
@@ -129,7 +131,7 @@ async function claudeSession(tokens, fallbackRefreshToken, withProfile) {
|
|
|
129
131
|
* @returns the session to store.
|
|
130
132
|
*/
|
|
131
133
|
export async function exchangeClaudeCode(code, verifier, redirectUri, state) {
|
|
132
|
-
const response = await
|
|
134
|
+
const response = await proxiedFetch(CLAUDE_TOKEN_URL, {
|
|
133
135
|
method: 'POST',
|
|
134
136
|
headers: { 'content-type': 'application/json' },
|
|
135
137
|
body: JSON.stringify({
|
|
@@ -151,7 +153,7 @@ export async function exchangeClaudeCode(code, verifier, redirectUri, state) {
|
|
|
151
153
|
* @returns the fresh session to store.
|
|
152
154
|
*/
|
|
153
155
|
export async function refreshClaude(session) {
|
|
154
|
-
const response = await
|
|
156
|
+
const response = await proxiedFetch(CLAUDE_TOKEN_URL, {
|
|
155
157
|
method: 'POST',
|
|
156
158
|
headers: { 'content-type': 'application/json' },
|
|
157
159
|
body: JSON.stringify({
|
|
@@ -237,7 +239,7 @@ function claudeLimitsWindows(value) {
|
|
|
237
239
|
* @param signal - caller cancellation from the RPC transport.
|
|
238
240
|
* @returns the mapped usage snapshot.
|
|
239
241
|
*/
|
|
240
|
-
export async function fetchClaudeUsage(session, fetchFn =
|
|
242
|
+
export async function fetchClaudeUsage(session, fetchFn = proxiedFetch, signal) {
|
|
241
243
|
const response = await fetchFn(CLAUDE_USAGE_URL, {
|
|
242
244
|
headers: {
|
|
243
245
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -287,8 +289,8 @@ function claudeReasoning(capabilities) {
|
|
|
287
289
|
.map(level => ({ id: ReasoningEffortId(level), name: level[0].toUpperCase() + level.slice(1) }));
|
|
288
290
|
return efforts.length > 0 ? { efforts } : undefined;
|
|
289
291
|
}
|
|
290
|
-
/** Fetch the live model catalog from the subscription endpoint. */
|
|
291
|
-
export async function fetchClaudeModels(session, fetchFn =
|
|
292
|
+
/** Fetch the live model catalog from the subscription endpoint. `signal` cancels the request. */
|
|
293
|
+
export async function fetchClaudeModels(session, fetchFn = proxiedFetch, signal) {
|
|
292
294
|
const response = await fetchFn(CLAUDE_MODELS_URL, {
|
|
293
295
|
headers: {
|
|
294
296
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -297,6 +299,7 @@ export async function fetchClaudeModels(session, fetchFn = fetch) {
|
|
|
297
299
|
'anthropic-dangerous-direct-browser-access': 'true',
|
|
298
300
|
'accept': 'application/json',
|
|
299
301
|
},
|
|
302
|
+
...signal === undefined ? {} : { signal },
|
|
300
303
|
});
|
|
301
304
|
if (!response.ok)
|
|
302
305
|
throw await httpLlmError(response, 'claude models API');
|
|
@@ -367,19 +370,56 @@ export function claudeRequestBody(options, messages, maxTokens, thinking, effort
|
|
|
367
370
|
export class ClaudeAdapter extends LlmAdapter {
|
|
368
371
|
options;
|
|
369
372
|
catalog;
|
|
373
|
+
/** In-memory catalogs for non-default accounts (the persisted cache is the default's). */
|
|
374
|
+
accountCatalogs = new Map();
|
|
375
|
+
/** Account whose snapshot currently lives in {@link catalog}; cleared on default change. */
|
|
376
|
+
catalogOwner;
|
|
370
377
|
constructor(options) {
|
|
371
378
|
super();
|
|
372
379
|
this.options = options;
|
|
373
380
|
this.catalog = new ModelCatalogCache(options.catalogStore);
|
|
374
381
|
}
|
|
375
|
-
async fetchCatalog() {
|
|
376
|
-
return fetchClaudeModels(await this.options.tokens.session(), this.options.fetchFn);
|
|
382
|
+
async fetchCatalog(account, signal) {
|
|
383
|
+
return fetchClaudeModels(await this.options.tokens.session(account), this.options.fetchFn, signal);
|
|
384
|
+
}
|
|
385
|
+
/** Drop cached catalogs after login/logout so the next list does not reuse a stale plan. */
|
|
386
|
+
clearAccountCatalog(account) {
|
|
387
|
+
if (account === undefined)
|
|
388
|
+
this.accountCatalogs.clear();
|
|
389
|
+
else
|
|
390
|
+
this.accountCatalogs.delete(account);
|
|
391
|
+
if (account === undefined || this.catalogOwner === account || this.catalogOwner === undefined) {
|
|
392
|
+
this.catalogOwner = undefined;
|
|
393
|
+
this.catalog.invalidate();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** Persisted cache for the default account; a throwaway cache for any other. */
|
|
397
|
+
async catalogFor(account) {
|
|
398
|
+
const defaultKey = await this.options.tokens.defaultAccount();
|
|
399
|
+
const key = account ?? defaultKey;
|
|
400
|
+
if (key === undefined || key === defaultKey) {
|
|
401
|
+
if (this.catalogOwner !== undefined && this.catalogOwner !== defaultKey) {
|
|
402
|
+
this.catalog.invalidate();
|
|
403
|
+
}
|
|
404
|
+
this.catalogOwner = defaultKey;
|
|
405
|
+
return this.catalog;
|
|
406
|
+
}
|
|
407
|
+
let cache = this.accountCatalogs.get(key);
|
|
408
|
+
if (cache === undefined) {
|
|
409
|
+
cache = new ModelCatalogCache();
|
|
410
|
+
this.accountCatalogs.set(key, cache);
|
|
411
|
+
}
|
|
412
|
+
return cache;
|
|
377
413
|
}
|
|
378
414
|
async discovered(model) {
|
|
379
415
|
if (!this.options.discovery)
|
|
380
416
|
return undefined;
|
|
381
|
-
const
|
|
382
|
-
return
|
|
417
|
+
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
418
|
+
return discoverAcrossAccounts(accounts, async (account) => {
|
|
419
|
+
const catalog = await this.catalogFor(account);
|
|
420
|
+
const models = await catalog.resolve(() => this.fetchCatalog(account));
|
|
421
|
+
return models?.find(entry => entry.id === model);
|
|
422
|
+
});
|
|
383
423
|
}
|
|
384
424
|
staticModels(provider) {
|
|
385
425
|
return this.options.models.map(model => ({
|
|
@@ -406,12 +446,31 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
406
446
|
}, `claude: provider "${provider}" retryPolicy`);
|
|
407
447
|
}
|
|
408
448
|
async listModels(provider) {
|
|
409
|
-
|
|
449
|
+
const own = await this.listOwnModels(provider);
|
|
450
|
+
const pool = this.options.pool?.();
|
|
451
|
+
if (pool === undefined)
|
|
452
|
+
return own;
|
|
453
|
+
const extra = await pool.modelsForProvider(provider);
|
|
454
|
+
const seen = new Set(own.map(model => model.id));
|
|
455
|
+
// Account pools reuse the catalog row; only configured tiers are extra.
|
|
456
|
+
return [...own, ...extra.filter(model => !seen.has(model.id))];
|
|
457
|
+
}
|
|
458
|
+
/** The provider's own catalog: union of every account, or one account when named. */
|
|
459
|
+
async listOwnModels(provider, account, signal) {
|
|
460
|
+
if (account === undefined) {
|
|
461
|
+
const accounts = (await this.options.tokens.list()).map(entry => entry.key);
|
|
462
|
+
if (accounts.length === 0)
|
|
463
|
+
return [];
|
|
464
|
+
return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), { timeoutMs: DISCOVERY_TIMEOUT_MS, ...signal === undefined ? {} : { signal } });
|
|
465
|
+
}
|
|
466
|
+
if (!await this.options.tokens.hasSession(account)) {
|
|
410
467
|
return [];
|
|
468
|
+
}
|
|
411
469
|
if (!this.options.discovery)
|
|
412
470
|
return this.staticModels(provider);
|
|
471
|
+
const catalog = await this.catalogFor(account);
|
|
413
472
|
try {
|
|
414
|
-
const models = await discoverOrRetryAuth(force => this.options.tokens.session(force),
|
|
473
|
+
const models = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
|
|
415
474
|
return models.map(model => ({
|
|
416
475
|
provider,
|
|
417
476
|
id: model.id,
|
|
@@ -420,6 +479,8 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
420
479
|
}));
|
|
421
480
|
}
|
|
422
481
|
catch (error) {
|
|
482
|
+
if (isDiscoveryAborted(error, signal))
|
|
483
|
+
throw error;
|
|
423
484
|
if (isMissingOrInvalidCredential(error))
|
|
424
485
|
return [];
|
|
425
486
|
this.options.onWarn?.(`claude model discovery failed; using the built-in catalog (${errorChain(error)})`);
|
|
@@ -427,6 +488,14 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
427
488
|
}
|
|
428
489
|
}
|
|
429
490
|
async resolveModel(provider, model) {
|
|
491
|
+
const pool = this.options.pool?.();
|
|
492
|
+
if (pool !== undefined && await pool.owns(provider, model)) {
|
|
493
|
+
return pool.resolveModel(provider, model);
|
|
494
|
+
}
|
|
495
|
+
return this.resolveOwnModel(provider, model);
|
|
496
|
+
}
|
|
497
|
+
/** Capability resolution of the provider's own models (the pool resolves members here). */
|
|
498
|
+
async resolveOwnModel(provider, model) {
|
|
430
499
|
const disc = await this.discovered(model);
|
|
431
500
|
const configured = this.options.models.find(entry => entry.id === model);
|
|
432
501
|
const reasoning = disc?.reasoning;
|
|
@@ -443,12 +512,24 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
443
512
|
};
|
|
444
513
|
}
|
|
445
514
|
async *stream(options) {
|
|
515
|
+
const pool = this.options.pool?.();
|
|
516
|
+
if (pool !== undefined && await pool.owns(options.provider, options.model)) {
|
|
517
|
+
yield* pool.stream(options);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
yield* this.streamCore(options);
|
|
521
|
+
}
|
|
522
|
+
/** Pool seam: stream through one specific account instead of the default. */
|
|
523
|
+
streamAccount(options, account) {
|
|
524
|
+
return this.streamCore(options, account);
|
|
525
|
+
}
|
|
526
|
+
async *streamCore(options, account) {
|
|
446
527
|
const watchdog = idleWatchdog(options.signal, this.options.streamIdleTimeoutMs);
|
|
447
528
|
try {
|
|
448
|
-
let session = await this.options.tokens.session();
|
|
529
|
+
let session = await this.options.tokens.session(account);
|
|
449
530
|
let response = await this.request(options, session, watchdog.signal);
|
|
450
531
|
if (response.status === 401) {
|
|
451
|
-
session = await this.options.tokens.session(true);
|
|
532
|
+
session = await this.options.tokens.session(account, true);
|
|
452
533
|
response = await this.request(options, session, watchdog.signal);
|
|
453
534
|
}
|
|
454
535
|
if (!response.ok)
|
|
@@ -494,7 +575,7 @@ export class ClaudeAdapter extends LlmAdapter {
|
|
|
494
575
|
? String(options.reasoningEffort)
|
|
495
576
|
: undefined;
|
|
496
577
|
const body = claudeRequestBody(options, messages, maxTokens, thinking, effort);
|
|
497
|
-
return
|
|
578
|
+
return proxiedFetch(CLAUDE_API_URL, {
|
|
498
579
|
method: 'POST',
|
|
499
580
|
headers: {
|
|
500
581
|
'authorization': `Bearer ${session.accessToken}`,
|