w-dispatch-ai 1.0.7 → 1.0.8

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.
Files changed (51) hide show
  1. package/README.md +27 -1
  2. package/dist/w-dispatch-ai.umd.js +2 -2
  3. package/dist/w-dispatch-ai.umd.js.map +1 -1
  4. package/docs/WDispatchAi.mjs.html +4 -3
  5. package/docs/adapters.mjs.html +2 -2
  6. package/docs/castPintOr.mjs.html +109 -0
  7. package/docs/dfTimeoutMs.mjs.html +2 -2
  8. package/docs/dispatchAi.mjs.html +14 -13
  9. package/docs/dispatchAiFallback.mjs.html +156 -97
  10. package/docs/dispatchAiWkf.mjs.html +2 -2
  11. package/docs/dispatchAntigravity.mjs.html +10 -14
  12. package/docs/dispatchApiOpenaiCompat.mjs.html +50 -64
  13. package/docs/dispatchClaude.mjs.html +8 -13
  14. package/docs/dispatchCodex.mjs.html +8 -13
  15. package/docs/dispatchOpencode.mjs.html +8 -13
  16. package/docs/getCliArgs.mjs.html +2 -2
  17. package/docs/getErrorResult.mjs.html +12 -5
  18. package/docs/getErrorType.mjs.html +171 -0
  19. package/docs/global.html +1275 -88
  20. package/docs/index.html +2 -2
  21. package/docs/resolveProviders.mjs.html +2 -2
  22. package/docs/wkf_callAiWithFallback.mjs.html +14 -10
  23. package/docs/wkf_extractJsonLoose.mjs.html +2 -2
  24. package/docs/wkf_runFanout.mjs.html +8 -8
  25. package/docs/wkf_runFanoutPipeline.mjs.html +14 -18
  26. package/docs/wkf_runRolePipeline.mjs.html +5 -4
  27. package/package.json +1 -1
  28. package/src/WDispatchAi.mjs +2 -1
  29. package/src/castPintOr.mjs +37 -0
  30. package/src/dispatchAi.mjs +12 -11
  31. package/src/dispatchAiFallback.mjs +154 -95
  32. package/src/dispatchAntigravity.mjs +8 -12
  33. package/src/dispatchApiOpenaiCompat.mjs +48 -62
  34. package/src/dispatchClaude.mjs +6 -11
  35. package/src/dispatchCodex.mjs +6 -11
  36. package/src/dispatchOpencode.mjs +6 -11
  37. package/src/getErrorResult.mjs +10 -3
  38. package/src/getErrorType.mjs +99 -0
  39. package/src/wkf/callAiWithFallback.mjs +12 -8
  40. package/src/wkf/runFanout.mjs +6 -6
  41. package/src/wkf/runFanoutPipeline.mjs +12 -16
  42. package/src/wkf/runRolePipeline.mjs +3 -2
  43. package/test/tools/fakeServerForApiTest.mjs +4 -1
  44. package/test/unit-callAiWithFallback.test.mjs +24 -0
  45. package/test/unit-castPintOr.test.mjs +28 -0
  46. package/test/unit-dispatchAiFallback.test.mjs +137 -0
  47. package/test/unit-dispatchApiOpenaiCompat.test.mjs +39 -0
  48. package/test/unit-getErrorResult.test.mjs +13 -1
  49. package/test/unit-getErrorType.test.mjs +36 -0
  50. package/test/unit-runFanout.test.mjs +14 -0
  51. package/test/unit-runRolePipeline.test.mjs +12 -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#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -85,7 +85,7 @@ export default dfTimeoutMs
85
85
  <br class="clear">
86
86
 
87
87
  <footer>
88
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
88
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 15 2026 08:45:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
89
89
  </footer>
90
90
 
91
91
  <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#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -53,18 +53,19 @@ import getErrorResult from './getErrorResult.mjs'
53
53
 
54
54
 
