w-dispatch-ai 1.0.3 → 1.0.4

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.
@@ -64,6 +64,23 @@ import runFanoutPipeline from './wkf/runFanoutPipeline.mjs'
64
64
  //
65
65
  // 【本函數為同步工廠會throw】providers無效屬設定錯誤, 應於啟動期即失敗(fail fast),
66
66
  // 與各dispatch函數「不reject」之約定不衝突——後者是執行期呼叫, 前者是組裝期設定。
67
+ //
68
+ // 【定義表之鍵名即條目id】該鍵名會成為dispatchAiFallback之條目id(游標鍵與日誌標籤),
69
+ // 須區分到「模型」而非只到「廠商」——鍵名取'claude'則日後無法同時掛sonnet與opus,
70
+ // 且日誌看不出實際用了哪個模型; 同一模型經不同路徑(REST/CLI/不同閘道)取得時,
71
+ // 額度池與故障域各自獨立而屬不同供應商, 鍵名須帶上路徑加以區分。
72
+ // 命名規則與取捨詳見dispatchAiFallback.mjs檔頭之「條目id之設計規則」。
73
+ //
74
+ // 【定義providers時如何選kind: CLI或API】判準是「該階段需不需要工具」:
75
+ // 需要讀本機檔案、grep、執行指令、抓網頁 → CLI類kind(opencode/claude/codex/antigravity);
76
+ // 純文字生成(素材皆已在prompt內) → API類kind(api-openai-compat), 免安裝免登入且較快。
77
+ // 工作流常態是混用: runFanout之候選生成與整合、runRolePipeline之審計修訂等
78
+ // 多屬純文字階段可走API; 唯獨需要實際翻閱專案檔案的階段必須走CLI。
79
+ // API類不支援工具且不會自建工具迴圈, 理由詳見adapters.mjs檔頭之選型判準區塊。
80
+ //
81
+ // 【工具無法向上層轉送】工作流各名額(如runFanout之agents)只是同行程之async函數呼叫,
82
+ // 非獨立agent; 模型回傳之tool_calls受會話束縛而無法外傳給上層agent(如hermes)代跑,
83
+ // 故「讓外殼提供工具給工作流內的模型使用」在本架構下不成立——需要工具就選CLI類kind。
67
84
 
68
85
 
