dsh-plugin-subscriptions 0.5.1 → 0.5.2
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 +6 -0
- package/README.zh.md +6 -0
- package/lib/auth/device-flow.d.ts +0 -9
- package/lib/auth/device-flow.js +2 -1
- package/lib/auth/rpc.d.ts +15 -1
- package/lib/auth/rpc.js +98 -3
- package/lib/client/SubscriptionsSection.d.ts +17 -0
- package/lib/client/SubscriptionsSection.js +170 -2
- package/lib/client/index.js +11 -0
- package/lib/client/locales.d.ts +64 -0
- package/lib/client/locales.js +64 -0
- package/lib/client.js +658 -167
- package/lib/client.js.map +1 -1
- package/lib/http.d.ts +114 -0
- package/lib/http.js +402 -0
- package/lib/index.js +464 -48
- package/lib/providers/antigravity.d.ts +90 -0
- package/lib/providers/antigravity.js +392 -0
- package/lib/providers/claude.js +7 -6
- package/lib/providers/codex.js +6 -5
- package/lib/providers/copilot.d.ts +1 -1
- package/lib/providers/copilot.js +9 -8
- package/lib/providers/grok.js +8 -7
- package/lib/tools/image-generate.js +2 -1
- package/lib/tools/video-generate.js +2 -1
- package/lib/tools/x-search.js +2 -1
- package/lib/translate/antigravity.d.ts +110 -0
- package/lib/translate/antigravity.js +303 -0
- package/package.json +14 -9
package/lib/providers/copilot.js
CHANGED
|
@@ -18,6 +18,7 @@ import { resolveImages } from '../translate/resolved.js';
|
|
|
18
18
|
import { streamChatCompletions, toChatMessages, toChatTools, } from '../translate/chat-completions.js';
|
|
19
19
|
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
20
20
|
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverOrRetryAuth, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError, TokenManager, } from './common.js';
|
|
21
|
+
import { proxiedFetch } from '../http.js';
|
|
21
22
|
/**
|
|
22
23
|
* Client id of the VS Code Copilot Chat GitHub App (pi-mono and
|
|
23
24
|
* copilot2api-go use the same value): the app is pre-authorized for the
|
|
@@ -57,7 +58,7 @@ let vscodeVersionInflight;
|
|
|
57
58
|
* @param forceRefresh - bypass the cache (a 401 `IDE token expired` retry).
|
|
58
59
|
* @returns a `major.minor.patch` version string.
|
|
59
60
|
*/
|
|
60
|
-
export async function latestVsCodeVersion(fetchFn =
|
|
61
|
+
export async function latestVsCodeVersion(fetchFn = proxiedFetch, forceRefresh = false) {
|
|
61
62
|
if (!forceRefresh && vscodeVersionCache !== undefined
|
|
62
63
|
&& Date.now() - vscodeVersionCache.at < VSCODE_VERSION_TTL_MS) {
|
|
63
64
|
return vscodeVersionCache.version;
|
|
@@ -122,7 +123,7 @@ export function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCOD
|
|
|
122
123
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
123
124
|
* @returns the Copilot API token and its expiry.
|
|
124
125
|
*/
|
|
125
|
-
export async function exchangeCopilotToken(githubToken, fetchFn =
|
|
126
|
+
export async function exchangeCopilotToken(githubToken, fetchFn = proxiedFetch) {
|
|
126
127
|
const response = await fetchFn(COPILOT_TOKEN_URL, {
|
|
127
128
|
headers: {
|
|
128
129
|
'authorization': `Bearer ${githubToken}`,
|
|
@@ -152,7 +153,7 @@ export async function exchangeCopilotToken(githubToken, fetchFn = fetch) {
|
|
|
152
153
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
153
154
|
* @returns the session to store.
|
|
154
155
|
*/
|
|
155
|
-
export async function completeCopilotLogin(githubToken, fetchFn =
|
|
156
|
+
export async function completeCopilotLogin(githubToken, fetchFn = proxiedFetch) {
|
|
156
157
|
const pair = await exchangeCopilotToken(githubToken, fetchFn);
|
|
157
158
|
let account;
|
|
158
159
|
try {
|
|
@@ -187,7 +188,7 @@ export async function completeCopilotLogin(githubToken, fetchFn = fetch) {
|
|
|
187
188
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
188
189
|
* @returns the fresh session to store.
|
|
189
190
|
*/
|
|
190
|
-
export async function refreshCopilot(session, fetchFn =
|
|
191
|
+
export async function refreshCopilot(session, fetchFn = proxiedFetch) {
|
|
191
192
|
const pair = await exchangeCopilotToken(session.refreshToken, fetchFn);
|
|
192
193
|
return {
|
|
193
194
|
accessToken: pair.accessToken,
|
|
@@ -244,7 +245,7 @@ function copilotReasoning(entry) {
|
|
|
244
245
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
245
246
|
* @returns discovered chat models in endpoint order.
|
|
246
247
|
*/
|
|
247
|
-
export async function fetchCopilotModels(session, fetchFn =
|
|
248
|
+
export async function fetchCopilotModels(session, fetchFn = proxiedFetch) {
|
|
248
249
|
const response = await fetchFn(COPILOT_MODELS_URL, {
|
|
249
250
|
headers: {
|
|
250
251
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -735,7 +736,7 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
735
736
|
// editor version is force-refreshed too: a 401 `IDE token expired`
|
|
736
737
|
// means GitHub raised its minimum VS Code version, and only a fresh
|
|
737
738
|
// Editor-Version header fixes that (a new token does not).
|
|
738
|
-
await latestVsCodeVersion(this.options.fetchFn ??
|
|
739
|
+
await latestVsCodeVersion(this.options.fetchFn ?? proxiedFetch, true);
|
|
739
740
|
session = await this.options.tokens.session(true);
|
|
740
741
|
response = await this.request(options, session, watchdog.signal, wire, scope);
|
|
741
742
|
}
|
|
@@ -771,13 +772,13 @@ export class CopilotAdapter extends LlmAdapter {
|
|
|
771
772
|
// Captured completed reasoning replays ahead of its tool call.
|
|
772
773
|
callId => this.replayFor(replayScopeKey, callId)))
|
|
773
774
|
: copilotChatRequestBody(options, toChatMessages(messages, options.system));
|
|
774
|
-
return
|
|
775
|
+
return proxiedFetch(wire === 'responses' ? COPILOT_RESPONSES_URL : COPILOT_API_URL, {
|
|
775
776
|
method: 'POST',
|
|
776
777
|
headers: {
|
|
777
778
|
'authorization': `Bearer ${session.accessToken}`,
|
|
778
779
|
'accept': 'text/event-stream',
|
|
779
780
|
'content-type': 'application/json',
|
|
780
|
-
...copilotHeaders(hasVision, await latestVsCodeVersion(this.options.fetchFn ??
|
|
781
|
+
...copilotHeaders(hasVision, await latestVsCodeVersion(this.options.fetchFn ?? proxiedFetch)),
|
|
781
782
|
},
|
|
782
783
|
body: JSON.stringify(body),
|
|
783
784
|
signal,
|
package/lib/providers/grok.js
CHANGED
|
@@ -8,6 +8,7 @@ import { decodeJwtPayload } from '../auth/jwt.js';
|
|
|
8
8
|
import { resolveImages } from '../translate/resolved.js';
|
|
9
9
|
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
10
10
|
import { httpLlmError, idleWatchdog, mapFetchFailure, ModelCatalogCache, discoverOrRetryAuth, isMissingOrInvalidCredential, oauthEndpointError, OAuthEndpointError, TokenManager, } from './common.js';
|
|
11
|
+
import { proxiedFetch } from '../http.js';
|
|
11
12
|
export const GROK_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';
|
|
12
13
|
export const GROK_DISCOVERY_URL = 'https://auth.x.ai/.well-known/openid-configuration';
|
|
13
14
|
export const GROK_API_URL = 'https://api.x.ai/v1/responses';
|
|
@@ -40,7 +41,7 @@ let discoveryCache;
|
|
|
40
41
|
export async function grokDiscovery() {
|
|
41
42
|
if (discoveryCache !== undefined)
|
|
42
43
|
return discoveryCache;
|
|
43
|
-
const response = await
|
|
44
|
+
const response = await proxiedFetch(GROK_DISCOVERY_URL);
|
|
44
45
|
if (!response.ok)
|
|
45
46
|
throw await oauthEndpointError(response, 'grok OIDC discovery');
|
|
46
47
|
const document = await response.json();
|
|
@@ -147,7 +148,7 @@ function grokSession(tokens, tokenEndpoint, fallbackRefreshToken) {
|
|
|
147
148
|
*/
|
|
148
149
|
export async function exchangeGrokCode(code, verifier, redirectUri, challenge) {
|
|
149
150
|
const discovery = await grokDiscovery();
|
|
150
|
-
const response = await
|
|
151
|
+
const response = await proxiedFetch(discovery.tokenEndpoint, {
|
|
151
152
|
method: 'POST',
|
|
152
153
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
153
154
|
body: new URLSearchParams({
|
|
@@ -174,7 +175,7 @@ export async function exchangeGrokCode(code, verifier, redirectUri, challenge) {
|
|
|
174
175
|
* @returns the fresh session to store.
|
|
175
176
|
*/
|
|
176
177
|
export async function refreshGrok(session) {
|
|
177
|
-
const response = await
|
|
178
|
+
const response = await proxiedFetch(session.tokenEndpoint, {
|
|
178
179
|
method: 'POST',
|
|
179
180
|
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
180
181
|
body: new URLSearchParams({
|
|
@@ -223,7 +224,7 @@ function grokResetsAt(value) {
|
|
|
223
224
|
* @param signal - caller cancellation from the RPC transport.
|
|
224
225
|
* @returns the mapped usage snapshot.
|
|
225
226
|
*/
|
|
226
|
-
export async function fetchGrokUsage(session, fetchFn =
|
|
227
|
+
export async function fetchGrokUsage(session, fetchFn = proxiedFetch, signal) {
|
|
227
228
|
const response = await fetchFn(GROK_BILLING_URL, {
|
|
228
229
|
headers: {
|
|
229
230
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -313,7 +314,7 @@ function grokCliReasoning(entry) {
|
|
|
313
314
|
* @param fetchFn - fetch implementation (injectable for tests).
|
|
314
315
|
* @returns model id → contributed metadata.
|
|
315
316
|
*/
|
|
316
|
-
export async function fetchGrokCliCatalog(session, fetchFn =
|
|
317
|
+
export async function fetchGrokCliCatalog(session, fetchFn = proxiedFetch) {
|
|
317
318
|
const response = await fetchFn(GROK_CLI_MODELS_URL, {
|
|
318
319
|
headers: {
|
|
319
320
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -385,7 +386,7 @@ function grokPriorMeta(prior) {
|
|
|
385
386
|
* catalog is down or omits a model.
|
|
386
387
|
* @returns discovered chat models in endpoint order.
|
|
387
388
|
*/
|
|
388
|
-
export async function fetchGrokModels(session, fetchFn =
|
|
389
|
+
export async function fetchGrokModels(session, fetchFn = proxiedFetch, onWarn, previous) {
|
|
389
390
|
const previousById = previous === undefined || previous.length === 0
|
|
390
391
|
? undefined
|
|
391
392
|
: new Map(previous.map(model => [model.id, model]));
|
|
@@ -562,7 +563,7 @@ export class GrokAdapter extends LlmAdapter {
|
|
|
562
563
|
store: false,
|
|
563
564
|
stream: true,
|
|
564
565
|
};
|
|
565
|
-
return
|
|
566
|
+
return proxiedFetch(GROK_API_URL, {
|
|
566
567
|
method: 'POST',
|
|
567
568
|
headers: {
|
|
568
569
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -18,6 +18,7 @@ import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
|
18
18
|
import { AttachmentId } from '@deepseek-ai/dsh-attachment';
|
|
19
19
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
20
20
|
import { httpLlmError, TokenManager } from '../providers/common.js';
|
|
21
|
+
import { proxiedFetch } from '../http.js';
|
|
21
22
|
/** Endpoint the codex generation request is posted to. */
|
|
22
23
|
export const IMAGE_GENERATE_URL = 'https://chatgpt.com/backend-api/codex/images/generations';
|
|
23
24
|
/** The image model the codex subscription endpoint serves. */
|
|
@@ -241,7 +242,7 @@ export function createImageGenerateTool(options) {
|
|
|
241
242
|
content: result.content.filter(block => block.type === 'text'),
|
|
242
243
|
}),
|
|
243
244
|
async execute(args, exec) {
|
|
244
|
-
const fetchFn = options.fetchFn ??
|
|
245
|
+
const fetchFn = options.fetchFn ?? proxiedFetch;
|
|
245
246
|
// Provider selection: the preferred provider (default gpt) when logged
|
|
246
247
|
// in, the other one as the fallback. A configured-but-logged-out manager
|
|
247
248
|
// still resolves through `session()` below so the standard log-in hint
|
|
@@ -13,6 +13,7 @@ import { basename, join } from 'node:path';
|
|
|
13
13
|
import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
|
|
14
14
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
15
15
|
import { httpLlmError, TokenManager } from '../providers/common.js';
|
|
16
|
+
import { proxiedFetch } from '../http.js';
|
|
16
17
|
/** Endpoint the generation request is posted to. */
|
|
17
18
|
export const VIDEO_GENERATE_URL = 'https://api.x.ai/v1/videos/generations';
|
|
18
19
|
/** The video model the grok subscription endpoint serves. */
|
|
@@ -197,7 +198,7 @@ export function createVideoGenerateTool(options) {
|
|
|
197
198
|
async execute(args, exec) {
|
|
198
199
|
const body = buildVideoGenerateBody(args);
|
|
199
200
|
const session = await options.tokens.session();
|
|
200
|
-
const fetchFn = options.fetchFn ??
|
|
201
|
+
const fetchFn = options.fetchFn ?? proxiedFetch;
|
|
201
202
|
const headers = {
|
|
202
203
|
'authorization': `Bearer ${session.accessToken}`,
|
|
203
204
|
'accept': 'application/json',
|
package/lib/tools/x-search.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
8
|
import { httpLlmError, TokenManager } from '../providers/common.js';
|
|
9
|
+
import { proxiedFetch } from '../http.js';
|
|
9
10
|
/** Endpoint the search request is posted to. */
|
|
10
11
|
export const X_SEARCH_URL = 'https://api.x.ai/v1/responses';
|
|
11
12
|
/** Grok model the search runs on (a catalog model of the grok provider). */
|
|
@@ -172,7 +173,7 @@ export function createXSearchTool(options) {
|
|
|
172
173
|
async execute(args, exec) {
|
|
173
174
|
const request = buildXSearchRequest(args);
|
|
174
175
|
const session = await options.tokens.session();
|
|
175
|
-
const response = await (options.fetchFn ??
|
|
176
|
+
const response = await (options.fetchFn ?? proxiedFetch)(X_SEARCH_URL, {
|
|
176
177
|
method: 'POST',
|
|
177
178
|
headers: {
|
|
178
179
|
'authorization': `Bearer ${session.accessToken}`,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness message/tool vocabulary to Antigravity's Gemini-shaped
|
|
3
|
+
* v1internal request envelope, plus response/SSE translation back to the
|
|
4
|
+
* harness streaming contract.
|
|
5
|
+
*/
|
|
6
|
+
import type { GenerateOptions, StreamChunk, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm';
|
|
7
|
+
import type { TranslatableMessage } from './resolved.js';
|
|
8
|
+
/** Minimal Gemini part shape used by v1internal. */
|
|
9
|
+
export interface AntigravityPart {
|
|
10
|
+
text?: string;
|
|
11
|
+
thought?: boolean;
|
|
12
|
+
thoughtSignature?: string;
|
|
13
|
+
inlineData?: {
|
|
14
|
+
mimeType: string;
|
|
15
|
+
data: string;
|
|
16
|
+
};
|
|
17
|
+
functionCall?: {
|
|
18
|
+
id?: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
args?: unknown;
|
|
21
|
+
};
|
|
22
|
+
functionResponse?: {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
response: unknown;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Full Antigravity request envelope. */
|
|
29
|
+
export interface AntigravityRequest {
|
|
30
|
+
project: string;
|
|
31
|
+
requestId: string;
|
|
32
|
+
model: string;
|
|
33
|
+
userAgent: 'antigravity';
|
|
34
|
+
requestType: 'agent';
|
|
35
|
+
request: {
|
|
36
|
+
contents: {
|
|
37
|
+
role: 'user' | 'model';
|
|
38
|
+
parts: AntigravityPart[];
|
|
39
|
+
}[];
|
|
40
|
+
sessionId: string;
|
|
41
|
+
systemInstruction?: {
|
|
42
|
+
parts: {
|
|
43
|
+
text: string;
|
|
44
|
+
}[];
|
|
45
|
+
};
|
|
46
|
+
tools?: {
|
|
47
|
+
functionDeclarations: Record<string, unknown>[];
|
|
48
|
+
}[];
|
|
49
|
+
toolConfig?: {
|
|
50
|
+
functionCallingConfig: {
|
|
51
|
+
mode: 'VALIDATED';
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
generationConfig?: Record<string, unknown>;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Map harness tool schemas to Gemini function declarations. */
|
|
58
|
+
export declare function toAntigravityTools(tools: readonly ToolSchema[]): {
|
|
59
|
+
functionDeclarations: Record<string, unknown>[];
|
|
60
|
+
}[];
|
|
61
|
+
/**
|
|
62
|
+
* Convert resolved harness messages into Gemini contents. Function response
|
|
63
|
+
* names are recovered from prior tool calls because DSH correlates results by
|
|
64
|
+
* id while the Gemini wire requires both id and name.
|
|
65
|
+
*/
|
|
66
|
+
export declare function toAntigravityContents(messages: readonly TranslatableMessage[]): {
|
|
67
|
+
role: 'user' | 'model';
|
|
68
|
+
parts: AntigravityPart[];
|
|
69
|
+
}[];
|
|
70
|
+
/** Build one v1internal generateContent/streamGenerateContent request. */
|
|
71
|
+
export declare function toAntigravityRequest(options: GenerateOptions, messages: readonly TranslatableMessage[], projectId: string): AntigravityRequest;
|
|
72
|
+
/** Antigravity SSE/non-stream response subset. */
|
|
73
|
+
export interface AntigravityResponseEvent {
|
|
74
|
+
response?: {
|
|
75
|
+
candidates?: {
|
|
76
|
+
content?: {
|
|
77
|
+
parts?: AntigravityPart[];
|
|
78
|
+
};
|
|
79
|
+
finishReason?: string;
|
|
80
|
+
}[];
|
|
81
|
+
usageMetadata?: {
|
|
82
|
+
promptTokenCount?: number;
|
|
83
|
+
candidatesTokenCount?: number;
|
|
84
|
+
thoughtsTokenCount?: number;
|
|
85
|
+
totalTokenCount?: number;
|
|
86
|
+
cachedContentTokenCount?: number;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Map Gemini usage metadata to the harness's disjoint counters. */
|
|
91
|
+
export declare function mapAntigravityUsage(metadata: NonNullable<NonNullable<AntigravityResponseEvent['response']>['usageMetadata']>): TokenUsage;
|
|
92
|
+
/** Push translator for both parsed SSE events and one non-stream response. */
|
|
93
|
+
export declare class AntigravityStreamTranslator {
|
|
94
|
+
private blocks;
|
|
95
|
+
private closed;
|
|
96
|
+
private nextIndex;
|
|
97
|
+
private sawContent;
|
|
98
|
+
private sawToolCall;
|
|
99
|
+
terminated: boolean;
|
|
100
|
+
private open;
|
|
101
|
+
private close;
|
|
102
|
+
private closeAll;
|
|
103
|
+
private finish;
|
|
104
|
+
/** Process one decoded Antigravity response frame. */
|
|
105
|
+
push(event: AntigravityResponseEvent): StreamChunk[];
|
|
106
|
+
}
|
|
107
|
+
/** Consume Antigravity's SSE response into the DSH streaming contract. */
|
|
108
|
+
export declare function streamAntigravity(stream: ReadableStream<Uint8Array>, onActivity?: () => void): AsyncGenerator<StreamChunk>;
|
|
109
|
+
/** Translate a non-stream generateContent response using the same state machine. */
|
|
110
|
+
export declare function parseAntigravityResponse(event: AntigravityResponseEvent): StreamChunk[];
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness message/tool vocabulary to Antigravity's Gemini-shaped
|
|
3
|
+
* v1internal request envelope, plus response/SSE translation back to the
|
|
4
|
+
* harness streaming contract.
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { CallId, EMPTY_RESPONSE_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
8
|
+
import { parseSse } from './sse.js';
|
|
9
|
+
/** Flatten a harness tool result to the JSON value Antigravity receives. */
|
|
10
|
+
function toolResultValue(block) {
|
|
11
|
+
const text = block.content.map(part => part.type === 'text' ? part.text : '').join('');
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(text);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { output: text, ...block.isError === true ? { isError: true } : {} };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Safely read per-block replay metadata emitted by this adapter. */
|
|
20
|
+
function replayBlocks(message) {
|
|
21
|
+
const source = message.source;
|
|
22
|
+
if (source?.kind !== 'model' || typeof source.replayState !== 'object' || source.replayState === null)
|
|
23
|
+
return [];
|
|
24
|
+
const envelope = source.replayState;
|
|
25
|
+
const response = envelope.response;
|
|
26
|
+
if (response?.kind !== 'antigravity' || response.version !== 1 || !Array.isArray(envelope.blocks))
|
|
27
|
+
return [];
|
|
28
|
+
return envelope.blocks.map((entry) => {
|
|
29
|
+
if (typeof entry !== 'object' || entry === null)
|
|
30
|
+
return {};
|
|
31
|
+
const signature = entry.thoughtSignature;
|
|
32
|
+
return typeof signature === 'string' && signature.length > 0 ? { thoughtSignature: signature } : {};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Map harness tool schemas to Gemini function declarations. */
|
|
36
|
+
export function toAntigravityTools(tools) {
|
|
37
|
+
if (tools.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
return [{
|
|
40
|
+
functionDeclarations: tools.map(tool => ({
|
|
41
|
+
name: tool.name,
|
|
42
|
+
description: tool.description,
|
|
43
|
+
parameters: tool.parameters,
|
|
44
|
+
})),
|
|
45
|
+
}];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Convert resolved harness messages into Gemini contents. Function response
|
|
49
|
+
* names are recovered from prior tool calls because DSH correlates results by
|
|
50
|
+
* id while the Gemini wire requires both id and name.
|
|
51
|
+
*/
|
|
52
|
+
export function toAntigravityContents(messages) {
|
|
53
|
+
const out = [];
|
|
54
|
+
const callNames = new Map();
|
|
55
|
+
for (const message of messages) {
|
|
56
|
+
if (message.role === 'system')
|
|
57
|
+
continue;
|
|
58
|
+
const role = message.role === 'assistant' ? 'model' : 'user';
|
|
59
|
+
const metadata = replayBlocks(message);
|
|
60
|
+
const parts = [];
|
|
61
|
+
for (let index = 0; index < message.content.length; index++) {
|
|
62
|
+
const block = message.content[index];
|
|
63
|
+
switch (block.type) {
|
|
64
|
+
case 'text':
|
|
65
|
+
// Antigravity's Claude-backed models reject empty text parts.
|
|
66
|
+
parts.push({ text: block.text.trim().length > 0 ? block.text : '.' });
|
|
67
|
+
break;
|
|
68
|
+
case 'image':
|
|
69
|
+
if ('dataBase64' in block) {
|
|
70
|
+
parts.push({ inlineData: { mimeType: block.mediaType, data: block.dataBase64 } });
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'tool-call': {
|
|
74
|
+
callNames.set(String(block.id), block.name);
|
|
75
|
+
let args;
|
|
76
|
+
try {
|
|
77
|
+
args = JSON.parse(block.arguments);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
args = {};
|
|
81
|
+
}
|
|
82
|
+
parts.push({
|
|
83
|
+
functionCall: { id: String(block.id), name: block.name, args },
|
|
84
|
+
...metadata[index]?.thoughtSignature === undefined
|
|
85
|
+
? {}
|
|
86
|
+
: { thoughtSignature: metadata[index].thoughtSignature },
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'tool-result': {
|
|
91
|
+
const id = String(block.toolCallId);
|
|
92
|
+
parts.push({
|
|
93
|
+
functionResponse: {
|
|
94
|
+
id,
|
|
95
|
+
name: callNames.get(id) ?? '',
|
|
96
|
+
response: toolResultValue(block),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
// Reasoning is not replayed without its provider signature. The
|
|
103
|
+
// signature-bearing metadata remains attached to tool-call blocks.
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (parts.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
const previous = out.at(-1);
|
|
110
|
+
if (previous?.role === role)
|
|
111
|
+
previous.parts.push(...parts);
|
|
112
|
+
else
|
|
113
|
+
out.push({ role, parts });
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/** Build one v1internal generateContent/streamGenerateContent request. */
|
|
118
|
+
export function toAntigravityRequest(options, messages, projectId) {
|
|
119
|
+
const tools = toAntigravityTools(options.tools ?? []);
|
|
120
|
+
const generationConfig = {
|
|
121
|
+
...options.maxTokens === undefined ? {} : { maxOutputTokens: options.maxTokens },
|
|
122
|
+
...options.temperature === undefined ? {} : { temperature: options.temperature },
|
|
123
|
+
...options.stop === undefined || options.stop.length === 0 ? {} : { stopSequences: options.stop },
|
|
124
|
+
...options.reasoningEffort === undefined ? {} : {
|
|
125
|
+
thinkingConfig: { thinkingLevel: String(options.reasoningEffort), includeThoughts: true },
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
const systemTexts = messages.flatMap(message => message.role === 'system'
|
|
129
|
+
? message.content.filter((block) => block.type === 'text').map(block => block.text)
|
|
130
|
+
: []);
|
|
131
|
+
const system = options.system ?? (systemTexts.length > 0 ? systemTexts.join('\n\n') : undefined);
|
|
132
|
+
const sessionId = options.sessionId === undefined ? randomUUID() : String(options.sessionId);
|
|
133
|
+
return {
|
|
134
|
+
project: projectId,
|
|
135
|
+
requestId: `agent/${String(Date.now())}/${randomUUID()}/4`,
|
|
136
|
+
model: options.model,
|
|
137
|
+
userAgent: 'antigravity',
|
|
138
|
+
requestType: 'agent',
|
|
139
|
+
request: {
|
|
140
|
+
contents: toAntigravityContents(messages),
|
|
141
|
+
sessionId,
|
|
142
|
+
...system === undefined || system.length === 0 ? {} : { systemInstruction: { parts: [{ text: system }] } },
|
|
143
|
+
...tools.length === 0 ? {} : {
|
|
144
|
+
tools,
|
|
145
|
+
toolConfig: { functionCallingConfig: { mode: 'VALIDATED' } },
|
|
146
|
+
},
|
|
147
|
+
...Object.keys(generationConfig).length === 0 ? {} : { generationConfig },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Map Gemini usage metadata to the harness's disjoint counters. */
|
|
152
|
+
export function mapAntigravityUsage(metadata) {
|
|
153
|
+
const cached = metadata.cachedContentTokenCount ?? 0;
|
|
154
|
+
return {
|
|
155
|
+
inputTokens: Math.max(0, (metadata.promptTokenCount ?? 0) - cached),
|
|
156
|
+
outputTokens: metadata.candidatesTokenCount ?? 0,
|
|
157
|
+
...cached > 0 ? { cacheReadTokens: cached } : {},
|
|
158
|
+
...metadata.thoughtsTokenCount === undefined ? {} : { reasoningTokens: metadata.thoughtsTokenCount },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Push translator for both parsed SSE events and one non-stream response. */
|
|
162
|
+
export class AntigravityStreamTranslator {
|
|
163
|
+
blocks = new Map();
|
|
164
|
+
closed = [];
|
|
165
|
+
nextIndex = 0;
|
|
166
|
+
sawContent = false;
|
|
167
|
+
sawToolCall = false;
|
|
168
|
+
terminated = false;
|
|
169
|
+
open(key, kind, chunks, values = {}) {
|
|
170
|
+
const block = { index: this.nextIndex++, kind, text: '', ...values };
|
|
171
|
+
this.blocks.set(key, block);
|
|
172
|
+
chunks.push({ type: 'block-start', index: block.index, blockType: kind });
|
|
173
|
+
return block;
|
|
174
|
+
}
|
|
175
|
+
close(key, chunks) {
|
|
176
|
+
const block = this.blocks.get(key);
|
|
177
|
+
if (block === undefined)
|
|
178
|
+
return;
|
|
179
|
+
this.blocks.delete(key);
|
|
180
|
+
let content;
|
|
181
|
+
if (block.kind === 'text')
|
|
182
|
+
content = { type: 'text', text: block.text };
|
|
183
|
+
else if (block.kind === 'reasoning')
|
|
184
|
+
content = { type: 'reasoning', text: block.text };
|
|
185
|
+
else
|
|
186
|
+
content = {
|
|
187
|
+
type: 'tool-call',
|
|
188
|
+
id: CallId(block.id ?? `call_${randomUUID().replaceAll('-', '')}`),
|
|
189
|
+
name: block.name ?? '',
|
|
190
|
+
arguments: block.text,
|
|
191
|
+
};
|
|
192
|
+
this.closed[block.index] = block.thoughtSignature === undefined ? {} : { thoughtSignature: block.thoughtSignature };
|
|
193
|
+
chunks.push({ type: 'block-end', index: block.index, block: content });
|
|
194
|
+
}
|
|
195
|
+
closeAll(chunks) {
|
|
196
|
+
for (const key of [...this.blocks.keys()])
|
|
197
|
+
this.close(key, chunks);
|
|
198
|
+
}
|
|
199
|
+
finish(reason) {
|
|
200
|
+
const replayState = {
|
|
201
|
+
response: { kind: 'antigravity', version: 1 },
|
|
202
|
+
blocks: this.closed,
|
|
203
|
+
};
|
|
204
|
+
if (!this.sawContent) {
|
|
205
|
+
return {
|
|
206
|
+
type: 'finish',
|
|
207
|
+
reason: { kind: 'error', failure: { message: 'Antigravity returned no content', code: EMPTY_RESPONSE_CODE } },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (reason === 'MAX_TOKENS')
|
|
211
|
+
return { type: 'finish', reason: { kind: 'max-tokens' }, replayState };
|
|
212
|
+
if (reason === 'SAFETY' || reason === 'RECITATION' || reason === 'BLOCKLIST') {
|
|
213
|
+
return {
|
|
214
|
+
type: 'finish',
|
|
215
|
+
reason: { kind: 'error', failure: { message: `Antigravity blocked the response (${reason})`, code: 'CONTENT_FILTER' } },
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { type: 'finish', reason: { kind: this.sawToolCall ? 'tool-calls' : 'stop' }, replayState };
|
|
219
|
+
}
|
|
220
|
+
/** Process one decoded Antigravity response frame. */
|
|
221
|
+
push(event) {
|
|
222
|
+
if (this.terminated)
|
|
223
|
+
return [];
|
|
224
|
+
const chunks = [];
|
|
225
|
+
const candidate = event.response?.candidates?.[0];
|
|
226
|
+
for (const [partIndex, part] of (candidate?.content?.parts ?? []).entries()) {
|
|
227
|
+
if (part.thought === true && typeof part.text === 'string' && part.text.length > 0) {
|
|
228
|
+
const block = this.blocks.get('reasoning') ?? this.open('reasoning', 'reasoning', chunks);
|
|
229
|
+
block.text += part.text;
|
|
230
|
+
if (part.thoughtSignature !== undefined)
|
|
231
|
+
block.thoughtSignature = part.thoughtSignature;
|
|
232
|
+
this.sawContent = true;
|
|
233
|
+
chunks.push({ type: 'reasoning-delta', index: block.index, text: part.text });
|
|
234
|
+
}
|
|
235
|
+
else if (typeof part.text === 'string' && part.text.length > 0) {
|
|
236
|
+
const block = this.blocks.get('text') ?? this.open('text', 'text', chunks);
|
|
237
|
+
block.text += part.text;
|
|
238
|
+
if (part.thoughtSignature !== undefined)
|
|
239
|
+
block.thoughtSignature = part.thoughtSignature;
|
|
240
|
+
this.sawContent = true;
|
|
241
|
+
chunks.push({ type: 'text-delta', index: block.index, text: part.text });
|
|
242
|
+
}
|
|
243
|
+
else if (part.functionCall !== undefined) {
|
|
244
|
+
const call = part.functionCall;
|
|
245
|
+
const id = typeof call.id === 'string' && call.id.length > 0
|
|
246
|
+
? call.id
|
|
247
|
+
: `call_${randomUUID().replaceAll('-', '')}`;
|
|
248
|
+
const key = `call:${id}:${String(partIndex)}`;
|
|
249
|
+
const args = JSON.stringify(call.args ?? {});
|
|
250
|
+
const block = this.open(key, 'tool-call', chunks, {
|
|
251
|
+
id,
|
|
252
|
+
name: call.name ?? '',
|
|
253
|
+
...part.thoughtSignature === undefined ? {} : { thoughtSignature: part.thoughtSignature },
|
|
254
|
+
});
|
|
255
|
+
block.text = args;
|
|
256
|
+
this.sawContent = true;
|
|
257
|
+
this.sawToolCall = true;
|
|
258
|
+
chunks.push({
|
|
259
|
+
type: 'tool-call-delta',
|
|
260
|
+
index: block.index,
|
|
261
|
+
id: CallId(id),
|
|
262
|
+
name: call.name ?? '',
|
|
263
|
+
argumentsDelta: args,
|
|
264
|
+
});
|
|
265
|
+
this.close(key, chunks);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (candidate?.finishReason !== undefined) {
|
|
269
|
+
this.closeAll(chunks);
|
|
270
|
+
const usage = event.response?.usageMetadata;
|
|
271
|
+
if (usage !== undefined)
|
|
272
|
+
chunks.push({ type: 'usage', usage: mapAntigravityUsage(usage) });
|
|
273
|
+
chunks.push(this.finish(candidate.finishReason));
|
|
274
|
+
this.terminated = true;
|
|
275
|
+
}
|
|
276
|
+
return chunks;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** Consume Antigravity's SSE response into the DSH streaming contract. */
|
|
280
|
+
export async function* streamAntigravity(stream, onActivity) {
|
|
281
|
+
const translator = new AntigravityStreamTranslator();
|
|
282
|
+
for await (const event of parseSse(stream, onActivity)) {
|
|
283
|
+
if (event.data === '[DONE]')
|
|
284
|
+
break;
|
|
285
|
+
let parsed;
|
|
286
|
+
try {
|
|
287
|
+
parsed = JSON.parse(event.data);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new LlmError(`malformed Antigravity SSE payload: ${event.data.slice(0, 120)}`, 'MALFORMED_RESPONSE');
|
|
291
|
+
}
|
|
292
|
+
yield* translator.push(parsed);
|
|
293
|
+
if (translator.terminated)
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!translator.terminated) {
|
|
297
|
+
throw new LlmError('Antigravity SSE stream ended before a finish chunk', 'STREAM_CLOSED');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/** Translate a non-stream generateContent response using the same state machine. */
|
|
301
|
+
export function parseAntigravityResponse(event) {
|
|
302
|
+
return new AntigravityStreamTranslator().push(event);
|
|
303
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscriptions",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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": {
|
|
@@ -49,12 +49,18 @@
|
|
|
49
49
|
]
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc && tsdown",
|
|
54
|
+
"test": "tsc -p tsconfig.test.json && node --test lib-test/test/",
|
|
55
|
+
"prepare": "tsdown -c tsdown.prepare.config.ts",
|
|
56
|
+
"prepublishOnly": "pnpm build && pnpm test"
|
|
57
|
+
},
|
|
52
58
|
"peerDependencies": {
|
|
53
59
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
54
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
56
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
57
|
-
"@deepseek-ai/dsh-tools": "^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",
|
|
58
64
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
59
65
|
},
|
|
60
66
|
"devDependencies": {
|
|
@@ -79,8 +85,7 @@
|
|
|
79
85
|
"tsdown": "^0.15.0",
|
|
80
86
|
"typescript": "^5.8.0"
|
|
81
87
|
},
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"test": "tsc -p tsconfig.test.json && node --test lib-test/test/"
|
|
88
|
+
"dependencies": {
|
|
89
|
+
"undici": "^7.0.0"
|
|
85
90
|
}
|
|
86
|
-
}
|
|
91
|
+
}
|