w-dispatch-ai 1.0.0 → 1.0.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 +108 -6
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +8 -4
- package/docs/adapters.mjs.html +5 -3
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +406 -0
- package/docs/dispatchAntigravity.mjs.html +264 -0
- package/docs/dispatchClaude.mjs.html +2 -2
- package/docs/dispatchCodex.mjs.html +2 -2
- package/docs/dispatchOpencode.mjs.html +2 -2
- package/docs/getCliArgs.mjs.html +2 -2
- package/docs/getErrorResult.mjs.html +2 -2
- package/docs/global.html +1600 -6
- package/docs/index.html +2 -2
- package/g.mjs +46 -2
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +6 -2
- package/src/adapters.mjs +3 -1
- package/src/dispatchAiFallback.mjs +334 -0
- package/src/dispatchAntigravity.mjs +192 -0
- package/test/tools/fakeCliForTest.mjs +8 -0
- package/test/unit-WDispatchAi.test.mjs +13 -6
- package/test/unit-adapters.test.mjs +5 -3
- package/test/unit-dispatchAi.test.mjs +7 -3
- package/test/unit-dispatchAiFallback.test.mjs +422 -0
- package/test/unit-dispatchAntigravity.test.mjs +173 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import omit from 'lodash-es/omit.js'
|
|
3
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
4
|
+
import isbol from 'wsemi/src/isbol.mjs'
|
|
5
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
6
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
7
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
8
|
+
import execCli from 'wsemi/src/execCli.mjs'
|
|
9
|
+
import getCliArgs from './getCliArgs.mjs'
|
|
10
|
+
import getErrorResult from './getErrorResult.mjs'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// dispatchAntigravity.mjs — 以Google Antigravity CLI(agy)呼叫AI模型
|
|
14
|
+
//
|
|
15
|
+
// agy為Google官方之Gemini CLI後繼者(Gemini CLI已於2026-06-18停服), 命令名為agy非antigravity。
|
|
16
|
+
// 實測其models清單除Gemini系外亦含claude與gpt-oss等模型, 屬多模型閘道。
|
|
17
|
+
//
|
|
18
|
+
// 【prompt走--print旗標, 不走stdin(2026-08-10於本機agy 1.1.11實測確認)】
|
|
19
|
+
// --print為帶值旗標(無值即回`flag needs an argument: -print`), 不可能由stdin取prompt;
|
|
20
|
+
// 把prompt塞stdin而args無--print會進互動模式卡住。此為與另三家轉接器最大差異。
|
|
21
|
+
//
|
|
22
|
+
// 【命令列長度上限(實測)】prompt作為命令列參數受Windows CreateProcess之32767字元上限:
|
|
23
|
+
// 純文字32600可spawn、32700即spawn ENAMETOOLONG(同步拋出, 會穿透execCli成rejection)。
|
|
24
|
+
// 且含引號之prompt經spawn轉義後長度會膨脹(實測2萬個引號轉義後仍炸), 故防護須兩層:
|
|
25
|
+
// 前置長度檢查(常見情境給清楚錯誤訊息) + try/catch兜底(轉義膨脹等漏網情境),
|
|
26
|
+
// 兩層皆回錯誤結果物件, 維持本套件「不reject」之契約。
|
|
27
|
+
//
|
|
28
|
+
// 【model與effort之衝突規則(實測)】帶檔位之slug(如gemini-3.1-pro-high)與--effort同時給定時,
|
|
29
|
+
// 檔位「不一致」才拒絕(`--model gemini-3.1-pro-high conflicts with --effort=low`, exit 1),
|
|
30
|
+
// 一致(gemini-3.1-pro-high + --effort high)則放行; 基礎slug(如gemini-3.1-pro)搭配effort亦放行。
|
|
31
|
+
// 本轉接器不預判slug格式(模型清單會演進), 衝突時由agy回報conflicts錯誤。
|
|
32
|
+
//
|
|
33
|
+
// 【雙逾時對齊】agy自身有--print-timeout(預設5m0s), 與execCli之timeoutMs為兩層獨立逾時。
|
|
34
|
+
// printTimeout未給時由timeoutMs推導並預留30秒緩衝, 令CLI先於外層逾時,
|
|
35
|
+
// 使錯誤訊息來自agy自身而非被外層強殺。
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
//預設值
|
|
39
|
+
let DEFAULT_EXE = 'agy'
|
|
40
|
+
let DEFAULT_TIMEOUT_MS = 300000 //agent型CLI, 對齊agy自身print-timeout預設5m0s, 不沿用套件通用120000
|
|
41
|
+
let MAX_PROMPT_LENGTH = 30000 //命令列上限32767扣除exe路徑與旗標後之保守值
|
|
42
|
+
let PRINT_TIMEOUT_BUFFER_S = 30
|
|
43
|
+
let MIN_PRINT_TIMEOUT_S = 30
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
//本轉接器自用之設定鍵, 其餘鍵一律原樣轉傳execCli
|
|
47
|
+
let OWN_KEYS = ['exe', 'model', 'effort', 'skipPermissions', 'printTimeout', 'addDirs', 'extraArgs', 'input']
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 以Google Antigravity CLI(agy)呼叫AI模型
|
|
52
|
+
*
|
|
53
|
+
* 特點:
|
|
54
|
+
* prompt作為`--print`旗標之值傳遞而非stdin(agy介面如此,塞stdin會進互動模式卡住),
|
|
55
|
+
* 故prompt受命令列長度上限約束,超過30000字元回傳錯誤結果物件;
|
|
56
|
+
* model須為`agy models`第一欄之slug(如gemini-3.6-flash-low),注意agy錯誤訊息列出的是顯示名稱而非slug;
|
|
57
|
+
* 帶檔位之slug(-high/-medium/-low結尾)與effort同時給定且檔位不一致時agy回conflicts錯誤(一致則放行),
|
|
58
|
+
* effort需agy>=1.1.11,建議搭配不帶檔位之基礎slug(如gemini-3.1-pro)使用;
|
|
59
|
+
* 預設帶`--dangerously-skip-permissions`令非互動print模式不卡權限確認,可給予skipPermissions為false保留權限閘門;
|
|
60
|
+
* agy自身之--print-timeout未給時由timeoutMs推導並預留30秒緩衝,令CLI先於外層逾時;
|
|
61
|
+
* 沿用agy既有OAuth登入狀態(首次須於桌面互動模式完成登入);
|
|
62
|
+
* 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
|
|
63
|
+
*
|
|
64
|
+
* @param {String} prompt 輸入提示詞字串,作為`--print`旗標之值傳遞,長度上限30000字元
|
|
65
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
66
|
+
* @param {String} [opt.exe='agy'] 輸入agy執行檔名稱或絕對路徑字串,命令名為agy非antigravity,給予名稱時由execCli自系統PATH解析,預設'agy'
|
|
67
|
+
* @param {String} [opt.model=''] 輸入模型slug字串,須為`agy models`第一欄之slug,例如'gemini-3.6-flash-low'、'gemini-3.1-pro-high',預設''代表不帶`--model`旗標由agy自行決定
|
|
68
|
+
* @param {String} [opt.effort=''] 輸入推理深度字串,可選'low'、'medium'、'high',需agy>=1.1.11,建議搭配不帶檔位之基礎slug;與帶檔位slug併用且檔位不一致時agy回conflicts錯誤,預設''代表不帶
|
|
69
|
+
* @param {Boolean} [opt.skipPermissions=true] 輸入是否帶`--dangerously-skip-permissions`旗標布林值,false代表保留CLI權限閘門,預設true
|
|
70
|
+
* @param {String} [opt.printTimeout=''] 輸入agy自身等待上限字串(如'10m'、'570s'),預設''代表由timeoutMs推導(扣30秒緩衝,下限30秒)
|
|
71
|
+
* @param {Array} [opt.addDirs=[]] 輸入加入workspace之目錄字串陣列,逐項展開為`--add-dir`,預設[]
|
|
72
|
+
* @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列(如--output-format、--json-schema、--mode),將接於固定旗標之後、`--print`之前,預設[]
|
|
73
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,agy為agent型CLI故預設較長之300000,預設300000
|
|
74
|
+
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
75
|
+
* @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
76
|
+
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
|
|
77
|
+
* @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
|
|
78
|
+
* @example
|
|
79
|
+
* //need agy cli in system PATH, and OAuth login completed
|
|
80
|
+
*
|
|
81
|
+
* import dispatchAntigravity from './src/dispatchAntigravity.mjs'
|
|
82
|
+
*
|
|
83
|
+
* let test = async () => {
|
|
84
|
+
*
|
|
85
|
+
* let r = await dispatchAntigravity('請只回覆兩個字:完成', { model: 'gemini-3.6-flash-low' })
|
|
86
|
+
* console.log(r.ok, r.stdout.trim())
|
|
87
|
+
* // => true 完成
|
|
88
|
+
*
|
|
89
|
+
* //基礎slug搭配effort(不可用帶檔位之slug併用effort)
|
|
90
|
+
* let r2 = await dispatchAntigravity('請只回覆兩個字:完成', { model: 'gemini-3.1-pro', effort: 'low' })
|
|
91
|
+
* console.log(r2.ok)
|
|
92
|
+
* // => true
|
|
93
|
+
*
|
|
94
|
+
* let re = await dispatchAntigravity('')
|
|
95
|
+
* console.log(re.ok, re.error)
|
|
96
|
+
* // => false 'prompt must be a non-empty string'
|
|
97
|
+
*
|
|
98
|
+
* }
|
|
99
|
+
* await test()
|
|
100
|
+
* .catch((err) => {
|
|
101
|
+
* console.log(err)
|
|
102
|
+
* })
|
|
103
|
+
*
|
|
104
|
+
*/
|
|
105
|
+
async function dispatchAntigravity(prompt, opt = {}) {
|
|
106
|
+
|
|
107
|
+
//check prompt, 不reject故以錯誤結果物件回報
|
|
108
|
+
if (!isestr(prompt)) {
|
|
109
|
+
return getErrorResult('prompt must be a non-empty string')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//check prompt length, agy之prompt走命令列旗標而非stdin, 受CreateProcess 32767字元上限約束
|
|
113
|
+
if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
114
|
+
return getErrorResult(`prompt too long (${prompt.length} chars > ${MAX_PROMPT_LENGTH}), agy passes prompt via --print flag and Windows command line is capped at 32767 chars`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//exe, 無效回退預設'agy', 由execCli自系統PATH解析實體路徑
|
|
118
|
+
let exe = get(opt, 'exe', null)
|
|
119
|
+
if (!isestr(exe)) {
|
|
120
|
+
exe = DEFAULT_EXE
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
//model, 無效時整段`--model`旗標不出現, 由agy自行決定使用模型
|
|
124
|
+
let model = get(opt, 'model', null)
|
|
125
|
+
|
|
126
|
+
//effort, 無效時整段`--effort`旗標不出現; 與帶檔位slug之衝突由agy判定(檔位不一致時conflicts錯誤, exit 1)
|
|
127
|
+
let effort = get(opt, 'effort', null)
|
|
128
|
+
|
|
129
|
+
//skipPermissions, 非布林值回退預設true(非互動print模式不卡權限確認)
|
|
130
|
+
let skipPermissions = get(opt, 'skipPermissions', null)
|
|
131
|
+
if (!isbol(skipPermissions)) {
|
|
132
|
+
skipPermissions = true
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
//timeoutMs, 先行取值以供printTimeout推導, agy專屬預設300000
|
|
136
|
+
let timeoutMs = get(opt, 'timeoutMs', null)
|
|
137
|
+
if (!ispint(timeoutMs)) {
|
|
138
|
+
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
timeoutMs = cint(timeoutMs)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//printTimeout, 未給時由timeoutMs推導並預留緩衝, 令agy先於外層逾時而回報自身錯誤訊息
|
|
145
|
+
let printTimeout = get(opt, 'printTimeout', null)
|
|
146
|
+
if (!isestr(printTimeout)) {
|
|
147
|
+
printTimeout = `${Math.max(MIN_PRINT_TIMEOUT_S, Math.floor(timeoutMs / 1000) - PRINT_TIMEOUT_BUFFER_S)}s`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//addDirs, 逐項展開為--add-dir(agy該旗標可重複)
|
|
151
|
+
let addDirs = get(opt, 'addDirs', null)
|
|
152
|
+
let addDirArgs = []
|
|
153
|
+
if (isarr(addDirs)) {
|
|
154
|
+
for (let d of addDirs) {
|
|
155
|
+
if (isestr(d)) {
|
|
156
|
+
addDirArgs.push('--add-dir', d)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//extraArgs
|
|
162
|
+
let extraArgs = get(opt, 'extraArgs', null)
|
|
163
|
+
|
|
164
|
+
//args, prompt作為--print之值放最後
|
|
165
|
+
let args = getCliArgs(
|
|
166
|
+
skipPermissions ? '--dangerously-skip-permissions' : [],
|
|
167
|
+
['--print-timeout', printTimeout],
|
|
168
|
+
isestr(model) ? ['--model', model] : [],
|
|
169
|
+
isestr(effort) ? ['--effort', effort] : [],
|
|
170
|
+
addDirArgs,
|
|
171
|
+
extraArgs,
|
|
172
|
+
['--print', prompt],
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
//optCli, 剔除本轉接器自用鍵後原樣轉傳, 令呼叫端可用execCli全部設定(例如onStdout、maxBuffer)
|
|
176
|
+
let optCli = omit(opt, OWN_KEYS)
|
|
177
|
+
|
|
178
|
+
//execCli, 不帶input(agy由--print取得prompt); try/catch兜底轉義膨脹等
|
|
179
|
+
//前置檢查漏網之spawn同步拋出(如ENAMETOOLONG), 維持不reject契約
|
|
180
|
+
try {
|
|
181
|
+
return await execCli(exe, args, {
|
|
182
|
+
...optCli,
|
|
183
|
+
timeoutMs,
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
return getErrorResult(`${err.code || 'UNKNOWN'}: ${err.message}`)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
export default dispatchAntigravity
|
|
@@ -40,6 +40,14 @@ if (mErr) {
|
|
|
40
40
|
//--fake-sleep=N, 令假CLI延遲N毫秒才結束, 供測試逾時路徑
|
|
41
41
|
let mSleep = args.map((a) => /^--fake-sleep=(\\d+)$/.exec(a)).find((m) => !!m)
|
|
42
42
|
|
|
43
|
+
//--fake-fail-key=SUBSTR, 注入之OPENCODE_AUTH_CONTENT含該子字串時, 模擬額度/金鑰錯誤
|
|
44
|
+
//供fallback測試構造「某把金鑰失敗、其他把成功」之差異行為
|
|
45
|
+
let mFailKey = args.map((a) => /^--fake-fail-key=(.+)$/.exec(a)).find((m) => !!m)
|
|
46
|
+
if (mFailKey && (process.env.OPENCODE_AUTH_CONTENT || '').includes(mFailKey[1])) {
|
|
47
|
+
process.stderr.write('Invalid API key.')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
let chunks = []
|
|
44
52
|
process.stdin.on('data', (c) => {
|
|
45
53
|
chunks.push(c)
|
|
@@ -3,9 +3,11 @@ import keys from 'lodash-es/keys.js'
|
|
|
3
3
|
import map from 'lodash-es/map.js'
|
|
4
4
|
import wi from '../src/WDispatchAi.mjs'
|
|
5
5
|
import dispatchAi from '../src/dispatchAi.mjs'
|
|
6
|
+
import dispatchAiFallback from '../src/dispatchAiFallback.mjs'
|
|
6
7
|
import dispatchOpencode from '../src/dispatchOpencode.mjs'
|
|
7
8
|
import dispatchClaude from '../src/dispatchClaude.mjs'
|
|
8
9
|
import dispatchCodex from '../src/dispatchCodex.mjs'
|
|
10
|
+
import dispatchAntigravity from '../src/dispatchAntigravity.mjs'
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
describe('WDispatchAi', function() {
|
|
@@ -15,33 +17,37 @@ describe('WDispatchAi', function() {
|
|
|
15
17
|
let rr = [
|
|
16
18
|
'KINDS',
|
|
17
19
|
'dispatchAi',
|
|
20
|
+
'dispatchAiFallback',
|
|
18
21
|
'dispatchOpencode',
|
|
19
22
|
'dispatchClaude',
|
|
20
23
|
'dispatchCodex',
|
|
24
|
+
'dispatchAntigravity',
|
|
21
25
|
]
|
|
22
26
|
assert.strict.deepEqual(r, rr)
|
|
23
27
|
})
|
|
24
28
|
|
|
25
29
|
it('KINDS為可用之供應商種類字串陣列', function() {
|
|
26
30
|
let r = wi.KINDS
|
|
27
|
-
let rr = ['opencode', 'claude', 'codex']
|
|
31
|
+
let rr = ['opencode', 'claude', 'codex', 'antigravity']
|
|
28
32
|
assert.strict.deepEqual(r, rr)
|
|
29
33
|
})
|
|
30
34
|
|
|
31
35
|
it('除KINDS外各鍵值皆為函數', function() {
|
|
32
36
|
let r = map(keys(wi), (k) => typeof wi[k])
|
|
33
|
-
let rr = ['object', 'function', 'function', 'function', 'function']
|
|
37
|
+
let rr = ['object', 'function', 'function', 'function', 'function', 'function', 'function']
|
|
34
38
|
assert.strict.deepEqual(r, rr)
|
|
35
39
|
})
|
|
36
40
|
|
|
37
41
|
it('各鍵值即為對應模組之預設匯出', function() {
|
|
38
42
|
let r = [
|
|
39
43
|
wi.dispatchAi === dispatchAi,
|
|
44
|
+
wi.dispatchAiFallback === dispatchAiFallback,
|
|
40
45
|
wi.dispatchOpencode === dispatchOpencode,
|
|
41
46
|
wi.dispatchClaude === dispatchClaude,
|
|
42
47
|
wi.dispatchCodex === dispatchCodex,
|
|
48
|
+
wi.dispatchAntigravity === dispatchAntigravity,
|
|
43
49
|
]
|
|
44
|
-
let rr = [true, true, true, true]
|
|
50
|
+
let rr = [true, true, true, true, true, true]
|
|
45
51
|
assert.strict.deepEqual(r, rr)
|
|
46
52
|
})
|
|
47
53
|
|
|
@@ -51,20 +57,20 @@ describe('WDispatchAi', function() {
|
|
|
51
57
|
let t = await wi.dispatchAi(kind, '')
|
|
52
58
|
r.push(t.error)
|
|
53
59
|
}
|
|
54
|
-
let rr = ['prompt must be a non-empty string', 'prompt must be a non-empty string', 'prompt must be a non-empty string']
|
|
60
|
+
let rr = ['prompt must be a non-empty string', 'prompt must be a non-empty string', 'prompt must be a non-empty string', 'prompt must be a non-empty string']
|
|
55
61
|
assert.strict.deepEqual(r, rr)
|
|
56
62
|
})
|
|
57
63
|
|
|
58
64
|
it('可由WDispatchAi呼叫dispatchAi', async function() {
|
|
59
65
|
let t = await wi.dispatchAi('gemini', 'abc')
|
|
60
66
|
let r = [t.ok, t.error]
|
|
61
|
-
let rr = [false, 'unknown ai kind: "gemini" (available: opencode, claude, codex)']
|
|
67
|
+
let rr = [false, 'unknown ai kind: "gemini" (available: opencode, claude, codex, antigravity)']
|
|
62
68
|
assert.strict.deepEqual(r, rr)
|
|
63
69
|
})
|
|
64
70
|
|
|
65
71
|
it('可由WDispatchAi呼叫各轉接器', async function() {
|
|
66
72
|
let r = []
|
|
67
|
-
for (let fn of [wi.dispatchOpencode, wi.dispatchClaude, wi.dispatchCodex]) {
|
|
73
|
+
for (let fn of [wi.dispatchOpencode, wi.dispatchClaude, wi.dispatchCodex, wi.dispatchAntigravity]) {
|
|
68
74
|
let t = await fn('')
|
|
69
75
|
r.push([t.ok, t.error])
|
|
70
76
|
}
|
|
@@ -72,6 +78,7 @@ describe('WDispatchAi', function() {
|
|
|
72
78
|
[false, 'prompt must be a non-empty string'],
|
|
73
79
|
[false, 'prompt must be a non-empty string'],
|
|
74
80
|
[false, 'prompt must be a non-empty string'],
|
|
81
|
+
[false, 'prompt must be a non-empty string'],
|
|
75
82
|
]
|
|
76
83
|
assert.strict.deepEqual(r, rr)
|
|
77
84
|
})
|
|
@@ -5,19 +5,20 @@ import adapters from '../src/adapters.mjs'
|
|
|
5
5
|
import dispatchOpencode from '../src/dispatchOpencode.mjs'
|
|
6
6
|
import dispatchClaude from '../src/dispatchClaude.mjs'
|
|
7
7
|
import dispatchCodex from '../src/dispatchCodex.mjs'
|
|
8
|
+
import dispatchAntigravity from '../src/dispatchAntigravity.mjs'
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
describe('adapters', function() {
|
|
11
12
|
|
|
12
13
|
it('對照表鍵名即為可用之供應商種類', function() {
|
|
13
14
|
let r = keys(adapters)
|
|
14
|
-
let rr = ['opencode', 'claude', 'codex']
|
|
15
|
+
let rr = ['opencode', 'claude', 'codex', 'antigravity']
|
|
15
16
|
assert.strict.deepEqual(r, rr)
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
it('各鍵值皆為函數', function() {
|
|
19
20
|
let r = map(keys(adapters), (k) => typeof adapters[k])
|
|
20
|
-
let rr = ['function', 'function', 'function']
|
|
21
|
+
let rr = ['function', 'function', 'function', 'function']
|
|
21
22
|
assert.strict.deepEqual(r, rr)
|
|
22
23
|
})
|
|
23
24
|
|
|
@@ -26,8 +27,9 @@ describe('adapters', function() {
|
|
|
26
27
|
adapters.opencode === dispatchOpencode,
|
|
27
28
|
adapters.claude === dispatchClaude,
|
|
28
29
|
adapters.codex === dispatchCodex,
|
|
30
|
+
adapters.antigravity === dispatchAntigravity,
|
|
29
31
|
]
|
|
30
|
-
let rr = [true, true, true]
|
|
32
|
+
let rr = [true, true, true, true]
|
|
31
33
|
assert.strict.deepEqual(r, rr)
|
|
32
34
|
})
|
|
33
35
|
|
|
@@ -20,7 +20,7 @@ describe('dispatchAi', function() {
|
|
|
20
20
|
it('未知kind時回傳錯誤結果物件且不reject', async function() {
|
|
21
21
|
let t = await dispatchAi('gemini', 'abc')
|
|
22
22
|
let r = [t.ok, t.error, t.attempts]
|
|
23
|
-
let rr = [false, 'unknown ai kind: "gemini" (available: opencode, claude, codex)', 0]
|
|
23
|
+
let rr = [false, 'unknown ai kind: "gemini" (available: opencode, claude, codex, antigravity)', 0]
|
|
24
24
|
assert.strict.deepEqual(r, rr)
|
|
25
25
|
})
|
|
26
26
|
|
|
@@ -46,7 +46,7 @@ describe('dispatchAi', function() {
|
|
|
46
46
|
|
|
47
47
|
it('prompt之檢核由各轉接器負責, 錯誤結果物件一致', async function() {
|
|
48
48
|
let r = []
|
|
49
|
-
for (let kind of ['opencode', 'claude', 'codex']) {
|
|
49
|
+
for (let kind of ['opencode', 'claude', 'codex', 'antigravity']) {
|
|
50
50
|
let t = await dispatchAi(kind, '')
|
|
51
51
|
r.push([t.ok, t.error])
|
|
52
52
|
}
|
|
@@ -54,13 +54,14 @@ describe('dispatchAi', function() {
|
|
|
54
54
|
[false, 'prompt must be a non-empty string'],
|
|
55
55
|
[false, 'prompt must be a non-empty string'],
|
|
56
56
|
[false, 'prompt must be a non-empty string'],
|
|
57
|
+
[false, 'prompt must be a non-empty string'],
|
|
57
58
|
]
|
|
58
59
|
assert.strict.deepEqual(r, rr)
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
it('各kind分派至對應轉接器, 由其固定旗標可辨識', async function() {
|
|
62
63
|
let r = []
|
|
63
|
-
for (let kind of ['opencode', 'claude', 'codex']) {
|
|
64
|
+
for (let kind of ['opencode', 'claude', 'codex', 'antigravity']) {
|
|
64
65
|
let t = await dispatchAi(kind, 'abc', { exe: fake.exe, model: 'mdl' })
|
|
65
66
|
let o = JSON.parse(t.stdout)
|
|
66
67
|
r.push([t.ok, o.args])
|
|
@@ -69,6 +70,7 @@ describe('dispatchAi', function() {
|
|
|
69
70
|
[true, ['run', '--agent', 'build', '-m', 'mdl']],
|
|
70
71
|
[true, ['-p', '--dangerously-skip-permissions', '--model', 'mdl']],
|
|
71
72
|
[true, ['exec', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-m', 'mdl']],
|
|
73
|
+
[true, ['--dangerously-skip-permissions', '--print-timeout', '270s', '--model', 'mdl', '--print', 'abc']],
|
|
72
74
|
]
|
|
73
75
|
assert.strict.deepEqual(r, rr)
|
|
74
76
|
})
|
|
@@ -86,6 +88,7 @@ describe('dispatchAi', function() {
|
|
|
86
88
|
{ kind: 'opencode', model: 'opencode/free', provider: 'opencode', key: 'sk-1' },
|
|
87
89
|
{ kind: 'claude', model: 'sonnet' },
|
|
88
90
|
{ kind: 'codex', model: 'gpt-5.6-luna' },
|
|
91
|
+
{ kind: 'antigravity', model: 'gemini-3.6-flash-low' },
|
|
89
92
|
]
|
|
90
93
|
let r = []
|
|
91
94
|
for (let item of items) {
|
|
@@ -97,6 +100,7 @@ describe('dispatchAi', function() {
|
|
|
97
100
|
['opencode', 'run', true],
|
|
98
101
|
['claude', '-p', true],
|
|
99
102
|
['codex', 'exec', true],
|
|
103
|
+
['antigravity', '--dangerously-skip-permissions', true],
|
|
100
104
|
]
|
|
101
105
|
assert.strict.deepEqual(r, rr)
|
|
102
106
|
})
|