69
86
  /**
@@ -83,11 +100,15 @@ import runFanoutPipeline from './wkf/runFanoutPipeline.mjs'
83
100
  *
84
101
  * import dispatchAiWkf from './src/dispatchAiWkf.mjs'
85
102
  *
103
+ * //定義表之鍵名即dispatchAiFallback之條目id, 須區分到模型而非只到廠商,
104
+ * //同一模型經不同路徑取得時須帶上路徑(REST與CLI屬兩個供應商, 能力與速度皆不同)
86
105
  * let wkf = dispatchAiWkf({
87
106
  * providers: {
88
- * 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
89
- * 'sonnet': { kind: 'claude', model: 'sonnet' },
90
- * 'luna': { kind: 'codex', model: 'gpt-5.6-luna' },
107
+ * 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
108
+ * 'oc:opencode/deepseek-v4-flash-free': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
109
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
110
+ * 'claude:opus': { kind: 'claude', model: 'opus' },
111
+ * 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
91
112
  * },
92
113
  * defaults: { timeoutMs: 300000 },
93
114
  * })
@@ -95,15 +116,19 @@ import runFanoutPipeline from './wkf/runFanoutPipeline.mjs'
95
116
  * let test = async () => {
96
117
  *
97
118
  * //單一名額: 主模型+遞補鏈
98
- * let r1 = await wkf.callAi('只回覆JSON: {"a":1}', { spec: { use: 'deepseek', fallback: ['sonnet'] }, check: (j) => j.a === 1 })
119
+ * let r1 = await wkf.callAi('只回覆JSON: {"a":1}', { spec: { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] }, check: (j) => j.a === 1 })
99
120
  * console.log(r1.ok, r1.json)
100
121
  * // => true { a: 1 }
101
122
  *
102
123
  * //Fanout工作流: 多開執行+單點整合
124
+ * //純文字階段用REST(快), 需要讀專案檔案之階段才用CLI(有工具)
103
125
  * let r2 = await wkf.runFanout({
104
126
  * task: '分析並只回覆JSON: {"essence":"..."}',
105
- * agents: [{ use: 'deepseek', fallback: ['sonnet'] }, { use: 'sonnet' }],
106
- * integrate: { use: 'luna' },
127
+ * agents: [
128
+ * { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
129
+ * { use: 'claude:sonnet' },
130
+ * ],
131
+ * integrate: { use: 'codex:gpt-5.6-luna' },
107
132
  * check: (j) => !!j.essence,
108
133
  * })
109
134
  * console.log(r2.ok, r2.integrated)
@@ -159,7 +184,7 @@ export default dispatchAiWkf
159
184
  <br class="clear">
160
185
 
161
186
  <footer>
162
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
187
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
163
188
  </footer>
164
189
 
165
190
  <script>prettyPrint();</script>
@@ -251,7 +251,7 @@ export default dispatchAntigravity
251
251
  <br class="clear">
252
252
 
253
253
  <footer>
254
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
254
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
255
255
  </footer>
256
256
 
257
257
  <script>prettyPrint();</script>
@@ -74,6 +74,19 @@ import getErrorResult from './getErrorResult.mjs'
74
74
  // 能力(不讀檔不跑指令), 天然無寫檔風險。
75
75
  // 注意: claude與codex走訂閱帳號登入態而非API金鑰, 無法比照, 仍須CLI轉接器。
76
76
  //
77
+ // 【本轉接器不支援工具, 需要工具請改用CLI類kind(2026-08-11實測後之決策)】
78
+ // 閘道端零內建工具: 實測Zen與Agnes皆對`tools:[{type:'web_search'}]`回400並要求
79
+ // function.parameters, 即只接受「呼叫端自行定義且自行執行」之function工具;
80
+ // 協定層雖支援function calling(Zen之nemotron-3-ultra-free與Agnes皆實測回
81
+ // finish_reason:'tool_calls'且tool_calls格式標準), 但工具之定義、執行、錯誤處理與
82
+ // 安全邊界全須本套件自行實作與維護, 等同重造CLI已提供之harness, 故不做。
83
+ // 又tool_calls有會話束縛(tool_call_id須於同一條messages串內回填, 且須保留前文),
84
+ // 該messages串活在本函數單次呼叫之生命週期內, 無法暫停後跨行程外傳給上層agent代跑
85
+ // ——工作流是被上層阻塞呼叫的函式, 沒有反向請求工具的通道; 同理工作流(如runFanout)
86
+ // 之各名額亦只是同行程之async函數呼叫, 無法把tool_calls往上層轉送。
87
+ // 故呼叫端若於body帶入tools, 本函數一律以TOOL_CALLS_UNSUPPORTED回報失敗而不假裝成功
88
+ // (實測Agnes於tool_calls時content為"\n\n"而非null, 不特別處理會靜默回傳空白內容)。
89
+ //
77
90
  // 【重試語意對齊execCli】4xx(429除外)為客戶端錯誤不可重試而立即中止;
78
91
  // 429/5xx/網路錯誤/逾時依maxRetries線性退避重試(間隔retryDelayMs*次數, 上限15000ms)。
79
92
  //
@@ -231,13 +244,31 @@ async function callOnce(url, headers, body, timeoutMs, validator) {
231
244
  }
232
245
  }
233
246
 
234
- //取出choices[0].message.content
247
+ //取出choices[0]
235
248
  let content = null
249
+ let finishReason = ''
250
+ let toolCalls = null
236
251
  try {
237
252
  let j = JSON.parse(txt)
238
253
  content = get(j, 'choices.0.message.content', null)
254
+ finishReason = get(j, 'choices.0.finish_reason', '')
255
+ toolCalls = get(j, 'choices.0.message.tool_calls', null)
239
256
  }
240
257
  catch {}
258
+
259
+ //tool_calls, 本轉接器不支援工具迴圈(見檔頭), 明確回報而不假裝成功
260
+ //(Agnes於tool_calls時content為"\n\n"非null, 不攔截會靜默回傳空白內容)
261
+ if (finishReason === 'tool_calls' || (toolCalls !== null &amp;&amp; toolCalls !== undefined)) {
262
+ return {
263
+ ok: false,
264
+ stdout: '',
265
+ stderr: strTruncate(txt, 1000, optTruncate),
266
+ code: res.status,
267
+ error: 'TOOL_CALLS_UNSUPPORTED: use a cli kind (opencode/claude/codex/antigravity) when tools are needed',
268
+ durationMs,
269
+ }
270
+ }
271
+
241
272
  if (content === null || content === undefined) {
242
273
  return {
243
274
  ok: false,
@@ -283,6 +314,8 @@ async function callOnce(url, headers, body, timeoutMs, validator) {
283
314
  *
284
315
  * 特點:
285
316
  * 免安裝CLI、免預先登入,給baseURL+key+model即可呼叫(如OpenCode Zen、Agnes等OpenAI相容閘道);
317
+ * 僅供純文字生成(摘要、分析、改寫、產出JSON等素材已在prompt內之任務)——
318
+ * 需要讀本機檔案、grep、執行指令、抓網頁等工具能力時,請改用CLI類kind(opencode/claude/codex/antigravity);
286
319
  * prompt走HTTP body,無命令列長度限制;
287
320
  * 錯誤依HTTP狀態碼分流:4xx(429除外)為客戶端錯誤不重試,429/5xx/網路錯誤/逾時依maxRetries線性退避重試;
288
321
  * 結果結構與逾時/驗證失敗之error字樣對齊execCli,可直接作為dispatchAi與dispatchAiFallback之kind('api-openai-compat')使用;
@@ -294,7 +327,7 @@ async function callOnce(url, headers, body, timeoutMs, validator) {
294
327
  * @param {String} opt.model 輸入模型ID字串,例如'deepseek-v4-flash-free'(Zen之模型名不帶opencode/前綴)、'agnes-2.0-flash'
295
328
  * @param {String} [opt.key=''] 輸入API key字串,以Bearer置於Authorization標頭,預設''代表不帶認證標頭
296
329
  * @param {String} [opt.system=''] 輸入system提示詞字串,將以system角色置於messages首位,預設''代表不帶
297
- * @param {Object} [opt.body={}] 輸入額外請求本體物件(如temperature、max_tokens、response_format),將併入預設body(同名鍵以此為準),預設{}
330
+ * @param {Object} [opt.body={}] 輸入額外請求本體物件(如temperature、max_tokens、response_format),將併入預設body(同名鍵以此為準),預設{}。注意本轉接器不支援工具,帶入tools而模型回tool_calls時一律以TOOL_CALLS_UNSUPPORTED回報失敗,需要工具請改用CLI類kind
298
331
  * @param {Object} [opt.headers={}] 輸入額外請求標頭物件,預設{}
299
332
  * @param {Number} [opt.timeoutMs=120000] 輸入逾時毫秒正整數,逾時將中止請求(含回應串流讀取),預設120000
300
333
  * @param {String|Function} [opt.validate=undefined] 輸入回覆內容驗證規則字串或自訂驗證函數,規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接,預設undefined代表不驗證
@@ -470,7 +503,7 @@ export default dispatchApiOpenaiCompat
470
503
  <br class="clear">
471
504
 
472
505
  <footer>
473
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
506
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
474
507
  </footer>
475
508
 
476
509
  <script>prettyPrint();</script>
@@ -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 Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
180
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57: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>
@@ -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 Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
181
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57: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>
@@ -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 Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
250
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57: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>
@@ -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 Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
116
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57: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>
@@ -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 Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
105
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57: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>
package/docs/global.html CHANGED
@@ -214,7 +214,7 @@
214
214
 
215
215
  <dt class="tag-source">Source:</dt>
216
216
  <dd class="tag-source"><ul class="dummy"><li>
217
- <a href="adapters.mjs.html">adapters.mjs</a>, <a href="adapters.mjs.html#line23">line 23</a>
217
+ <a href="adapters.mjs.html">adapters.mjs</a>, <a href="adapters.mjs.html#line57">line 57</a>
218
218
  </li></ul></dd>
219
219
 
220
220
 
@@ -492,7 +492,7 @@ console.log(buildChain(providers, { use: 'a', fallback: ['b', 'c'] }))
492
492
 
493
493
  <dt class="tag-source">Source:</dt>
494
494
  <dd class="tag-source"><ul class="dummy"><li>
495
- <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line57">line 57</a>
495
+ <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line70">line 70</a>
496
496
  </li></ul></dd>
497
497
 
498
498
 
@@ -661,7 +661,7 @@ parse+check接進遞補層之validate——非法回覆視為該家失敗而
661
661
 
662
662
  <dt class="tag-source">Source:</dt>
663
663
  <dd class="tag-source"><ul class="dummy"><li>
664
- <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line124">line 124</a>
664
+ <a href="wkf_callAiWithFallback.mjs.html">wkf/callAiWithFallback.mjs</a>, <a href="wkf_callAiWithFallback.mjs.html#line125">line 125</a>
665
665
  </li></ul></dd>
666
666
 
667
667
 
@@ -712,20 +712,21 @@ parse+check接進遞補層之validate——非法回覆視為該家失敗而
712
712
 
713
713
  import callAiWithFallback from './src/wkf/callAiWithFallback.mjs'
714
714
 
715
+ //鍵名須區分到模型並帶上路徑, 詳見dispatchAiFallback.mjs檔頭之id設計規則
715
716
  let providers = {
716
- 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
717
- 'sonnet': { kind: 'claude', model: 'sonnet' },
717
+ 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
718
+ 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
718
719
  }
719
720
 
720
721
  let test = async () => {
721
722
 
722
723
  let r = await callAiWithFallback('只回覆JSON: {"a":1}', {
723
724
  providers,
724
- spec: { use: 'deepseek', fallback: ['sonnet'] },
725
+ spec: { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
725
726
  check: (j) => j.a === 1,
726
727
  })
727
728
  console.log(r.ok, r.json, r.providerId)
728
- // => true { a: 1 } 'deepseek'
729
+ // => true { a: 1 } 'zen:deepseek-v4-flash-free'
729
730
 
730
731
  }
731
732
  await test()
@@ -1405,7 +1406,7 @@ await test()
1405
1406
 
1406
1407
  <dt class="tag-source">Source:</dt>
1407
1408
  <dd class="tag-source"><ul class="dummy"><li>
1408
- <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line123">line 123</a>
1409
+ <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line136">line 136</a>
1409
1410
  </li></ul></dd>
1410
1411
 
1411
1412
 
@@ -2162,7 +2163,7 @@ providers陣列順序即優先序,排前面的先用;
2162
2163
 
2163
2164
  <dt class="tag-source">Source:</dt>
2164
2165
  <dd class="tag-source"><ul class="dummy"><li>
2165
- <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line152">line 152</a>
2166
+ <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line186">line 186</a>
2166
2167
  </li></ul></dd>
2167
2168
 
2168
2169
 
@@ -2218,21 +2219,29 @@ let test = async () => {
2218
2219
  let r = await dispatchAiFallback('請只回覆兩個字:完成', {
2219
2220
  providers: [
2220
2221
  {
2221
- id: 'deepseek',
2222
+ //id區分到模型且帶路徑: 同一模型經REST與CLI取得屬兩個供應商
2223
+ id: 'zen:deepseek-v4-flash-free',
2224
+ kind: 'api-openai-compat',
2225
+ baseURL: 'https://opencode.ai/zen/v1',
2226
+ model: 'deepseek-v4-flash-free',
2227
+ keys: ['sk-aaa', 'sk-bbb'], //多把金鑰, 某把失敗自動換下一把
2228
+ },
2229
+ {
2230
+ id: 'oc:opencode/deepseek-v4-flash-free', //同一模型之CLI版(有工具, 較慢)
2222
2231
  kind: 'opencode',
2223
2232
  model: 'opencode/deepseek-v4-flash-free',
2224
2233
  provider: 'opencode',
2225
- keys: ['sk-aaa', 'sk-bbb'], //多把金鑰, 某把失敗自動換下一把
2234
+ keys: ['sk-aaa', 'sk-bbb'],
2226
2235
  timeoutMs: 180000,
2227
2236
  },
2228
- { id: 'claude', kind: 'claude', model: 'sonnet' }, //deepseek全敗時遞補
2229
- { id: 'codex', kind: 'codex', model: 'gpt-5.6-luna', sandbox: 'read-only' },
2237
+ { id: 'claude:sonnet', kind: 'claude', model: 'sonnet' }, //以上全敗時遞補
2238
+ { id: 'codex:gpt-5.6-luna', kind: 'codex', model: 'gpt-5.6-luna', sandbox: 'read-only' },
2230
2239
  ],
2231
2240
  budgetMs: 600000,
2232
2241
  onEvent: (ev) => console.log(ev.type, ev.providerId, ev.keyIndex),
2233
2242
  })
2234
2243
  console.log(r.ok, r.providerId, r.keyIndex, r.tried.length)
2235
- // => true 'deepseek' 0 1
2244
+ // => true 'zen:deepseek-v4-flash-free' 0 1
2236
2245
 
2237
2246
  }
2238
2247
  await test()
@@ -2463,7 +2472,7 @@ await test()
2463
2472
  </td>
2464
2473
 
2465
2474
 
2466
- <td class="description last"><p>輸入群組識別字串,游標以此為鍵,多金鑰條目應給予穩定id,預設為條目索引字串</p></td>
2475
+ <td class="description last"><p>輸入群組識別字串,游標以此為鍵、亦為日誌標籤,本套件不解讀其內容。須區分到「模型」而非只到「廠商」(如'claude:sonnet'而非'claude'),同一模型經不同路徑取得時須帶上路徑(如'poolside:laguna-s-2.1'與'or:poolside/laguna-s-2.1:free'),且務必唯一。省略時回退為陣列索引字串——索引是位置不是身分,日後插入條目會令後續條目繼承他人游標進度,故正式設定一律明給。詳見本檔檔頭之id設計規則</p></td>
2467
2476
  </tr>
2468
2477
 
2469
2478
 
@@ -2871,7 +2880,7 @@ defaults為共用呼叫設定,各工作流之callOpt與名額規格可逐項
2871
2880
 
2872
2881
  <dt class="tag-source">Source:</dt>
2873
2882
  <dd class="tag-source"><ul class="dummy"><li>
2874
- <a href="dispatchAiWkf.mjs.html">dispatchAiWkf.mjs</a>, <a href="dispatchAiWkf.mjs.html#line72">line 72</a>
2883
+ <a href="dispatchAiWkf.mjs.html">dispatchAiWkf.mjs</a>, <a href="dispatchAiWkf.mjs.html#line97">line 97</a>
2875
2884
  </li></ul></dd>
2876
2885
 
2877
2886
 
@@ -2922,11 +2931,15 @@ defaults為共用呼叫設定,各工作流之callOpt與名額規格可逐項
2922
2931
 
2923
2932
  import dispatchAiWkf from './src/dispatchAiWkf.mjs'
2924
2933
 
2934
+ //定義表之鍵名即dispatchAiFallback之條目id, 須區分到模型而非只到廠商,
2935
+ //同一模型經不同路徑取得時須帶上路徑(REST與CLI屬兩個供應商, 能力與速度皆不同)
2925
2936
  let wkf = dispatchAiWkf({
2926
2937
  providers: {
2927
- 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
2928
- 'sonnet': { kind: 'claude', model: 'sonnet' },
2929
- 'luna': { kind: 'codex', model: 'gpt-5.6-luna' },
2938
+ 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
2939
+ 'oc:opencode/deepseek-v4-flash-free': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
2940
+ 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
2941
+ 'claude:opus': { kind: 'claude', model: 'opus' },
2942
+ 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
2930
2943
  },
2931
2944
  defaults: { timeoutMs: 300000 },
2932
2945
  })
@@ -2934,15 +2947,19 @@ let wkf = dispatchAiWkf({
2934
2947
  let test = async () => {
2935
2948
 
2936
2949
  //單一名額: 主模型+遞補鏈
2937
- let r1 = await wkf.callAi('只回覆JSON: {"a":1}', { spec: { use: 'deepseek', fallback: ['sonnet'] }, check: (j) => j.a === 1 })
2950
+ let r1 = await wkf.callAi('只回覆JSON: {"a":1}', { spec: { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] }, check: (j) => j.a === 1 })
2938
2951
  console.log(r1.ok, r1.json)
2939
2952
  // => true { a: 1 }
2940
2953
 
2941
2954
  //Fanout工作流: 多開執行+單點整合
2955
+ //純文字階段用REST(快), 需要讀專案檔案之階段才用CLI(有工具)
2942
2956
  let r2 = await wkf.runFanout({
2943
2957
  task: '分析並只回覆JSON: {"essence":"..."}',
2944
- agents: [{ use: 'deepseek', fallback: ['sonnet'] }, { use: 'sonnet' }],
2945
- integrate: { use: 'luna' },
2958
+ agents: [
2959
+ { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
2960
+ { use: 'claude:sonnet' },
2961
+ ],
2962
+ integrate: { use: 'codex:gpt-5.6-luna' },
2946
2963
  check: (j) => !!j.essence,
2947
2964
  })
2948
2965
  console.log(r2.ok, r2.integrated)
@@ -3894,6 +3911,8 @@ await test()
3894
3911
  <dd class="tag-description"><ul class="dummy"><li><p>以fetch直呼OpenAI相容API(chat/completions)呼叫AI模型</p>
3895
3912
  <p>特點:
3896
3913
  免安裝CLI、免預先登入,給baseURL+key+model即可呼叫(如OpenCode Zen、Agnes等OpenAI相容閘道);
3914
+ 僅供純文字生成(摘要、分析、改寫、產出JSON等素材已在prompt內之任務)——
3915
+ 需要讀本機檔案、grep、執行指令、抓網頁等工具能力時,請改用CLI類kind(opencode/claude/codex/antigravity);
3897
3916
  prompt走HTTP body,無命令列長度限制;
3898
3917
  錯誤依HTTP狀態碼分流:4xx(429除外)為客戶端錯誤不重試,429/5xx/網路錯誤/逾時依maxRetries線性退避重試;
3899
3918
  結果結構與逾時/驗證失敗之error字樣對齊execCli,可直接作為dispatchAi與dispatchAiFallback之kind('api-openai-compat')使用;
@@ -3903,7 +3922,7 @@ prompt走HTTP body,無命令列長度限制;
3903
3922
 
3904
3923
  <dt class="tag-source">Source:</dt>
3905
3924
  <dd class="tag-source"><ul class="dummy"><li>
3906
- <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line293">line 293</a>
3925
+ <a href="dispatchApiOpenaiCompat.mjs.html">dispatchApiOpenaiCompat.mjs</a>, <a href="dispatchApiOpenaiCompat.mjs.html#line326">line 326</a>
3907
3926
  </li></ul></dd>
3908
3927
 
3909
3928
 
@@ -4300,7 +4319,7 @@ await test()
4300
4319
  </td>
4301
4320
 
4302
4321
 
4303
- <td class="description last"><p>輸入額外請求本體物件(如temperature、max_tokens、response_format),將併入預設body(同名鍵以此為準),預設{}</p></td>
4322
+ <td class="description last"><p>輸入額外請求本體物件(如temperature、max_tokens、response_format),將併入預設body(同名鍵以此為準),預設{}。注意本轉接器不支援工具,帶入tools而模型回tool_calls時一律以TOOL_CALLS_UNSUPPORTED回報失敗,需要工具請改用CLI類kind</p></td>
4304
4323
  </tr>
4305
4324
 
4306
4325
 
@@ -7096,7 +7115,7 @@ console.log(getErrorResult(null).error)
7096
7115
 
7097
7116
  <dt class="tag-source">Source:</dt>
7098
7117
  <dd class="tag-source"><ul class="dummy"><li>
7099
- <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line56">line 56</a>
7118
+ <a href="dispatchAiFallback.mjs.html">dispatchAiFallback.mjs</a>, <a href="dispatchAiFallback.mjs.html#line82">line 82</a>
7100
7119
  </li></ul></dd>
7101
7120
 
7102
7121
 
@@ -7312,8 +7331,8 @@ console.log(getErrorResult(null).error)
7312
7331
  import runFanout from './src/wkf/runFanout.mjs'
7313
7332
 
7314
7333
  let providers = {
7315
- 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
7316
- 'sonnet': { kind: 'claude', model: 'sonnet' },
7334
+ 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
7335
+ 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
7317
7336
  }
7318
7337
 
7319
7338
  let test = async () => {
@@ -7322,10 +7341,10 @@ let test = async () => {
7322
7341
  providers,
7323
7342
  task: '分析並只回覆JSON: {"essence":"..."}',
7324
7343
  agents: [
7325
- { use: 'deepseek', fallback: ['sonnet'] },
7326
- { use: 'sonnet' },
7344
+ { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
7345
+ { use: 'claude:sonnet' },
7327
7346
  ],
7328
- integrate: { use: 'sonnet' },
7347
+ integrate: { use: 'claude:sonnet' },
7329
7348
  check: (j) => !!j.essence,
7330
7349
  })
7331
7350
  console.log(r.ok, r.integrated, r.candidates.length)
@@ -7861,8 +7880,8 @@ await test()
7861
7880
  import runFanoutPipeline from './src/wkf/runFanoutPipeline.mjs'
7862
7881
 
7863
7882
  let providers = {
7864
- 'sonnet': { kind: 'claude', model: 'sonnet' },
7865
- 'luna': { kind: 'codex', model: 'gpt-5.6-luna' },
7883
+ 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
7884
+ 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
7866
7885
  }
7867
7886
 
7868
7887
  let test = async () => {
@@ -7870,10 +7889,10 @@ let test = async () => {
7870
7889
  let r = await runFanoutPipeline({
7871
7890
  providers,
7872
7891
  task: '分析並只回覆JSON: {"essence":"..."}',
7873
- agents: [{ use: 'sonnet' }, { use: 'luna' }],
7874
- integrate: { use: 'sonnet' },
7892
+ agents: [{ use: 'claude:sonnet' }, { use: 'codex:gpt-5.6-luna' }],
7893
+ integrate: { use: 'claude:sonnet' },
7875
7894
  stages: [
7876
- { id: 'audit', use: 'luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
7895
+ { id: 'audit', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
7877
7896
  ],
7878
7897
  check: (j) => !!j.essence,
7879
7898
  })
@@ -8447,8 +8466,8 @@ await test()
8447
8466
  import runRolePipeline from './src/wkf/runRolePipeline.mjs'
8448
8467
 
8449
8468
  let providers = {
8450
- 'sonnet': { kind: 'claude', model: 'sonnet' },
8451
- 'luna': { kind: 'codex', model: 'gpt-5.6-luna' },
8469
+ 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
8470
+ 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
8452
8471
  }
8453
8472
 
8454
8473
  let test = async () => {
@@ -8457,8 +8476,8 @@ let test = async () => {
8457
8476
  providers,
8458
8477
  input: '原始任務',
8459
8478
  stages: [
8460
- { id: 'draft', use: 'sonnet', prompt: (ctx) => `就「${ctx.input}」寫初稿, 只回覆JSON: {"text":"..."}` },
8461
- { id: 'review', use: 'luna', prompt: (ctx) => `審閱並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.prev)}` },
8479
+ { id: 'draft', use: 'claude:sonnet', prompt: (ctx) => `就「${ctx.input}」寫初稿, 只回覆JSON: {"text":"..."}` },
8480
+ { id: 'review', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審閱並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.prev)}` },
8462
8481
  ],
8463
8482
  })
8464
8483
  console.log(r.ok, r.order, r.failedStage)
@@ -8785,7 +8804,7 @@ await test()
8785
8804
  <br class="clear">
8786
8805
 
8787
8806
  <footer>
8788
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
8807
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
8789
8808
  </footer>
8790
8809
 
8791
8810
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
@@ -146,20 +146,21 @@ function buildChain(providers, spec) {
146
146
  *
147
147
  * import callAiWithFallback from './src/wkf/callAiWithFallback.mjs'
148
148
  *
149
+ * //鍵名須區分到模型並帶上路徑, 詳見dispatchAiFallback.mjs檔頭之id設計規則
149
150
  * let providers = {
150
- * 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
151
- * 'sonnet': { kind: 'claude', model: 'sonnet' },
151
+ * 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
152
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
152
153
  * }
153
154
  *
154
155
  * let test = async () => {
155
156
  *
156
157
  * let r = await callAiWithFallback('只回覆JSON: {"a":1}', {
157
158
  * providers,
158
- * spec: { use: 'deepseek', fallback: ['sonnet'] },
159
+ * spec: { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
159
160
  * check: (j) => j.a === 1,
160
161
  * })
161
162
  * console.log(r.ok, r.json, r.providerId)
162
- * // => true { a: 1 } 'deepseek'
163
+ * // => true { a: 1 } 'zen:deepseek-v4-flash-free'
163
164
  *
164
165
  * }
165
166
  * await test()
@@ -268,7 +269,7 @@ export { buildChain, NO_SIDE_EFFECT }
268
269
  <br class="clear">
269
270
 
270
271
  <footer>
271
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
272
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
272
273
  </footer>
273
274
 
274
275
  <script>prettyPrint();</script>
@@ -167,7 +167,7 @@ export default extractJsonLoose
167
167
  <br class="clear">
168
168
 
169
169
  <footer>
170
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
170
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
171
171
  </footer>
172
172
 
173
173
  <script>prettyPrint();</script>
@@ -110,8 +110,8 @@ ${candidates.map((c, i) => `【候選 ${i + 1}】\n${JSON.stringify(c)}`).join('
110
110
  * import runFanout from './src/wkf/runFanout.mjs'
111
111
  *
112
112
  * let providers = {
113
- * 'deepseek': { kind: 'opencode', model: 'opencode/deepseek-v4-flash-free', provider: 'opencode', keys: ['sk-xxx'] },
114
- * 'sonnet': { kind: 'claude', model: 'sonnet' },
113
+ * 'zen:deepseek-v4-flash-free': { kind: 'api-openai-compat', baseURL: 'https://opencode.ai/zen/v1', model: 'deepseek-v4-flash-free', keys: ['sk-xxx'] },
114
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
115
115
  * }
116
116
  *
117
117
  * let test = async () => {
@@ -120,10 +120,10 @@ ${candidates.map((c, i) => `【候選 ${i + 1}】\n${JSON.stringify(c)}`).join('
120
120
  * providers,
121
121
  * task: '分析並只回覆JSON: {"essence":"..."}',
122
122
  * agents: [
123
- * { use: 'deepseek', fallback: ['sonnet'] },
124
- * { use: 'sonnet' },
123
+ * { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
124
+ * { use: 'claude:sonnet' },
125
125
  * ],
126
- * integrate: { use: 'sonnet' },
126
+ * integrate: { use: 'claude:sonnet' },
127
127
  * check: (j) => !!j.essence,
128
128
  * })
129
129
  * console.log(r.ok, r.integrated, r.candidates.length)
@@ -214,7 +214,7 @@ export { defaultIntegratePrompt }
214
214
  <br class="clear">
215
215
 
216
216
  <footer>
217
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
217
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
218
218
  </footer>
219
219
 
220
220
  <script>prettyPrint();</script>
@@ -87,8 +87,8 @@ import runRolePipeline from './runRolePipeline.mjs'
87
87
  * import runFanoutPipeline from './src/wkf/runFanoutPipeline.mjs'
88
88
  *
89
89
  * let providers = {
90
- * 'sonnet': { kind: 'claude', model: 'sonnet' },
91
- * 'luna': { kind: 'codex', model: 'gpt-5.6-luna' },
90
+ * 'claude:sonnet': { kind: 'claude', model: 'sonnet' },
91
+ * 'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
92
92
  * }
93
93
  *
94
94
  * let test = async () => {
@@ -96,10 +96,10 @@ import runRolePipeline from './runRolePipeline.mjs'
96
96
  * let r = await runFanoutPipeline({
97
97
  * providers,
98
98
  * task: '分析並只回覆JSON: {"essence":"..."}',
99
- * agents: [{ use: 'sonnet' }, { use: 'luna' }],
100
- * integrate: { use: 'sonnet' },
99
+ * agents: [{ use: 'claude:sonnet' }, { use: 'codex:gpt-5.6-luna' }],
100
+ * integrate: { use: 'claude:sonnet' },
101
101
  * stages: [
102
- * { id: 'audit', use: 'luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
102
+ * { id: 'audit', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
103
103
  * ],
104
104
  * check: (j) => !!j.essence,
105
105
  * })
@@ -165,7 +165,7 @@ export default runFanoutPipeline
165
165
  <br class="clear">
166
166
 
167
167
  <footer>
168
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 11 2026 13:18:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
168
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Aug 13 2026 21:57:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
169
169
  </footer>
170
170
 
171
171
  <script>prettyPrint();</script>