@lay111/dsh-plugin-google 1.0.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 +40 -0
- package/bin/auth-cli.js +35 -0
- package/bin/setup.js +152 -0
- package/install.sh +13 -0
- package/package.json +45 -0
- package/src/adapter.js +48 -0
- package/src/api.js +178 -0
- package/src/auth.js +252 -0
- package/src/index.js +38 -0
- package/src/models.js +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# dsh-plugin-google (Google Antigravity Native Plugin for DSH)
|
|
2
|
+
|
|
3
|
+
原生 DeepSeek Harness / Cordis 插件,为 DSH 提供零代理(Zero-Proxy)直连 Google Antigravity Pro 订阅及全部大模型的能力。
|
|
4
|
+
|
|
5
|
+
## 🌟 支持的模型列表
|
|
6
|
+
|
|
7
|
+
- `gemini-3.7-flash`: **Gemini 3.7 Flash High (10k Thinking Budget)** (默认旗舰)
|
|
8
|
+
- `gemini-3.7-flash-standard`: **Gemini 3.7 Flash (Standard)**
|
|
9
|
+
- `gemini-3.6-flash`: **Gemini 3.6 Flash**
|
|
10
|
+
- `gemini-3.5-flash`: **Gemini 3.5 Flash**
|
|
11
|
+
- `gemini-3.1-pro`: **Gemini 3.1 Pro (Agent)**
|
|
12
|
+
- `claude-sonnet-4.6`: **Claude Sonnet 4.6 (Thinking)**
|
|
13
|
+
- `claude-opus-4.6`: **Claude Opus 4.6 (Thinking)**
|
|
14
|
+
- `gpt-oss-120b`: **GPT-OSS 120B (Medium)**
|
|
15
|
+
|
|
16
|
+
## 🚀 如何在 DSH 中挂载此插件
|
|
17
|
+
|
|
18
|
+
在你的 profile 的 `cordis.patch.yml` 中添加:
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
- id: dsh-antigravity
|
|
22
|
+
name: /home/lay/test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
或者设置默认模型为:
|
|
26
|
+
```yaml
|
|
27
|
+
agent-default-model:
|
|
28
|
+
provider: antigravity
|
|
29
|
+
model: gemini-3.7-flash
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 🔑 认证管理
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# 登录 Google 账号:
|
|
36
|
+
node /home/lay/test/bin/auth-cli.js login
|
|
37
|
+
|
|
38
|
+
# 查看登录状态:
|
|
39
|
+
node /home/lay/test/bin/auth-cli.js status
|
|
40
|
+
```
|
package/bin/auth-cli.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startGoogleOAuthLogin, getActiveAccount, loadTokens, saveTokens } from '../src/auth.js';
|
|
3
|
+
|
|
4
|
+
const cmd = process.argv[2];
|
|
5
|
+
|
|
6
|
+
if (cmd === 'login') {
|
|
7
|
+
startGoogleOAuthLogin()
|
|
8
|
+
.then((acc) => {
|
|
9
|
+
console.log(`\n🎉 Logged in successfully: ${acc.email}`);
|
|
10
|
+
process.exit(0);
|
|
11
|
+
})
|
|
12
|
+
.catch((err) => {
|
|
13
|
+
console.error('Login error:', err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
} else if (cmd === 'status') {
|
|
17
|
+
const active = getActiveAccount();
|
|
18
|
+
const tokens = loadTokens();
|
|
19
|
+
console.log(`Google Antigravity Auth Status:`);
|
|
20
|
+
console.log(` Active Account: ${active ? active.email : 'None'}`);
|
|
21
|
+
console.log(` Project ID: ${active ? active.projectId : 'N/A'}`);
|
|
22
|
+
console.log(` Total Accounts: ${tokens.accounts.length}`);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
} else if (cmd === 'logout') {
|
|
25
|
+
saveTokens({ accounts: [], activeAccount: null });
|
|
26
|
+
console.log('Cleared all saved Google accounts.');
|
|
27
|
+
process.exit(0);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(`Usage:
|
|
30
|
+
node bin/auth-cli.js login # Start Google OAuth login flow
|
|
31
|
+
node bin/auth-cli.js status # Show current login status
|
|
32
|
+
node bin/auth-cli.js logout # Clear saved credentials
|
|
33
|
+
`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { startGoogleOAuthLogin, getActiveAccount } from '../src/auth.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
|
|
12
|
+
const HOME = os.homedir();
|
|
13
|
+
const DSH_HOME = path.join(HOME, '.dsh');
|
|
14
|
+
const DSH_TUI_HOME = path.join(HOME, '.dsh-tui');
|
|
15
|
+
const LOCAL_BIN = path.join(HOME, '.local', 'bin');
|
|
16
|
+
|
|
17
|
+
console.log(`
|
|
18
|
+
╭──────────────────────────────────────────────────────────╮
|
|
19
|
+
│ DeepSeek Harness · Google Antigravity One-Click Setup │
|
|
20
|
+
│ 全自动配置 TUI / Web / 全局 Google Pro 满血模型矩阵 │
|
|
21
|
+
╰──────────────────────────────────────────────────────────╯
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
// 1. Ensure required directories
|
|
25
|
+
fs.mkdirSync(DSH_HOME, { recursive: true });
|
|
26
|
+
fs.mkdirSync(DSH_TUI_HOME, { recursive: true });
|
|
27
|
+
fs.mkdirSync(LOCAL_BIN, { recursive: true });
|
|
28
|
+
fs.mkdirSync(path.join(DSH_HOME, 'profiles', 'dsh-tui'), { recursive: true });
|
|
29
|
+
fs.mkdirSync(path.join(DSH_HOME, 'profiles', 'web'), { recursive: true });
|
|
30
|
+
|
|
31
|
+
console.log('📦 [1/4] 配置全局 DSH 默认设置...');
|
|
32
|
+
// 2. Configure ~/.dsh/settings.yaml
|
|
33
|
+
const settingsPath = path.join(DSH_HOME, 'settings.yaml');
|
|
34
|
+
const settingsContent = `agent-default-model:
|
|
35
|
+
provider: antigravity
|
|
36
|
+
model: gemini-3.7-flash
|
|
37
|
+
`;
|
|
38
|
+
fs.writeFileSync(settingsPath, settingsContent, 'utf-8');
|
|
39
|
+
|
|
40
|
+
console.log('🎨 [2/4] 挂载插件至 TUI & Web 运行时 Profile...');
|
|
41
|
+
// 3. Configure TUI Profile
|
|
42
|
+
const tuiPatchPath = path.join(DSH_HOME, 'profiles', 'dsh-tui', 'cordis.patch.yml');
|
|
43
|
+
const tuiPatchContent = `- id: dsh-antigravity
|
|
44
|
+
name: ${PLUGIN_ROOT}
|
|
45
|
+
|
|
46
|
+
- id: dsh-tui
|
|
47
|
+
name: '@deepseek-harness-tui/dsh-tui'
|
|
48
|
+
config:
|
|
49
|
+
provider: antigravity
|
|
50
|
+
model: gemini-3.7-flash
|
|
51
|
+
fullscreen: true
|
|
52
|
+
`;
|
|
53
|
+
fs.writeFileSync(tuiPatchPath, tuiPatchContent, 'utf-8');
|
|
54
|
+
|
|
55
|
+
const tuiPkgPath = path.join(DSH_HOME, 'profiles', 'dsh-tui', 'package.json');
|
|
56
|
+
if (!fs.existsSync(tuiPkgPath)) {
|
|
57
|
+
fs.writeFileSync(
|
|
58
|
+
tuiPkgPath,
|
|
59
|
+
JSON.stringify(
|
|
60
|
+
{
|
|
61
|
+
name: 'dsh-profile-dsh-tui',
|
|
62
|
+
private: true,
|
|
63
|
+
dependencies: { '@deepseek-harness-tui/dsh-tui': '^0.9.3' },
|
|
64
|
+
dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-harness-tui/dsh-tui'] } },
|
|
65
|
+
},
|
|
66
|
+
null,
|
|
67
|
+
2,
|
|
68
|
+
),
|
|
69
|
+
'utf-8',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 4. Configure Web Profile
|
|
74
|
+
const webPatchPath = path.join(DSH_HOME, 'profiles', 'web', 'cordis.patch.yml');
|
|
75
|
+
const webPatchContent = `- id: dsh-antigravity
|
|
76
|
+
name: ${PLUGIN_ROOT}
|
|
77
|
+
`;
|
|
78
|
+
fs.writeFileSync(webPatchPath, webPatchContent, 'utf-8');
|
|
79
|
+
|
|
80
|
+
const webPkgPath = path.join(DSH_HOME, 'profiles', 'web', 'package.json');
|
|
81
|
+
if (!fs.existsSync(webPkgPath)) {
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
webPkgPath,
|
|
84
|
+
JSON.stringify(
|
|
85
|
+
{
|
|
86
|
+
name: 'dsh-profile-web',
|
|
87
|
+
private: true,
|
|
88
|
+
dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] } },
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2,
|
|
92
|
+
),
|
|
93
|
+
'utf-8',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 5. Configure ~/.dsh-tui preferences
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(DSH_TUI_HOME, 'model.json'),
|
|
100
|
+
JSON.stringify({ provider: 'antigravity', model: 'gemini-3.7-flash' }, null, 2),
|
|
101
|
+
'utf-8',
|
|
102
|
+
);
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
path.join(DSH_TUI_HOME, 'model-recents.json'),
|
|
105
|
+
JSON.stringify({ models: [{ provider: 'antigravity', id: 'gemini-3.7-flash' }] }, null, 2),
|
|
106
|
+
'utf-8',
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
console.log('🔗 [3/4] 注册全局快捷命令 (dst / dsh-google-auth)...');
|
|
110
|
+
const tuiBin = path.join(DSH_HOME, 'profiles', 'dsh-tui', 'node_modules', '@deepseek-harness-tui', 'dsh-tui', 'bin', 'dsh-tui.js');
|
|
111
|
+
if (fs.existsSync(tuiBin)) {
|
|
112
|
+
try {
|
|
113
|
+
fs.unlinkSync(path.join(LOCAL_BIN, 'dst'));
|
|
114
|
+
fs.unlinkSync(path.join(LOCAL_BIN, 'dsh-tui'));
|
|
115
|
+
} catch {}
|
|
116
|
+
try {
|
|
117
|
+
fs.symlinkSync(tuiBin, path.join(LOCAL_BIN, 'dst'));
|
|
118
|
+
fs.symlinkSync(tuiBin, path.join(LOCAL_BIN, 'dsh-tui'));
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
fs.unlinkSync(path.join(LOCAL_BIN, 'dsh-google-auth'));
|
|
124
|
+
} catch {}
|
|
125
|
+
try {
|
|
126
|
+
fs.symlinkSync(path.join(PLUGIN_ROOT, 'bin', 'auth-cli.js'), path.join(LOCAL_BIN, 'dsh-google-auth'));
|
|
127
|
+
} catch {}
|
|
128
|
+
|
|
129
|
+
console.log('🔑 [4/4] 检查 Google 账号登录状态...');
|
|
130
|
+
const active = getActiveAccount();
|
|
131
|
+
|
|
132
|
+
async function runLogin() {
|
|
133
|
+
if (active) {
|
|
134
|
+
console.log(`\n✓ 当前已登录账号: ${active.email} (项目: ${active.projectId})`);
|
|
135
|
+
console.log(`\n🎉 安装完成!你可以直接输入以下命令启动:`);
|
|
136
|
+
console.log(` 👉 终端全屏 TUI 模式: dst`);
|
|
137
|
+
console.log(` 👉 本地 Web 浏览器模式: dsh web\n`);
|
|
138
|
+
} else {
|
|
139
|
+
console.log(`\n未检测到已登录的 Google 账号,正在为你唤起浏览器授权登录...`);
|
|
140
|
+
try {
|
|
141
|
+
const account = await startGoogleOAuthLogin();
|
|
142
|
+
console.log(`\n🎉 登录成功: ${account.email}!`);
|
|
143
|
+
console.log(`\n🎉 所有配置已就绪!你可以直接输入:`);
|
|
144
|
+
console.log(` 👉 终端全屏 TUI 模式: dst`);
|
|
145
|
+
console.log(` 👉 本地 Web 浏览器模式: dsh web\n`);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
console.log(`\n若浏览器未自动完成,可随时在终端运行 dsh-google-auth login 手动完成登录。`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
runLogin();
|
package/install.sh
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "🚀 开始一键安装 DeepSeek Harness + Google Antigravity 原生支持..."
|
|
5
|
+
|
|
6
|
+
# 1. 确保安装了 dsh 官方底座
|
|
7
|
+
if ! command -v dsh &> /dev/null; then
|
|
8
|
+
echo "📦 正在安装 @deepseek-ai/dsh 底层引擎..."
|
|
9
|
+
npm install -g @deepseek-ai/dsh
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# 2. 执行自动化配置脚本
|
|
13
|
+
node "$(dirname "$0")/bin/setup.js"
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lay111/dsh-plugin-google",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Native DeepSeek Harness Plugin for Google Antigravity Pro & Gemini/Claude Models",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"dsh-google-setup": "bin/setup.js",
|
|
13
|
+
"dsh-google-auth": "bin/auth-cli.js"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"bin",
|
|
21
|
+
"install.sh",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
26
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.2",
|
|
27
|
+
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"@deepseek-ai/dsh-commands": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"deepseek-harness",
|
|
36
|
+
"dsh",
|
|
37
|
+
"cordis",
|
|
38
|
+
"antigravity",
|
|
39
|
+
"gemini",
|
|
40
|
+
"claude",
|
|
41
|
+
"plugin"
|
|
42
|
+
],
|
|
43
|
+
"author": "lay111",
|
|
44
|
+
"license": "MIT"
|
|
45
|
+
}
|
package/src/adapter.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ANTIGRAVITY_MODELS, findModel } from './models.js';
|
|
2
|
+
import { getValidAccessToken } from './auth.js';
|
|
3
|
+
import { streamAntigravity } from './api.js';
|
|
4
|
+
|
|
5
|
+
export class AntigravityLlmAdapter {
|
|
6
|
+
providerInfo(provider) {
|
|
7
|
+
return {
|
|
8
|
+
id: 'antigravity',
|
|
9
|
+
name: 'Google Antigravity Pro',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
providerRetryPolicy(provider) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async listModels(provider) {
|
|
18
|
+
return ANTIGRAVITY_MODELS.map((model) => ({
|
|
19
|
+
provider: 'antigravity',
|
|
20
|
+
id: model.id,
|
|
21
|
+
name: model.name,
|
|
22
|
+
description: model.description,
|
|
23
|
+
inputModalities: ['text'],
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async resolveModelInfo(provider, modelId, signal) {
|
|
28
|
+
const model = findModel(modelId);
|
|
29
|
+
return {
|
|
30
|
+
provider: 'antigravity',
|
|
31
|
+
id: model.id,
|
|
32
|
+
name: model.name,
|
|
33
|
+
contextWindow: model.contextWindow,
|
|
34
|
+
maxTokens: model.maxTokens,
|
|
35
|
+
reasoningEfforts: model.supportsThinking
|
|
36
|
+
? [
|
|
37
|
+
{ id: 'high', name: 'High Thinking' },
|
|
38
|
+
{ id: 'low', name: 'Low Thinking' },
|
|
39
|
+
]
|
|
40
|
+
: undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async *generate(options, runtime) {
|
|
45
|
+
const { accessToken, projectId } = await getValidAccessToken();
|
|
46
|
+
yield* streamAntigravity(options, accessToken, projectId);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { USER_AGENT } from './auth.js';
|
|
2
|
+
import { findModel } from './models.js';
|
|
3
|
+
|
|
4
|
+
const ANTIGRAVITY_ENDPOINT = 'https://cloudaicompanion.googleapis.com/v1:streamGenerateChat';
|
|
5
|
+
|
|
6
|
+
export function formatMessagesToAntigravity(messages, modelSpec, projectId) {
|
|
7
|
+
const contents = [];
|
|
8
|
+
let systemInstruction = null;
|
|
9
|
+
|
|
10
|
+
for (const message of messages) {
|
|
11
|
+
if (message.role === 'system') {
|
|
12
|
+
systemInstruction = {
|
|
13
|
+
parts: [{ text: typeof message.content === 'string' ? message.content : '' }],
|
|
14
|
+
};
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (typeof message.content === 'string') {
|
|
20
|
+
if (message.content.length > 0) {
|
|
21
|
+
parts.push({ text: message.content });
|
|
22
|
+
}
|
|
23
|
+
} else if (Array.isArray(message.content)) {
|
|
24
|
+
for (const block of message.content) {
|
|
25
|
+
if (block.type === 'text') {
|
|
26
|
+
parts.push({ text: block.text });
|
|
27
|
+
} else if (block.type === 'thought') {
|
|
28
|
+
parts.push({ thought: true, text: block.text });
|
|
29
|
+
} else if (block.type === 'tool-call') {
|
|
30
|
+
parts.push({
|
|
31
|
+
functionCall: {
|
|
32
|
+
name: block.name,
|
|
33
|
+
args: typeof block.arguments === 'string' ? JSON.parse(block.arguments || '{}') : block.arguments || {},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
} else if (block.type === 'tool-result') {
|
|
37
|
+
contents.push({
|
|
38
|
+
role: 'user',
|
|
39
|
+
parts: [
|
|
40
|
+
{
|
|
41
|
+
functionResponse: {
|
|
42
|
+
name: block.name || 'tool',
|
|
43
|
+
response: { result: block.content },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (parts.length > 0) {
|
|
53
|
+
contents.push({
|
|
54
|
+
role: message.role === 'assistant' ? 'model' : 'user',
|
|
55
|
+
parts,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const payload = {
|
|
61
|
+
project: projectId || 'aicode-consumers',
|
|
62
|
+
model: modelSpec.wireId,
|
|
63
|
+
request: {
|
|
64
|
+
contents,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (modelSpec.supportsThinking) {
|
|
69
|
+
payload.request.generationConfig = {
|
|
70
|
+
thinkingConfig: {
|
|
71
|
+
includeThoughts: true,
|
|
72
|
+
thinkingBudget: modelSpec.thinkingBudget || 10000,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (systemInstruction) {
|
|
78
|
+
payload.request.systemInstruction = systemInstruction;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return payload;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function* streamAntigravity(options, accessToken, projectId) {
|
|
85
|
+
const modelSpec = findModel(options.model);
|
|
86
|
+
const payload = formatMessagesToAntigravity(options.messages || [], modelSpec, projectId);
|
|
87
|
+
|
|
88
|
+
if (options.tools && options.tools.length > 0) {
|
|
89
|
+
payload.request.tools = [
|
|
90
|
+
{
|
|
91
|
+
functionDeclarations: options.tools.map((tool) => ({
|
|
92
|
+
name: tool.name,
|
|
93
|
+
description: tool.description || '',
|
|
94
|
+
parameters: tool.parameters || {},
|
|
95
|
+
})),
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const response = await fetch(ANTIGRAVITY_ENDPOINT, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
Authorization: `Bearer ${accessToken}`,
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'User-Agent': USER_AGENT,
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify(payload),
|
|
108
|
+
signal: options.signal,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const errorText = await response.text();
|
|
113
|
+
throw new Error(`Google API Error (${response.status}): ${errorText}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const reader = response.body.getReader();
|
|
117
|
+
const decoder = new TextDecoder();
|
|
118
|
+
let buffer = '';
|
|
119
|
+
let blockIndex = 0;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
while (true) {
|
|
123
|
+
const { done, value } = await reader.read();
|
|
124
|
+
if (done) break;
|
|
125
|
+
|
|
126
|
+
buffer += decoder.decode(value, { stream: true });
|
|
127
|
+
const lines = buffer.split('\n');
|
|
128
|
+
buffer = lines.pop() || '';
|
|
129
|
+
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
if (!trimmed) continue;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const item = JSON.parse(trimmed);
|
|
136
|
+
const candidate = item.response?.candidates?.[0];
|
|
137
|
+
if (!candidate) continue;
|
|
138
|
+
|
|
139
|
+
for (const part of candidate.content?.parts || []) {
|
|
140
|
+
if (part.thought) {
|
|
141
|
+
yield {
|
|
142
|
+
type: 'reasoning-delta',
|
|
143
|
+
index: blockIndex,
|
|
144
|
+
text: part.text || '',
|
|
145
|
+
};
|
|
146
|
+
} else if (part.text) {
|
|
147
|
+
yield {
|
|
148
|
+
type: 'text-delta',
|
|
149
|
+
index: blockIndex,
|
|
150
|
+
text: part.text || '',
|
|
151
|
+
};
|
|
152
|
+
} else if (part.functionCall) {
|
|
153
|
+
const callId = `call_${Math.random().toString(36).slice(2, 10)}`;
|
|
154
|
+
yield {
|
|
155
|
+
type: 'tool-call-delta',
|
|
156
|
+
index: blockIndex++,
|
|
157
|
+
id: callId,
|
|
158
|
+
name: part.functionCall.name,
|
|
159
|
+
argumentsDelta: JSON.stringify(part.functionCall.args || {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (candidate.finishReason) {
|
|
165
|
+
yield {
|
|
166
|
+
type: 'finish',
|
|
167
|
+
reason: candidate.finishReason.toLowerCase() === 'stop' ? 'stop' : 'stop',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// ignore partial json
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} finally {
|
|
176
|
+
reader.releaseLock();
|
|
177
|
+
}
|
|
178
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), '.antigravity-proxy');
|
|
8
|
+
const TOKENS_FILE = path.join(CONFIG_DIR, 'tokens.json');
|
|
9
|
+
|
|
10
|
+
export const OAUTH_CLIENT_ID =
|
|
11
|
+
'1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
|
|
12
|
+
export const OAUTH_CLIENT_SECRET = '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';
|
|
13
|
+
export const OAUTH_REDIRECT_PORT = 51121;
|
|
14
|
+
export const OAUTH_REDIRECT_URI = `http://127.0.0.1:${OAUTH_REDIRECT_PORT}/oauth-callback`;
|
|
15
|
+
|
|
16
|
+
export const SCOPES = [
|
|
17
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
18
|
+
'https://www.googleapis.com/auth/userinfo.email',
|
|
19
|
+
'https://www.googleapis.com/auth/userinfo.profile',
|
|
20
|
+
'https://www.googleapis.com/auth/cclog',
|
|
21
|
+
'https://www.googleapis.com/auth/experimentsandconfigs',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const USER_AGENT = `antigravity/hub/2.8.0 (aidev_client; os_type=${process.platform}; arch=${process.arch}; cl=963137146)`;
|
|
25
|
+
|
|
26
|
+
export function ensureConfigDir() {
|
|
27
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadTokens() {
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(TOKENS_FILE)) {
|
|
35
|
+
return JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf-8'));
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// ignore
|
|
39
|
+
}
|
|
40
|
+
return { accounts: [], activeAccount: null };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function saveTokens(data) {
|
|
44
|
+
ensureConfigDir();
|
|
45
|
+
fs.writeFileSync(TOKENS_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getActiveAccount() {
|
|
49
|
+
const data = loadTokens();
|
|
50
|
+
if (!data.activeAccount || !data.accounts || data.accounts.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return data.accounts.find((acc) => acc.email === data.activeAccount) || null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function updateAccount(account) {
|
|
57
|
+
const data = loadTokens();
|
|
58
|
+
const index = data.accounts.findIndex((acc) => acc.email === account.email);
|
|
59
|
+
if (index >= 0) {
|
|
60
|
+
data.accounts[index] = account;
|
|
61
|
+
} else {
|
|
62
|
+
data.accounts.push(account);
|
|
63
|
+
}
|
|
64
|
+
data.activeAccount = account.email;
|
|
65
|
+
saveTokens(data);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getAuthorizationUrl() {
|
|
69
|
+
const params = new URLSearchParams({
|
|
70
|
+
client_id: OAUTH_CLIENT_ID,
|
|
71
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
72
|
+
response_type: 'code',
|
|
73
|
+
scope: SCOPES.join(' '),
|
|
74
|
+
access_type: 'offline',
|
|
75
|
+
prompt: 'select_account consent',
|
|
76
|
+
});
|
|
77
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function exchangeCodeForTokens(code) {
|
|
81
|
+
const response = await fetch('https://oauth2.googleapis.com/token', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
84
|
+
body: new URLSearchParams({
|
|
85
|
+
code,
|
|
86
|
+
client_id: OAUTH_CLIENT_ID,
|
|
87
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
88
|
+
redirect_uri: OAUTH_REDIRECT_URI,
|
|
89
|
+
grant_type: 'authorization_code',
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const errorText = await response.text();
|
|
95
|
+
throw new Error(`Exchange code failed: ${errorText}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return response.json();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function refreshAccessToken(refreshToken) {
|
|
102
|
+
const response = await fetch('https://oauth2.googleapis.com/token', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
105
|
+
body: new URLSearchParams({
|
|
106
|
+
refresh_token: refreshToken,
|
|
107
|
+
client_id: OAUTH_CLIENT_ID,
|
|
108
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
109
|
+
grant_type: 'refresh_token',
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
const errorText = await response.text();
|
|
115
|
+
throw new Error(`Token refresh failed: ${errorText}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return response.json();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function getUserInfo(accessToken) {
|
|
122
|
+
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
123
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw new Error(`Get userinfo failed: ${response.statusText}`);
|
|
127
|
+
}
|
|
128
|
+
return response.json();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function discoverProjectId(accessToken) {
|
|
132
|
+
const endpoints = [
|
|
133
|
+
'https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist',
|
|
134
|
+
'https://cloudaicompanion.googleapis.com/v1:loadContext',
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
for (const endpoint of endpoints) {
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetch(endpoint, {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: {
|
|
142
|
+
Authorization: `Bearer ${accessToken}`,
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
'User-Agent': USER_AGENT,
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify({}),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (response.ok) {
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
if (data.cloudaicompanionProjectNumber) {
|
|
152
|
+
return String(data.cloudaicompanionProjectNumber);
|
|
153
|
+
}
|
|
154
|
+
if (data.projectNumber) {
|
|
155
|
+
return String(data.projectNumber);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
// Continue
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return 'aicode-consumers';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function getValidAccessToken() {
|
|
166
|
+
const account = getActiveAccount();
|
|
167
|
+
if (!account) {
|
|
168
|
+
throw new Error('No Google account logged in. Please run `dsh-google-auth login` or `/auth/google` in DSH.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (Date.now() > account.expiresAt - 120000) {
|
|
172
|
+
const refreshed = await refreshAccessToken(account.refreshToken);
|
|
173
|
+
account.accessToken = refreshed.access_token;
|
|
174
|
+
account.expiresAt = Date.now() + refreshed.expires_in * 1000;
|
|
175
|
+
updateAccount(account);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
accessToken: account.accessToken,
|
|
180
|
+
projectId: account.projectId || 'aicode-consumers',
|
|
181
|
+
email: account.email,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function startGoogleOAuthLogin() {
|
|
186
|
+
const authUrl = getAuthorizationUrl();
|
|
187
|
+
console.log(`\n======================================================`);
|
|
188
|
+
console.log(`🔑 Google Antigravity Pro OAuth Login`);
|
|
189
|
+
console.log(`Opening browser: ${authUrl}\n======================================================\n`);
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
spawn('xdg-open', [authUrl], { detached: true, stdio: 'ignore' }).unref();
|
|
193
|
+
} catch {}
|
|
194
|
+
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
let handled = false;
|
|
197
|
+
const server = http.createServer(async (req, res) => {
|
|
198
|
+
const url = new URL(req.url, `http://127.0.0.1:${OAUTH_REDIRECT_PORT}`);
|
|
199
|
+
if (url.pathname === '/oauth-callback' || url.pathname === '/oauth/callback') {
|
|
200
|
+
const code = url.searchParams.get('code');
|
|
201
|
+
const error = url.searchParams.get('error');
|
|
202
|
+
|
|
203
|
+
if (error) {
|
|
204
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
205
|
+
res.end('<h1>Authorization Failed</h1>');
|
|
206
|
+
if (!handled) {
|
|
207
|
+
handled = true;
|
|
208
|
+
server.close();
|
|
209
|
+
reject(new Error(error));
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (code) {
|
|
215
|
+
try {
|
|
216
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
217
|
+
res.end('<h1>Authorization Successful! You can close this window now.</h1>');
|
|
218
|
+
|
|
219
|
+
const tokens = await exchangeCodeForTokens(code);
|
|
220
|
+
const userInfo = await getUserInfo(tokens.access_token);
|
|
221
|
+
const projectId = await discoverProjectId(tokens.access_token);
|
|
222
|
+
|
|
223
|
+
const account = {
|
|
224
|
+
email: userInfo.email,
|
|
225
|
+
name: userInfo.name || userInfo.email,
|
|
226
|
+
accessToken: tokens.access_token,
|
|
227
|
+
refreshToken: tokens.refresh_token,
|
|
228
|
+
expiresAt: Date.now() + tokens.expires_in * 1000,
|
|
229
|
+
projectId,
|
|
230
|
+
isActive: true,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
updateAccount(account);
|
|
234
|
+
if (!handled) {
|
|
235
|
+
handled = true;
|
|
236
|
+
server.close();
|
|
237
|
+
resolve(account);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
if (!handled) {
|
|
241
|
+
handled = true;
|
|
242
|
+
server.close();
|
|
243
|
+
reject(err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
server.listen(OAUTH_REDIRECT_PORT, '127.0.0.1');
|
|
251
|
+
});
|
|
252
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AntigravityLlmAdapter } from './adapter.js';
|
|
2
|
+
import { startGoogleOAuthLogin, getActiveAccount } from './auth.js';
|
|
3
|
+
|
|
4
|
+
export const name = 'dsh-antigravity';
|
|
5
|
+
export const inject = ['llm'];
|
|
6
|
+
|
|
7
|
+
export function apply(ctx) {
|
|
8
|
+
const adapter = new AntigravityLlmAdapter();
|
|
9
|
+
|
|
10
|
+
// 1. Register the native LLM adapter with DeepSeek Harness
|
|
11
|
+
ctx.llm.registerAdapter(['antigravity'], adapter);
|
|
12
|
+
|
|
13
|
+
// 2. Register slash command if commands service is present
|
|
14
|
+
if (ctx.commands) {
|
|
15
|
+
ctx.commands.command('auth/google', 'Log in to Google Antigravity Pro').action(async () => {
|
|
16
|
+
console.log('Initiating Google OAuth login flow...');
|
|
17
|
+
const account = await startGoogleOAuthLogin();
|
|
18
|
+
console.log(`Successfully logged in as: ${account.email}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
ctx.commands.command('auth/status', 'Check Google Antigravity account status').action(() => {
|
|
22
|
+
const active = getActiveAccount();
|
|
23
|
+
if (active) {
|
|
24
|
+
console.log(`Active Account: ${active.email} (Project: ${active.projectId})`);
|
|
25
|
+
} else {
|
|
26
|
+
console.log('No Google Antigravity account currently logged in.');
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ctx.logger?.info('Google Antigravity plugin mounted successfully.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
name,
|
|
36
|
+
inject,
|
|
37
|
+
apply,
|
|
38
|
+
};
|
package/src/models.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const ANTIGRAVITY_MODELS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'gemini-3.7-flash',
|
|
4
|
+
name: 'Gemini 3.7 Flash High (Thinking)',
|
|
5
|
+
wireId: 'gemini-3.7-flash-high',
|
|
6
|
+
contextWindow: 1048576,
|
|
7
|
+
maxTokens: 65536,
|
|
8
|
+
supportsThinking: true,
|
|
9
|
+
thinkingBudget: 10000,
|
|
10
|
+
description: 'Google flagship coding model with 10k thinking budget (Fast & Deep)',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'gemini-3.7-flash-standard',
|
|
14
|
+
name: 'Gemini 3.7 Flash (Standard)',
|
|
15
|
+
wireId: 'gemini-3.7-flash',
|
|
16
|
+
contextWindow: 1048576,
|
|
17
|
+
maxTokens: 65536,
|
|
18
|
+
supportsThinking: false,
|
|
19
|
+
description: 'Standard low-latency mode without extended thinking',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'gemini-3.6-flash',
|
|
23
|
+
name: 'Gemini 3.6 Flash',
|
|
24
|
+
wireId: 'gemini-3.6-flash',
|
|
25
|
+
contextWindow: 1048576,
|
|
26
|
+
maxTokens: 65536,
|
|
27
|
+
supportsThinking: true,
|
|
28
|
+
thinkingBudget: 4000,
|
|
29
|
+
description: 'Fast lightweight Flash model',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'gemini-3.5-flash',
|
|
33
|
+
name: 'Gemini 3.5 Flash',
|
|
34
|
+
wireId: 'gemini-3.5-flash',
|
|
35
|
+
contextWindow: 1048576,
|
|
36
|
+
maxTokens: 65536,
|
|
37
|
+
supportsThinking: true,
|
|
38
|
+
thinkingBudget: 4000,
|
|
39
|
+
description: 'Classic Flash model',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'gemini-3.1-pro',
|
|
43
|
+
name: 'Gemini 3.1 Pro (Agent)',
|
|
44
|
+
wireId: 'gemini-3.1-pro-low',
|
|
45
|
+
contextWindow: 1048576,
|
|
46
|
+
maxTokens: 65536,
|
|
47
|
+
supportsThinking: true,
|
|
48
|
+
thinkingBudget: 8000,
|
|
49
|
+
description: 'Pro-tier deep reasoning agent model',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'claude-sonnet-4.6',
|
|
53
|
+
name: 'Claude Sonnet 4.6 (Thinking)',
|
|
54
|
+
wireId: 'claude-sonnet-4-6-thinking',
|
|
55
|
+
contextWindow: 200000,
|
|
56
|
+
maxTokens: 64000,
|
|
57
|
+
supportsThinking: true,
|
|
58
|
+
description: 'Anthropic Claude Sonnet with reasoning chain via Antigravity',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'claude-opus-4.6',
|
|
62
|
+
name: 'Claude Opus 4.6 (Thinking)',
|
|
63
|
+
wireId: 'claude-opus-4-6-thinking',
|
|
64
|
+
contextWindow: 200000,
|
|
65
|
+
maxTokens: 64000,
|
|
66
|
+
supportsThinking: true,
|
|
67
|
+
description: 'Anthropic Claude Opus for highest-tier architecture and logic modeling',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'gpt-oss-120b',
|
|
71
|
+
name: 'GPT-OSS 120B (Medium)',
|
|
72
|
+
wireId: 'gpt-oss-120b-medium',
|
|
73
|
+
contextWindow: 128000,
|
|
74
|
+
maxTokens: 32768,
|
|
75
|
+
supportsThinking: true,
|
|
76
|
+
description: 'Open source 120B parameter reasoning model',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export function findModel(id) {
|
|
81
|
+
return (
|
|
82
|
+
ANTIGRAVITY_MODELS.find((m) => m.id === id) ||
|
|
83
|
+
ANTIGRAVITY_MODELS.find((m) => m.wireId === id) ||
|
|
84
|
+
ANTIGRAVITY_MODELS[0]
|
|
85
|
+
);
|
|
86
|
+
}
|