Members
NO_SIDE_EFFECT
- Description:
防副作用prompt前綴:禁止寫入類副作用、豁免唯讀查閱
用法:
NO_SIDE_EFFECT + prompt後交dispatchAiFallback; callAiWithFallback預設自動掛上(傳promptPrefix:''關閉), 直接呼叫dispatchAiFallback的呼叫端自行前綴
- Source:
防副作用prompt前綴:禁止寫入類副作用、豁免唯讀查閱
用法:NO_SIDE_EFFECT + prompt後交dispatchAiFallback;
callAiWithFallback預設自動掛上(傳promptPrefix:''關閉),
直接呼叫dispatchAiFallback的呼叫端自行前綴
Example
import NO_SIDE_EFFECT from './src/wkf/noSideEffectPrefix.mjs'
//let r = await dispatchAiFallback(NO_SIDE_EFFECT + prompt, { providers })
WDispatchAi
- Description:
AI供應商分派
- Source:
AI供應商分派
Example
詳見dispatchAi、dispatchAiFallback、dispatchAiWkf、dispatchOpencode、dispatchClaude、dispatchCodex、dispatchAntigravity、dispatchApiOpenaiCompat、dispatchApiOpenaiResponses、dispatchApiTypesafeSystemone、resolveProviders、getQuotaClaude、getQuotaCodex、getQuotaAntigravity範例
adapters
- Description:
各AI供應商種類(kind)對CLI轉接器函數之對照表
本對照表為kind之唯一來源,dispatchAi以其鍵值分派,WDispatchAi以其鍵名產生KINDS, 新增供應商時僅須於此加入一個鍵值對即可
- Source:
各AI供應商種類(kind)對CLI轉接器函數之對照表
本對照表為kind之唯一來源,dispatchAi以其鍵值分派,WDispatchAi以其鍵名產生KINDS, 新增供應商時僅須於此加入一個鍵值對即可
Example
import adapters from './src/adapters.mjs'
console.log(Object.keys(adapters))
// => ['opencode', 'claude', 'codex', 'antigravity', 'api-openai-compat', 'api-openai-responses', 'api-typesafe-systemone']
dfQuotaTimeoutMs
- Description:
額度查詢類函數統一之預設逾時毫秒
- Source:
額度查詢類函數統一之預設逾時毫秒
Example
import dfQuotaTimeoutMs from './src/quota/dfQuotaTimeoutMs.mjs'
console.log(dfQuotaTimeoutMs)
// => 20000
dfTimeoutMs
- Description:
全套件統一之預設逾時毫秒
- Source:
全套件統一之預設逾時毫秒
Example
import dfTimeoutMs from './src/dfTimeoutMs.mjs'
console.log(dfTimeoutMs)
// => 300000
Methods
(async) agyPrint(exe, cmd, o) → {Promise}
- Description:
執行agy之print模式讀取型slash指令並解析其JSON回應
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
exe |
String | 輸入agy執行檔 |
cmd |
String | 輸入slash指令字串, 例如'/usage' |
o |
Object | 輸入設定物件{timeoutMs, cwd, logFile} |
Returns:
回傳物件{ok, data, response, stderr, error, errorType, code}
- Type
- Promise
attachErrorType(r) → {Object}
- Description:
失敗結果補上errorType欄位(已帶有效errorType或成功結果則原樣回傳)
- Source:
Example
import { attachErrorType } from './src/getErrorType.mjs'
console.log(attachErrorType({ ok: false, error: 'TIMEOUT after 10s' }).errorType)
// => 'timeout'
console.log(attachErrorType({ ok: true, stdout: 'abc' }).errorType)
// => undefined
Parameters:
| Name | Type | Description |
|---|---|---|
r |
Object | 輸入結果物件 |
Returns:
回傳結果物件,失敗且未帶errorType時追加之
- Type
- Object
budgetFor(providers) → {Number}
- Description:
計算遞補鏈走滿所需之時間預算(=各條目timeoutMs之總和, 未帶者以套件統一預設計)
特點: 條目timeoutMs取正整數者計入,未帶或無效者以dfTimeoutMs(全套件統一預設300000)計—— 與dispatchAiFallback執行時之實際取值一致,故總和即為走滿全鏈之上限; 輸入非陣列回傳0
- Source:
Example
import budgetFor from './src/budgetFor.mjs'
console.log(budgetFor([{ timeoutMs: 180000 }, { timeoutMs: 240000 }]))
// => 420000
console.log(budgetFor([{ timeoutMs: 180000 }, {}])) //未帶者以套件統一預設300000計
// => 480000
Parameters:
| Name | Type | Description |
|---|---|---|
providers |
Array | 輸入供應商條目物件陣列(dispatchAiFallback之providers) |
Returns:
回傳時間預算毫秒整數
- Type
- Number
buildChain(providers, spec) → {Object}
- Description:
依providers定義表把「名稱規格」展開成dispatchAiFallback的providers陣列
- Source:
Example
import { buildChain } from './src/wkf/callAiWithFallback.mjs'
let providers = { a: { kind: 'claude' }, b: { kind: 'codex' } }
console.log(buildChain(providers, { use: 'a', fallback: ['b', 'c'] }))
// => { chain: [ { id: 'a', kind: 'claude' }, { id: 'b', kind: 'codex' } ], missing: [ 'c' ] }
Parameters:
| Name | Type | Description |
|---|---|---|
providers |
Object | 輸入定義表物件(名稱 → 條目) |
spec |
Object | 輸入名額規格物件{ use, fallback } |
Returns:
回傳物件,內含chain(條目物件陣列,id一律用名稱)與missing(查無定義之名稱字串陣列)
- Type
- Object
buildValidator(rule) → {function|null}
- Description:
建立驗證函式(規則語法同execCli之validate)
特點: 傳入自訂函式時直接使用; 規則字串支援'nonempty'、'json'、'min:100',多規則可用逗號串接(須全部通過); 規則本身無效(如'min:abc')視為驗證失敗而非靜默跳過——避免打錯規則卻以為有在驗
- Source:
Example
import buildValidator from './src/buildValidator.mjs'
let v = buildValidator('nonempty,min:3')
console.log(v('abcd'), v('ab'), v(''))
// => true false false
console.log(buildValidator(null))
// => null
Parameters:
| Name | Type | Description |
|---|---|---|
rule |
String | function | 輸入驗證規則字串('nonempty'、'json'、'min:100', 逗號可串接)或自訂函式 |
Returns:
回傳驗證函式,無有效規則回傳null
- Type
- function | null
(async) callAiWithFallback(prompt, optopt) → {Promise}
- Description:
呼叫一個AI名額(主模型+自帶遞補鏈),回覆經parse+check驗證後回傳結構化結果
特點: spec.use為主模型名稱、spec.fallback為遞補名稱陣列,依序展開為遞補鏈,名稱查無定義即回報錯誤(fail fast); parse+check接進遞補層之validate——非法回覆視為該家失敗而自動換下一家,不把壞結果帶回來; 預設掛防寫檔前綴(promptPrefix傳空字串可關閉); 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need cli in system PATH
import callAiWithFallback from './src/wkf/callAiWithFallback.mjs'
//鍵名須區分到模型並帶上路徑, 詳見dispatchAiFallback.mjs檔頭之id設計規則
let providers = {
'agnes:agnes-3.0-flash': { kind: 'api-openai-compat', baseURL: 'https://apihub.agnes-ai.com/v1', model: 'agnes-3.0-flash', keys: ['sk-xxx'] },
'claude:sonnet': { kind: 'claude', model: 'sonnet' },
}
let test = async () => {
let r = await callAiWithFallback('只回覆JSON: {"a":1}', {
providers,
spec: { use: 'agnes:agnes-3.0-flash', fallback: ['claude:sonnet'] },
check: (j) => j.a === 1,
})
console.log(r.ok, r.json, r.providerId)
// => true { a: 1 } 'agnes:agnes-3.0-flash'
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否取得可用結果布林值)、json(解析後物件,rawText模式下為文字)、providerId(實際使用之名稱)、keyIndex、keyId、ms(總耗時毫秒)、tried(遞補嘗試歷程陣列)、usage(api類之token用量原樣透傳,CLI類為null)、finishReason(REST文字類之正規化終止原因字串,CLI類為'')、truncated(是否為截斷內容布林值,接受搶救時ok亦可能為true)、error(錯誤訊息字串)、errorType(僅失敗時,機器可讀錯誤類別字串,一覽見getErrorType.mjs檔頭),本函數不會reject
- Type
- Promise
(async) callOnce(url, headers, body, timeoutMs, validator, acceptTruncated) → {Promise}
- Description:
單次HTTP呼叫(內部使用, 不含重試邏輯)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
url |
String | 輸入完整端點網址字串 |
headers |
Object | 輸入請求標頭物件 |
body |
Object | 輸入請求本體物件 |
timeoutMs |
Number | 輸入逾時毫秒 |
validator |
function | null | 輸入驗證函式 |
acceptTruncated |
Boolean | 輸入是否明示接受截斷內容 |
Returns:
回傳Promise,resolve回傳結果物件
- Type
- Promise
(async) callOnce(url, headers, body, timeoutMs, validator, acceptTruncated) → {Promise}
- Description:
單次HTTP呼叫(內部使用, 不含重試邏輯)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
url |
String | 輸入完整端點網址字串 |
headers |
Object | 輸入請求標頭物件 |
body |
Object | 輸入請求本體物件 |
timeoutMs |
Number | 輸入逾時毫秒 |
validator |
function | null | 輸入驗證函式 |
acceptTruncated |
Boolean | 輸入是否明示接受截斷內容 |
Returns:
回傳Promise,resolve回傳結果物件
- Type
- Promise
(async) callOnce(url, headers, body, ids, timeoutMs, validator) → {Promise}
- Description:
單次HTTP呼叫(內部使用, 不含重試邏輯)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
url |
String | 輸入完整端點網址字串 |
headers |
Object | 輸入請求標頭物件 |
body |
Object | 輸入請求本體物件 |
ids |
Array | 輸入請求之題目id陣列,用於檢核回應是否每題皆有答案 |
timeoutMs |
Number | 輸入逾時毫秒 |
validator |
function | null | 輸入驗證函式 |
Returns:
回傳Promise,resolve回傳結果物件
- Type
- Promise
castPintOr(v, df) → {Number|*}
- Description:
正整數正規化:有效正整數即轉整數回傳,否則回傳預設值
本套件各層之數值設定(timeoutMs、budgetMs、minAttemptMs、cooldownMs、retryDelayMs等) 皆採同一寬容策略「無效即靜默回退預設」,統一收斂於此, 避免同一7行判斷區塊散落各轉接器(曾重複6處)
- Source:
Example
import castPintOr from './src/castPintOr.mjs'
console.log(castPintOr(5000, 300000))
// => 5000
console.log(castPintOr('abc', 300000))
// => 300000
console.log(castPintOr(undefined, null))
// => null
Parameters:
| Name | Type | Description |
|---|---|---|
v |
* | 輸入待正規化之值 |
df |
* | 輸入無效時之預設值(可為null代表不限) |
Returns:
回傳正規化後之正整數,v非有效正整數時回傳df
- Type
- Number | *
collectAdditional(data) → {Array}
- Description:
取出additional_rate_limits之集合並統一為陣列
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
data |
Object | 輸入回應物件 |
Returns:
回傳單筆物件陣列, 無則[]
- Type
- Array
createFileStore(optopt) → {Object}
- Description:
建立dispatchAiFallback之store的檔案持久化(游標與冷卻跨行程有效)
特點: 採排除式passthrough——state原封存還,僅剔除本層自用欄位(預設只有at:人讀的最後更新時間戳), 日後套件擴充state欄位(如cooling)即自動相容,不需改本函數; 檔案不存在或非法JSON時get回空物件(套件視為全新狀態); stamp可注入時間戳函數(排程環境建議注入時區錨定者),寫入時補進at欄位供人工debug
- Source:
Example
import createFileStore from './src/wkf/createFileStore.mjs'
import dispatchAiFallback from './src/dispatchAiFallback.mjs'
let store = createFileStore({ dir: './state' })
//let r = await dispatchAiFallback(prompt, { providers, store, cooldownMs: 3600000 })
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳store物件,內含get、set(可直接餵dispatchAiFallback之store)與file(狀態檔路徑)
- Type
- Object
createUsageCounter(optopt) → {Object}
- Description:
建立逐日、逐鍵之用量計數器(純觀測, 不參與任何判斷)
特點: onEvent可直接掛進dispatchAiFallback/dispatchAiWkf,於type為'try'時依keyOf取鍵累加—— 嘗試時即記帳,行程被外部時限中途砍掉已發出的請求仍有紀錄; 逐日分桶並僅保留最近keepDays天,避免檔案無限成長; getDate可注入時區錨定之日期函數——預設隨系統時區,排程session之系統時區可能為UTC+0 而使日界錯8小時,排程環境務必注入
- Source:
Example
import createUsageCounter from './src/wkf/createUsageCounter.mjs'
let usage = createUsageCounter({ dir: './state' })
//let r = await dispatchAiFallback(prompt, { providers, onEvent: usage.onEvent })
//console.log(usage.today())
// => { today: '2026-08-18', byKey: { 'agnes:agnes-2.5-flash#0': 3 }, total: 3 }
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳計數器物件,內含onEvent(可直接餵dispatch之onEvent)、bump(手動累加)、today(今日統計)與file(用量檔路徑)
- Type
- Object
decodeJwtPayload(jwt) → {Object|null}
- Description:
解出JWT之payload, 僅供顯示用途, 不驗簽
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
jwt |
String | 輸入JWT字串 |
Returns:
回傳payload物件, 解析失敗回傳null
- Type
- Object | null
defaultIntegratePrompt(candidates, optopt) → {String}
- Description:
預設整合提示詞模板:把成功候選JSON併入整合任務
- Source:
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
candidates |
Array | 輸入成功候選物件陣列 |
||
opt |
Object |
<optional> |
{}
|
輸入設定物件(取schema作為輸出格式示意),預設{} |
Returns:
回傳整合提示詞字串
- Type
- String
describeNonJsonBody(bytes, headers) → {String}
- Description:
組出「HTTP 200但本體非JSON」之錯誤訊息
- Source:
Example
import describeNonJsonBody from './src/describeNonJsonBody.mjs'
let h = new Headers({ 'content-type': 'application/json' })
console.log(describeNonJsonBody(new Uint8Array([0x8b, 0xef, 0x02]), h))
// => 'INVALID_RESPONSE: body is not JSON (3 bytes, first bytes 8bef02, content-encoding=none, content-type=application/json)'
Parameters:
| Name | Type | Description |
|---|---|---|
bytes |
Uint8Array | 輸入回應本體之原始位元組(未解碼) |
headers |
Headers | Object | 輸入回應標頭(fetch之Headers,或具get方法之物件) |
Returns:
回傳錯誤訊息字串,以BODY_NOT_JSON開頭,附位元組數、前16位元組hex、content-encoding與content-type
- Type
- String
directWindow(it) → {Object|null}
- Description:
單筆本身即為窗口時取出之
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
it |
Object | 輸入單筆物件 |
Returns:
回傳窗口物件, 無回傳null
- Type
- Object | null
(async) dispatchAi(kind, prompt, optopt) → {Promise}
- Description:
依供應商種類(kind)分派至對應之轉接器
kind清單以adapters.mjs對照表為唯一來源(目前為'opencode'、'claude'、'codex'、'antigravity'、 'api-openai-compat',CLI或API之選型判準見adapters.mjs檔頭)。 各家金鑰模式不同(2026-08-08起於本機實測確認):opencode與api-openai-compat支援逐次注入金鑰, 故可多把金鑰輪替;claude/codex/antigravity沿用CLI既有登入狀態,無逐次金鑰概念。 故「輪替」的單位是「供應商條目」而非單純的金鑰:一個條目即一組(kind, model, 可選的key/provider), 輪到誰就用誰的轉接器與模型
- Source:
Example
//need claude, codex or opencode cli in system PATH
import dispatchAi from './src/dispatchAi.mjs'
let test = async () => {
let r = await dispatchAi('claude', '請只回覆兩個字:完成', { model: 'sonnet' })
console.log(r.ok, r.stdout.trim())
// => true '完成'
let re = await dispatchAi('gemini', 'abc')
console.log(re.ok, re.error.indexOf('unknown ai kind: "gemini"') === 0)
// => false true
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
kind |
String | 輸入供應商種類字串,須為adapters.mjs對照表之鍵名,目前可選'opencode'、'claude'、'codex'、'antigravity'、'api-openai-compat' |
||
prompt |
String | 輸入提示詞字串 |
||
opt |
Object |
<optional> |
{}
|
輸入設定物件,原樣轉傳對應轉接器,各轉接器可用設定詳見dispatchOpencode、dispatchClaude、dispatchCodex、dispatchAntigravity、dispatchApiOpenaiCompat,預設{} |
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、errorType(僅失敗時,機器可讀錯誤類別字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
- Type
- Promise
(async) dispatchAiFallback(prompt, optopt) → {Promise}
- Description:
依供應商清單順序自動遞補調用AI,組內多金鑰以游標輪替
特點: providers陣列順序即優先序,排前面的先用; 條目本身即該次調用之opt(除id與keys外原樣透傳對應轉接器),與dispatchAi「條目直接當opt」同一約定; 條目給予keys(多把金鑰)時以游標輪替,某把失敗自動換下一把,全數失敗才遞補下一組; 與金鑰無關之失敗(逾時/執行檔不存在/參數錯誤/輸出未過驗證/未知kind/截斷/工具不支援/HTTP 200但本體非JSON)直接整組跳過,不逐把空耗; 跨次執行僅記憶游標(經store注入持久化),不設金鑰停用清單——額度視窗形態多樣(5小時滾動/逐時/逐日), 停用會把已恢復的金鑰閒置,而重探的代價僅一次快速失敗; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need opencode, claude, codex cli in system PATH
import dispatchAiFallback from './src/dispatchAiFallback.mjs'
let test = async () => {
let r = await dispatchAiFallback('請只回覆兩個字:完成', {
providers: [
{
//id區分到模型且帶路徑: 同一模型經REST與CLI取得屬兩個供應商
id: 'agnes:agnes-3.0-flash',
kind: 'api-openai-compat',
baseURL: 'https://apihub.agnes-ai.com/v1',
model: 'agnes-3.0-flash',
keys: ['sk-aaa', 'sk-bbb'], //多把金鑰, 某把失敗自動換下一把
},
{
id: 'oc:opencode/muse-spark-1.3-contributor-free', //CLI版(有工具, 較慢)
kind: 'opencode',
model: 'opencode/muse-spark-1.3-contributor-free',
useStoredAuth: false, //opencode自家免費模型以匿名存取, 不沿用本機auth.json之登入
timeoutMs: 180000,
},
{ id: 'claude:sonnet', kind: 'claude', model: 'sonnet' }, //以上全敗時遞補
{ id: 'codex:gpt-5.6-luna', kind: 'codex', model: 'gpt-5.6-luna', sandbox: 'read-only' },
],
budgetMs: 600000,
onEvent: (ev) => console.log(ev.type, ev.providerId, ev.keyIndex),
})
console.log(r.ok, r.providerId, r.keyIndex, r.tried.length)
// => true 'agnes:agnes-3.0-flash' 0 1
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,一律以stdin傳入子進程 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳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類無此欄;REST文字類轉接器另帶finishReason與truncated(是否截斷),同樣流出於結果與tried各項;本函數不會reject
- Type
- Promise
dispatchAiWkf(opt) → {Object}
- Description:
建立AI工作流執行環境(工廠),注入provider定義表與共用預設後回傳綁定版API
特點: providers為名稱對dispatchAiFallback條目之定義表,之後各工作流以名稱宣告主模型與遞補鏈; defaults為共用呼叫設定,各工作流之callOpt與名額規格可逐項覆寫; 回傳之各函數皆不reject;本工廠為同步函數,providers無效時throw(設定錯誤應於啟動期即失敗)
- Source:
Example
//need cli in system PATH
import dispatchAiWkf from './src/dispatchAiWkf.mjs'
//定義表之鍵名即dispatchAiFallback之條目id, 須區分到模型而非只到廠商,
//同一模型經不同路徑取得時須帶上路徑(REST與CLI屬兩個供應商, 能力與速度皆不同)
let wkf = dispatchAiWkf({
providers: {
'agnes:agnes-3.0-flash': { kind: 'api-openai-compat', baseURL: 'https://apihub.agnes-ai.com/v1', model: 'agnes-3.0-flash', keys: ['sk-xxx'] },
'oc:opencode/muse-spark-1.3-contributor-free': { kind: 'opencode', model: 'opencode/muse-spark-1.3-contributor-free', useStoredAuth: false },
'claude:sonnet': { kind: 'claude', model: 'sonnet' },
'claude:opus': { kind: 'claude', model: 'opus' },
'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
},
defaults: { timeoutMs: 300000 },
})
let test = async () => {
//單一名額: 主模型+遞補鏈
let r1 = await wkf.callAi('只回覆JSON: {"a":1}', { spec: { use: 'agnes:agnes-3.0-flash', fallback: ['claude:sonnet'] }, check: (j) => j.a === 1 })
console.log(r1.ok, r1.json)
// => true { a: 1 }
//Fanout工作流: 多開執行+單點整合
//純文字階段用REST(快), 需要讀專案檔案之階段才用CLI(有工具)
let r2 = await wkf.runFanout({
task: '分析並只回覆JSON: {"essence":"..."}',
agents: [
{ use: 'agnes:agnes-3.0-flash', fallback: ['claude:sonnet'] },
{ use: 'claude:sonnet' },
],
integrate: { use: 'codex:gpt-5.6-luna' },
check: (j) => !!j.essence,
})
console.log(r2.ok, r2.integrated)
// => true true
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object | 輸入設定物件 Properties
|
Returns:
回傳綁定版API物件,內含callAi(單一名額呼叫)、runFanout(多開+整合)、runRolePipeline(串行角色鏈)、runFanoutPipeline(多開+整合+角色鏈)、providers(定義表原樣)
- Type
- Object
(async) dispatchAntigravity(prompt, optopt) → {Promise}
- Description:
以Google Antigravity CLI(agy)呼叫AI模型
特點: prompt作為
--print旗標之值傳遞而非stdin(agy介面如此,塞stdin會進互動模式卡住), 故prompt受命令列長度上限約束,超過30000字元回傳錯誤結果物件; model須為agy models第一欄之slug(如gemini-3.6-flash-low),注意agy錯誤訊息列出的是顯示名稱而非slug; 帶檔位之slug(-high/-medium/-low結尾)與effort同時給定且檔位不一致時agy回conflicts錯誤(一致則放行), effort需agy>=1.1.11,建議搭配不帶檔位之基礎slug(如gemini-3.1-pro)使用; 預設帶--dangerously-skip-permissions令非互動print模式不卡權限確認,可給予skipPermissions為false保留權限閘門; agy自身之--print-timeout未給時由timeoutMs推導並預留30秒緩衝,令CLI先於外層逾時; 沿用agy既有OAuth登入狀態(首次須於桌面互動模式完成登入); 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need agy cli in system PATH, and OAuth login completed
import dispatchAntigravity from './src/dispatchAntigravity.mjs'
let test = async () => {
let r = await dispatchAntigravity('請只回覆兩個字:完成', { model: 'gemini-3.6-flash-low' })
console.log(r.ok, r.stdout.trim())
// => true 完成
//基礎slug搭配effort(不可用帶檔位之slug併用effort)
let r2 = await dispatchAntigravity('請只回覆兩個字:完成', { model: 'gemini-3.1-pro', effort: 'low' })
console.log(r2.ok)
// => true
let re = await dispatchAntigravity('')
console.log(re.ok, re.error)
// => false 'prompt must be a non-empty string'
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,作為 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
- Type
- Promise
(async) dispatchApiOpenaiCompat(prompt, optopt) → {Promise}
- Description:
以fetch直呼OpenAI相容API(chat/completions)呼叫AI模型
特點: 免安裝CLI、免預先登入,給baseURL+key+model即可呼叫(如OpenCode Zen、Agnes等OpenAI相容閘道); 僅供純文字生成(摘要、分析、改寫、產出JSON等素材已在prompt內之任務)—— 需要讀本機檔案、grep、執行指令、抓網頁等工具能力時,請改用CLI類kind(opencode/claude/codex/antigravity); prompt走HTTP body,無命令列長度限制; 錯誤依HTTP狀態碼分流:4xx(429除外)為客戶端錯誤不重試,429/5xx/網路錯誤/逾時依maxRetries線性退避重試; 結果結構與逾時/驗證失敗之error字樣對齊execCli,可直接作為dispatchAi與dispatchAiFallback之kind('api-openai-compat')使用; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need network, no cli required
import dispatchApiOpenaiCompat from './src/dispatchApiOpenaiCompat.mjs'
let test = async () => {
//Agnes
let r1 = await dispatchApiOpenaiCompat('請只回覆兩個字:完成', {
baseURL: 'https://apihub.agnes-ai.com/v1',
key: 'sk-xxxxxx',
model: 'agnes-3.0-flash',
})
console.log(r1.ok, r1.stdout.trim())
// => true 完成
//OpenCode Zen(即opencode CLI之自家閘道), 模型名不帶opencode/前綴; 走此端點者見providers.mjs檔頭之端點表
//注意Zen之免費模型自2026-09-17起多數禁止REST直呼(403 FreeTierError), 該類模型須改走opencode CLI(kind:'opencode');
//閘門係逐模型套用(如space-bunny-free於2026-09-24實測REST仍200), 能否走REST以實測為準, 例外見providers.mjs檔頭
let r2 = await dispatchApiOpenaiCompat('請只回覆兩個字:完成', {
baseURL: 'https://opencode.ai/zen/v1',
key: 'sk-xxxxxx',
model: 'kimi-k2.7-code', //付費模型不受免費層限制
})
console.log(r2.ok, r2.stdout.trim())
// => true 完成
let re = await dispatchApiOpenaiCompat('abc', { baseURL: 'https://apihub.agnes-ai.com/v1', key: 'sk-bad', model: 'agnes-3.0-flash' })
console.log(re.ok, re.code, re.error)
// => false 401 HTTP 401
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,作為user訊息置於HTTP body |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(回覆內容字串)、stderr(失敗時之原始回應本體)、code(HTTP狀態碼,網路錯誤與逾時為null)、error(錯誤訊息字串,成功時為空字串)、errorType(僅失敗時,機器可讀錯誤類別字串,一覽見getErrorType.mjs檔頭)、durationMs(耗時毫秒)、attempts(實際嘗試次數)、usage(原始回應之token用量物件原樣透傳,無則null)、finishReason(已取得回應時之正規化終止原因字串,缺值為'')、truncated(已取得回應時是否被截斷布林值),本函數不會reject
- Type
- Promise
(async) dispatchApiOpenaiResponses(prompt, optopt) → {Promise}
- Description:
以fetch直呼OpenAI Responses API(/responses)呼叫AI模型
特點: 免安裝CLI、免預先登入,給baseURL+key+model即可呼叫(如OpenCode Zen之muse-spark系與GPT系); 端點型別與dispatchApiOpenaiCompat不同——Zen之端點依模型家族而異,收錄前須查官方文件端點欄 (https://opencode.ai/docs/zh-tw/zen/),打錯端點會得到HTTP 500而非404,詳見providers.mjs檔頭; 僅供純文字生成,需要工具能力請改用CLI類kind(opencode/claude/codex/antigravity); status非completed(如max_output_tokens耗盡)一律以INCOMPLETE_RESPONSE回報,不回半截內容; 結果結構與dispatchApiOpenaiCompat完全一致,可直接作為dispatchAi與dispatchAiFallback之kind('api-openai-responses')使用; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need network, no cli required
import dispatchApiOpenaiResponses from './src/dispatchApiOpenaiResponses.mjs'
let test = async () => {
//OpenCode Zen之muse-spark系走/responses(非chat/completions), 詳見providers.mjs檔頭
let r = await dispatchApiOpenaiResponses('請只回覆兩個字:完成', {
baseURL: 'https://opencode.ai/zen/v1',
key: 'sk-xxxxxx',
model: 'muse-spark-1.3-contributor-free',
})
console.log(r.ok, r.stdout.trim())
// => true 完成
let re = await dispatchApiOpenaiResponses('abc', { baseURL: 'https://opencode.ai/zen/v1', key: 'sk-bad', model: 'muse-spark-1.3-contributor-free' })
console.log(re.ok, re.code, re.errorType)
// => false 401 http
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,作為input置於HTTP body |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(回覆內容字串)、stderr(失敗時之原始回應本體)、code(HTTP狀態碼,網路錯誤與逾時為null)、error(錯誤訊息字串,成功時為空字串)、errorType(僅失敗時,機器可讀錯誤類別字串,一覽見getErrorType.mjs檔頭)、durationMs(耗時毫秒)、attempts(實際嘗試次數)、usage(原始回應之token用量物件原樣透傳,欄位名為input_tokens/output_tokens/total_tokens,無則null)、finishReason(已取得回應時之正規化終止原因字串,completed為'stop'、max_output_tokens為'length')、truncated(已取得回應時是否被截斷布林值,僅status為incomplete時為true),本函數不會reject
- Type
- Promise
(async) dispatchApiTypesafeSystemone(prompt, optopt) → {Promise}
- Description:
以fetch直呼TypeSafe AI之System One API(POST /v1/systemone),對prompt(即state)回答型別化問題
特點: 非文字生成——jev等System One模型對state回答呼叫端定義之questions(noul是非題、choice單選題、score量表), 每題回傳受限於選項之答案與機率;官方無CLI,僅此API路徑; prompt即state,結構化內容以JSON.stringify(物件)傳入(實測答案與物件state一致); stdout為answers之JSON字串,另於結果追加answers(已解析物件)與modelResolved(實際模型版本); 請求之題目id於回應缺任一即以INVALID_RESPONSE回報; 不可與文字生成條目混在同一條遞補鏈,經工作流callAi呼叫時須傳promptPrefix:''(理由見檔頭); 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need network and a TypeSafe api key, no cli required
import dispatchApiTypesafeSystemone from './src/dispatchApiTypesafeSystemone.mjs'
let test = async () => {
let r = await dispatchApiTypesafeSystemone('房間浴室水龍頭一直滴水,吵到睡不著', {
key: 'apikey_xxxxxx',
questions: {
category: {
type: 'choice',
instructions: '這則客房訊息屬於哪一類?',
criteria: {
'設備故障報修': '客人回報房間硬體設備損壞、水電問題或故障',
'索取備品': '客人需要毛巾、牙刷、礦泉水等客房備品',
'其他': null,
},
},
urgent: { type: 'noul', instructions: '客人是否表達急迫性?' },
},
})
console.log(r.ok, r.answers.category.choice, r.answers.urgent.noul)
// => true 設備故障報修 0.86 (2026-09-17實測; noul為機率, 每次可能差0.01)
let re = await dispatchApiTypesafeSystemone('abc', { key: 'apikey_bad', questions: { a: { type: 'noul', instructions: 'x?' } } })
console.log(re.ok, re.code, re.errorType)
// => false 401 http
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入被評估之內容字串,作為state置於HTTP body,結構化內容請先JSON.stringify |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(answers之JSON字串)、stderr(失敗時之原始回應本體)、code(HTTP狀態碼,網路錯誤與逾時為null)、error(錯誤訊息字串,成功時為空字串)、errorType(僅失敗時,機器可讀錯誤類別字串,一覽見getErrorType.mjs檔頭)、durationMs(耗時毫秒)、attempts(實際嘗試次數)、usage(原始回應之token用量物件原樣透傳,欄位名為input_tokens/output_tokens,無則null)、answers(成功時為已解析之答案物件,否則null)、modelResolved(回應所載之實際模型版本字串,如'jev-1.13.0',無則''),本函數不會reject
- Type
- Promise
(async) dispatchClaude(prompt, optopt) → {Promise}
- Description:
以Claude Code CLI呼叫Claude模型
特點: prompt一律走stdin而非位置參數,因摘要內文可達數萬字,當命令列參數會spawn ENAMETOOLONG; 沿用Claude Code既有登入狀態,無逐次注入API key之概念,故無key參數; 未給model時不帶
--model旗標,由CLI自行決定使用模型; 預設帶--dangerously-skip-permissions令非互動之-p模式不因權限確認而卡住, 惟prompt含不可信內容(例如待摘要之網頁)時該內容之指示亦將無權限閘門執行, 可給予skipPermissions為false保留CLI權限閘門; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need claude cli in system PATH
import dispatchClaude from './src/dispatchClaude.mjs'
let test = async () => {
let r = await dispatchClaude('請只回覆兩個字:完成', { model: 'sonnet' })
console.log(r.ok, r.stdout.trim())
// => true '完成'
let re = await dispatchClaude('')
console.log(re.ok, re.error)
// => false 'prompt must be a non-empty string'
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,一律以stdin傳入子進程(Claude Code之stdin上限10MB,超過即非零離開) |
|||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
- Type
- Promise
(async) dispatchCodex(prompt, optopt) → {Promise}
- Description:
以OpenAI Codex CLI呼叫GPT模型
特點: prompt一律走stdin而非位置參數,因摘要內文可達數萬字,當命令列參數會spawn ENAMETOOLONG; 固定帶
--skip-git-repo-check,令非git倉庫之工作目錄亦可執行; 沿用Codex CLI既有登入狀態,無逐次注入API key之概念,故無key參數; 未給model時不帶-m旗標,由CLI自行決定使用模型; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need codex cli in system PATH
import dispatchCodex from './src/dispatchCodex.mjs'
let test = async () => {
let r = await dispatchCodex('請只回覆兩個字:完成', { model: 'gpt-5.6-luna', sandbox: 'read-only' })
console.log(r.ok, r.stdout.includes('完成'))
// => true true
let re = await dispatchCodex('abc', { exe: 'codex-not-exist' })
console.log(re.ok, re.error.includes('ENOENT'))
// => false true
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,一律以stdin傳入子進程 |
|||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
- Type
- Promise
(async) dispatchOpencode(prompt, optopt) → {Promise}
- Description:
以opencode CLI呼叫AI模型
特點: prompt一律走stdin而非位置參數,因摘要內文可達數萬字,當命令列參數會spawn ENAMETOOLONG, 而opencode run未帶位置message時即從stdin讀取; 同時給予key與provider時,以OPENCODE_AUTH_CONTENT環境變數逐次注入金鑰, 該注入僅作用於當次子進程且不改寫auth.json,故可多把金鑰輪替並與其他程序並行; 未給key或provider時沿用CLI既有登入狀態(auth.json),useStoredAuth為false則改以匿名存取; 使用opencode未內建之第三方provider時,須另以config給予其provider定義; 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need opencode cli in system PATH
import dispatchOpencode from './src/dispatchOpencode.mjs'
let test = async () => {
//沿用CLI既有登入狀態(auth.json)
let r1 = await dispatchOpencode('請只回覆兩個字:完成', { model: 'opencode/muse-spark-1.3-contributor-free' })
console.log(r1.ok, r1.stdout.includes('完成'))
// => true true
//不沿用本機登入之匿名存取(opencode自家免費模型建議如此, 見檔頭【Zen免費層閘門】)
let r2 = await dispatchOpencode('請只回覆兩個字:完成', {
model: 'opencode/muse-spark-1.3-contributor-free',
useStoredAuth: false,
})
console.log(r2.ok)
// => true
//opencode未內建之第三方provider, 須另以config給予其定義
let r3 = await dispatchOpencode('請只回覆兩個字:完成', {
model: 'agnes-ai/agnes-2.0-flash',
provider: 'agnes-ai',
key: 'sk-xxxxxx',
config: {
provider: {
'agnes-ai': {
npm: '@ai-sdk/openai-compatible',
name: 'Agnes',
options: { baseURL: 'https://apihub.agnes-ai.com/v1' },
models: { 'agnes-2.0-flash': { name: 'Agnes 2.0 Flash' } },
},
},
},
})
console.log(r3.ok)
// => true
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
prompt |
String | 輸入提示詞字串,一律以stdin傳入子進程 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、stdout(標準輸出字串)、stderr(標準錯誤字串)、code(離開碼)、error(錯誤訊息字串,成功時為空字串)、durationMs(耗時毫秒)、attempts(實際嘗試次數),本函數不會reject
- Type
- Promise
extractJsonLoose(text) → {Object|Array|null}
- Description:
從文字中抽取第一個完整的JSON物件或陣列
特點: 先去除ANSI色碼與code fence標記後嘗試整段解析(最常見情境之最快路徑); 整段非法時自第一個
{或[起以括號配對(跳過字串與跳脫)取得第一個完整片段再解析; 僅接受物件與陣列,純量(字串/數字/布林)回傳null; 括號未閉合(輸出被截斷)或片段非法一律回傳null,不throw
- Source:
Example
import extractJsonLoose from './src/wkf/extractJsonLoose.mjs'
console.log(extractJsonLoose('{"a":1}'))
// => { a: 1 }
console.log(extractJsonLoose('說明文字\n```json\n{"a":1}\n```\n後記'))
// => { a: 1 }
console.log(extractJsonLoose('{"a":1')) //截斷
// => null
console.log(extractJsonLoose('純文字回覆'))
// => null
Parameters:
| Name | Type | Description |
|---|---|---|
text |
String | 輸入AI回覆文字字串 |
Returns:
回傳解析成功之物件或陣列,失敗回傳null
- Type
- Object | Array | null
extractOutputText(output) → {String}
- Description:
自Responses API之output陣列取出文字內容(僅取message型元素之output_text)
reasoning型元素為模型思考過程(Zen實測其content為空陣列),不屬回覆內容故略過; 多個message元素依序串接
- Source:
Example
import { extractOutputText } from './src/dispatchApiOpenaiResponses.mjs'
let output = [
{ type: 'reasoning', content: [] },
{ type: 'message', content: [{ type: 'output_text', text: '完成' }] },
]
console.log(extractOutputText(output))
// => '完成'
Parameters:
| Name | Type | Description |
|---|---|---|
output |
Array | 輸入回應之output陣列 |
Returns:
回傳串接後之文字內容,無有效內容回傳空字串
- Type
- String
(async) fetchQuotaJson(url, optopt) → {Promise}
- Description:
各額度查詢端點共用之HTTP請求並解析JSON
本函數不會reject,一律以結果物件之ok與error欄位回報成敗, 並將失敗歸入機器可讀之errorType,令呼叫端無須解析錯誤訊息字串即可分流處置
- Source:
Example
//need network
import fetchQuotaJson from './src/quota/fetchQuotaJson.mjs'
let test = async () => {
let r = await fetchQuotaJson('https://api.anthropic.com/api/oauth/usage', {
headers: { Authorization: 'Bearer xxx' },
})
console.log(r.ok, r.status)
// => false 401
}
test()
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
url |
String | 輸入查詢端點網址字串 |
|||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、status(HTTP狀態碼整數,未連上為0)、data(解析後之JSON物件,失敗為null)、error(錯誤訊息字串,成功時為空字串,已套用redact)、errorType(錯誤類別字串:params/auth/forbidden/ratelimit/http/parse/timeout/network/toolarge)、durationMs(耗時毫秒),本函數不會reject
- Type
- Promise
finOf(r) → {Object}
- Description:
取結果之截斷資訊供tried各項記錄(REST文字類轉接器才帶, 其餘kind回空物件)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
r |
Object | 輸入dispatchAi結果物件 |
Returns:
回傳物件,含truncated與finishReason,或空物件
- Type
- Object
firstStr(o, keys) → {String}
- Description:
取第一個為非空字串之鍵值
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
o |
Object | 輸入物件 |
keys |
Array | 輸入鍵陣列 |
Returns:
回傳字串, 皆無回傳''
- Type
- String
fromBucket(g, b) → {Object|null}
- Description:
將群組內之單一桶正規化為統一窗口物件
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
g |
Object | 輸入群組物件 |
b |
Object | 輸入桶物件 |
Returns:
回傳統一窗口物件, 桶無id或無剩餘比例回傳null
- Type
- Object | null
fromCodexUsageHttp(data) → {Object}
- Description:
將chatgpt.com之/wham/usage回應對映為統一結構
主窗口(rate_limit.primary_window / secondary_window)、程式碼審查限額(code_review_rate_limit)、 以及寬容解析之additional_rate_limits皆納入windows;額外限額之窗口鍵為「<識別>:primary|secondary」, scope為其顯示名或識別。任何欄位缺漏皆略過該窗口而不拋錯
- Source:
Example
import fromCodexUsageHttp from './src/quota/fromCodexUsageHttp.mjs'
let m = fromCodexUsageHttp({
email: 'a@b.c',
plan_type: 'plus',
rate_limit: { primary_window: { used_percent: 42, limit_window_seconds: 18000, reset_after_seconds: 100 } },
additional_rate_limits: [{ limit_name: 'codex-spark', primary_window: { used_percent: 12, limit_window_seconds: 18000 } }],
})
console.log(m.windows.map((w) => `${w.key}=${w.label}`))
// => [ 'primary=5小時', 'codex-spark:primary=5小時(codex-spark)' ]
Parameters:
| Name | Type | Description |
|---|---|---|
data |
Object | 輸入/wham/usage之回應物件 |
Returns:
回傳物件,內含email(字串,無則'')、plan(字串,無則'')、windows(統一窗口陣列)、credits(點數與重置券資訊物件)
- Type
- Object
fromLimitItem(it) → {Object}
- Description:
將limits[]之單筆正規化為統一窗口物件
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
it |
Object | 輸入limits[]之單筆物件 |
Returns:
回傳統一窗口物件
- Type
- Object
fromRpcWindow(o, key, scope) → {Object|null}
- Description:
將app-server之單一窗口(primary/secondary)正規化為統一窗口物件
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
o |
Object | 輸入窗口物件{usedPercent, windowDurationMins, resetsAt} |
key |
String | 輸入窗口鍵字串 |
scope |
String | 輸入範圍字串 |
Returns:
回傳統一窗口物件, 輸入非物件回傳null
- Type
- Object | null
fromTopField(data, key, windowSeconds, scope) → {Object|null}
- Description:
將頂層舊欄位正規化為統一窗口物件
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
data |
Object | 輸入端點回應物件 |
key |
String | 輸入欄位鍵字串 |
windowSeconds |
Number | 輸入窗口長度秒數 |
scope |
String | 輸入適用範圍字串 |
Returns:
回傳統一窗口物件, 該欄位不存在或為null回傳null
- Type
- Object | null
fromWindow(o, key, scope) → {Object|null}
- Description:
將單一窗口{used_percent, limit_window_seconds, reset_at, reset_after_seconds}正規化
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
o |
Object | 輸入窗口物件 |
key |
String | 輸入窗口鍵字串 |
scope |
String | 輸入範圍字串 |
Returns:
回傳統一窗口物件, 輸入非物件回傳null
- Type
- Object | null
getCliArgs(…args) → {Array}
- Description:
將各段命令列參數展平為字串陣列,並濾除非有效字串
各轉接器之參數為「固定旗標」加「可選旗標」加「額外旗標」之組合, 其中可選旗標於未給值時須整段消失(例如未給model就不可出現懸空的
--model), 故呼叫端須以「整段陣列給或不給」表達,本函數僅負責展平與濾除非有效字串,不判斷旗標配對; 過濾之必要在於Nodejs之spawn要求各參數必為字串,混入undefined或數字會直接拋出TypeError, 破壞本套件「不reject、僅以結果物件回報」之約定
- Source:
Example
import getCliArgs from './src/getCliArgs.mjs'
console.log(getCliArgs('-p', ['--model', 'sonnet']))
// => ['-p', '--model', 'sonnet']
console.log(getCliArgs('-p', null, 123, ['', '--verbose']))
// => ['-p', '--verbose']
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
args |
String | Array |
<repeatable> |
輸入參數字串或參數字串陣列,可給多個 |
Returns:
回傳展平且濾除非有效字串後之參數字串陣列
- Type
- Array
getErrorResult(error, errorTypeopt) → {Object}
- Description:
產生與execCli同結構之錯誤結果物件
本套件各dispatch函數一律不reject,參數檢核失敗時即以本函數回傳錯誤結果物件, 其欄位與wsemi之execCli回傳結構一致,故呼叫端可用同一套欄位判斷成敗, 無須區分「參數錯誤」與「CLI執行失敗」兩種來源
- Source:
Example
import getErrorResult from './src/getErrorResult.mjs'
console.log(getErrorResult('prompt must be a non-empty string'))
// => { ok: false, stdout: '', stderr: '', code: null, error: 'prompt must be a non-empty string', errorType: 'params', durationMs: 0, attempts: 0 }
console.log(getErrorResult(null).error)
// => 'unknown error'
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
error |
String | 輸入錯誤訊息字串 |
||
errorType |
String |
<optional> |
'params'
|
輸入機器可讀之錯誤類別字串(一覽見getErrorType.mjs檔頭),預設'params'(參數/設定檢核失敗) |
Returns:
回傳結果物件,內含ok(布林值,恆為false)、stdout(空字串)、stderr(空字串)、code(null)、error(錯誤訊息字串)、errorType(錯誤類別字串)、durationMs(0)、attempts(0)
- Type
- Object
getErrorType(r) → {String}
- Description:
由失敗結果物件推導機器可讀之errorType(僅機械可判者,其餘歸'exec',不猜測)
判準與dispatchAiFallback之isKeyIndependentFail同組:error以TIMEOUT開頭為'timeout'、 含ENOENT或ENAMETOOLONG為'spawn'、恰為OUTPUT_VALIDATION_FAILED為'validation', 其餘失敗一律'exec'(各家CLI字樣不同且隨版本漂移,不維護簽章表)
- Source:
Example
import getErrorType from './src/getErrorType.mjs'
console.log(getErrorType({ error: 'TIMEOUT after 300s' }))
// => 'timeout'
console.log(getErrorType({ error: 'spawn cli ENOENT' }))
// => 'spawn'
console.log(getErrorType({ error: 'Exit code 1' }))
// => 'exec'
Parameters:
| Name | Type | Description |
|---|---|---|
r |
Object | 輸入失敗結果物件(取其error欄位判別) |
Returns:
回傳errorType字串
- Type
- String
(async) getQuotaAntigravity(emailopt, optopt) → {Promise}
- Description:
查詢Antigravity(Google側, agy CLI)帳號之當前額度
以agy自身之print模式取得額度(官方、無頭、不碰憑證):
agy -p "/usage" --output-format json, 並以同一次執行之--log-file取得登入帳號email。 額度綁定本機agy之登入憑證,email參數之作用為比對——不符時ok為false且matched為false; 未給email時不比對。本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need agy cli (>=1.1.11) logged in
import getQuotaAntigravity from './src/quota/getQuotaAntigravity.mjs'
let test = async () => {
let r = await getQuotaAntigravity('firsemisphere@gmail.com')
console.log(r.ok, r.email)
// => true firsemisphere@gmail.com
console.log(r.windows.map((w) => `${w.label}:${w.remainingPercent}%`).join(' '))
// => 7天(Gemini Models):85% 5小時(Gemini Models):90% 7天(Claude and GPT models):100% 5小時(Claude and GPT models):100% (即時值)
}
test()
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
email |
String |
<optional> |
''
|
輸入欲查詢之帳號email字串,預設''代表不比對而直接回報本機當前帳號 |
||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok、provider('antigravity')、email(agy登入帳號)、matched、plan(空字串,/usage不提供方案別)、planTier(空字串)、source('agy-print-usage')、windows(每群組之5小時與7天窗口,scope為群組名)、credits(withCredits時為{remainingCredits, upgradeUri},否則null)、raw(含agy版本、authMethod、原始回應)、error、errorType、durationMs,本函數不會reject
- Type
- Promise
(async) getQuotaClaude(emailopt, optopt) → {Promise}
- Description:
查詢Claude(Claude Code訂閱)帳號之當前額度
額度綁定本機Claude Code之登入憑證,無「給email查任意帳號」之公開介面, 故email參數之作用為比對——查出本機實際登入者後與其核對, 不符時ok為false且matched為false,並於error載明本機實際登入之帳號; 未給email時不比對,直接回報本機當前帳號之額度。 本函數唯讀憑證、不自行刷新權杖(理由見檔頭);不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need claude code logged in
import getQuotaClaude from './src/quota/getQuotaClaude.mjs'
let test = async () => {
let r = await getQuotaClaude('firsemisphere2@gmail.com')
console.log(r.ok, r.plan)
// => true max
console.log(r.windows[0].label, r.windows[0].usedPercent)
// => 5小時 11 (百分比為查詢當下之即時值, 每次不同)
}
test()
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
email |
String |
<optional> |
''
|
輸入欲查詢之帳號email字串,預設''代表不比對而直接回報本機當前帳號 |
|||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok、provider('claude')、email(本機實際登入帳號)、matched、plan(方案別,例如'max')、planTier(級距,例如'default_claude_max_20x')、source('anthropic-oauth-usage-api')、windows(額度窗口陣列,含5小時、7天、7天模型別)、credits(額外用量與花費資訊)、raw(原始回應)、error、errorType、durationMs,本函數不會reject
- Type
- Promise
(async) getQuotaCodex(emailopt, optopt) → {Promise}
- Description:
查詢Codex(ChatGPT訂閱)帳號之當前額度
主路徑以codex app-server之JSON-RPC取得帳號與額度(認證與刷新由codex自理), codex不存在或RPC失敗時退回直打chatgpt.com之用量端點。 額度綁定本機Codex CLI之登入憑證,無「給email查任意帳號」之公開介面, 故email參數之作用為比對——不符時ok為false且matched為false,並於error載明本機實際登入之帳號; 未給email時不比對。本函數不會reject,一律以結果物件之ok與error欄位回報成敗
- Source:
Example
//need codex cli logged in
import getQuotaCodex from './src/quota/getQuotaCodex.mjs'
let test = async () => {
let r = await getQuotaCodex('firsemisphere@gmail.com')
console.log(r.ok, r.plan, r.source)
// => true plus codex-app-server
console.log(r.windows[0].label, r.windows[0].usedPercent)
// => 5小時 37 (百分比為查詢當下之即時值, 每次不同)
}
test()
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
email |
String |
<optional> |
''
|
輸入欲查詢之帳號email字串,預設''代表不比對而直接回報本機當前帳號 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok、provider('codex')、email、matched、plan(例如'plus')、planTier(空字串)、source('codex-app-server'或'chatgpt-wham-usage-api')、windows(含5小時與7天,另有依limitId分列之窗口)、credits(點數、重置券清單、限額狀態)、raw、error、errorType、durationMs,本函數不會reject
- Type
- Promise
initState(store) → {Object}
- Description:
初始化游標與冷卻狀態(store有效即載入持久化狀態, 否則用行程內記憶體)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
store |
Object | 輸入狀態持久化物件{get,set},無效代表用行程內記憶體 |
Returns:
回傳物件,內含state(狀態物件,保證有cursors與cooling)與saveState(寫回函數,store無效或寫入失敗皆靜默)
- Type
- Object
isKeyIndependentFail(r) → {Boolean}
- Description:
判斷失敗結果是否與「哪一把金鑰」無關(換組內金鑰必然再敗, 應整組跳過)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
r |
Object | 輸入dispatchAi失敗結果物件 |
Returns:
回傳是否應整組跳過之布林值
- Type
- Boolean
judgeTruncated(o) → {Object}
- Description:
裁定一次已截斷之回應應失敗或放行(呼叫端先判定truncated為true才呼叫)
- Source:
Example
import { judgeTruncated } from './src/checkTruncation.mjs'
console.log(judgeTruncated({ finishReason: 'length', content: '', reasoningTokens: 600, label: 'finish_reason=length' }).error)
// => 'INCOMPLETE_RESPONSE: finish_reason=length; no visible output (reasoning may have used up the output token limit, reasoning_tokens=600)'
console.log(judgeTruncated({ finishReason: 'length', content: '[{"a":1},', acceptTruncated: true, label: 'finish_reason=length' }).accept)
// => true
Parameters:
| Name | Type | Description | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
o |
Object | 輸入設定物件 Properties
|
Returns:
回傳物件,內含accept(是否放行布林值)、error(不放行時之錯誤訊息字串)、threw(validate拋錯訊息字串)
- Type
- Object
normalizeFinishReason(v) → {String}
- Description:
正規化終止原因字串(去頭尾空白、轉小寫;非字串回傳空字串)
- Source:
Example
import { normalizeFinishReason } from './src/checkTruncation.mjs'
console.log(normalizeFinishReason(' LENGTH '), normalizeFinishReason(null))
// => 'length' ''
Parameters:
| Name | Type | Description |
|---|---|---|
v |
* | 輸入原始終止原因 |
Returns:
回傳正規化字串
- Type
- String
pickWindow(it, snake, camel) → {Object|null}
- Description:
自單筆取出指定窗口(頂層、rate_limit、rateLimit之下, snake或camel)
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
it |
Object | 輸入單筆物件 |
snake |
String | 輸入snake鍵, 例如'primary_window' |
camel |
String | 輸入camel鍵, 例如'primaryWindow' |
Returns:
回傳窗口物件, 無回傳null
- Type
- Object | null
(async) readBodyCapped(r, maxBytes) → {Promise}
- Description:
讀取回應本文, 超過上限即中止
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
r |
Object | 輸入fetch之Response |
maxBytes |
Number | 輸入上限位元組 |
Returns:
回傳物件{ok, text, bytes}, 超限時ok為false且bytes為已讀位元組(或content-length)
- Type
- Promise
readEnvFile(file) → {Object}
- Description:
讀取.env檔為鍵值物件(金鑰來源, 不寫入process.env)
特點: 只解析
KEY=value形式(KEY限英數底線),忽略空行、註解與無效行; value修剪空白並剝除成對之首尾引號(單雙引號皆可); 檔案不存在或不可讀一律回空物件不throw(金鑰缺失交由resolveProviders之skipped回報)
- Source:
Example
import readEnvFile from './src/readEnvFile.mjs'
import resolveProviders from './src/resolveProviders.mjs'
import providersAll from './src/providers.mjs'
//金鑰放.env(變數值以逗號分隔多把), 讀成物件交resolveProviders, 不污染process.env
let env = readEnvFile('./.env')
let { providers, skipped } = resolveProviders(providersAll, { env, pick: ['agnes:agnes-3.0-flash'] })
Parameters:
| Name | Type | Description |
|---|---|---|
file |
String | 輸入.env檔案路徑字串 |
Returns:
回傳鍵值物件(變數名 → 字串值),讀取失敗回空物件
- Type
- Object
readJsonOrNull(fp) → {Object|Array|null}
- Description:
讀取JSON檔,任何失敗一律回傳null
檔案不存在、無讀取權限、內容非合法JSON三種情形對呼叫端而言皆為「取不到」, 無須區分,故一律回null,由呼叫端決定後續處置(例如回報未登入)
- Source:
Example
import readJsonOrNull from './src/quota/readJsonOrNull.mjs'
console.log(readJsonOrNull('./package.json') !== null)
// => true
console.log(readJsonOrNull('./no-such-file.json'))
// => null
Parameters:
| Name | Type | Description |
|---|---|---|
fp |
String | 輸入檔案路徑字串 |
Returns:
回傳解析後之JSON值,讀取或解析失敗回傳null
- Type
- Object | Array | null
redactText(text, redact) → {String}
- Description:
將文字中之機密值替換為遮蔽字
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
text |
String | 輸入文字 |
redact |
Array | 輸入機密值字串陣列 |
Returns:
回傳替換後文字
- Type
- String
reorderByCooling(providers, state, cooldownMs, saveState) → {Array}
- Description:
依冷卻狀態重排providers:冷卻中的條目「只降序不移除」——移到鏈尾, 前面全敗時仍會被嘗試, 故不存在把已恢復服務冰住的問題(此為與「金鑰停用清單」的關鍵差異, 後者已被否決)。 僅追蹤有明給id之條目(索引式id會因重排而錯位); 過期紀錄順手清除並寫回
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
providers |
Array | 輸入供應商條目陣列 |
state |
Object | 輸入狀態物件(取其cooling) |
cooldownMs |
Number | 輸入冷卻視窗毫秒正整數 |
saveState |
function | 輸入狀態寫回函數 |
Returns:
回傳重排後之條目陣列(active在前, 冷卻中殿後, 各自保持原相對順序)
- Type
- Array
resolveProviders(providers, optopt) → {Object}
- Description:
展開providers定義條目:envVar → keys,並可依id自選子集
特點: 條目之envVar依env來源(預設process.env)展開為keys陣列(變數值以逗號分隔多把),envVar欄位自輸出移除; 缺對應環境變數(或值為空)之條目停用並列入skipped,不中斷不throw; 無envVar之條目(訂閱登入態CLI)原樣通過;條目已自帶有效keys者以keys為準; opt.pick可依id自選子集並以pick順序回傳(順序即遞補優先序); opt.exes依kind注入CLI執行檔路徑、opt.patch依id淺合併覆寫欄位——皆於展開後施作, providers與table同源產出故必然一致; 輸入陣列與條目皆不被改動(輸出為淺拷貝)
- Source:
Example
import resolveProviders from './src/resolveProviders.mjs'
import readEnvFile from './src/readEnvFile.mjs'
import providersAll from './src/providers.mjs'
//金鑰放.env(變數值以逗號分隔多把), 以readEnvFile讀成物件, 不污染process.env
let env = readEnvFile('./.env')
//全取: envVar展開為keys, 缺環境變數者列入skipped
let { providers, table, skipped } = resolveProviders(providersAll, { env })
console.log(providers.length, skipped)
// => 17 []
//pick打錯字時, missing附拼寫提示hints(最接近之可用id)
let rm = resolveProviders(providersAll, { env, pick: ['poolside/laguna-s-2.1'] })
console.log(rm.missing, rm.hints)
// => [ 'poolside/laguna-s-2.1' ] { 'poolside/laguna-s-2.1': 'poolside:laguna-s-2.1' }
//自選: 依pick順序回傳(順序即遞補優先序), 可直接餵dispatchAiFallback
let r2 = resolveProviders(providersAll, { env, pick: ['agnes:agnes-3.0-flash', 'claude:sonnet'] })
console.log(r2.providers.map((p) => p.id))
// => [ 'agnes:agnes-3.0-flash', 'claude:sonnet' ]
//後處理: exes逐kind注入執行檔、patch逐id覆寫欄位, 陣列與table同步生效
let r3 = resolveProviders(providersAll, {
env,
pick: ['agnes:agnes-3.0-flash', 'claude:sonnet'],
exes: { claude: 'C:/Users/x/.local/bin/claude.exe' },
patch: { 'claude:sonnet': { timeoutMs: 360000 } },
})
console.log(r3.table['claude:sonnet'].exe, r3.table['claude:sonnet'].timeoutMs)
// => 'C:/Users/x/.local/bin/claude.exe' 360000
//table可直接餵dispatchAiWkf之providers定義表
//let wkf = dispatchAiWkf({ providers: r2.table, defaults: { timeoutMs: 1200000 } })
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
providers |
Array | 輸入providers條目物件陣列(如providers.mjs之預設匯出) |
|||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳物件,內含providers(可直接餵dispatchAiFallback之條目陣列)、table(id對條目之物件,可直接餵dispatchAiWkf)、skipped(缺環境變數而停用之{id,envVar}陣列)、missing(pick查無之id字串陣列)、hints(missing id對最接近可用id之拼寫提示物件,pick查無多半是打錯字,附最接近id供直接定位;僅為相似度最高者非保證正解,無missing時為空物件)
- Type
- Object
(async) runFanout(optopt) → {Promise}
- Description:
執行Fanout工作流:多開執行與單點整合
特點: 前段各名額並行執行同一任務,各名額可指定主模型(use)與自帶遞補鏈(fallback); 後段為單一整合名額,同樣可帶遞補鏈; 個別名額失敗不中斷整輪,成功候選未達minCandidates時以首位候選為成果(integrated:false)不硬整合; 成功候選完整保留於回傳(部分接受、便於接續重試整合); 本函數不會reject
- Source:
Example
//need cli in system PATH
import runFanout from './src/wkf/runFanout.mjs'
let providers = {
'agnes:agnes-3.0-flash': { kind: 'api-openai-compat', baseURL: 'https://apihub.agnes-ai.com/v1', model: 'agnes-3.0-flash', keys: ['sk-xxx'] },
'claude:sonnet': { kind: 'claude', model: 'sonnet' },
}
let test = async () => {
let r = await runFanout({
providers,
task: '分析並只回覆JSON: {"essence":"..."}',
agents: [
{ use: 'agnes:agnes-3.0-flash', fallback: ['claude:sonnet'] },
{ use: 'claude:sonnet' },
],
integrate: { use: 'claude:sonnet' },
check: (j) => !!j.essence,
})
console.log(r.ok, r.integrated, r.candidates.length)
// => true true 2
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(工作流成果)、integrated(是否經過整合布林值)、agents(各名額完整結果陣列)、candidates(成功候選陣列)、integrateDetail(整合呼叫完整結果)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
- Type
- Promise
(async) runFanoutPipeline(optopt) → {Promise}
- Description:
執行FanoutPipeline工作流(Fanout+RolePipeline):多開+整合+串行角色鏈
特點: 前段同runFanout(agents各名額可自帶fallback、integrate單點整合); 後段同runRolePipeline(stages各階段可自帶AI/fallback/提示詞),其input即前段成果; 本函數不會reject
- Source:
Example
//need cli in system PATH
import runFanoutPipeline from './src/wkf/runFanoutPipeline.mjs'
let providers = {
'claude:sonnet': { kind: 'claude', model: 'sonnet' },
'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
}
let test = async () => {
let r = await runFanoutPipeline({
providers,
task: '分析並只回覆JSON: {"essence":"..."}',
agents: [{ use: 'claude:sonnet' }, { use: 'codex:gpt-5.6-luna' }],
integrate: { use: 'claude:sonnet' },
stages: [
{ id: 'audit', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審計此稿並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.input)}` },
],
check: (j) => !!j.essence,
})
console.log(r.ok, r.A.integrated, r.B.order)
// => true true [ 'audit' ]
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(工作流成果)、A(前段runFanout完整結果)、B(後段runRolePipeline完整結果)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
- Type
- Promise
(async) runRolePipeline(optopt) → {Promise}
- Description:
執行RolePipeline工作流:多角色串行鏈
特點: 各階段可各自指定use/fallback/prompt/check(含rawText純文字階段); 階段成果依序傳遞,最末階段成果即工作流成果; 失敗即止但已完成成果完整回傳(部分接受、便於接續重跑失敗段); 本函數不會reject
- Source:
Example
//need cli in system PATH
import runRolePipeline from './src/wkf/runRolePipeline.mjs'
let providers = {
'claude:sonnet': { kind: 'claude', model: 'sonnet' },
'codex:gpt-5.6-luna': { kind: 'codex', model: 'gpt-5.6-luna' },
}
let test = async () => {
let r = await runRolePipeline({
providers,
input: '原始任務',
stages: [
{ id: 'draft', use: 'claude:sonnet', prompt: (ctx) => `就「${ctx.input}」寫初稿, 只回覆JSON: {"text":"..."}` },
{ id: 'review', use: 'codex:gpt-5.6-luna', prompt: (ctx) => `審閱並修訂, 只回覆同格式JSON: ${JSON.stringify(ctx.prev)}` },
],
})
console.log(r.ok, r.order, r.failedStage)
// => true [ 'draft', 'review' ] null
}
await test()
.catch((err) => {
console.log(err)
})
Parameters:
| Name | Type | Attributes | Default | Description | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳Promise,resolve回傳結果物件,內含ok(布林值)、result(最末階段成果)、stages(id對階段完整呼叫結果之物件)、results(id對階段成果之物件)、order(階段id順序陣列)、failedStage(失敗階段id,無失敗為null)、totalMs(總耗時毫秒)、error(錯誤訊息字串),本函數不會reject
- Type
- Promise
safeValidate(validator, text) → {Object}
- Description:
以不拋錯方式執行驗證函式(拋錯視同拒絕)
- Source:
Example
import { safeValidate } from './src/checkTruncation.mjs'
console.log(safeValidate((s) => s.length > 1, 'ab'))
// => { pass: true, threw: '' }
console.log(safeValidate(() => { throw new Error('x') }, 'ab'))
// => { pass: false, threw: 'x' }
Parameters:
| Name | Type | Description |
|---|---|---|
validator |
function | 輸入驗證函式(text)=>Boolean |
text |
String | 輸入待驗證文字 |
Returns:
回傳物件,內含pass(是否通過布林值)與threw(拋錯訊息字串,未拋錯為空字串)
- Type
- Object
salvageTruncatedArray(text) → {Array|null}
- Description:
自截斷的JSON陣列文字搶救出前段完整元素(頂層元素須為物件)
特點: 自第一個
[起逐字元掃描(字串與跳脫感知),追蹤「最後一個完整結束的頂層物件元素」, 補上]重新解析——回傳的每個元素皆完整合法,不是半成品; 陣列有正常閉合(=非截斷)、無[、或救回後為空陣列,一律回null不硬救; 典型用法:extractJsonLoose回null時之後備,或組成自訂parse注入callAiWithFallback
- Source:
Example
import salvageTruncatedArray from './src/wkf/salvageTruncatedArray.mjs'
console.log(salvageTruncatedArray('[{"a":1},{"b":2},{"c":'))
// => [ { a: 1 }, { b: 2 } ]
console.log(salvageTruncatedArray('[{"a":1}]')) //有閉合=非截斷, 不在搶救範圍
// => null
Parameters:
| Name | Type | Description |
|---|---|---|
text |
String | 輸入模型輸出文字字串 |
Returns:
回傳搶救出之非空陣列,無法搶救回null
- Type
- Array | null
toQuotaLabel(sec) → {String}
- Description:
由額度窗口長度秒數推導人類可讀之中文標籤
能整除日數即以「N天」表示,其次「N小時」、「N分鐘」,皆不整除則「N秒」; 無效或非正數回傳空字串,令呼叫端可用空字串判斷「供應商未提供窗口長度」
- Source:
Example
import toQuotaLabel from './src/quota/toQuotaLabel.mjs'
console.log(toQuotaLabel(18000))
// => 5小時
console.log(toQuotaLabel(604800))
// => 7天
console.log(toQuotaLabel(900))
// => 15分鐘
console.log(toQuotaLabel(null))
// => (空字串)
Parameters:
| Name | Type | Description |
|---|---|---|
sec |
Number | 輸入窗口長度秒數 |
Returns:
回傳中文標籤字串,無效秒數回傳空字串
- Type
- String
toQuotaResult(provider, optopt) → {Object}
- Description:
將各家額度查詢結果正規化為統一結構,並判定帳號是否相符
帳號比對採去空白且不分大小寫之比較,因email本地部分雖理論上區分大小寫, 但三家供應商實務上皆以不分大小寫視為同一帳號; 未指定email時不做比對,matched為null,ok僅取決於查詢本身是否成功
- Source:
Example
import toQuotaResult from './src/quota/toQuotaResult.mjs'
let r = toQuotaResult('claude', { email: 'a@b.com', emailWant: 'c@d.com' })
console.log(r.ok, r.matched)
// => false false
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
provider |
String | 輸入供應商種類字串,例如'claude'、'codex'、'antigravity' |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳結果物件,內含ok(查詢成功且帳號相符布林值)、provider、email(本機實際登入帳號)、matched(帳號是否相符布林值,未指定email時為null)、plan、planTier、source、windows(窗口陣列)、credits、raw、error、errorType、durationMs
- Type
- Object
toQuotaScopedLabel(windowSeconds, scope) → {String}
- Description:
組出帶適用範圍之額度窗口標籤
無範圍時回傳空字串,令toQuotaWindow自行由windowSeconds推導預設標籤; 有範圍但窗口秒數無效(供應商未提供)時僅以範圍為標籤
- Source:
Example
import toQuotaScopedLabel from './src/quota/toQuotaScopedLabel.mjs'
console.log(toQuotaScopedLabel(604800, 'Fable'))
// => 7天(Fable)
console.log(toQuotaScopedLabel(null, 'codex-spark'))
// => codex-spark
console.log(toQuotaScopedLabel(18000, ''))
// => (空字串)
Parameters:
| Name | Type | Description |
|---|---|---|
windowSeconds |
Number | 輸入窗口長度秒數 |
scope |
String | 輸入適用範圍字串,例如模型名或群組名 |
Returns:
回傳標籤字串,例如'7天(Fable)';無範圍回傳''
- Type
- String
toQuotaWindow(optopt) → {Object}
- Description:
將各家額度窗口正規化為統一結構
resetAt可給ISO字串或unix秒數(整數),一律轉為ISO字串; resetAt與resetAfterSeconds任一有值即自動補算另一; usedPercent夾至0~100,remainingPercent由其推得,未知時兩者皆為null(不假造100); label未給時由windowSeconds推導,故供應商調整窗口長度時標籤自動跟著正確
- Source:
Example
import toQuotaWindow from './src/quota/toQuotaWindow.mjs'
let w = toQuotaWindow({ key: 'five_hour', windowSeconds: 18000, usedPercent: 11 })
console.log(w.label, w.remainingPercent)
// => 5小時 89
Parameters:
| Name | Type | Attributes | Default | Description | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opt |
Object |
<optional> |
{}
|
輸入設定物件,預設{} Properties
|
Returns:
回傳窗口物件,內含key(供應商原生識別,各家不同)、label、windowSeconds(跨家比較之正規化維度:18000=5小時、604800=7天)、usedPercent、remainingPercent、resetAt(ISO字串)、resetAfterSeconds、scope、active、severity
- Type
- Object
(async) viaAppServer(opt) → {Promise}
- Description:
以app-server取得額度並正規化
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
opt |
Object | 輸入設定物件 |
Returns:
回傳結果片段物件{ok, email, plan, accountType, windows, credits, raw, error, errorType}
- Type
- Promise
(async) viaHttp(opt, codexHome, timeoutMs) → {Promise}
- Description:
以備援端點取得額度並正規化
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
opt |
Object | 輸入設定物件 |
codexHome |
String | 輸入codex設定目錄字串 |
timeoutMs |
Number | 輸入逾時毫秒 |
Returns:
回傳結果片段物件{ok, email, plan, windows, credits, raw, error, errorType}
- Type
- Promise
windowToSeconds(w) → {Number|null}
- Description:
由桶之window字串推得窗口秒數
- Source:
Parameters:
| Name | Type | Description |
|---|---|---|
w |
String | 輸入window字串, 例如'5h'、'weekly' |
Returns:
回傳秒數, 無法推得回傳null
- Type
- Number | null