w-dispatch-ai 1.0.0 → 1.0.1
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 +80 -1
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +6 -4
- package/docs/adapters.mjs.html +2 -2
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +406 -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 +875 -4
- package/docs/index.html +2 -2
- package/g.mjs +36 -0
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +4 -2
- package/src/dispatchAiFallback.mjs +334 -0
- package/test/tools/fakeCliForTest.mjs +8 -0
- package/test/unit-WDispatchAi.test.mjs +5 -2
- package/test/unit-dispatchAiFallback.test.mjs +422 -0
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
<pre class="prettyprint source linenums"><code>import keys from 'lodash-es/keys.js'
|
|
49
49
|
import adapters from './adapters.mjs'
|
|
50
50
|
import dispatchAi from './dispatchAi.mjs'
|
|
51
|
+
import dispatchAiFallback from './dispatchAiFallback.mjs'
|
|
51
52
|
import dispatchOpencode from './dispatchOpencode.mjs'
|
|
52
53
|
import dispatchClaude from './dispatchClaude.mjs'
|
|
53
54
|
import dispatchCodex from './dispatchCodex.mjs'
|
|
@@ -67,15 +68,16 @@ let KINDS = keys(adapters)
|
|
|
67
68
|
/**
|
|
68
69
|
* AI供應商分派
|
|
69
70
|
*
|
|
70
|
-
* @returns {Object} 回傳物件,其內含KINDS(可用供應商種類字串陣列),以及dispatchAi、dispatchOpencode、dispatchClaude、dispatchCodex之async函數
|
|
71
|
+
* @returns {Object} 回傳物件,其內含KINDS(可用供應商種類字串陣列),以及dispatchAi、dispatchAiFallback、dispatchOpencode、dispatchClaude、dispatchCodex之async函數
|
|
71
72
|
* @example
|
|
72
73
|
*
|
|
73
|
-
* 詳見dispatchAi、dispatchOpencode、dispatchClaude、dispatchCodex範例
|
|
74
|
+
* 詳見dispatchAi、dispatchAiFallback、dispatchOpencode、dispatchClaude、dispatchCodex範例
|
|
74
75
|
*
|
|
75
76
|
*/
|
|
76
77
|
let WDispatchAi = {
|
|
77
78
|
KINDS,
|
|
78
79
|
dispatchAi,
|
|
80
|
+
dispatchAiFallback,
|
|
79
81
|
dispatchOpencode,
|
|
80
82
|
dispatchClaude,
|
|
81
83
|
dispatchCodex,
|
|
@@ -97,7 +99,7 @@ export default WDispatchAi
|
|
|
97
99
|
<br class="clear">
|
|
98
100
|
|
|
99
101
|
<footer>
|
|
100
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
102
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
101
103
|
</footer>
|
|
102
104
|
|
|
103
105
|
<script>prettyPrint();</script>
|
package/docs/adapters.mjs.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -87,7 +87,7 @@ export default adapters
|
|
|
87
87
|
<br class="clear">
|
|
88
88
|
|
|
89
89
|
<footer>
|
|
90
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
90
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
91
91
|
</footer>
|
|
92
92
|
|
|
93
93
|
<script>prettyPrint();</script>
|
package/docs/dispatchAi.mjs.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -120,7 +120,7 @@ export default dispatchAi
|
|
|
120
120
|
<br class="clear">
|
|
121
121
|
|
|
122
122
|
<footer>
|
|
123
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
123
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
124
124
|
</footer>
|
|
125
125
|
|
|
126
126
|
<script>prettyPrint();</script>
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>dispatchAiFallback.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">dispatchAiFallback.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import omit from 'lodash-es/omit.js'
|
|
50
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
51
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
52
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
53
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
54
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
55
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
56
|
+
import dispatchAi from './dispatchAi.mjs'
|
|
57
|
+
import getErrorResult from './getErrorResult.mjs'
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
// dispatchAiFallback.mjs — 多供應商自動遞補層
|
|
61
|
+
//
|
|
62
|
+
// 【兩層策略】群組之間依providers宣告順序(優先序), 群組之內(keys多把)以游標輪替(額度均攤)。
|
|
63
|
+
//
|
|
64
|
+
// 【失敗分流】只分兩路:
|
|
65
|
+
// 與金鑰無關之失敗(TIMEOUT/ENOENT/參數錯誤/驗證失敗/未知kind) → 整組跳過——
|
|
66
|
+
// 同組各金鑰共用同一exe與model, 換金鑰必然再敗一次, 純屬空耗;
|
|
67
|
+
// 其餘失敗(含額度上限/金鑰無效/服務回錯等一切未分類者) → 換組內下一把, 不記憶、不停用。
|
|
68
|
+
// 不可把正確性建立在「錯誤分類器必須窮盡」之上——實測各家額度/金鑰錯誤訊息
|
|
69
|
+
// 含中文(无效的令牌)與無特徵字串(UnknownError), 正則涵蓋不了; 而額度視窗有5小時滾動、
|
|
70
|
+
// 逐小時、逐日等多種形態, 「命中即停用到當日結束」會把已復活的金鑰冰到隔天。
|
|
71
|
+
// 故跨次執行不設停用清單: 額度恢復的偵測就是「下次再打一次」, 代價僅一次快速失敗。
|
|
72
|
+
//
|
|
73
|
+
// 【跨次記憶只有游標】成功後游標推進至下一把, 令額度在同組多把金鑰間自動均攤;
|
|
74
|
+
// 死金鑰的代價也被游標攤平——這輪從key1敗轉key2成功後游標停在key3, 下輪不會先碰key1。
|
|
75
|
+
//
|
|
76
|
+
// 【時間預算】budgetMs限制整輪遞補的總時長, 剩餘預算會壓進每次呼叫的timeoutMs,
|
|
77
|
+
// 防止多家連續卡逾時而撞破外部排程的執行上限。
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
//fallback層自用之設定鍵, 其餘鍵作為各attempt之共用預設原樣轉傳
|
|
81
|
+
let FALLBACK_KEYS = ['providers', 'budgetMs', 'minAttemptMs', 'store', 'onEvent']
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
//providers條目自用之設定鍵, 其餘鍵(含kind)即該條目之opt原樣轉傳對應轉接器
|
|
85
|
+
let ENTRY_KEYS = ['id', 'keys']
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
//預設值
|
|
89
|
+
let DEFAULT_MIN_ATTEMPT_MS = 20000
|
|
90
|
+
let DEFAULT_TIMEOUT_MS = 120000
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
//memoryState, 未注入store時之行程內預設狀態(跨呼叫有效, 重啟歸零)
|
|
94
|
+
let memoryState = { cursors: {} }
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 判斷失敗結果是否與「哪一把金鑰」無關(換組內金鑰必然再敗, 應整組跳過)
|
|
99
|
+
*
|
|
100
|
+
* @param {Object} r 輸入dispatchAi失敗結果物件
|
|
101
|
+
* @returns {Boolean} 回傳是否應整組跳過之布林值
|
|
102
|
+
*/
|
|
103
|
+
function isKeyIndependentFail(r) {
|
|
104
|
+
|
|
105
|
+
let error = get(r, 'error', '')
|
|
106
|
+
if (!isestr(error)) {
|
|
107
|
+
error = ''
|
|
108
|
+
}
|
|
109
|
+
let code = get(r, 'code', null)
|
|
110
|
+
|
|
111
|
+
//逾時, 該服務卡住, 同服務其他金鑰只會再空耗一次完整timeout
|
|
112
|
+
if (error.indexOf('TIMEOUT') === 0) {
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//執行檔不存在, 同組共用同一exe
|
|
117
|
+
if (error.includes('ENOENT')) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//參數錯誤, 同組共用同一組旗標
|
|
122
|
+
if (code === 2) {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
//輸出未過驗證, CLI正常結束(code=0)且模型有回應, 換金鑰仍是同一模型之產出習慣
|
|
127
|
+
//注意判定依據是error字串而非code===0(code===0且ok===true是成功)
|
|
128
|
+
if (error === 'OUTPUT_VALIDATION_FAILED') {
|
|
129
|
+
return true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//kind無效, 屬條目設定錯誤
|
|
133
|
+
if (error.indexOf('unknown ai kind') === 0) {
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 依供應商清單順序自動遞補調用AI,組內多金鑰以游標輪替
|
|
143
|
+
*
|
|
144
|
+
* 特點:
|
|
145
|
+
* providers陣列順序即優先序,排前面的先用;
|
|
146
|
+
* 條目本身即該次調用之opt(除id與keys外原樣透傳對應轉接器),與dispatchAi「條目直接當opt」同一約定;
|
|
147
|
+
* 條目給予keys(多把金鑰)時以游標輪替,某把失敗自動換下一把,全數失敗才遞補下一組;
|
|
148
|
+
* 與金鑰無關之失敗(逾時/執行檔不存在/參數錯誤/輸出未過驗證/未知kind)直接整組跳過,不逐把空耗;
|
|
149
|
+
* 跨次執行僅記憶游標(經store注入持久化),不設金鑰停用清單——額度視窗形態多樣(5小時滾動/逐時/逐日),
|
|
150
|
+
* 停用會把已恢復的金鑰閒置,而重探的代價僅一次快速失敗;
|
|
151
|
+
* 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
|
|
152
|
+
*
|
|
153
|
+
* @param {String} prompt 輸入提示詞字串,一律以stdin傳入子進程
|
|
154
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
155
|
+
* @param {Array} opt.providers 輸入供應商條目物件陣列,順序即優先序。各條目除下列鍵外,其餘鍵(kind、model、exe、provider、config、sandbox、timeoutMs等)即該條目之opt原樣透傳對應轉接器
|
|
156
|
+
* @param {String} [opt.providers[].id=條目索引字串] 輸入群組識別字串,游標以此為鍵,多金鑰條目應給予穩定id,預設為條目索引字串
|
|
157
|
+
* @param {Array} [opt.providers[].keys=[]] 輸入同一服務之多把API key字串陣列,逐次注入輪替(kind為opencode時須同時於條目給予provider),省略代表沿用CLI既有登入狀態之單一虛擬金鑰
|
|
158
|
+
* @param {Number} [opt.budgetMs=null] 輸入整輪遞補之時間上限毫秒正整數,剩餘預算會壓進每次呼叫之timeoutMs,預設null代表不限
|
|
159
|
+
* @param {Number} [opt.minAttemptMs=20000] 輸入單次嘗試之最低剩餘預算毫秒正整數,剩餘低於此值即停止嘗試回報budget exhausted,預設20000
|
|
160
|
+
* @param {Object} [opt.store=null] 輸入狀態持久化物件{get:()=>state,set:(state)=>{}},state內含cursors(逐群組游標),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖
|
|
161
|
+
* @param {Function} [opt.onEvent=null] 輸入事件回調函數(ev)=>{},ev.type可為'try'、'ok'、'next-key'、'skip-group'、'budget-out',回調拋出例外不影響主流程,預設null
|
|
162
|
+
* @param {Number} [opt.timeoutMs=120000] 輸入各attempt共用之逾時毫秒正整數,條目可覆寫,預設120000
|
|
163
|
+
* @param {String|Function} [opt.validate=undefined] 輸入各attempt共用之stdout驗證規則,條目可覆寫,預設undefined
|
|
164
|
+
* @param {Number} [opt.maxRetries=0] 輸入各attempt共用之同家重試次數非負整數,韌性建議交給換家而非重試同一家,預設0
|
|
165
|
+
* @returns {Promise} 回傳Promise,resolve回傳結果物件,除execCli既有欄位(ok、stdout、stderr、code、error、durationMs、attempts、pid)外,追加providerId(實際使用之群組)、keyIndex(實際使用之金鑰索引,無keys時為null)、kind、model、tried(全部嘗試歷程陣列,成功時亦回傳),本函數不會reject
|
|
166
|
+
* @example
|
|
167
|
+
* //need opencode, claude, codex cli in system PATH
|
|
168
|
+
*
|
|
169
|
+
* import dispatchAiFallback from './src/dispatchAiFallback.mjs'
|
|
170
|
+
*
|
|
171
|
+
* let test = async () => {
|
|
172
|
+
*
|
|
173
|
+
* let r = await dispatchAiFallback('請只回覆兩個字:完成', {
|
|
174
|
+
* providers: [
|
|
175
|
+
* {
|
|
176
|
+
* id: 'deepseek',
|
|
177
|
+
* kind: 'opencode',
|
|
178
|
+
* model: 'opencode/deepseek-v4-flash-free',
|
|
179
|
+
* provider: 'opencode',
|
|
180
|
+
* keys: ['sk-aaa', 'sk-bbb'], //多把金鑰, 某把失敗自動換下一把
|
|
181
|
+
* timeoutMs: 180000,
|
|
182
|
+
* },
|
|
183
|
+
* { id: 'claude', kind: 'claude', model: 'sonnet' }, //deepseek全敗時遞補
|
|
184
|
+
* { id: 'codex', kind: 'codex', model: 'gpt-5.6-luna', sandbox: 'read-only' },
|
|
185
|
+
* ],
|
|
186
|
+
* budgetMs: 600000,
|
|
187
|
+
* onEvent: (ev) => console.log(ev.type, ev.providerId, ev.keyIndex),
|
|
188
|
+
* })
|
|
189
|
+
* console.log(r.ok, r.providerId, r.keyIndex, r.tried.length)
|
|
190
|
+
* // => true 'deepseek' 0 1
|
|
191
|
+
*
|
|
192
|
+
* }
|
|
193
|
+
* await test()
|
|
194
|
+
* .catch((err) => {
|
|
195
|
+
* console.log(err)
|
|
196
|
+
* })
|
|
197
|
+
*
|
|
198
|
+
*/
|
|
199
|
+
async function dispatchAiFallback(prompt, opt = {}) {
|
|
200
|
+
|
|
201
|
+
//check prompt, 於頂層先擋, 否則會逐組空跑同一錯誤
|
|
202
|
+
if (!isestr(prompt)) {
|
|
203
|
+
return { ...getErrorResult('prompt must be a non-empty string'), tried: [] }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
//providers, 濾除非物件條目
|
|
207
|
+
let providersRaw = get(opt, 'providers', null)
|
|
208
|
+
let providers = isarr(providersRaw) ? providersRaw.filter(isobj) : []
|
|
209
|
+
if (providers.length === 0) {
|
|
210
|
+
return { ...getErrorResult('providers must be a non-empty array'), tried: [] }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//budgetMs, 無效視為不限
|
|
214
|
+
let budgetMs = get(opt, 'budgetMs', null)
|
|
215
|
+
if (!ispint(budgetMs)) {
|
|
216
|
+
budgetMs = null
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
budgetMs = cint(budgetMs)
|
|
220
|
+
}
|
|
221
|
+
let deadline = (budgetMs === null) ? null : Date.now() + budgetMs
|
|
222
|
+
|
|
223
|
+
//minAttemptMs, 無效回退預設20000
|
|
224
|
+
let minAttemptMs = get(opt, 'minAttemptMs', null)
|
|
225
|
+
if (!ispint(minAttemptMs)) {
|
|
226
|
+
minAttemptMs = DEFAULT_MIN_ATTEMPT_MS
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
minAttemptMs = cint(minAttemptMs)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
//store, 須同時具get與set函數才視為有效, 否則用行程內記憶體
|
|
233
|
+
let store = get(opt, 'store', null)
|
|
234
|
+
let useStore = isobj(store) && isfun(store.get) && isfun(store.set)
|
|
235
|
+
let state = null
|
|
236
|
+
if (useStore) {
|
|
237
|
+
try {
|
|
238
|
+
state = store.get()
|
|
239
|
+
}
|
|
240
|
+
catch {}
|
|
241
|
+
}
|
|
242
|
+
if (!isobj(state)) {
|
|
243
|
+
state = useStore ? { cursors: {} } : memoryState
|
|
244
|
+
}
|
|
245
|
+
if (!isobj(state.cursors)) {
|
|
246
|
+
state.cursors = {}
|
|
247
|
+
}
|
|
248
|
+
let saveState = () => {
|
|
249
|
+
if (useStore) {
|
|
250
|
+
try {
|
|
251
|
+
store.set(state)
|
|
252
|
+
}
|
|
253
|
+
catch {}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
//onEvent, 回調拋出例外不得中斷主流程
|
|
258
|
+
let onEvent = get(opt, 'onEvent', null)
|
|
259
|
+
let emit = (ev) => {
|
|
260
|
+
if (isfun(onEvent)) {
|
|
261
|
+
try {
|
|
262
|
+
onEvent(ev)
|
|
263
|
+
}
|
|
264
|
+
catch {}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
//sharedOpt, 剔除fallback層自用鍵後作為各attempt共用預設, 條目覆寫之
|
|
269
|
+
let sharedOpt = omit(opt, FALLBACK_KEYS)
|
|
270
|
+
|
|
271
|
+
let tried = []
|
|
272
|
+
let lastResult = null
|
|
273
|
+
let lastMeta = null
|
|
274
|
+
|
|
275
|
+
//逐群組(宣告順序即優先序), 單向單輪不回頭
|
|
276
|
+
for (let ig = 0; ig < providers.length; ig++) {
|
|
277
|
+
let entry = providers[ig]
|
|
278
|
+
|
|
279
|
+
//id, 無效回退條目索引字串
|
|
280
|
+
let id = get(entry, 'id', null)
|
|
281
|
+
if (!isestr(id)) {
|
|
282
|
+
id = String(ig)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//kind與model僅供事件與回傳meta, kind有效性由dispatchAi判定
|
|
286
|
+
let kind = get(entry, 'kind', null)
|
|
287
|
+
let model = get(entry, 'model', null)
|
|
288
|
+
|
|
289
|
+
//keys, 濾除非有效字串, 空陣列視同未給(登入態單一虛擬金鑰)
|
|
290
|
+
let keysRaw = get(entry, 'keys', null)
|
|
291
|
+
let keys = isarr(keysRaw) ? keysRaw.filter(isestr) : []
|
|
292
|
+
let nk = keys.length
|
|
293
|
+
|
|
294
|
+
//entryOpt, 剔除條目自用鍵後即該條目之opt
|
|
295
|
+
let entryOpt = omit(entry, ENTRY_KEYS)
|
|
296
|
+
|
|
297
|
+
//游標, 逐群組記錄, 以現行keys長度取模自癒(金鑰陣列改動時不出界)
|
|
298
|
+
let cursor = 0
|
|
299
|
+
if (nk > 0) {
|
|
300
|
+
let c = get(state.cursors, id, 0)
|
|
301
|
+
cursor = ispint(c) ? cint(c) % nk : 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
//組內逐把嘗試, 每把至多一次, 全敗即組盡遞補下一組
|
|
305
|
+
let nAttempts = (nk > 0) ? nk : 1
|
|
306
|
+
let skipGroup = false
|
|
307
|
+
for (let a = 0; a < nAttempts && !skipGroup; a++) {
|
|
308
|
+
|
|
309
|
+
//keyIndex, 無keys時為null
|
|
310
|
+
let keyIndex = (nk > 0) ? (cursor + a) % nk : null
|
|
311
|
+
let keyId = (keyIndex === null) ? id : `${id}#${keyIndex}`
|
|
312
|
+
|
|
313
|
+
//attemptOpt, 共用預設 <- 條目覆寫 <- 本把金鑰
|
|
314
|
+
let attemptOpt = { ...sharedOpt, ...entryOpt }
|
|
315
|
+
if (nk > 0) {
|
|
316
|
+
attemptOpt.key = keys[keyIndex]
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
//有效timeout, 有預算時以剩餘預算封頂
|
|
320
|
+
let effTimeout = get(attemptOpt, 'timeoutMs', null)
|
|
321
|
+
if (!ispint(effTimeout)) {
|
|
322
|
+
effTimeout = DEFAULT_TIMEOUT_MS
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
effTimeout = cint(effTimeout)
|
|
326
|
+
}
|
|
327
|
+
if (deadline !== null) {
|
|
328
|
+
let remaining = deadline - Date.now()
|
|
329
|
+
|
|
330
|
+
//剩餘不足一次最低嘗試, 停止遞補回報預算用盡
|
|
331
|
+
if (remaining < minAttemptMs) {
|
|
332
|
+
emit({ type: 'budget-out', providerId: id, keyIndex, keyId, remainingMs: remaining })
|
|
333
|
+
tried.push({ providerId: id, keyIndex, keyId, outcome: 'budget-out' })
|
|
334
|
+
return { ...getErrorResult('budget exhausted'), tried }
|
|
335
|
+
}
|
|
336
|
+
effTimeout = Math.min(effTimeout, remaining)
|
|
337
|
+
}
|
|
338
|
+
attemptOpt.timeoutMs = effTimeout
|
|
339
|
+
|
|
340
|
+
//dispatch
|
|
341
|
+
emit({ type: 'try', providerId: id, keyIndex, keyId, kind, model })
|
|
342
|
+
let r = await dispatchAi(kind, prompt, attemptOpt)
|
|
343
|
+
|
|
344
|
+
//成功, 推進游標(額度均攤)並回傳
|
|
345
|
+
if (r.ok) {
|
|
346
|
+
if (nk > 0) {
|
|
347
|
+
state.cursors[id] = (keyIndex + 1) % nk
|
|
348
|
+
saveState()
|
|
349
|
+
}
|
|
350
|
+
emit({ type: 'ok', providerId: id, keyIndex, keyId, durationMs: r.durationMs })
|
|
351
|
+
tried.push({ providerId: id, keyIndex, keyId, outcome: 'ok', durationMs: r.durationMs })
|
|
352
|
+
return { ...r, providerId: id, keyIndex, kind, model, tried }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
//失敗分流
|
|
356
|
+
lastResult = r
|
|
357
|
+
lastMeta = { providerId: id, keyIndex, kind, model }
|
|
358
|
+
if (isKeyIndependentFail(r)) {
|
|
359
|
+
|
|
360
|
+
//與金鑰無關, 整組跳過
|
|
361
|
+
emit({ type: 'skip-group', providerId: id, keyIndex, keyId, error: r.error })
|
|
362
|
+
tried.push({ providerId: id, keyIndex, keyId, outcome: 'skip-group', error: r.error, durationMs: r.durationMs })
|
|
363
|
+
skipGroup = true
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
|
|
367
|
+
//其餘(含額度上限/金鑰無效/未分類), 換組內下一把, 不記憶不停用
|
|
368
|
+
emit({ type: 'next-key', providerId: id, keyIndex, keyId, error: r.error })
|
|
369
|
+
tried.push({ providerId: id, keyIndex, keyId, outcome: 'next-key', error: r.error, durationMs: r.durationMs })
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
//全數失敗, 回傳最後一筆失敗結果與完整歷程
|
|
376
|
+
let r = lastResult || getErrorResult('all providers failed')
|
|
377
|
+
return { ...r, ...(lastMeta || {}), tried }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
export default dispatchAiFallback
|
|
382
|
+
</code></pre>
|
|
383
|
+
</article>
|
|
384
|
+
</section>
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<br class="clear">
|
|
394
|
+
|
|
395
|
+
<footer>
|
|
396
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
397
|
+
</footer>
|
|
398
|
+
|
|
399
|
+
<script>prettyPrint();</script>
|
|
400
|
+
<script src="scripts/polyfill.js"></script>
|
|
401
|
+
<script src="scripts/linenumber.js"></script>
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
</body>
|
|
406
|
+
</html>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -177,7 +177,7 @@ export default dispatchClaude
|
|
|
177
177
|
<br class="clear">
|
|
178
178
|
|
|
179
179
|
<footer>
|
|
180
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
180
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
181
181
|
</footer>
|
|
182
182
|
|
|
183
183
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -178,7 +178,7 @@ export default dispatchCodex
|
|
|
178
178
|
<br class="clear">
|
|
179
179
|
|
|
180
180
|
<footer>
|
|
181
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
181
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
182
182
|
</footer>
|
|
183
183
|
|
|
184
184
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -247,7 +247,7 @@ export default dispatchOpencode
|
|
|
247
247
|
<br class="clear">
|
|
248
248
|
|
|
249
249
|
<footer>
|
|
250
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
250
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
251
251
|
</footer>
|
|
252
252
|
|
|
253
253
|
<script>prettyPrint();</script>
|
package/docs/getCliArgs.mjs.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -113,7 +113,7 @@ export default getCliArgs
|
|
|
113
113
|
<br class="clear">
|
|
114
114
|
|
|
115
115
|
<footer>
|
|
116
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
116
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
117
117
|
</footer>
|
|
118
118
|
|
|
119
119
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -102,7 +102,7 @@ export default getErrorResult
|
|
|
102
102
|
<br class="clear">
|
|
103
103
|
|
|
104
104
|
<footer>
|
|
105
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026
|
|
105
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Aug 09 2026 23:14:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
106
106
|
</footer>
|
|
107
107
|
|
|
108
108
|
<script>prettyPrint();</script>
|