w-dispatch-ai 1.0.4 → 1.0.6
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 +94 -17
- 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 +2 -2
- package/docs/dfTimeoutMs.mjs.html +98 -0
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +16 -11
- package/docs/dispatchAiWkf.mjs.html +11 -2
- package/docs/dispatchAntigravity.mjs.html +23 -12
- package/docs/dispatchApiOpenaiCompat.mjs.html +5 -4
- package/docs/dispatchClaude.mjs.html +17 -4
- package/docs/dispatchCodex.mjs.html +16 -3
- package/docs/dispatchOpencode.mjs.html +16 -3
- package/docs/getCliArgs.mjs.html +2 -2
- package/docs/getErrorResult.mjs.html +2 -2
- package/docs/global.html +501 -35
- package/docs/index.html +2 -2
- package/docs/resolveProviders.mjs.html +189 -0
- package/docs/wkf_callAiWithFallback.mjs.html +29 -13
- package/docs/wkf_extractJsonLoose.mjs.html +2 -2
- package/docs/wkf_runFanout.mjs.html +2 -2
- package/docs/wkf_runFanoutPipeline.mjs.html +2 -2
- package/docs/wkf_runRolePipeline.mjs.html +2 -2
- package/g.mjs +7 -7
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +6 -2
- package/src/dfTimeoutMs.mjs +26 -0
- package/src/dispatchAiFallback.mjs +14 -9
- package/src/dispatchAiWkf.mjs +9 -0
- package/src/dispatchAntigravity.mjs +21 -10
- package/src/dispatchApiOpenaiCompat.mjs +3 -2
- package/src/dispatchClaude.mjs +15 -2
- package/src/dispatchCodex.mjs +14 -1
- package/src/dispatchOpencode.mjs +14 -1
- package/src/providers.mjs +133 -0
- package/src/resolveProviders.mjs +117 -0
- package/src/wkf/callAiWithFallback.mjs +27 -11
- package/test/unit-WDispatchAi.test.mjs +9 -3
- package/test/unit-callAiWithFallback.test.mjs +15 -0
- package/test/unit-dispatchAi.test.mjs +1 -1
- package/test/unit-dispatchAiFallback.test.mjs +25 -0
- package/test/unit-dispatchAiWkf.test.mjs +8 -0
- package/test/unit-dispatchAntigravity.test.mjs +25 -5
- package/test/unit-providers.test.mjs +66 -0
- package/test/unit-resolveProviders.test.mjs +85 -0
package/src/dispatchAiWkf.mjs
CHANGED
|
@@ -24,6 +24,15 @@ import runFanoutPipeline from './wkf/runFanoutPipeline.mjs'
|
|
|
24
24
|
// 額度池與故障域各自獨立而屬不同供應商, 鍵名須帶上路徑加以區分。
|
|
25
25
|
// 命名規則與取捨詳見dispatchAiFallback.mjs檔頭之「條目id之設計規則」。
|
|
26
26
|
//
|
|
27
|
+
// 【timeout速覽】(完整說明見README之「Timeout 總覽」)
|
|
28
|
+
// 單次AI嘗試: timeoutMs, 全套件統一預設300000(5分鐘, 單一來源dfTimeoutMs.mjs),
|
|
29
|
+
// 直接呼叫轉接器與工作流內皆同一數字;
|
|
30
|
+
// 單一名額(遞補鏈): budgetMs, 預設null不限; minAttemptMs開工門檻預設20000(無budget時不作用);
|
|
31
|
+
// 工作流總時長: 無獨立參數(刻意)——由結構推導: Fanout≈最慢名額+整合(並行),
|
|
32
|
+
// RolePipeline≈Σ各階段(序列); 要上限就對每名額設budgetMs。
|
|
33
|
+
// 最壞情況公式: 名額≤K×timeoutMs(K=鏈組數, 逾時每組只燒一次即跳組)。
|
|
34
|
+
// 調整四層(細者覆蓋粗者): defaults(此處) → 各工作流callOpt → 階段/名額規格 → provider條目。
|
|
35
|
+
//
|
|
27
36
|
// 【定義providers時如何選kind: CLI或API】判準是「該階段需不需要工具」:
|
|
28
37
|
// 需要讀本機檔案、grep、執行指令、抓網頁 → CLI類kind(opencode/claude/codex/antigravity);
|
|
29
38
|
// 純文字生成(素材皆已在prompt內) → API類kind(api-openai-compat), 免安裝免登入且較快。
|
|
@@ -8,6 +8,7 @@ import cint from 'wsemi/src/cint.mjs'
|
|
|
8
8
|
import execCli from 'wsemi/src/execCli.mjs'
|
|
9
9
|
import getCliArgs from './getCliArgs.mjs'
|
|
10
10
|
import getErrorResult from './getErrorResult.mjs'
|
|
11
|
+
import dfTimeoutMs from './dfTimeoutMs.mjs'
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
// dispatchAntigravity.mjs — 以Google Antigravity CLI(agy)呼叫AI模型
|
|
@@ -37,7 +38,7 @@ import getErrorResult from './getErrorResult.mjs'
|
|
|
37
38
|
|
|
38
39
|
//預設值
|
|
39
40
|
let DEFAULT_EXE = 'agy'
|
|
40
|
-
let DEFAULT_TIMEOUT_MS = 300000
|
|
41
|
+
let DEFAULT_TIMEOUT_MS = dfTimeoutMs //全套件統一預設300000(恰對齊agy自身print-timeout預設5m0s)
|
|
41
42
|
let MAX_PROMPT_LENGTH = 30000 //命令列上限32767扣除exe路徑與旗標後之保守值
|
|
42
43
|
let PRINT_TIMEOUT_BUFFER_S = 30
|
|
43
44
|
let MIN_PRINT_TIMEOUT_S = 30
|
|
@@ -68,10 +69,10 @@ let OWN_KEYS = ['exe', 'model', 'effort', 'skipPermissions', 'printTimeout', 'ad
|
|
|
68
69
|
* @param {String} [opt.effort=''] 輸入推理深度字串,可選'low'、'medium'、'high',需agy>=1.1.11,建議搭配不帶檔位之基礎slug;與帶檔位slug併用且檔位不一致時agy回conflicts錯誤,預設''代表不帶
|
|
69
70
|
* @param {Boolean} [opt.skipPermissions=true] 輸入是否帶`--dangerously-skip-permissions`旗標布林值,false代表保留CLI權限閘門,預設true
|
|
70
71
|
* @param {String} [opt.printTimeout=''] 輸入agy自身等待上限字串(如'10m'、'570s'),預設''代表由timeoutMs推導(扣30秒緩衝,下限30秒)
|
|
71
|
-
* @param {Array} [opt.addDirs
|
|
72
|
+
* @param {Array} [opt.addDirs=自動納入cwd] 輸入加入workspace之目錄字串陣列,逐項展開為`--add-dir`。agy以自身scratch目錄為工作區而不採子進程cwd,故未給本參數時自動納入有效cwd令檔案可視範圍與其他CLI轉接器一致;明示給陣列(含空陣列[]代表不揭露任何目錄)則完全尊重呼叫端
|
|
72
73
|
* @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列(如--output-format、--json-schema、--mode),將接於固定旗標之後、`--print`之前,預設[]
|
|
73
|
-
* @param {Number} [opt.timeoutMs=300000]
|
|
74
|
-
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
74
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000(恰對齊agy自身print-timeout預設5m0s)
|
|
75
|
+
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()。注意本參數不影響agy之檔案可視範圍(agy以自身scratch目錄為工作區),可視範圍由addDirs決定(未給addDirs時自動納入本目錄)
|
|
75
76
|
* @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
76
77
|
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
|
|
77
78
|
* @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
|
|
@@ -132,7 +133,7 @@ async function dispatchAntigravity(prompt, opt = {}) {
|
|
|
132
133
|
skipPermissions = true
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
//timeoutMs, 先行取值以供printTimeout推導,
|
|
136
|
+
//timeoutMs, 先行取值以供printTimeout推導, 無效回退全套件統一預設300000
|
|
136
137
|
let timeoutMs = get(opt, 'timeoutMs', null)
|
|
137
138
|
if (!ispint(timeoutMs)) {
|
|
138
139
|
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
@@ -148,13 +149,23 @@ async function dispatchAntigravity(prompt, opt = {}) {
|
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
//addDirs, 逐項展開為--add-dir(agy該旗標可重複)
|
|
152
|
+
//未給時自動納入有效cwd(明給的或預設process.cwd()): agy以自身scratch目錄為工作區,
|
|
153
|
+
//不採子進程之cwd, 僅--add-dir能擴其檔案可視範圍——僅給cwd時弱模型直接回「找不到檔案」、
|
|
154
|
+
//強模型得自行摸索(實測116s vs 帶--add-dir僅6s), 且兩者皆ok:true屬靜默失敗,
|
|
155
|
+
//極易被誤判為模型能力不足(2026-08-13以隨機token實測確認)。
|
|
156
|
+
//明示addDirs(含空陣列[]代表不揭露任何目錄)則完全尊重呼叫端, 不自動加入
|
|
151
157
|
let addDirs = get(opt, 'addDirs', null)
|
|
158
|
+
if (!isarr(addDirs)) {
|
|
159
|
+
let cwdEff = get(opt, 'cwd', null)
|
|
160
|
+
if (!isestr(cwdEff)) {
|
|
161
|
+
cwdEff = process.cwd()
|
|
162
|
+
}
|
|
163
|
+
addDirs = [cwdEff]
|
|
164
|
+
}
|
|
152
165
|
let addDirArgs = []
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
addDirArgs.push('--add-dir', d)
|
|
157
|
-
}
|
|
166
|
+
for (let d of addDirs) {
|
|
167
|
+
if (isestr(d)) {
|
|
168
|
+
addDirArgs.push('--add-dir', d)
|
|
158
169
|
}
|
|
159
170
|
}
|
|
160
171
|
|
|
@@ -11,6 +11,7 @@ import strleft from 'wsemi/src/strleft.mjs'
|
|
|
11
11
|
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
12
12
|
import strTruncate from 'wsemi/src/strTruncate.mjs'
|
|
13
13
|
import getErrorResult from './getErrorResult.mjs'
|
|
14
|
+
import dfTimeoutMs from './dfTimeoutMs.mjs'
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
// dispatchApiOpenaiCompat.mjs — 以fetch直呼OpenAI相容API(chat/completions)
|
|
@@ -50,7 +51,7 @@ import getErrorResult from './getErrorResult.mjs'
|
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
//預設值
|
|
53
|
-
let DEFAULT_TIMEOUT_MS =
|
|
54
|
+
let DEFAULT_TIMEOUT_MS = dfTimeoutMs //全套件統一預設300000
|
|
54
55
|
let DEFAULT_RETRY_DELAY_MS = 5000
|
|
55
56
|
let MAX_RETRY_DELAY_MS = 15000
|
|
56
57
|
|
|
@@ -282,7 +283,7 @@ async function callOnce(url, headers, body, timeoutMs, validator) {
|
|
|
282
283
|
* @param {String} [opt.system=''] 輸入system提示詞字串,將以system角色置於messages首位,預設''代表不帶
|
|
283
284
|
* @param {Object} [opt.body={}] 輸入額外請求本體物件(如temperature、max_tokens、response_format),將併入預設body(同名鍵以此為準),預設{}。注意本轉接器不支援工具,帶入tools而模型回tool_calls時一律以TOOL_CALLS_UNSUPPORTED回報失敗,需要工具請改用CLI類kind
|
|
284
285
|
* @param {Object} [opt.headers={}] 輸入額外請求標頭物件,預設{}
|
|
285
|
-
* @param {Number} [opt.timeoutMs=
|
|
286
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將中止請求(含回應串流讀取),全套件統一預設300000
|
|
286
287
|
* @param {String|Function} [opt.validate=undefined] 輸入回覆內容驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
287
288
|
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,4xx(429除外)不重試,預設0
|
|
288
289
|
* @param {Number} [opt.retryDelayMs=5000] 輸入重試間隔毫秒正整數,實際間隔為retryDelayMs乘以重試次數且上限15000ms,預設5000
|
package/src/dispatchClaude.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import get from 'lodash-es/get.js'
|
|
2
2
|
import omit from 'lodash-es/omit.js'
|
|
3
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
4
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
3
5
|
import isbol from 'wsemi/src/isbol.mjs'
|
|
4
6
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
5
7
|
import execCli from 'wsemi/src/execCli.mjs'
|
|
6
8
|
import getCliArgs from './getCliArgs.mjs'
|
|
7
9
|
import getErrorResult from './getErrorResult.mjs'
|
|
10
|
+
import dfTimeoutMs from './dfTimeoutMs.mjs'
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
// dispatchClaude.mjs — 以Claude Code CLI呼叫Claude模型
|
|
@@ -44,7 +47,7 @@ let OWN_KEYS = ['exe', 'model', 'skipPermissions', 'extraArgs', 'input']
|
|
|
44
47
|
* @param {String} [opt.model=''] 輸入模型別名或模型ID字串,例如'sonnet'、'opus',預設''代表不帶`--model`旗標
|
|
45
48
|
* @param {Boolean} [opt.skipPermissions=true] 輸入是否帶`--dangerously-skip-permissions`旗標布林值,false代表保留CLI權限閘門,預設true
|
|
46
49
|
* @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,將接於固定旗標之後,預設[]
|
|
47
|
-
* @param {Number} [opt.timeoutMs=
|
|
50
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
|
|
48
51
|
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
49
52
|
* @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
50
53
|
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
|
|
@@ -56,7 +59,7 @@ let OWN_KEYS = ['exe', 'model', 'skipPermissions', 'extraArgs', 'input']
|
|
|
56
59
|
*
|
|
57
60
|
* let test = async () => {
|
|
58
61
|
*
|
|
59
|
-
* let r = await dispatchClaude('請只回覆兩個字:完成', { model: 'sonnet'
|
|
62
|
+
* let r = await dispatchClaude('請只回覆兩個字:完成', { model: 'sonnet' })
|
|
60
63
|
* console.log(r.ok, r.stdout.trim())
|
|
61
64
|
* // => true '完成'
|
|
62
65
|
*
|
|
@@ -104,6 +107,15 @@ async function dispatchClaude(prompt, opt = {}) {
|
|
|
104
107
|
extraArgs,
|
|
105
108
|
)
|
|
106
109
|
|
|
110
|
+
//timeoutMs, 無效回退全套件統一預設(dfTimeoutMs=300000), 各轉接器一致令呼叫方無須記多套數字
|
|
111
|
+
let timeoutMs = get(opt, 'timeoutMs', null)
|
|
112
|
+
if (!ispint(timeoutMs)) {
|
|
113
|
+
timeoutMs = dfTimeoutMs
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
timeoutMs = cint(timeoutMs)
|
|
117
|
+
}
|
|
118
|
+
|
|
107
119
|
//optCli, 剔除本轉接器自用鍵後原樣轉傳, 令呼叫端可用execCli全部設定(例如onStdout、maxBuffer)
|
|
108
120
|
let optCli = omit(opt, OWN_KEYS)
|
|
109
121
|
|
|
@@ -111,6 +123,7 @@ async function dispatchClaude(prompt, opt = {}) {
|
|
|
111
123
|
return execCli(exe, args, {
|
|
112
124
|
...optCli,
|
|
113
125
|
input: prompt,
|
|
126
|
+
timeoutMs,
|
|
114
127
|
})
|
|
115
128
|
}
|
|
116
129
|
|
package/src/dispatchCodex.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import get from 'lodash-es/get.js'
|
|
2
2
|
import omit from 'lodash-es/omit.js'
|
|
3
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
4
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
3
5
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
4
6
|
import execCli from 'wsemi/src/execCli.mjs'
|
|
5
7
|
import getCliArgs from './getCliArgs.mjs'
|
|
6
8
|
import getErrorResult from './getErrorResult.mjs'
|
|
9
|
+
import dfTimeoutMs from './dfTimeoutMs.mjs'
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
// dispatchCodex.mjs — 以OpenAI Codex CLI呼叫GPT模型
|
|
@@ -44,7 +47,7 @@ let OWN_KEYS = ['exe', 'model', 'sandbox', 'extraArgs', 'input']
|
|
|
44
47
|
* @param {String} [opt.model=''] 輸入模型ID字串,例如'gpt-5.6-luna',預設''代表不帶`-m`旗標
|
|
45
48
|
* @param {String} [opt.sandbox='workspace-write'] 輸入沙箱模式字串,例如'read-only'、'workspace-write'、'danger-full-access',預設'workspace-write'
|
|
46
49
|
* @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,例如['--config', 'model_reasoning_effort="max"'],將接於固定旗標之後,預設[]
|
|
47
|
-
* @param {Number} [opt.timeoutMs=
|
|
50
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
|
|
48
51
|
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
49
52
|
* @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
50
53
|
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
|
|
@@ -105,6 +108,15 @@ async function dispatchCodex(prompt, opt = {}) {
|
|
|
105
108
|
extraArgs,
|
|
106
109
|
)
|
|
107
110
|
|
|
111
|
+
//timeoutMs, 無效回退全套件統一預設(dfTimeoutMs=300000), 各轉接器一致令呼叫方無須記多套數字
|
|
112
|
+
let timeoutMs = get(opt, 'timeoutMs', null)
|
|
113
|
+
if (!ispint(timeoutMs)) {
|
|
114
|
+
timeoutMs = dfTimeoutMs
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
timeoutMs = cint(timeoutMs)
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
//optCli, 剔除本轉接器自用鍵後原樣轉傳, 令呼叫端可用execCli全部設定(例如onStdout、maxBuffer)
|
|
109
121
|
let optCli = omit(opt, OWN_KEYS)
|
|
110
122
|
|
|
@@ -112,6 +124,7 @@ async function dispatchCodex(prompt, opt = {}) {
|
|
|
112
124
|
return execCli(exe, args, {
|
|
113
125
|
...optCli,
|
|
114
126
|
input: prompt,
|
|
127
|
+
timeoutMs,
|
|
115
128
|
})
|
|
116
129
|
}
|
|
117
130
|
|
package/src/dispatchOpencode.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import get from 'lodash-es/get.js'
|
|
2
2
|
import omit from 'lodash-es/omit.js'
|
|
3
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
4
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
3
5
|
import isobj from 'wsemi/src/isobj.mjs'
|
|
4
6
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
5
7
|
import execCli from 'wsemi/src/execCli.mjs'
|
|
6
8
|
import getCliArgs from './getCliArgs.mjs'
|
|
7
9
|
import getErrorResult from './getErrorResult.mjs'
|
|
10
|
+
import dfTimeoutMs from './dfTimeoutMs.mjs'
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
// dispatchOpencode.mjs — 以opencode CLI呼叫AI模型
|
|
@@ -60,7 +63,7 @@ let OWN_KEYS = ['exe', 'model', 'key', 'provider', 'agent', 'config', 'extraArgs
|
|
|
60
63
|
* @param {String} [opt.agent='build'] 輸入opencode代理名稱字串,預設'build'
|
|
61
64
|
* @param {Array} [opt.extraArgs=[]] 輸入額外命令列旗標字串陣列,將接於固定旗標之後,預設[]
|
|
62
65
|
* @param {Object} [opt.env=undefined] 輸入本次調用額外注入之環境變數物件,同時給予key與provider時會再併入OPENCODE_AUTH_CONTENT,預設undefined
|
|
63
|
-
* @param {Number} [opt.timeoutMs=
|
|
66
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入逾時毫秒正整數,逾時將強制關閉子進程及其子孫程序,全套件統一預設300000
|
|
64
67
|
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
65
68
|
* @param {String|Function} [opt.validate=undefined] 輸入stdout驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
|
|
66
69
|
* @param {Number} [opt.maxRetries=0] 輸入失敗後最大重試次數非負整數,預設0
|
|
@@ -173,6 +176,15 @@ async function dispatchOpencode(prompt, opt = {}) {
|
|
|
173
176
|
}
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
//timeoutMs, 無效回退全套件統一預設(dfTimeoutMs=300000), 各轉接器一致令呼叫方無須記多套數字
|
|
180
|
+
let timeoutMs = get(opt, 'timeoutMs', null)
|
|
181
|
+
if (!ispint(timeoutMs)) {
|
|
182
|
+
timeoutMs = dfTimeoutMs
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
timeoutMs = cint(timeoutMs)
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
//optCli, 剔除本轉接器自用鍵後原樣轉傳, 令呼叫端可用execCli全部設定(例如onStdout、maxBuffer)
|
|
177
189
|
let optCli = omit(opt, OWN_KEYS)
|
|
178
190
|
|
|
@@ -181,6 +193,7 @@ async function dispatchOpencode(prompt, opt = {}) {
|
|
|
181
193
|
...optCli,
|
|
182
194
|
input: prompt,
|
|
183
195
|
env,
|
|
196
|
+
timeoutMs,
|
|
184
197
|
})
|
|
185
198
|
}
|
|
186
199
|
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// providers.mjs — 預設providers定義檔(實測可用之供應商條目, 開發端可直接全用或自選)
|
|
2
|
+
//
|
|
3
|
+
// 【性質】純資料檔, 不讀環境變數。金鑰以envVar間接引用(機密不落設定檔),
|
|
4
|
+
// 使用前須經resolveProviders展開(envVar → keys, 缺變數者自動停用並回報);
|
|
5
|
+
// 展開後之陣列可直接餵dispatchAiFallback, table可直接餵dispatchAiWkf。
|
|
6
|
+
//
|
|
7
|
+
// 【id命名】依「路徑:模型」規則(詳dispatchAiFallback.mjs檔頭之id設計規則):
|
|
8
|
+
// oc:=經opencode CLI(有工具、較慢) / agy:=antigravity CLI / claude:/codex:=訂閱登入態CLI
|
|
9
|
+
// zen:/agnes:/poolside:=REST直呼(免CLI免登入、快, 純文字生成)
|
|
10
|
+
// 同一模型之CLI版與REST版屬不同供應商(能力與額度池皆不同), 故各為一條。
|
|
11
|
+
//
|
|
12
|
+
// 【使用方式】
|
|
13
|
+
// import providers from 'w-dispatch-ai/src/providers.mjs'
|
|
14
|
+
// import resolveProviders from 'w-dispatch-ai/src/resolveProviders.mjs'
|
|
15
|
+
// process.loadEnvFile('./.env') //OPENCODE_KEYS/AGNES_KEYS/POOLSIDE_KEYS, 逗號分隔多把
|
|
16
|
+
// let { providers: ps, table, skipped } = resolveProviders(providers) //全取
|
|
17
|
+
// let r2 = resolveProviders(providers, { pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'] }) //自選, pick順序即遞補優先序
|
|
18
|
+
//
|
|
19
|
+
// 【timeout規劃】各條目刻意不帶timeoutMs, 由上層依任務型態統一給定、條目僅於特例覆寫:
|
|
20
|
+
// 簡單任務(秒級~分鐘級): 沿用套件統一預設即可(全轉接器一律300000=5分鐘, 見dfTimeoutMs.mjs)。
|
|
21
|
+
// 複雜任務(單一AI工作約15分鐘、fallback須能走到最末):
|
|
22
|
+
// timeoutMs: 1200000 (20min=15min工作+33%餘裕, 單次嘗試上限)
|
|
23
|
+
// minAttemptMs: 1200000 (剩餘預算不足完整視窗即不開工——開了也不可能完成, 純浪費)
|
|
24
|
+
// budgetMs: 鏈長K×timeoutMs (逾時型失敗每組只燒一次timeout即跳組, 故K×20min
|
|
25
|
+
// 保證能走到最末; 例K=4 → 4800000=80min。無外部排程時限則可null=不限)
|
|
26
|
+
// agy之printTimeout由timeoutMs自動推導(−30s), 無須另設。
|
|
27
|
+
// 工作流層總時長: RolePipeline為各階段序列相加(M階段≈M×名額預算);
|
|
28
|
+
// Fanout各名額並行(≈單一名額預算+整合名額預算), 外部排程上限須據此預留。
|
|
29
|
+
// 外部調整四層(細者覆蓋粗者): dispatchAiWkf之defaults(全域) → 各工作流callOpt
|
|
30
|
+
// → 各階段/名額規格 → 各provider條目。
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
let providers = [
|
|
34
|
+
|
|
35
|
+
//cli版
|
|
36
|
+
{
|
|
37
|
+
id: 'oc:opencode/deepseek-v4-flash-free',
|
|
38
|
+
model: 'opencode/deepseek-v4-flash-free',
|
|
39
|
+
kind: 'opencode',
|
|
40
|
+
envVar: 'OPENCODE_KEYS',
|
|
41
|
+
provider: 'opencode',
|
|
42
|
+
config: {
|
|
43
|
+
permission: { edit: 'deny', write: 'deny', bash: 'deny' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'oc:agnes-ai/agnes-2.5-flash',
|
|
48
|
+
model: 'agnes-ai/agnes-2.5-flash',
|
|
49
|
+
kind: 'opencode',
|
|
50
|
+
envVar: 'AGNES_KEYS',
|
|
51
|
+
provider: 'agnes-ai',
|
|
52
|
+
config: {
|
|
53
|
+
provider: {
|
|
54
|
+
'agnes-ai': {
|
|
55
|
+
npm: '@ai-sdk/openai-compatible',
|
|
56
|
+
name: 'Agnes',
|
|
57
|
+
options: { baseURL: 'https://apihub.agnes-ai.com/v1' },
|
|
58
|
+
models: { 'agnes-2.5-flash': { name: 'Agnes 2.5 Flash' } },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
permission: { edit: 'deny', write: 'deny', bash: 'deny' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'oc:poolside/poolside/laguna-s-2.1',
|
|
66
|
+
model: 'poolside/poolside/laguna-s-2.1',
|
|
67
|
+
kind: 'opencode',
|
|
68
|
+
envVar: 'POOLSIDE_KEYS',
|
|
69
|
+
provider: 'poolside',
|
|
70
|
+
config: {
|
|
71
|
+
provider: {
|
|
72
|
+
'poolside': {
|
|
73
|
+
npm: '@ai-sdk/openai-compatible',
|
|
74
|
+
name: 'Poolside',
|
|
75
|
+
options: { baseURL: 'https://inference.poolside.ai/v1' },
|
|
76
|
+
models: { 'poolside/laguna-s-2.1': { name: 'Laguna S 2.1' } },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
permission: { edit: 'deny', write: 'deny', bash: 'deny' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'agy:gemini-3.7-flash-high',
|
|
84
|
+
model: 'gemini-3.7-flash-high',
|
|
85
|
+
kind: 'antigravity',
|
|
86
|
+
addDirs: ['.'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'claude:sonnet',
|
|
90
|
+
model: 'sonnet',
|
|
91
|
+
kind: 'claude',
|
|
92
|
+
extraArgs: ['--disallowedTools', 'Write,Edit,NotebookEdit,Bash'],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'codex:gpt-5.6-luna',
|
|
96
|
+
model: 'gpt-5.6-luna',
|
|
97
|
+
kind: 'codex',
|
|
98
|
+
sandbox: 'read-only',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
//api版
|
|
102
|
+
{
|
|
103
|
+
id: 'zen:deepseek-v4-flash-free',
|
|
104
|
+
model: 'deepseek-v4-flash-free',
|
|
105
|
+
kind: 'api-openai-compat',
|
|
106
|
+
envVar: 'OPENCODE_KEYS',
|
|
107
|
+
baseURL: 'https://opencode.ai/zen/v1',
|
|
108
|
+
body: { max_tokens: 8192 },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'agnes:agnes-2.5-flash',
|
|
112
|
+
model: 'agnes-2.5-flash',
|
|
113
|
+
kind: 'api-openai-compat',
|
|
114
|
+
envVar: 'AGNES_KEYS',
|
|
115
|
+
baseURL: 'https://apihub.agnes-ai.com/v1',
|
|
116
|
+
body: { max_tokens: 8192 },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'poolside:laguna-s-2.1',
|
|
120
|
+
model: 'poolside/laguna-s-2.1',
|
|
121
|
+
kind: 'api-openai-compat',
|
|
122
|
+
envVar: 'POOLSIDE_KEYS',
|
|
123
|
+
baseURL: 'https://inference.poolside.ai/v1',
|
|
124
|
+
body: {
|
|
125
|
+
max_tokens: 8192,
|
|
126
|
+
chat_template_kwargs: { enable_thinking: false },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
export default providers
|
|
@@ -0,0 +1,117 @@
|
|
|
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 isobj from 'wsemi/src/isobj.mjs'
|
|
5
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
6
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// resolveProviders.mjs — 把providers定義檔展開為可直接使用的條目
|
|
10
|
+
//
|
|
11
|
+
// 【為何需要】providers.mjs之條目以envVar間接引用金鑰(機密不落設定檔, 只寫變數名),
|
|
12
|
+
// 而dispatchAiFallback只認keys陣列——本函數負責envVar → keys之展開,
|
|
13
|
+
// 缺對應環境變數者停用該條目並列入skipped回報(不中斷、不throw),
|
|
14
|
+
// 訂閱登入態條目(claude/codex等無envVar)原樣通過。
|
|
15
|
+
//
|
|
16
|
+
// 【自選】opt.pick給id陣列即可只取用部分條目, 且依pick之順序回傳
|
|
17
|
+
// (順序即dispatchAiFallback之優先序); 查無之id列入missing回報。
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 展開providers定義條目:envVar → keys,並可依id自選子集
|
|
22
|
+
*
|
|
23
|
+
* 特點:
|
|
24
|
+
* 條目之envVar依env來源(預設process.env)展開為keys陣列(變數值以逗號分隔多把),envVar欄位自輸出移除;
|
|
25
|
+
* 缺對應環境變數(或值為空)之條目停用並列入skipped,不中斷不throw;
|
|
26
|
+
* 無envVar之條目(訂閱登入態CLI)原樣通過;條目已自帶有效keys者以keys為準;
|
|
27
|
+
* opt.pick可依id自選子集並以pick順序回傳(順序即遞補優先序);
|
|
28
|
+
* 輸入陣列與條目皆不被改動(輸出為淺拷貝)
|
|
29
|
+
*
|
|
30
|
+
* @param {Array} providers 輸入providers條目物件陣列(如providers.mjs之預設匯出)
|
|
31
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
32
|
+
* @param {Object} [opt.env=process.env] 輸入金鑰來源物件(變數名 → 逗號分隔之金鑰字串),預設process.env
|
|
33
|
+
* @param {Array} [opt.pick=null] 輸入自選id字串陣列,依此順序回傳對應條目,查無之id列入missing,預設null代表全取(依原順序)
|
|
34
|
+
* @returns {Object} 回傳物件,內含providers(可直接餵dispatchAiFallback之條目陣列)、table(id對條目之物件,可直接餵dispatchAiWkf)、skipped(缺環境變數而停用之{id,envVar}陣列)、missing(pick查無之id字串陣列)
|
|
35
|
+
* @example
|
|
36
|
+
*
|
|
37
|
+
* import resolveProviders from './src/resolveProviders.mjs'
|
|
38
|
+
* import providersAll from './src/providers.mjs'
|
|
39
|
+
*
|
|
40
|
+
* process.loadEnvFile('./.env') //金鑰放.env, 變數值以逗號分隔多把
|
|
41
|
+
*
|
|
42
|
+
* //全取: envVar展開為keys, 缺環境變數者列入skipped
|
|
43
|
+
* let { providers, table, skipped } = resolveProviders(providersAll)
|
|
44
|
+
* console.log(providers.length, skipped)
|
|
45
|
+
* // => 10 []
|
|
46
|
+
*
|
|
47
|
+
* //自選: 依pick順序回傳(順序即遞補優先序), 可直接餵dispatchAiFallback
|
|
48
|
+
* let r2 = resolveProviders(providersAll, { pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'] })
|
|
49
|
+
* console.log(r2.providers.map((p) => p.id))
|
|
50
|
+
* // => [ 'agnes:agnes-2.5-flash', 'claude:sonnet' ]
|
|
51
|
+
*
|
|
52
|
+
* //table可直接餵dispatchAiWkf之providers定義表
|
|
53
|
+
* //let wkf = dispatchAiWkf({ providers: r2.table, defaults: { timeoutMs: 1200000 } })
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
function resolveProviders(providers, opt = {}) {
|
|
57
|
+
|
|
58
|
+
//env, 無效回退process.env(瀏覽器環境無process則空物件)
|
|
59
|
+
let env = get(opt, 'env', null)
|
|
60
|
+
if (!isobj(env)) {
|
|
61
|
+
env = (typeof process !== 'undefined' && process && isobj(process.env)) ? process.env : {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//providersRaw
|
|
65
|
+
let providersRaw = isarr(providers) ? providers.filter(isobj) : []
|
|
66
|
+
|
|
67
|
+
//pick, 依id自選並依pick順序回傳
|
|
68
|
+
let pick = get(opt, 'pick', null)
|
|
69
|
+
let missing = []
|
|
70
|
+
let selected = providersRaw
|
|
71
|
+
if (isearr(pick)) {
|
|
72
|
+
selected = []
|
|
73
|
+
for (let id of pick) {
|
|
74
|
+
let entry = providersRaw.find((p) => get(p, 'id', null) === id)
|
|
75
|
+
if (entry) {
|
|
76
|
+
selected.push(entry)
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
missing.push(String(id))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//展開envVar → keys
|
|
85
|
+
let out = []
|
|
86
|
+
let skipped = []
|
|
87
|
+
for (let entry of selected) {
|
|
88
|
+
let envVar = get(entry, 'envVar', null)
|
|
89
|
+
|
|
90
|
+
//無envVar(訂閱登入態CLI)或已自帶有效keys, 原樣通過(僅移除envVar欄位)
|
|
91
|
+
if (!isestr(envVar) || isearr(get(entry, 'keys', null))) {
|
|
92
|
+
out.push(omit(entry, ['envVar']))
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//envVar → keys, 變數值以逗號分隔多把
|
|
97
|
+
let keys = String(get(env, envVar, '') || '').split(',').map((k) => k.trim()).filter(Boolean)
|
|
98
|
+
if (keys.length === 0) {
|
|
99
|
+
|
|
100
|
+
//缺環境變數: 停用該條目並回報, 不中斷(設計providers原則§四.4)
|
|
101
|
+
skipped.push({ id: get(entry, 'id', ''), envVar })
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
out.push({ ...omit(entry, ['envVar']), keys })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
//table, id → 條目, 可直接作dispatchAiWkf之providers定義表
|
|
108
|
+
let table = {}
|
|
109
|
+
for (let entry of out) {
|
|
110
|
+
table[entry.id] = entry
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { providers: out, table, skipped, missing }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
export default resolveProviders
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import get from 'lodash-es/get.js'
|
|
2
|
+
import omit from 'lodash-es/omit.js'
|
|
2
3
|
import isearr from 'wsemi/src/isearr.mjs'
|
|
3
4
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
4
5
|
import isfun from 'wsemi/src/isfun.mjs'
|
|
5
6
|
import isobj from 'wsemi/src/isobj.mjs'
|
|
6
7
|
import dispatchAiFallback from '../dispatchAiFallback.mjs'
|
|
8
|
+
import dfTimeoutMs from '../dfTimeoutMs.mjs'
|
|
7
9
|
import extractJsonLoose from './extractJsonLoose.mjs'
|
|
8
10
|
|
|
9
11
|
|
|
@@ -23,13 +25,29 @@ import extractJsonLoose from './extractJsonLoose.mjs'
|
|
|
23
25
|
// 【防寫檔前綴】agentic CLI對cwd隔離免疫(會自行解析專案根目錄寫檔),
|
|
24
26
|
// 故預設在prompt前掛「禁止建檔」約束(實測有效); 不需要時傳promptPrefix:''關閉。
|
|
25
27
|
// 殷鑑: 2026-08-10執行任務歷史.md遭AI覆寫、評比腳本繞過前綴又產生根目錄孤兒檔。
|
|
28
|
+
//
|
|
29
|
+
// 措辭須豁免「唯讀查閱」(2026-08-13 A/B實測): 各CLI取得檔案內容的途徑不同——
|
|
30
|
+
// opencode與claude有獨立的read/grep工具, 不受「禁止執行指令」約束;
|
|
31
|
+
// 但codex讀檔即是執行shell指令, 舊措辭「禁止執行任何指令」對它等同「禁止讀檔」,
|
|
32
|
+
// 凡需讀專案的任務會得到一句「請貼上檔案內容」而非成果, 且該拒答是合法字串,
|
|
33
|
+
// 會通過驗證被遞補層當成「成功」, 整條fallback鏈就此停住——兜底地位被靜默廢掉。
|
|
34
|
+
// 實測: 舊措辭下codex回「無法讀取該檔案」, 改為下列措辭後正常讀檔作答(11.1s),
|
|
35
|
+
// 防副作用(建檔/改檔/刪檔/改動系統狀態)之本旨不變。
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
//本層自用之設定鍵, 其餘鍵(timeoutMs/budgetMs/minAttemptMs/maxRetries/cwd/store/onEvent/
|
|
39
|
+
//retryDelayMs/maxBuffer等)一律原樣轉傳dispatchAiFallback——與各轉接器「剔除自用鍵後
|
|
40
|
+
//原樣轉傳」同一約定; 曾因白名單式轉送漏掉minAttemptMs, 令README教學之工作流層
|
|
41
|
+
//budget保護靜默失效(2026-08-14使用端實測回報), 故改採omit式轉傳杜絕同類漏鍵
|
|
42
|
+
let OWN_KEYS = ['providers', 'spec', 'check', 'parse', 'rawText', 'promptPrefix']
|
|
26
43
|
|
|
27
44
|
|
|
28
|
-
//預設防寫檔前綴
|
|
45
|
+
//預設防寫檔前綴(禁副作用, 但豁免唯讀查閱——codex以shell讀檔, 一律禁指令等同禁讀檔)
|
|
29
46
|
let NO_SIDE_EFFECT = [
|
|
30
47
|
'【執行約束】你只需把結果輸出在回覆內容中。',
|
|
31
|
-
'
|
|
32
|
-
'
|
|
48
|
+
'禁止建立、修改或刪除任何檔案,也不要執行任何會改動磁碟或系統狀態的指令——',
|
|
49
|
+
'呼叫端只讀取你的回覆文字,任何寫入磁碟的動作都不會被採用,只會製造無人讀取的垃圾檔。',
|
|
50
|
+
'唯讀查閱(讀取檔案、搜尋內容、列出目錄)不在此限,需要時請照常使用。',
|
|
33
51
|
'', '',
|
|
34
52
|
].join('\n')
|
|
35
53
|
|
|
@@ -87,12 +105,13 @@ function buildChain(providers, spec) {
|
|
|
87
105
|
* @param {Function} [opt.parse=extractJsonLoose] 輸入回覆解析函數(stdout)=>Object|null,預設寬鬆JSON抽取
|
|
88
106
|
* @param {Boolean} [opt.rawText=false] 輸入是否以純文字模式運作布林值,true代表不解析JSON(json欄位為修剪後文字、check收文字),預設false
|
|
89
107
|
* @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt前綴字串,預設為防寫檔約束,傳''關閉
|
|
90
|
-
* @param {Number} [opt.timeoutMs=300000]
|
|
108
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入單次嘗試逾時毫秒正整數,全套件統一預設300000
|
|
91
109
|
* @param {Number} [opt.budgetMs=null] 輸入整條遞補鏈之時間預算毫秒正整數,預設null代表不限
|
|
110
|
+
* @param {Number} [opt.minAttemptMs=20000] 輸入搭配budgetMs之開工門檻毫秒正整數,剩餘預算低於此值即不再開工,預設20000
|
|
92
111
|
* @param {Number} [opt.maxRetries=0] 輸入同家重試次數非負整數,預設0(韌性交給遞補;端點不穩偶發空回之模型可調高令同鍵重試)
|
|
93
112
|
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
94
113
|
* @param {Object} [opt.store=null] 輸入游標持久化物件{get,set},預設null代表用行程內記憶體
|
|
95
|
-
* @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null
|
|
114
|
+
* @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null。除上列外之其餘鍵(retryDelayMs、maxBuffer、onStdout等)亦一律原樣轉傳dispatchAiFallback
|
|
96
115
|
* @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否取得可用結果布林值)、json(解析後物件,rawText模式下為文字)、providerId(實際使用之名稱)、keyIndex、keyId、ms(總耗時毫秒)、tried(遞補嘗試歷程陣列)、error(錯誤訊息字串),本函數不會reject
|
|
97
116
|
* @example
|
|
98
117
|
* //need cli in system PATH
|
|
@@ -175,15 +194,12 @@ async function callAiWithFallback(prompt, opt = {}) {
|
|
|
175
194
|
return check ? check(j) === true : true
|
|
176
195
|
}
|
|
177
196
|
|
|
197
|
+
//剔除本層自用鍵後原樣轉傳(含minAttemptMs/retryDelayMs/maxBuffer等), providers與validate由本層給定
|
|
178
198
|
let r = await dispatchAiFallback(promptPrefix + prompt, {
|
|
199
|
+
...omit(opt, OWN_KEYS),
|
|
179
200
|
providers: chain,
|
|
180
201
|
validate,
|
|
181
|
-
timeoutMs: get(opt, 'timeoutMs', null) || 300000
|
|
182
|
-
budgetMs: get(opt, 'budgetMs', null) || undefined,
|
|
183
|
-
maxRetries: get(opt, 'maxRetries', null) || 0,
|
|
184
|
-
cwd: get(opt, 'cwd', null) || process.cwd(),
|
|
185
|
-
store: get(opt, 'store', null) || undefined,
|
|
186
|
-
onEvent: get(opt, 'onEvent', null) || undefined,
|
|
202
|
+
timeoutMs: get(opt, 'timeoutMs', null) || dfTimeoutMs, //全套件統一預設300000
|
|
187
203
|
})
|
|
188
204
|
|
|
189
205
|
//result, 已過validate故此處parse必然成功(同一解析器), 重解析僅為取出物件
|
|
@@ -10,6 +10,8 @@ import dispatchClaude from '../src/dispatchClaude.mjs'
|
|
|
10
10
|
import dispatchCodex from '../src/dispatchCodex.mjs'
|
|
11
11
|
import dispatchAntigravity from '../src/dispatchAntigravity.mjs'
|
|
12
12
|
import dispatchApiOpenaiCompat from '../src/dispatchApiOpenaiCompat.mjs'
|
|
13
|
+
import providers from '../src/providers.mjs'
|
|
14
|
+
import resolveProviders from '../src/resolveProviders.mjs'
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
describe('WDispatchAi', function() {
|
|
@@ -26,6 +28,8 @@ describe('WDispatchAi', function() {
|
|
|
26
28
|
'dispatchCodex',
|
|
27
29
|
'dispatchAntigravity',
|
|
28
30
|
'dispatchApiOpenaiCompat',
|
|
31
|
+
'providers',
|
|
32
|
+
'resolveProviders',
|
|
29
33
|
]
|
|
30
34
|
assert.strict.deepEqual(r, rr)
|
|
31
35
|
})
|
|
@@ -36,9 +40,9 @@ describe('WDispatchAi', function() {
|
|
|
36
40
|
assert.strict.deepEqual(r, rr)
|
|
37
41
|
})
|
|
38
42
|
|
|
39
|
-
it('
|
|
43
|
+
it('各鍵值型別正確(KINDS與providers為物件, 其餘為函數)', function() {
|
|
40
44
|
let r = map(keys(wi), (k) => typeof wi[k])
|
|
41
|
-
let rr = ['object', 'function', 'function', 'function', 'function', 'function', 'function', 'function', 'function']
|
|
45
|
+
let rr = ['object', 'function', 'function', 'function', 'function', 'function', 'function', 'function', 'function', 'object', 'function']
|
|
42
46
|
assert.strict.deepEqual(r, rr)
|
|
43
47
|
})
|
|
44
48
|
|
|
@@ -52,8 +56,10 @@ describe('WDispatchAi', function() {
|
|
|
52
56
|
wi.dispatchCodex === dispatchCodex,
|
|
53
57
|
wi.dispatchAntigravity === dispatchAntigravity,
|
|
54
58
|
wi.dispatchApiOpenaiCompat === dispatchApiOpenaiCompat,
|
|
59
|
+
wi.providers === providers,
|
|
60
|
+
wi.resolveProviders === resolveProviders,
|
|
55
61
|
]
|
|
56
|
-
let rr = [true, true, true, true, true, true, true, true]
|
|
62
|
+
let rr = [true, true, true, true, true, true, true, true, true, true]
|
|
57
63
|
assert.strict.deepEqual(r, rr)
|
|
58
64
|
})
|
|
59
65
|
|