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.
Files changed (44) hide show
  1. package/README.md +45 -6
  2. package/README.zh.md +43 -4
  3. package/lib/auth/rpc.d.ts +36 -2
  4. package/lib/auth/rpc.js +47 -5
  5. package/lib/client/ImageGenerateToolview.d.ts +1 -1
  6. package/lib/client/SpeedSelect.d.ts +25 -2
  7. package/lib/client/SpeedSelect.js +10 -6
  8. package/lib/client/SubscriptionsSection.d.ts +74 -0
  9. package/lib/client/SubscriptionsSection.js +325 -4
  10. package/lib/client/VideoGenerateToolview.d.ts +1 -1
  11. package/lib/client/index.d.ts +1 -9
  12. package/lib/client/index.js +7 -4
  13. package/lib/client/locales.d.ts +28 -0
  14. package/lib/client/locales.js +28 -0
  15. package/lib/client.js +458 -10
  16. package/lib/client.js.map +1 -1
  17. package/lib/compat.d.ts +36 -0
  18. package/lib/compat.js +20 -0
  19. package/lib/index.d.ts +5 -1
  20. package/lib/index.js +865 -111
  21. package/lib/model-defaults.d.ts +23 -0
  22. package/lib/model-defaults.js +237 -0
  23. package/lib/providers/claude.d.ts +24 -3
  24. package/lib/providers/claude.js +35 -24
  25. package/lib/providers/codex.d.ts +21 -0
  26. package/lib/providers/codex.js +37 -10
  27. package/lib/providers/common.d.ts +70 -6
  28. package/lib/providers/common.js +118 -19
  29. package/lib/providers/copilot.d.ts +10 -0
  30. package/lib/providers/copilot.js +21 -8
  31. package/lib/providers/grok.d.ts +21 -0
  32. package/lib/providers/grok.js +37 -7
  33. package/lib/providers/pool-usage.d.ts +23 -2
  34. package/lib/providers/pool-usage.js +70 -15
  35. package/lib/providers/rate-limit.d.ts +192 -0
  36. package/lib/providers/rate-limit.js +338 -0
  37. package/lib/translate/anthropic.js +5 -4
  38. package/lib/translate/chat-completions.js +5 -4
  39. package/lib/translate/responses.js +5 -4
  40. package/package.json +21 -21
  41. package/lib/providers/antigravity.d.ts +0 -90
  42. package/lib/providers/antigravity.js +0 -392
  43. package/lib/translate/antigravity.d.ts +0 -110
  44. 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 { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmError, } from '@deepseek-ai/dsh-llm';
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: CallId(block.callId),
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: CallId(opened.callId),
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: CallId(block.callId),
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 { CallId, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
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: CallId(block.callId),
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: CallId(block.callId),
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: CallId(block.callId),
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 { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
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: CallId(block.callId),
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: CallId(callId),
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: CallId(block.callId),
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.5.3",
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-runtime",
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": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/vendor/cordis",
68
- "@deepseek-ai/dsh-api-remotes": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/api/remotes",
69
- "@deepseek-ai/dsh-attachment": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/attachment/attachment",
70
- "@deepseek-ai/dsh-client-connection": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/connection",
71
- "@deepseek-ai/dsh-client-locale": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/locale",
72
- "@deepseek-ai/dsh-client-runtime": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/runtime",
73
- "@deepseek-ai/dsh-client-ui-settings": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/ui-settings",
74
- "@deepseek-ai/dsh-client-ui-conversation": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/ui-conversation",
75
- "@deepseek-ai/dsh-client-ui-commands": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/ui-commands",
76
- "@deepseek-ai/dsh-client-ui-slots": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/client/ui-slots",
77
- "@deepseek-ai/dsh-home-paths": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/util/home-paths",
78
- "@deepseek-ai/dsh-host-apiproxy": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/host/apiproxy",
79
- "@deepseek-ai/dsh-llm": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/llm/llm",
80
- "@deepseek-ai/dsh-tools": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/packages/core/tools",
81
- "@deepseek-ai/schemastery": "link:/Users/v1ki/Documents/projs/source/deepseek-harness/vendor/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
- }