dsh-plugin-subscriptions 0.5.3 → 0.6.0
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 +45 -6
- package/README.zh.md +43 -4
- package/lib/auth/rpc.d.ts +36 -2
- package/lib/auth/rpc.js +47 -5
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/SpeedSelect.d.ts +25 -2
- package/lib/client/SpeedSelect.js +10 -6
- package/lib/client/SubscriptionsSection.d.ts +74 -0
- package/lib/client/SubscriptionsSection.js +325 -4
- package/lib/client/VideoGenerateToolview.d.ts +1 -1
- package/lib/client/index.d.ts +1 -9
- package/lib/client/index.js +7 -4
- package/lib/client/locales.d.ts +28 -0
- package/lib/client/locales.js +28 -0
- package/lib/client.js +458 -10
- package/lib/client.js.map +1 -1
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/index.d.ts +5 -1
- package/lib/index.js +865 -111
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/providers/claude.d.ts +24 -3
- package/lib/providers/claude.js +35 -24
- package/lib/providers/codex.d.ts +21 -0
- package/lib/providers/codex.js +37 -10
- package/lib/providers/common.d.ts +70 -6
- package/lib/providers/common.js +118 -19
- package/lib/providers/copilot.d.ts +10 -0
- package/lib/providers/copilot.js +21 -8
- package/lib/providers/grok.d.ts +21 -0
- package/lib/providers/grok.js +37 -7
- package/lib/providers/pool-usage.d.ts +23 -2
- package/lib/providers/pool-usage.js +70 -15
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/translate/anthropic.js +5 -4
- package/lib/translate/chat-completions.js +5 -4
- package/lib/translate/responses.js +5 -4
- package/package.json +21 -21
- package/lib/providers/antigravity.d.ts +0 -90
- package/lib/providers/antigravity.js +0 -392
- package/lib/translate/antigravity.d.ts +0 -110
- package/lib/translate/antigravity.js +0 -303
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* schema mapping, and a push-model SSE-event → StreamChunk state machine
|
|
5
5
|
* ({@link AnthropicStreamTranslator}) so tests need no streams.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmError, } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { ToolCallId } from '../compat.js';
|
|
8
9
|
import { parseSse } from './sse.js';
|
|
9
10
|
/**
|
|
10
11
|
* The Claude Code identity block. The subscription endpoint rejects requests
|
|
@@ -252,7 +253,7 @@ function closeBlock(block) {
|
|
|
252
253
|
case 'tool-call':
|
|
253
254
|
return {
|
|
254
255
|
type: 'tool-call',
|
|
255
|
-
id:
|
|
256
|
+
id: ToolCallId(block.callId),
|
|
256
257
|
name: block.name ?? '',
|
|
257
258
|
arguments: block.text,
|
|
258
259
|
};
|
|
@@ -362,7 +363,7 @@ export class AnthropicStreamTranslator {
|
|
|
362
363
|
chunks.push({
|
|
363
364
|
type: 'tool-call-delta',
|
|
364
365
|
index: opened.index,
|
|
365
|
-
id:
|
|
366
|
+
id: ToolCallId(opened.callId),
|
|
366
367
|
...block.name === undefined ? {} : { name: block.name },
|
|
367
368
|
argumentsDelta: '',
|
|
368
369
|
});
|
|
@@ -393,7 +394,7 @@ export class AnthropicStreamTranslator {
|
|
|
393
394
|
chunks.push({
|
|
394
395
|
type: 'tool-call-delta',
|
|
395
396
|
index: block.index,
|
|
396
|
-
id:
|
|
397
|
+
id: ToolCallId(block.callId),
|
|
397
398
|
...block.name === undefined ? {} : { name: block.name },
|
|
398
399
|
argumentsDelta: delta.partial_json ?? '',
|
|
399
400
|
});
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* ({@link ChatCompletionsStreamTranslator}) mirroring the Responses
|
|
6
6
|
* translator, so tests need no streams.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
9
|
+
import { ToolCallId } from '../compat.js';
|
|
9
10
|
import { parseSse } from './sse.js';
|
|
10
11
|
/** Flatten a tool result's content to plain text for a `tool` message. */
|
|
11
12
|
function toolResultText(block) {
|
|
@@ -153,7 +154,7 @@ function closeBlock(block) {
|
|
|
153
154
|
case 'tool-call':
|
|
154
155
|
return {
|
|
155
156
|
type: 'tool-call',
|
|
156
|
-
id:
|
|
157
|
+
id: ToolCallId(block.callId),
|
|
157
158
|
name: block.name ?? '',
|
|
158
159
|
arguments: block.text,
|
|
159
160
|
};
|
|
@@ -285,7 +286,7 @@ export class ChatCompletionsStreamTranslator {
|
|
|
285
286
|
chunks.push({
|
|
286
287
|
type: 'tool-call-delta',
|
|
287
288
|
index: block.index,
|
|
288
|
-
id:
|
|
289
|
+
id: ToolCallId(block.callId),
|
|
289
290
|
...block.name === undefined ? {} : { name: block.name },
|
|
290
291
|
argumentsDelta: '',
|
|
291
292
|
});
|
|
@@ -295,7 +296,7 @@ export class ChatCompletionsStreamTranslator {
|
|
|
295
296
|
chunks.push({
|
|
296
297
|
type: 'tool-call-delta',
|
|
297
298
|
index: block.index,
|
|
298
|
-
id:
|
|
299
|
+
id: ToolCallId(block.callId),
|
|
299
300
|
argumentsDelta: call.function.arguments,
|
|
300
301
|
});
|
|
301
302
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* assembly, tool schema mapping, and a push-model SSE-event → StreamChunk
|
|
5
5
|
* state machine ({@link ResponsesStreamTranslator}) so tests need no streams.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { ToolCallId } from '../compat.js';
|
|
8
9
|
import { parseSse } from './sse.js';
|
|
9
10
|
/** Flatten a tool result's content to plain text for `function_call_output`. */
|
|
10
11
|
function toolResultText(block) {
|
|
@@ -165,7 +166,7 @@ function closeBlock(block) {
|
|
|
165
166
|
case 'tool-call':
|
|
166
167
|
return {
|
|
167
168
|
type: 'tool-call',
|
|
168
|
-
id:
|
|
169
|
+
id: ToolCallId(block.callId),
|
|
169
170
|
name: block.name ?? '',
|
|
170
171
|
arguments: block.text,
|
|
171
172
|
};
|
|
@@ -251,7 +252,7 @@ export class ResponsesStreamTranslator {
|
|
|
251
252
|
chunks.push({
|
|
252
253
|
type: 'tool-call-delta',
|
|
253
254
|
index: block.index,
|
|
254
|
-
id:
|
|
255
|
+
id: ToolCallId(callId),
|
|
255
256
|
...item.name === undefined ? {} : { name: item.name },
|
|
256
257
|
argumentsDelta: '',
|
|
257
258
|
});
|
|
@@ -286,7 +287,7 @@ export class ResponsesStreamTranslator {
|
|
|
286
287
|
chunks.push({
|
|
287
288
|
type: 'tool-call-delta',
|
|
288
289
|
index: block.index,
|
|
289
|
-
id:
|
|
290
|
+
id: ToolCallId(block.callId),
|
|
290
291
|
...block.name === undefined ? {} : { name: block.name },
|
|
291
292
|
argumentsDelta: event.delta ?? '',
|
|
292
293
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscriptions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Use ChatGPT (Codex), Claude, Grok (X Premium), and GitHub Copilot subscriptions as DeepSeek Harness LLM providers, with OAuth login from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"client": {
|
|
44
44
|
"platform": "web",
|
|
45
45
|
"inject": [
|
|
46
|
-
"@deepseek-ai/dsh-client-
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
47
47
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
48
48
|
"@deepseek-ai/dsh-client-locale"
|
|
49
49
|
]
|
|
@@ -57,28 +57,28 @@
|
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
-
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
|
|
61
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
|
|
62
|
-
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
63
|
-
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2 || ^0.1.2-alpha.1",
|
|
64
64
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@deepseek-ai/cordis": "
|
|
68
|
-
"@deepseek-ai/dsh-api-remotes": "
|
|
69
|
-
"@deepseek-ai/dsh-attachment": "
|
|
70
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
71
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
72
|
-
"@deepseek-ai/dsh-client-
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-
|
|
74
|
-
"@deepseek-ai/dsh-client-ui-
|
|
75
|
-
"@deepseek-ai/dsh-client-ui-
|
|
76
|
-
"@deepseek-ai/dsh-client-ui-
|
|
77
|
-
"@deepseek-ai/dsh-
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
79
|
-
"@deepseek-ai/dsh-llm": "
|
|
80
|
-
"@deepseek-ai/dsh-tools": "
|
|
81
|
-
"@deepseek-ai/schemastery": "
|
|
67
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
68
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.3",
|
|
69
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-alpha.3",
|
|
70
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.3",
|
|
71
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.3",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.3",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.3",
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-commands": "0.1.2-alpha.3",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.3",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.3",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.3",
|
|
78
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.3",
|
|
79
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.3",
|
|
80
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.3",
|
|
81
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
82
82
|
"@types/node": "^24.0.0",
|
|
83
83
|
"@types/react": "~18.3.1",
|
|
84
84
|
"react": "^18.2.0",
|
|
@@ -1,90 +0,0 @@
|
|
|
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 { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
7
|
-
import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
8
|
-
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
9
|
-
import type { FlowSpec } from '../auth/oauth-flow.js';
|
|
10
|
-
import type { AntigravitySession } from '../auth/store.js';
|
|
11
|
-
import type { AntigravityRequest } from '../translate/antigravity.js';
|
|
12
|
-
import { TokenManager } from './common.js';
|
|
13
|
-
import type { CatalogPersistence, DiscoveredModel, FetchFn, ModelEntry, ProviderUsage } from './common.js';
|
|
14
|
-
export declare const ANTIGRAVITY_AUTHORIZE_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
15
|
-
export declare const ANTIGRAVITY_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
16
|
-
export declare const ANTIGRAVITY_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
17
|
-
export declare const ANTIGRAVITY_DEFAULT_BASE_URL = "https://daily-cloudcode-pa.googleapis.com";
|
|
18
|
-
export declare const ANTIGRAVITY_PROD_BASE_URL = "https://cloudcode-pa.googleapis.com";
|
|
19
|
-
export declare const ANTIGRAVITY_DEFAULT_USER_AGENT = "antigravity/1.104.0 dsh-plugin-subscriptions";
|
|
20
|
-
export declare const ANTIGRAVITY_PREEMPT_MS: number;
|
|
21
|
-
/** Antigravity, not Gemini CLI, OAuth scopes from the local reference clients. */
|
|
22
|
-
export declare const ANTIGRAVITY_SCOPES: readonly ["openid", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/cclog", "https://www.googleapis.com/auth/experimentsandconfigs"];
|
|
23
|
-
/** OAuth client configuration. Values must come from config/environment. */
|
|
24
|
-
export interface AntigravityOAuthConfig {
|
|
25
|
-
clientId: string;
|
|
26
|
-
clientSecret?: string;
|
|
27
|
-
}
|
|
28
|
-
/** Runtime endpoint configuration. */
|
|
29
|
-
export interface AntigravityRuntimeConfig {
|
|
30
|
-
baseURL?: string;
|
|
31
|
-
userAgent?: string;
|
|
32
|
-
/** Activate an eligible account when loadCodeAssist has no project yet. */
|
|
33
|
-
onboard?: boolean;
|
|
34
|
-
}
|
|
35
|
-
/** Resolve and validate a user-supplied OAuth config without embedded credentials. */
|
|
36
|
-
export declare function resolveAntigravityOAuthConfig(config?: Partial<AntigravityOAuthConfig>): AntigravityOAuthConfig;
|
|
37
|
-
/** Normalize the configured API origin and reject paths/credentials. */
|
|
38
|
-
export declare function antigravityBaseURL(value?: string): string;
|
|
39
|
-
/** Google authorization-code + PKCE flow for Antigravity. */
|
|
40
|
-
export declare function antigravityFlow(oauth: AntigravityOAuthConfig): FlowSpec;
|
|
41
|
-
interface AntigravityAccountInfo {
|
|
42
|
-
projectId: string;
|
|
43
|
-
account?: string;
|
|
44
|
-
plan?: string;
|
|
45
|
-
}
|
|
46
|
-
/** Shared Antigravity API headers. */
|
|
47
|
-
export declare function antigravityHeaders(accessToken: string, userAgent?: string): Record<string, string>;
|
|
48
|
-
/** Read (and, when enabled, initialize) the Antigravity project/account. */
|
|
49
|
-
export declare function discoverAntigravityAccount(accessToken: string, runtime?: AntigravityRuntimeConfig, fetchFn?: FetchFn): Promise<AntigravityAccountInfo>;
|
|
50
|
-
/** Exchange a Google OAuth authorization code and discover the Antigravity project. */
|
|
51
|
-
export declare function exchangeAntigravityCode(code: string, verifier: string, redirectUri: string, oauth: AntigravityOAuthConfig, runtime?: AntigravityRuntimeConfig, fetchFn?: FetchFn): Promise<AntigravitySession>;
|
|
52
|
-
/** Refresh a stored Antigravity Google token, preserving project/account metadata. */
|
|
53
|
-
export declare function refreshAntigravity(session: AntigravitySession, oauth: AntigravityOAuthConfig, fetchFn?: FetchFn): Promise<AntigravitySession>;
|
|
54
|
-
/** Refresh failures that require a fresh Google consent grant. */
|
|
55
|
-
export declare function isAntigravityPermanentRefreshError(error: unknown): boolean;
|
|
56
|
-
/** Fetch the authenticated account's live Antigravity model catalog. */
|
|
57
|
-
export declare function fetchAntigravityModels(session: AntigravitySession, runtime?: AntigravityRuntimeConfig, fetchFn?: FetchFn): Promise<DiscoveredModel[]>;
|
|
58
|
-
/** Fetch plan and per-model quota windows when the upstream exposes them. */
|
|
59
|
-
export declare function fetchAntigravityUsage(session: AntigravitySession, runtime?: AntigravityRuntimeConfig, fetchFn?: FetchFn, signal?: AbortSignal): Promise<ProviderUsage>;
|
|
60
|
-
/** URL for either v1internal generation transport. */
|
|
61
|
-
export declare function antigravityGenerateURL(baseURL: string | undefined, stream: boolean): string;
|
|
62
|
-
/** Forward one already-built payload to generateContent or streamGenerateContent. */
|
|
63
|
-
export declare function requestAntigravityContent(session: AntigravitySession, payload: AntigravityRequest, stream: boolean, runtime?: AntigravityRuntimeConfig, fetchFn?: FetchFn, signal?: AbortSignal): Promise<Response>;
|
|
64
|
-
export interface AntigravityAdapterOptions {
|
|
65
|
-
models: readonly ModelEntry[];
|
|
66
|
-
streamIdleTimeoutMs: number;
|
|
67
|
-
tokens: TokenManager<AntigravitySession>;
|
|
68
|
-
discovery: boolean;
|
|
69
|
-
runtime?: AntigravityRuntimeConfig;
|
|
70
|
-
onWarn?: (message: string) => void;
|
|
71
|
-
fetchFn?: FetchFn;
|
|
72
|
-
resolveAttachments?: () => AttachmentStore | undefined;
|
|
73
|
-
catalogStore?: CatalogPersistence;
|
|
74
|
-
}
|
|
75
|
-
/** DSH provider adapter for the `antigravity` route. */
|
|
76
|
-
export declare class AntigravityAdapter extends LlmAdapter {
|
|
77
|
-
private readonly options;
|
|
78
|
-
private readonly catalog;
|
|
79
|
-
constructor(options: AntigravityAdapterOptions);
|
|
80
|
-
providerInfo(provider: string): LlmProviderInfo;
|
|
81
|
-
private staticModels;
|
|
82
|
-
private fetchCatalog;
|
|
83
|
-
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
84
|
-
private discovered;
|
|
85
|
-
resolveModel(provider: string, model: string): Promise<LlmResolvedModelInfo>;
|
|
86
|
-
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
|
87
|
-
/** Non-stream forwarding seam used by tests and future DSH complete calls. */
|
|
88
|
-
generate(options: GenerateOptions): Promise<StreamChunk[]>;
|
|
89
|
-
}
|
|
90
|
-
export {};
|
|
@@ -1,392 +0,0 @@
|
|
|
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
|
-
}
|