w-dispatch-ai 1.0.35 → 1.0.37
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 +13 -7
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +2 -2
- package/docs/adapters.mjs.html +2 -2
- package/docs/budgetFor.mjs.html +2 -2
- package/docs/buildValidator.mjs.html +2 -2
- package/docs/castPintOr.mjs.html +2 -2
- package/docs/checkTruncation.mjs.html +199 -0
- package/docs/dfTimeoutMs.mjs.html +2 -2
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +33 -8
- package/docs/dispatchAiWkf.mjs.html +2 -2
- package/docs/dispatchAntigravity.mjs.html +2 -2
- package/docs/dispatchApiOpenaiCompat.mjs.html +98 -30
- package/docs/dispatchApiOpenaiResponses.mjs.html +102 -25
- package/docs/dispatchApiTypesafeSystemone.mjs.html +23 -15
- 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/getErrorType.mjs.html +6 -4
- package/docs/global.html +1237 -117
- package/docs/index.html +2 -2
- package/docs/quota_dfQuotaTimeoutMs.mjs.html +2 -2
- package/docs/quota_fetchQuotaJson.mjs.html +2 -2
- package/docs/quota_fromCodexUsageHttp.mjs.html +2 -2
- package/docs/quota_getQuotaAntigravity.mjs.html +2 -2
- package/docs/quota_getQuotaClaude.mjs.html +2 -2
- package/docs/quota_getQuotaCodex.mjs.html +2 -2
- package/docs/quota_readJsonOrNull.mjs.html +2 -2
- package/docs/quota_toQuotaLabel.mjs.html +2 -2
- package/docs/quota_toQuotaResult.mjs.html +2 -2
- package/docs/quota_toQuotaScopedLabel.mjs.html +2 -2
- package/docs/quota_toQuotaWindow.mjs.html +2 -2
- package/docs/readEnvFile.mjs.html +2 -2
- package/docs/resolveProviders.mjs.html +3 -3
- package/docs/wkf_callAiWithFallback.mjs.html +63 -20
- package/docs/wkf_createFileStore.mjs.html +2 -2
- package/docs/wkf_createUsageCounter.mjs.html +2 -2
- package/docs/wkf_extractJsonLoose.mjs.html +2 -2
- package/docs/wkf_noSideEffectPrefix.mjs.html +2 -2
- package/docs/wkf_runFanout.mjs.html +4 -2
- package/docs/wkf_runFanoutPipeline.mjs.html +2 -2
- package/docs/wkf_runRolePipeline.mjs.html +2 -2
- package/docs/wkf_salvageTruncatedArray.mjs.html +11 -2
- package/package.json +1 -1
- package/src/checkTruncation.mjs +127 -0
- package/src/dispatchAiFallback.mjs +31 -6
- package/src/dispatchApiOpenaiCompat.mjs +96 -28
- package/src/dispatchApiOpenaiResponses.mjs +100 -23
- package/src/dispatchApiTypesafeSystemone.mjs +21 -13
- package/src/getErrorType.mjs +4 -2
- package/src/providers.mjs +41 -8
- package/src/resolveProviders.mjs +1 -1
- package/src/wkf/callAiWithFallback.mjs +61 -18
- package/src/wkf/runFanout.mjs +2 -0
- package/src/wkf/salvageTruncatedArray.mjs +9 -0
- package/test/tools/fakeServerForApiTest.mjs +79 -2
- package/test/unit-dispatchApiOpenaiCompat.test.mjs +169 -1
- package/test/unit-dispatchApiOpenaiResponses.test.mjs +63 -5
- package/test/unit-dispatchApiTypesafeSystemone.test.mjs +17 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Note:
|
|
|
23
23
|
- `dispatchCodex` needs [OpenAI Codex CLI](https://github.com/openai/codex) (`codex`) in system PATH, and uses its existing login state.
|
|
24
24
|
- `dispatchOpencode` needs [opencode CLI](https://opencode.ai/) (`opencode`) in system PATH. Unlike the other two, it accepts a per-call `key`+`provider`, injected through `OPENCODE_AUTH_CONTENT`, so multiple api keys can be rotated without rewriting `auth.json`.
|
|
25
25
|
- `dispatchAntigravity` needs [Google Antigravity CLI](https://antigravity.google/) (`agy`, not `antigravity`) in system PATH, and uses its existing OAuth login state (first login requires an interactive desktop session). Unlike the other three, agy takes the prompt via the `--print` flag instead of stdin, so the prompt is capped at 30000 chars (Windows command line limit); longer prompts return an error result.
|
|
26
|
-
- `dispatchApiOpenaiCompat` needs **no cli and no login**: it calls any OpenAI-compatible endpoint directly by fetch. Known-working gateways (verified 2026-08-11): [OpenCode Zen](https://opencode.ai/docs/zen) `https://opencode.ai/zen/v1` (same `sk-...` keys as opencode cli, model names without the `opencode/` prefix, e.g. `kimi-k2.7-code`; **its free models reject REST since 2026-09-17 with 403 FreeTierError and must go through the opencode cli instead**) and Agnes `https://apihub.agnes-ai.com/v1` (model `agnes-3.0-flash`). Note claude/codex use subscription login state, not api keys, so they cannot be called this way.
|
|
26
|
+
- `dispatchApiOpenaiCompat` needs **no cli and no login**: it calls any OpenAI-compatible endpoint directly by fetch. Known-working gateways (verified 2026-08-11): [OpenCode Zen](https://opencode.ai/docs/zen) `https://opencode.ai/zen/v1` (same `sk-...` keys as opencode cli, model names without the `opencode/` prefix, e.g. `kimi-k2.7-code`; **most of its free models reject REST since 2026-09-17 with 403 FreeTierError and must go through the opencode cli instead** (the gate is applied per model: `jev-1.13-free` on `/systemone` and `space-bunny-free` were verified to still accept REST)) and Agnes `https://apihub.agnes-ai.com/v1` (model `agnes-3.0-flash`). Note claude/codex use subscription login state, not api keys, so they cannot be called this way.
|
|
27
27
|
- Each cli adapter also accepts an `exe` option to pin the executable path, useful when the CLI is not in PATH (e.g. Windows Task Scheduler environments).
|
|
28
28
|
- For the other three adapters the prompt is always passed through stdin, never as a positional argument, so a prompt of tens of thousands of characters will not cause `ENAMETOOLONG`.
|
|
29
29
|
- All functions never reject. Success or failure is reported by the `ok` and `error` fields of the result object.
|
|
@@ -298,9 +298,10 @@ Codex 0.149 起 Windows 預設走 elevated 沙箱(專用使用者 `CodexSandbo
|
|
|
298
298
|
| `key` | String | `''` | API key,以`Bearer`置於`Authorization`標頭,省略代表不帶認證 |
|
|
299
299
|
| `system` | String | `''` | system提示詞,置於messages首位 |
|
|
300
300
|
| `body` | Object | `{}` | 額外請求本體(`temperature`、`max_tokens`、`response_format`等),同名鍵覆寫預設 |
|
|
301
|
-
| `headers` | Object | `{}` |
|
|
301
|
+
| `headers` | Object | `{}` | 額外請求標頭,同名鍵覆寫預設。**預設帶 `Accept-Encoding: identity`**:伺服器若壓縮了回應卻漏標 `Content-Encoding`,Node 內建 fetch 不會解壓,本轉接器只拿到亂碼而回 `INVALID_RESPONSE`(2026-09-24 使用端回報於 Zen;三個 REST 轉接器同步)。要改回允許壓縮可給 `{ 'Accept-Encoding': 'gzip, deflate, br' }` |
|
|
302
302
|
| `timeoutMs` | Integer | `300000` | 逾時毫秒,逾時中止請求(含回應串流讀取);全套件統一預設 |
|
|
303
|
-
| `maxRetries` | Integer | `0` | 失敗重試次數;**4xx(429除外)
|
|
303
|
+
| `maxRetries` | Integer | `0` | 失敗重試次數;**4xx(429除外)為客戶端錯誤不重試**,截斷(見`acceptTruncated`)亦不重試(同一請求必然再截斷),429/5xx/網路錯誤/逾時線性退避重試 |
|
|
304
|
+
| `acceptTruncated` | Boolean | `false` | **截斷預設失敗**:`finish_reason`為`length`或`content_filter`時,於`validate`之前回`errorType: 'incomplete'`(結果帶`truncated: true`,遞補層整組跳過)。`true`才放行`length`之截斷:有`validate`交其裁決、無則直接接受,結果仍標`truncated: true`;`content_filter`與可見輸出為空者一律失敗(空輸出之訊息附`reasoning_tokens`,常見於推理耗盡`max_tokens`)。`dispatchApiOpenaiResponses`同規則(`status: 'incomplete'`即截斷;`failed`不屬截斷)。工作流層`callAi`之預設見`salvageTruncatedArray`列 |
|
|
304
305
|
| `retryDelayMs` | Integer | `5000` | 重試間隔,實際為`retryDelayMs`×次數且上限15000ms |
|
|
305
306
|
|
|
306
307
|
結果結構對齊execCli:`stdout`為回覆內容、`code`為HTTP狀態碼(網路錯誤/逾時為`null`)、逾時`error`以`TIMEOUT`開頭、驗證失敗為`OUTPUT_VALIDATION_FAILED`——故可直接作為`dispatchAiFallback`條目(`kind: 'api-openai-compat'`,`keys`多金鑰輪替同樣適用)與工作流provider。
|
|
@@ -320,6 +321,7 @@ Codex 0.149 起 Windows 預設走 elevated 沙箱(專用使用者 `CodexSandbo
|
|
|
320
321
|
| `fetch` | 網路層錯誤(DNS/連線拒絕) | api類 |
|
|
321
322
|
| `tool-unsupported` | 模型回tool_calls而api類不支援工具 | api類 |
|
|
322
323
|
| `invalid-response` | 回應結構不合規(缺`choices[0].message.content`、缺`output`陣列,或 systemone 缺`answers`/缺所請求題目之答案) | api類 |
|
|
324
|
+
| `incomplete` | 回應未完整:截斷(`finish_reason`為`length`/`content_filter`、Responses API 之`status: 'incomplete'`;結果帶`truncated: true`)或 Responses API 之其餘非完成狀態(如`failed`,`truncated: false`)。截斷判定只適用 REST 文字類;CLI 類拿不到終止訊號,截斷無從判別(已知限制) | api類 |
|
|
323
325
|
| `aborted` | `shouldStop`中止 | fallback層 |
|
|
324
326
|
| `budget` | 時間預算用盡 | fallback層 |
|
|
325
327
|
|
|
@@ -334,7 +336,7 @@ Codex 0.149 起 Windows 預設走 elevated 沙箱(專用使用者 `CodexSandbo
|
|
|
334
336
|
| `baseURL` | String | `'https://api.typesafe.ai/v1'` | 將於尾端接上`/systemone` |
|
|
335
337
|
| `model` | String | `'jev-latest'` | 另有`'jev-preview'`;回應之實際版本見結果之`modelResolved`(如`'jev-1.13.0'`) |
|
|
336
338
|
| `key` | String | `''` | API key(`.env` 慣用 `TYPESAFE_KEYS`),以`Bearer`置於`Authorization`標頭 |
|
|
337
|
-
| `body`/`headers` | Object | `{}` |
|
|
339
|
+
| `body`/`headers` | Object | `{}` | 額外請求本體/標頭,同名鍵覆寫預設(標頭預設同 `dispatchApiOpenaiCompat` 帶 `Accept-Encoding: identity`) |
|
|
338
340
|
| `timeoutMs`/`validate`/`maxRetries`/`retryDelayMs` | | | 同 `dispatchApiOpenaiCompat`(4xx 除 429 外不重試) |
|
|
339
341
|
|
|
340
342
|
| 題型 `type` | `criteria` | 答案欄位 |
|
|
@@ -415,6 +417,8 @@ let r = await wdi.dispatchAiFallback(state, { providers: jev, questions })
|
|
|
415
417
|
| 執行檔不存在 | `error`含`ENOENT` | 整組跳過 |
|
|
416
418
|
| 參數錯誤 | `code === 2` | 整組跳過 |
|
|
417
419
|
| 輸出未過驗證 | `error === 'OUTPUT_VALIDATION_FAILED'` | 整組跳過 |
|
|
420
|
+
| 截斷(REST 文字類) | `truncated === true` | 整組跳過(同模型同請求換金鑰必然再截斷;`status: 'failed'`不屬此列,照「其餘」換下一把) |
|
|
421
|
+
| 模型回工具呼叫而 api 類不支援 | `error`以`TOOL_CALLS_UNSUPPORTED`開頭 | 整組跳過 |
|
|
418
422
|
| kind無效 | `error`以`unknown ai kind`開頭 | 整組跳過 |
|
|
419
423
|
| 其餘(含額度上限、金鑰無效、服務回錯) | — | 換組內下一把 |
|
|
420
424
|
|
|
@@ -467,7 +471,7 @@ let r = await wdi.dispatchAiFallback(state, { providers: jev, questions })
|
|
|
467
471
|
keyIndex: 1, //實際使用之金鑰索引, 無keys時為null
|
|
468
472
|
kind: 'api-openai-compat',
|
|
469
473
|
model: 'agnes-2.0-flash',
|
|
470
|
-
tried: [ //完整嘗試歷程, 成功時亦回傳; 失敗項另含stdout(被拒回覆)與stderr(錯誤輸出, 皆已截斷)
|
|
474
|
+
tried: [ //完整嘗試歷程, 成功時亦回傳; 失敗項另含stdout(被拒回覆)與stderr(錯誤輸出, 皆已截斷)供診斷; REST文字類各項另帶truncated與finishReason
|
|
471
475
|
{ providerId: 'agnes:agnes-2.0-flash', keyIndex: 0, keyId: 'agnes:agnes-2.0-flash#0', outcome: 'next-key', error: 'HTTP 401', durationMs: 105 },
|
|
472
476
|
{ providerId: 'agnes:agnes-2.0-flash', keyIndex: 1, keyId: 'agnes:agnes-2.0-flash#1', outcome: 'ok', durationMs: 1161 },
|
|
473
477
|
],
|
|
@@ -565,7 +569,7 @@ let wkf2 = wdi.dispatchAiWkf({ providers: table, defaults: {
|
|
|
565
569
|
#### providers.mjs(內建供應商定義檔):
|
|
566
570
|
[src/providers.mjs](https://github.com/yuda-lyu/w-dispatch-ai/blob/master/src/providers.mjs) 收錄各供應商條目(CLI版與REST版),金鑰以 `envVar` 間接引用(機密只放 `.env`),經 `resolveProviders` 展開後即可直接使用或以 `pick` 自選。
|
|
567
571
|
|
|
568
|
-
**zen免費模型清單為「更新日快照」**:`zen:`
|
|
572
|
+
**zen免費模型清單為「更新日快照」**:`zen:` 系起於 2026-08-21 經 `GET /zen/v1/models` 查得之免費模型(`*-free`),其後依實測增刪(最近一次 2026-09-24);因 2026-09-17 起之 Zen 免費層閘門擋下多數對話型免費模型之 REST 直呼,`zen:` 現僅收 REST 實測可通者(`zen:jev-1.13-free`、`zen:space-bunny-free`),其餘免費模型改收 `oc:` 版,增刪經過見 providers.mjs 檔頭之漂移紀錄。不做好用篩選——新模型會上線、舊模型可能下架或限流,**不保證清單即為當前最新可用狀態**;且各模型能力/速度/輸出習慣差異極大(各條目註解記錄已測特性,如批次涵蓋率、實測耗時),由呼叫端自行評估選用。暫時打不通的條目依本套件哲學保留不移除:恢復的偵測就是下次再打一次,`fallback`/`cooldownMs` 即為此而生。
|
|
569
573
|
|
|
570
574
|
```alias
|
|
571
575
|
import wdi from 'w-dispatch-ai'
|
|
@@ -606,6 +610,8 @@ let merged = [...wdi.providers.filter((p) => !extra.some((e) => e.id === p.id)),
|
|
|
606
610
|
let resolved = wdi.resolveProviders(merged, { env, pick: [...] })
|
|
607
611
|
```
|
|
608
612
|
|
|
613
|
+
**推理模型請放寬 `body.max_tokens`**:推理模型的 `max_tokens` 含推理 token(2026-09-24 實測 `space-bunny-free` 列 10 個縣市一題即用 5222,其中推理 4849),照抄上例之 8192 容易截斷;截斷預設判失敗換家(`errorType: 'incomplete'`,見`acceptTruncated`),截斷頻繁等於白白換家;內建之 `zen:space-bunny-free` 即用 32768。
|
|
614
|
+
|
|
609
615
|
**警語:動「輸入」、不要動「回傳」**——把條目 push 進回傳的 `providers` 陣列不會同步進 `table`,兩者當場分歧;合併輸入再呼叫則兩種輸出同源產出、必然一致。另同 id 重複條目屬設定錯誤(共用游標、日誌無法區分),合併時務必如上例先濾再接。
|
|
610
616
|
|
|
611
617
|
**配套工具**(皆為選用,深層引入或由聚合物件取用):
|
|
@@ -615,7 +621,7 @@ let resolved = wdi.resolveProviders(merged, { env, pick: [...] })
|
|
|
615
621
|
| `createFileStore({ dir })` | `dispatchAiFallback`之`store`的檔案持久化——排程任務每次執行都是新行程,記憶體游標/冷卻每次歸零;本實作採**排除式passthrough**(state原封存還,僅剔自用欄位`at`),日後套件擴充state欄位自動相容(殷鑑:白名單store曾把1.0.7新增的`cooling`靜默丟棄) |
|
|
616
622
|
| `createUsageCounter({ dir })` | 逐日逐鍵用量計帳,`onEvent`直接掛進dispatch即於`try`事件記帳;**純觀測絕不據以節流**(額度視窗形態多樣,臆測門檻擋自己的呼叫等同拿猜測當事實);排程環境務必注入`getDate`錨定時區 |
|
|
617
623
|
| `budgetFor(chain)` | 遞補鏈走滿全鏈之時間預算(Σ各條目`timeoutMs`,未帶者以統一預設300000計);與外部排程硬上限取小者交`budgetMs` |
|
|
618
|
-
| `salvageTruncatedArray(text)` | 截斷JSON陣列之前段搶救(救回的每個元素皆完整合法);**不併入預設解析**——「判失敗換家重產」與「搶救前段部分接受」是同一問題的兩種合法策略,組成自訂`parse
|
|
624
|
+
| `salvageTruncatedArray(text)` | 截斷JSON陣列之前段搶救(救回的每個元素皆完整合法);**不併入預設解析**——「判失敗換家重產」與「搶救前段部分接受」是同一問題的兩種合法策略,組成自訂`parse`注入即可。REST 文字類截斷預設失敗,但工作流`callAi`之`acceptTruncated`預設為「有自訂`parse`且非`rawText`」,故注入自訂`parse`即同意接受截斷內容、既有用法不必改;結果之`truncated: true`可辨識救回的是半批;直接呼叫轉接器時須自行給`acceptTruncated: true`。限制:只救頂層元素為物件之陣列,從第一個`[`起算(外包物件如`{"items":[…`會救回內層陣列、前言含`[`會失效) |
|
|
619
625
|
| `NO_SIDE_EFFECT` | 防副作用prompt前綴之單一來源(措辭含唯讀查閱豁免——codex以shell讀檔,一律禁指令等同禁讀檔);工作流`callAi`預設自動掛上,直呼`dispatchAiFallback`者自行前綴 |
|
|
620
626
|
|
|
621
627
|
#### 訂閱額度查詢(quota):`getQuotaClaude`/`getQuotaCodex`/`getQuotaAntigravity`
|