55
55
  /**
56
- * 依供應商種類(kind)分派至對應之CLI轉接器
56
+ * 依供應商種類(kind)分派至對應之轉接器
57
57
  *
58
- * 三種供應商的差異(2026-08-08於本機實測確認):
59
- * opencode支援逐次注入金鑰(OPENCODE_AUTH_CONTENT),故可多把金鑰輪替;
60
- * claudecodex則沿用CLI既有登入狀態,無逐次金鑰概念。
58
+ * kind清單以adapters.mjs對照表為唯一來源(目前為'opencode'、'claude'、'codex'、'antigravity'、
59
+ * 'api-openai-compat',CLI或API之選型判準見adapters.mjs檔頭)
60
+ * 各家金鑰模式不同(2026-08-08起於本機實測確認):opencodeapi-openai-compat支援逐次注入金鑰,
61
+ * 故可多把金鑰輪替;claude/codex/antigravity沿用CLI既有登入狀態,無逐次金鑰概念。
61
62
  * 故「輪替」的單位是「供應商條目」而非單純的金鑰:一個條目即一組(kind, model, 可選的key/provider),
62
- * 輪到誰就用誰的CLI與模型
63
+ * 輪到誰就用誰的轉接器與模型
63
64
  *
64
- * @param {String} kind 輸入供應商種類字串,可選'opencode'、'claude'、'codex'
65
- * @param {String} prompt 輸入提示詞字串,一律以stdin傳入子進程
66
- * @param {Object} [opt={}] 輸入設定物件,原樣轉傳對應轉接器,各轉接器可用設定詳見dispatchOpencode、dispatchClaude、dispatchCodex,預設{}
67
- * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
65
+ * @param {String} kind 輸入供應商種類字串,須為adapters.mjs對照表之鍵名,目前可選'opencode'、'claude'、'codex'、'antigravity'、'api-openai-compat'
66
+ * @param {String} prompt 輸入提示詞字串
67
+ * @param {Object} [opt={}] 輸入設定物件,原樣轉傳對應轉接器,各轉接器可用設定詳見dispatchOpencode、dispatchClaude、dispatchCodex、dispatchAntigravity、dispatchApiOpenaiCompat,預設{}
68
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、errorType(僅失敗時,機器可讀錯誤類別字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
68
69
  * @example
69
70
  * //need claude, codex or opencode cli in system PATH
70
71
  *
@@ -77,8 +78,8 @@ import getErrorResult from './getErrorResult.mjs'
77
78
  * // => true '完成'
78
79
  *
79
80
  * let re = await dispatchAi('gemini', 'abc')
80
- * console.log(re.ok, re.error)
81
- * // => false 'unknown ai kind: "gemini" (available: opencode, claude, codex)'
81
+ * console.log(re.ok, re.error.indexOf('unknown ai kind: "gemini"') === 0)
82
+ * // => false true
82
83
  *
83
84
  * }
84
85
  * await test()
@@ -120,7 +121,7 @@ export default dispatchAi
120
121
  <br class="clear">
121
122
 
122
123
  <footer>
123
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
124
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 15 2026 08:45:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
124
125
  </footer>
125
126
 
126
127
  <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#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -55,6 +55,7 @@ import ispint from 'wsemi/src/ispint.mjs'
55
55
  import cint from 'wsemi/src/cint.mjs'
56
56
  import dispatchAi from './dispatchAi.mjs'
57
57
  import getErrorResult from './getErrorResult.mjs'
58
+ import castPintOr from './castPintOr.mjs'
58
59
  import dfTimeoutMs from './dfTimeoutMs.mjs'
59
60
 
60
61
 
@@ -79,9 +80,23 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
79
80
  // (使用端實測: 一次107秒的多階段請求中72秒耗在重複踩同一組429, 啟用冷卻後降至15秒)。
80
81
  // 設計與「金鑰停用清單」(已否決)的關鍵差異: 以「條目」為單位、短視窗、且「只降序不移除」
81
82
  // ——冷卻中的條目移到鏈尾而非移除, 前面全敗時照樣會被嘗試, 故不存在把已恢復服務冰住的問題;
82
- // 任一次成功立即解除。觸發限於限流(HTTP 429, 僅api-openai-compat可靠偵測; CLI類之限流
83
- // 埋在stderr文字中不納入)與逾時(TIMEOUT開頭, 各kind皆可)兩類——其餘失敗已有換金鑰換家
84
- // 機制處理, 納入反而誤傷。狀態存於state.cooling, 與cursors同走store持久化。
83
+ // 任一次成功立即解除。內建觸發限於限流(HTTP 429, 僅api-openai-compat可靠偵測; CLI類之
84
+ // 限流埋在stderr文字中, 各家字樣不同且隨版本漂移, 本套件不維護簽章表)與逾時(TIMEOUT開頭,
85
+ // 各kind皆可)兩類——其餘失敗已有換金鑰換家機制處理, 納入反而誤傷。
86
+ // CLI類限流之偵測採依賴注入: 呼叫端於實測中觀察到穩定字樣時, 以coolDetect(r)=>Boolean
87
+ // 自行判定(收到完整失敗結果含stderr), 命中即視同冷卻觸發——簽章表由觀察到字樣的呼叫端
88
+ // 維護, 漏判僅退回現狀(每階段重探一次), 誤判也只是降尾非移除, 兩邊代價都有上限。
89
+ // 狀態存於state.cooling, 與cursors同走store持久化。
90
+ //
91
+ // 【中止(shouldStop)】呼叫端(如server於客戶端斷線後)可注入shouldStop()=>Boolean,
92
+ // 於「每次嘗試之間」檢查, true即停止遞補回報ABORTED——把「斷線後仍空耗整條鏈」
93
+ // 縮成「至多再耗當前這一家」。檢查點只此一處: 工作流各層經omit轉傳自動獲得,
94
+ // 中止後每個後續呼叫進門即回ABORTED, 整條工作流自然快速收束, 不需逐層實作。
95
+ // 不中止進行中之嘗試(不殺子進程/不斷開請求), 此為已知設計取捨(避免侵入execCli層)。
96
+ //
97
+ // 【meta保留鍵】「剔除自用鍵後原樣轉傳」令條目即調校點, 但呼叫端放進條目/opt的任何
98
+ // 自有欄位都會被靜默轉傳——保留meta一鍵保證永不轉傳, 呼叫端要掛分類/標籤/註記
99
+ // 一律放meta, 與轉傳機制永久絕緣(工作流各層之規格物件同此約定)。
85
100
  //
86
101
  // 【時間預算】budgetMs限制整輪遞補的總時長, 剩餘預算會壓進每次呼叫的timeoutMs,
87
102
  // 防止多家連續卡逾時而撞破外部排程的執行上限。
@@ -116,11 +131,12 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
116
131
 
117
132
 
118
133
  //fallback層自用之設定鍵, 其餘鍵作為各attempt之共用預設原樣轉傳
119
- let FALLBACK_KEYS = ['providers', 'budgetMs', 'minAttemptMs', 'cooldownMs', 'store', 'onEvent']
134
+ //meta為保留鍵: 呼叫端掛自有資訊(分類/標籤/註記)用, 保證永不轉傳(見檔頭【meta保留鍵】)
135
+ let FALLBACK_KEYS = ['providers', 'budgetMs', 'minAttemptMs', 'cooldownMs', 'shouldStop', 'coolDetect', 'store', 'onEvent', 'meta']
120
136
 
121
137
 
122
138
  //providers條目自用之設定鍵, 其餘鍵(含kind)即該條目之opt原樣轉傳對應轉接器
123
- let ENTRY_KEYS = ['id', 'keys']
139
+ let ENTRY_KEYS = ['id', 'keys', 'meta']
124
140
 
125
141
 
126
142
  //預設值
@@ -132,6 +148,79 @@ let DEFAULT_TIMEOUT_MS = dfTimeoutMs //全套件統一預設300000
132
148
  let memoryState = { cursors: {} }
133
149
 
134
150
 
151
+ /**
152
+ * 初始化游標與冷卻狀態(store有效即載入持久化狀態, 否則用行程內記憶體)
153
+ *
154
+ * @param {Object} store 輸入狀態持久化物件{get,set},無效代表用行程內記憶體
155
+ * @returns {Object} 回傳物件,內含state(狀態物件,保證有cursors與cooling)與saveState(寫回函數,store無效或寫入失敗皆靜默)
156
+ */
157
+ function initState(store) {
158
+ let useStore = isobj(store) &amp;&amp; isfun(store.get) &amp;&amp; isfun(store.set)
159
+ let state = null
160
+ if (useStore) {
161
+ try {
162
+ state = store.get()
163
+ }
164
+ catch {}
165
+ }
166
+ if (!isobj(state)) {
167
+ state = useStore ? { cursors: {} } : memoryState
168
+ }
169
+ if (!isobj(state.cursors)) {
170
+ state.cursors = {}
171
+ }
172
+ if (!isobj(state.cooling)) {
173
+ state.cooling = {}
174
+ }
175
+ let saveState = () => {
176
+ if (useStore) {
177
+ try {
178
+ store.set(state)
179
+ }
180
+ catch {}
181
+ }
182
+ }
183
+ return { state, saveState }
184
+ }
185
+
186
+
187
+ /**
188
+ * 依冷卻狀態重排providers:冷卻中的條目「只降序不移除」——移到鏈尾, 前面全敗時仍會被嘗試,
189
+ * 故不存在把已恢復服務冰住的問題(此為與「金鑰停用清單」的關鍵差異, 後者已被否決)。
190
+ * 僅追蹤有明給id之條目(索引式id會因重排而錯位); 過期紀錄順手清除並寫回
191
+ *
192
+ * @param {Array} providers 輸入供應商條目陣列
193
+ * @param {Object} state 輸入狀態物件(取其cooling)
194
+ * @param {Number} cooldownMs 輸入冷卻視窗毫秒正整數
195
+ * @param {Function} saveState 輸入狀態寫回函數
196
+ * @returns {Array} 回傳重排後之條目陣列(active在前, 冷卻中殿後, 各自保持原相對順序)
197
+ */
198
+ function reorderByCooling(providers, state, cooldownMs, saveState) {
199
+ let now = Date.now()
200
+ let act = []
201
+ let cool = []
202
+ let dirty = false
203
+ for (let p of providers) {
204
+ let pid = get(p, 'id', null)
205
+ let ts = isestr(pid) ? get(state.cooling, pid, null) : null
206
+ if (ispint(ts) &amp;&amp; (now - ts) &lt; cooldownMs) {
207
+ cool.push(p)
208
+ }
209
+ else {
210
+ if (isestr(pid) &amp;&amp; state.cooling[pid] !== undefined) {
211
+ delete state.cooling[pid] //冷卻已過期, 清除
212
+ dirty = true
213
+ }
214
+ act.push(p)
215
+ }
216
+ }
217
+ if (dirty) {
218
+ saveState()
219
+ }
220
+ return [...act, ...cool]
221
+ }
222
+
223
+
135
224
  /**
136
225
  * 判斷失敗結果是否與「哪一把金鑰」無關(換組內金鑰必然再敗, 應整組跳過)
137
226
  *
@@ -193,15 +282,19 @@ function isKeyIndependentFail(r) {
193
282
  * @param {Array} opt.providers 輸入供應商條目物件陣列,順序即優先序。各條目除下列鍵外,其餘鍵(kind、model、exe、provider、config、sandbox、timeoutMs等)即該條目之opt原樣透傳對應轉接器
194
283
  * @param {String} [opt.providers[].id=條目索引字串] 輸入群組識別字串,游標以此為鍵、亦為日誌標籤,本套件不解讀其內容。須區分到「模型」而非只到「廠商」(如'claude:sonnet'而非'claude'),同一模型經不同路徑取得時須帶上路徑(如'poolside:laguna-s-2.1'與'or:poolside/laguna-s-2.1:free'),且務必唯一。省略時回退為陣列索引字串——索引是位置不是身分,日後插入條目會令後續條目繼承他人游標進度,故正式設定一律明給。詳見本檔檔頭之id設計規則
195
284
  * @param {Array} [opt.providers[].keys=[]] 輸入同一服務之多把API key字串陣列,逐次注入輪替(kind為opencode時須同時於條目給予provider),省略代表沿用CLI既有登入狀態之單一虛擬金鑰
285
+ * @param {*} [opt.providers[].meta=undefined] 輸入呼叫端自有資訊(分類、標籤、註記),保留鍵保證永不轉傳對應轉接器——條目其餘鍵一律原樣轉傳,自有欄位放此鍵可與轉傳機制永久絕緣,預設undefined
196
286
  * @param {Number} [opt.budgetMs=null] 輸入整輪遞補之時間上限毫秒正整數,剩餘預算會壓進每次呼叫之timeoutMs,預設null代表不限
197
287
  * @param {Number} [opt.minAttemptMs=20000] 輸入單次嘗試之最低剩餘預算毫秒正整數,剩餘低於此值即停止嘗試回報budget exhausted,預設20000
198
288
  * @param {Object} [opt.store=null] 輸入狀態持久化物件{get:()=>state,set:(state)=>{}},state內含cursors(逐群組游標)與cooling(供應商冷卻時間戳,僅cooldownMs>0時使用),省略代表用行程內記憶體(跨呼叫有效,重啟歸零)。假定單行程序列調用,並行請自行加鎖
199
- * @param {Number} [opt.cooldownMs=0] 輸入供應商冷卻視窗毫秒非負整數,>0啟用:條目(限有明給id者)遭遇限流(HTTP 429,僅api類可偵測)或逾時(TIMEOUT開頭)後,於冷卻視窗內之後續呼叫中被移至鏈尾——只降序不移除,前面全敗時仍會被嘗試,任一次成功立即解除;注意啟用時「providers順序即優先序」會被暫時重排,此即本機制之目的;預設0代表不啟用
200
- * @param {Function} [opt.onEvent=null] 輸入事件回調函數(ev)=>{},ev.type可為'try''ok''next-key''skip-group'、'budget-out';失敗事件(next-key/skip-group)另帶stdout(被拒回覆)stderr(錯誤輸出)供診斷,兩者於失敗路徑已由轉接器截斷;回調拋出例外不影響主流程,預設null
289
+ * @param {Number} [opt.cooldownMs=0] 輸入供應商冷卻視窗毫秒非負整數,>0啟用:條目(限有明給id者)遭遇限流(HTTP 429,僅api類可偵測;CLI類可經coolDetect注入判定)或逾時(TIMEOUT開頭)後,於冷卻視窗內之後續呼叫中被移至鏈尾——只降序不移除,前面全敗時仍會被嘗試,任一次成功立即解除;注意啟用時「providers順序即優先序」會被暫時重排,此即本機制之目的;預設0代表不啟用
290
+ * @param {Function} [opt.coolDetect=null] 輸入冷卻觸發判定函數(r)=>Boolean,收完整失敗結果物件(含stdoutstderrcodeerror),回傳true即視同冷卻觸發(內建429/TIMEOUT觸發不受影響)——CLI類限流埋在stderr且各家字樣不同,簽章表由觀察到字樣的呼叫端維護,如(r)=>/FreeUsageLimitError/i.test(r.stderr||'');僅cooldownMs>0時有效,回調拋出例外視同false,預設null
291
+ * @param {Function} [opt.shouldStop=null] 輸入中止判定函數()=>Boolean,於每次嘗試之間檢查,回傳true即停止遞補回報ABORTED(不中止進行中之嘗試)——供呼叫端於成果已無人接收時(如客戶端斷線)止損;經工作流層原樣轉傳,中止後各後續呼叫進門即回ABORTED令整條工作流快速收束;回調拋出例外視同false,預設null
292
+ * @param {*} [opt.meta=undefined] 輸入呼叫端自有資訊,保留鍵保證永不轉傳各轉接器,預設undefined
293
+ * @param {Function} [opt.onEvent=null] 輸入事件回調函數(ev)=>{},ev.type可為'try'、'ok'、'next-key'、'skip-group'、'budget-out'、'aborted'、'cooled'(冷卻觸發,帶error與cooldownMs,僅cooldownMs>0時出現);失敗事件(next-key/skip-group)另帶errorType、stdout(被拒回覆)與stderr(錯誤輸出)供診斷,後兩者於失敗路徑已由轉接器截斷;回調拋出例外不影響主流程,預設null
201
294
  * @param {Number} [opt.timeoutMs=300000] 輸入各attempt共用之逾時毫秒正整數,條目可覆寫,全套件統一預設300000
202
295
  * @param {String|Function} [opt.validate=undefined] 輸入各attempt共用之stdout驗證規則,條目可覆寫,預設undefined
203
296
  * @param {Number} [opt.maxRetries=0] 輸入各attempt共用之同家重試次數非負整數,韌性建議交給換家而非重試同一家,預設0
204
- * @returns {Promise} 回傳Promise,resolve回傳結果物件,除execCli既有欄位(ok、stdout、stderr、code、error、durationMs、attempts、pid)外,追加providerId(實際使用之群組)、keyIndex(實際使用之金鑰索引,無keys時為null)、kind、model、tried(全部嘗試歷程陣列,成功時亦回傳;失敗項含stdout與stderr供診斷被拒原因),本函數不會reject
297
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,除execCli既有欄位(ok、stdout、stderr、code、error、durationMs、attempts、pid)外,追加providerId(實際使用之群組)、keyIndex(實際使用之金鑰索引,無keys時為null)、kind、model、tried(全部嘗試歷程陣列,成功時亦回傳;失敗項含errorType、stdout與stderr供診斷被拒原因);失敗結果帶機器可讀之errorType(一覽見getErrorType.mjs檔頭);api類轉接器提供usage(token用量)時原樣流出於結果與tried各項,CLI類無此欄;本函數不會reject
205
298
  * @example
206
299
  * //need opencode, claude, codex cli in system PATH
207
300
  *
@@ -258,87 +351,44 @@ async function dispatchAiFallback(prompt, opt = {}) {
258
351
  }
259
352
 
260
353
  //budgetMs, 無效視為不限
261
- let budgetMs = get(opt, 'budgetMs', null)
262
- if (!ispint(budgetMs)) {
263
- budgetMs = null
264
- }
265
- else {
266
- budgetMs = cint(budgetMs)
267
- }
354
+ let budgetMs = castPintOr(get(opt, 'budgetMs', null), null)
268
355
  let deadline = (budgetMs === null) ? null : Date.now() + budgetMs
269
356
 
270
357
  //minAttemptMs, 無效回退預設20000
271
- let minAttemptMs = get(opt, 'minAttemptMs', null)
272
- if (!ispint(minAttemptMs)) {
273
- minAttemptMs = DEFAULT_MIN_ATTEMPT_MS
274
- }
275
- else {
276
- minAttemptMs = cint(minAttemptMs)
358
+ let minAttemptMs = castPintOr(get(opt, 'minAttemptMs', null), DEFAULT_MIN_ATTEMPT_MS)
359
+
360
+ //state與saveState, store須同時具get與set函數才視為有效, 否則用行程內記憶體
361
+ let { state, saveState } = initState(get(opt, 'store', null))
362
+
363
+ //cooldownMs, 無效視為0=不啟用(現行行為零改變)
364
+ let cooldownMs = castPintOr(get(opt, 'cooldownMs', null), 0)
365
+
366
+ //coolDetect, 冷卻觸發之注入判定(CLI類限流簽章由呼叫端維護, 見檔頭), 僅cooldownMs>0時有意義
367
+ let coolDetect = get(opt, 'coolDetect', null)
368
+ if (!isfun(coolDetect)) {
369
+ coolDetect = null
277
370
  }
278
371
 
279
- //store, 須同時具get與set函數才視為有效, 否則用行程內記憶體
280
- let store = get(opt, 'store', null)
281
- let useStore = isobj(store) &amp;&amp; isfun(store.get) &amp;&amp; isfun(store.set)
282
- let state = null
283
- if (useStore) {
372
+ //shouldStop, 中止判定, 於每次嘗試之間檢查; 回調拋出例外視同false(不中止), 不得中斷主流程
373
+ let shouldStop = get(opt, 'shouldStop', null)
374
+ if (!isfun(shouldStop)) {
375
+ shouldStop = null
376
+ }
377
+ let stopRequested = () => {
378
+ if (shouldStop === null) {
379
+ return false
380
+ }
284
381
  try {
285
- state = store.get()
382
+ return shouldStop() === true
286
383
  }
287
- catch {}
288
- }
289
- if (!isobj(state)) {
290
- state = useStore ? { cursors: {} } : memoryState
291
- }
292
- if (!isobj(state.cursors)) {
293
- state.cursors = {}
294
- }
295
- if (!isobj(state.cooling)) {
296
- state.cooling = {}
297
- }
298
- let saveState = () => {
299
- if (useStore) {
300
- try {
301
- store.set(state)
302
- }
303
- catch {}
384
+ catch {
385
+ return false
304
386
  }
305
387
  }
306
388
 
307
- //cooldownMs, 無效視為0=不啟用(現行行為零改變)
308
- let cooldownMs = get(opt, 'cooldownMs', null)
309
- if (!ispint(cooldownMs)) {
310
- cooldownMs = 0
311
- }
312
- else {
313
- cooldownMs = cint(cooldownMs)
314
- }
315
-
316
- //供應商冷卻: 冷卻中的條目「只降序不移除」——移到鏈尾, 前面全敗時仍會被嘗試,
317
- //故不存在把已恢復服務冰住的問題(此為與「金鑰停用清單」的關鍵差異, 後者已被否決)。
318
- //僅追蹤有明給id之條目(索引式id會因重排而錯位); 過期項順手清除
389
+ //供應商冷卻: 冷卻中的條目降至鏈尾(細節見reorderByCooling)
319
390
  if (cooldownMs > 0) {
320
- let now = Date.now()
321
- let act = []
322
- let cool = []
323
- let dirty = false
324
- for (let p of providers) {
325
- let pid = get(p, 'id', null)
326
- let ts = isestr(pid) ? get(state.cooling, pid, null) : null
327
- if (ispint(ts) &amp;&amp; (now - ts) &lt; cooldownMs) {
328
- cool.push(p)
329
- }
330
- else {
331
- if (isestr(pid) &amp;&amp; state.cooling[pid] !== undefined) {
332
- delete state.cooling[pid] //冷卻已過期, 清除
333
- dirty = true
334
- }
335
- act.push(p)
336
- }
337
- }
338
- providers = [...act, ...cool]
339
- if (dirty) {
340
- saveState()
341
- }
391
+ providers = reorderByCooling(providers, state, cooldownMs, saveState)
342
392
  }
343
393
 
344
394
  //onEvent, 回調拋出例外不得中斷主流程
@@ -394,6 +444,13 @@ async function dispatchAiFallback(prompt, opt = {}) {
394
444
  let skipGroup = false
395
445
  for (let a = 0; a &lt; nAttempts &amp;&amp; !skipGroup; a++) {
396
446
 
447
+ //中止檢查(嘗試邊界): 成果已無人接收時止損, 不中止進行中之嘗試(見檔頭【中止】)
448
+ if (stopRequested()) {
449
+ emit({ type: 'aborted', providerId: id, keyIndex: null, keyId: id })
450
+ tried.push({ providerId: id, keyIndex: null, keyId: id, outcome: 'aborted' })
451
+ return { ...getErrorResult('ABORTED', 'aborted'), tried }
452
+ }
453
+
397
454
  //keyIndex, 無keys時為null
398
455
  let keyIndex = (nk > 0) ? (cursor + a) % nk : null
399
456
  let keyId = (keyIndex === null) ? id : `${id}#${keyIndex}`
@@ -405,13 +462,7 @@ async function dispatchAiFallback(prompt, opt = {}) {
405
462
  }
406
463
 
407
464
  //有效timeout, 有預算時以剩餘預算封頂
408
- let effTimeout = get(attemptOpt, 'timeoutMs', null)
409
- if (!ispint(effTimeout)) {
410
- effTimeout = DEFAULT_TIMEOUT_MS
411
- }
412
- else {
413
- effTimeout = cint(effTimeout)
414
- }
465
+ let effTimeout = castPintOr(get(attemptOpt, 'timeoutMs', null), DEFAULT_TIMEOUT_MS)
415
466
  if (deadline !== null) {
416
467
  let remaining = deadline - Date.now()
417
468
 
@@ -419,7 +470,7 @@ async function dispatchAiFallback(prompt, opt = {}) {
419
470
  if (remaining &lt; minAttemptMs) {
420
471
  emit({ type: 'budget-out', providerId: id, keyIndex, keyId, remainingMs: remaining })
421
472
  tried.push({ providerId: id, keyIndex, keyId, outcome: 'budget-out' })
422
- return { ...getErrorResult('budget exhausted'), tried }
473
+ return { ...getErrorResult('budget exhausted', 'budget'), tried }
423
474
  }
424
475
  effTimeout = Math.min(effTimeout, remaining)
425
476
  }
@@ -440,20 +491,28 @@ async function dispatchAiFallback(prompt, opt = {}) {
440
491
  saveState()
441
492
  }
442
493
  emit({ type: 'ok', providerId: id, keyIndex, keyId, durationMs: r.durationMs })
443
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'ok', durationMs: r.durationMs })
494
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'ok', durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
444
495
  return { ...r, providerId: id, keyIndex, kind, model, tried }
445
496
  }
446
497
 
447
498
  //失敗分流
448
499
  lastResult = r
449
500
  lastMeta = { providerId: id, keyIndex, kind, model }
450
- //冷卻觸發: 限流(HTTP 429, 僅api類可偵測)與逾時(TIMEOUT開頭, CLI與api皆可)兩類——
451
- //其餘失敗(金鑰無效/服務端錯誤)已有換金鑰換家機制處理, 納入冷卻反而誤傷
501
+ //冷卻觸發: 內建為限流(HTTP 429, 僅api類可偵測)與逾時(TIMEOUT開頭, CLI與api皆可)——
502
+ //其餘失敗(金鑰無效/服務端錯誤)已有換金鑰換家機制處理, 納入冷卻反而誤傷;
503
+ //CLI類限流簽章經coolDetect注入判定(呼叫端維護, 見檔頭), 拋出例外視同false
452
504
  if (cooldownMs > 0 &amp;&amp; idExplicit) {
453
505
  let isCoolTrigger = (r.code === 429) || (isestr(r.error) &amp;&amp; r.error.indexOf('TIMEOUT') === 0)
506
+ if (!isCoolTrigger &amp;&amp; coolDetect !== null) {
507
+ try {
508
+ isCoolTrigger = coolDetect(r) === true
509
+ }
510
+ catch {}
511
+ }
454
512
  if (isCoolTrigger) {
455
513
  state.cooling[id] = Date.now()
456
514
  saveState()
515
+ emit({ type: 'cooled', providerId: id, keyIndex, keyId, error: r.error, cooldownMs })
457
516
  }
458
517
  }
459
518
 
@@ -462,22 +521,22 @@ async function dispatchAiFallback(prompt, opt = {}) {
462
521
  if (isKeyIndependentFail(r)) {
463
522
 
464
523
  //與金鑰無關, 整組跳過
465
- emit({ type: 'skip-group', providerId: id, keyIndex, keyId, error: r.error, stdout: r.stdout, stderr: r.stderr })
466
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'skip-group', error: r.error, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs })
524
+ emit({ type: 'skip-group', providerId: id, keyIndex, keyId, error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr })
525
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'skip-group', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
467
526
  skipGroup = true
468
527
  }
469
528
  else {
470
529
 
471
530
  //其餘(含額度上限/金鑰無效/未分類), 換組內下一把, 不記憶不停用
472
- emit({ type: 'next-key', providerId: id, keyIndex, keyId, error: r.error, stdout: r.stdout, stderr: r.stderr })
473
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'next-key', error: r.error, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs })
531
+ emit({ type: 'next-key', providerId: id, keyIndex, keyId, error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr })
532
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'next-key', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
474
533
  }
475
534
 
476
535
  }
477
536
  }
478
537
 
479
- //全數失敗, 回傳最後一筆失敗結果與完整歷程
480
- let r = lastResult || getErrorResult('all providers failed')
538
+ //全數失敗, 回傳最後一筆失敗結果(含其errorType)與完整歷程
539
+ let r = lastResult || getErrorResult('all providers failed', 'exec')
481
540
  return { ...r, ...(lastMeta || {}), tried }
482
541
  }
483
542
 
@@ -497,7 +556,7 @@ export default dispatchAiFallback
497
556
  <br class="clear">
498
557
 
499
558
  <footer>
500
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
559
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 15 2026 08:45:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
501
560
  </footer>
502
561
 
503
562
  <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#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -193,7 +193,7 @@ export default dispatchAiWkf
193
193
  <br class="clear">
194
194
 
195
195
  <footer>
196
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
196
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 15 2026 08:45:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
197
197
  </footer>
198
198
 
199
199
  <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#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</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#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</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#extractJsonLoose">extractJsonLoose</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#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -50,11 +50,11 @@ import omit from 'lodash-es/omit.js'
50
50
  import isarr from 'wsemi/src/isarr.mjs'
51
51
  import isbol from 'wsemi/src/isbol.mjs'
52
52
  import isestr from 'wsemi/src/isestr.mjs'
53
- import ispint from 'wsemi/src/ispint.mjs'
54
- import cint from 'wsemi/src/cint.mjs'
55
53
  import execCli from 'wsemi/src/execCli.mjs'
56
54
  import getCliArgs from './getCliArgs.mjs'
57
55
  import getErrorResult from './getErrorResult.mjs'
56
+ import { attachErrorType } from './getErrorType.mjs'
57
+ import castPintOr from './castPintOr.mjs'
58
58
  import dfTimeoutMs from './dfTimeoutMs.mjs'
59
59
 
60
60
 
@@ -181,13 +181,7 @@ async function dispatchAntigravity(prompt, opt = {}) {
181
181
  }
182
182
 
183
183
  //timeoutMs, 先行取值以供printTimeout推導, 無效回退全套件統一預設300000
184
- let timeoutMs = get(opt, 'timeoutMs', null)
185
- if (!ispint(timeoutMs)) {
186
- timeoutMs = DEFAULT_TIMEOUT_MS
187
- }
188
- else {
189
- timeoutMs = cint(timeoutMs)
190
- }
184
+ let timeoutMs = castPintOr(get(opt, 'timeoutMs', null), DEFAULT_TIMEOUT_MS)
191
185
 
192
186
  //printTimeout, 未給時由timeoutMs推導並預留緩衝, 令agy先於外層逾時而回報自身錯誤訊息
193
187
  let printTimeout = get(opt, 'printTimeout', null)
@@ -234,15 +228,17 @@ async function dispatchAntigravity(prompt, opt = {}) {
234
228
  let optCli = omit(opt, OWN_KEYS)
235
229
 
236
230
  //execCli, 不帶input(agy由--print取得prompt); try/catch兜底轉義膨脹等
237
- //前置檢查漏網之spawn同步拋出(如ENAMETOOLONG), 維持不reject契約
231
+ //前置檢查漏網之spawn同步拋出(如ENAMETOOLONG), 維持不reject契約;
232
+ //失敗結果補上機器可讀之errorType(僅機械可判者, 見getErrorType.mjs)
238
233
  try {
239
- return await execCli(exe, args, {
234
+ let r = await execCli(exe, args, {
240
235
  ...optCli,
241
236
  timeoutMs,
242
237
  })
238
+ return attachErrorType(r)
243
239
  }
244
240
  catch (err) {
245
- return getErrorResult(`${err.code || 'UNKNOWN'}: ${err.message}`)
241
+ return getErrorResult(`${err.code || 'UNKNOWN'}: ${err.message}`, 'spawn')
246
242
  }
247
243
  }
248
244
 
@@ -262,7 +258,7 @@ export default dispatchAntigravity
262
258
  <br class="clear">
263
259
 
264
260
  <footer>
265
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Aug 14 2026 15:11:06 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
261
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Aug 15 2026 08:45:29 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
266
262
  </footer>
267
263
 
268
264
  <script>prettyPrint();</script>