w-dispatch-ai 1.0.2 → 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.
- package/README.md +123 -35
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +8 -4
- package/docs/adapters.mjs.html +43 -7
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +42 -8
- package/docs/dispatchAiWkf.mjs.html +197 -0
- package/docs/dispatchAntigravity.mjs.html +2 -2
- package/docs/dispatchApiOpenaiCompat.mjs.html +516 -0
- package/docs/dispatchClaude.mjs.html +2 -2
- package/docs/dispatchCodex.mjs.html +2 -2
- package/docs/dispatchOpencode.mjs.html +2 -2
- package/docs/getCliArgs.mjs.html +2 -2
- package/docs/getErrorResult.mjs.html +2 -2
- package/docs/global.html +5768 -1469
- package/docs/index.html +2 -2
- package/docs/wkf_callAiWithFallback.mjs.html +282 -0
- package/docs/wkf_extractJsonLoose.mjs.html +180 -0
- package/docs/wkf_runFanout.mjs.html +227 -0
- package/docs/wkf_runFanoutPipeline.mjs.html +178 -0
- package/docs/wkf_runRolePipeline.mjs.html +195 -0
- package/g.mjs +41 -25
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +6 -2
- package/src/adapters.mjs +41 -5
- package/src/dispatchAiFallback.mjs +40 -6
- package/src/dispatchAiWkf.mjs +125 -0
- package/src/dispatchApiOpenaiCompat.mjs +444 -0
- package/src/wkf/callAiWithFallback.mjs +210 -0
- package/src/wkf/extractJsonLoose.mjs +108 -0
- package/src/wkf/runFanout.mjs +155 -0
- package/src/wkf/runFanoutPipeline.mjs +106 -0
- package/src/wkf/runRolePipeline.mjs +123 -0
- package/test/tools/fakeServerForApiTest.mjs +151 -0
- package/test/unit-WDispatchAi.test.mjs +13 -6
- package/test/unit-adapters.test.mjs +5 -3
- package/test/unit-callAiWithFallback.test.mjs +146 -0
- package/test/unit-dispatchAi.test.mjs +1 -1
- package/test/unit-dispatchAiWkf.test.mjs +118 -0
- package/test/unit-dispatchApiOpenaiCompat.test.mjs +264 -0
- package/test/unit-extractJsonLoose.test.mjs +78 -0
- package/test/unit-runFanout.test.mjs +166 -0
- package/test/unit-runFanoutPipeline.test.mjs +86 -0
- package/test/unit-runRolePipeline.test.mjs +163 -0
package/docs/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -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
|
|
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>
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>wkf/callAiWithFallback.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">wkf/callAiWithFallback.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
|
|
49
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
50
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
51
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
52
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
53
|
+
import dispatchAiFallback from '../dispatchAiFallback.mjs'
|
|
54
|
+
import extractJsonLoose from './extractJsonLoose.mjs'
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
// callAiWithFallback.mjs — 工作流的最小呼叫單元: 一個「AI名額」=主模型+自帶遞補鏈
|
|
58
|
+
//
|
|
59
|
+
// 【設計】呼叫端以名稱宣告主模型與遞補(use:'deepseek', fallback:['agnes-ai','sonnet']),
|
|
60
|
+
// 本函數依providers定義表把名稱展開成dispatchAiFallback的providers陣列
|
|
61
|
+
// (順序即優先序), 故「各AI名額可各自指定fallback」天然成立。
|
|
62
|
+
// 名稱查無定義時視為設定錯誤直接回報(fail fast), 不靜默略過——
|
|
63
|
+
// 否則fallback名稱打錯字只會讓遞補鏈無感知地短一截, 事後無從察覺。
|
|
64
|
+
//
|
|
65
|
+
// 【JSON驗證接進遞補層】parse+check包成dispatchAiFallback的validate:
|
|
66
|
+
// 回覆非法(空回、截斷、缺欄位)時為OUTPUT_VALIDATION_FAILED, 遞補層視為
|
|
67
|
+
// 與金鑰無關之失敗而「整組跳過換下一家」(不換組內金鑰——同模型換金鑰仍是
|
|
68
|
+
// 同樣的產出習慣); 端點不穩而偶發空回的模型, 以maxRetries調高令同鍵重試。
|
|
69
|
+
//
|
|
70
|
+
// 【防寫檔前綴】agentic CLI對cwd隔離免疫(會自行解析專案根目錄寫檔),
|
|
71
|
+
// 故預設在prompt前掛「禁止建檔」約束(實測有效); 不需要時傳promptPrefix:''關閉。
|
|
72
|
+
// 殷鑑: 2026-08-10執行任務歷史.md遭AI覆寫、評比腳本繞過前綴又產生根目錄孤兒檔。
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
//預設防寫檔前綴
|
|
76
|
+
let NO_SIDE_EFFECT = [
|
|
77
|
+
'【執行約束】你只需把結果輸出在回覆內容中。',
|
|
78
|
+
'禁止建立、修改或刪除任何檔案,禁止執行任何指令——呼叫端只讀取你的回覆文字,',
|
|
79
|
+
'任何寫入磁碟的動作都不會被採用,只會製造無人讀取的垃圾檔。',
|
|
80
|
+
'', '',
|
|
81
|
+
].join('\n')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 依providers定義表把「名稱規格」展開成dispatchAiFallback的providers陣列
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} providers 輸入定義表物件(名稱 → 條目)
|
|
88
|
+
* @param {Object} spec 輸入名額規格物件{ use, fallback }
|
|
89
|
+
* @returns {Object} 回傳物件,內含chain(條目物件陣列,id一律用名稱)與missing(查無定義之名稱字串陣列)
|
|
90
|
+
* @example
|
|
91
|
+
*
|
|
92
|
+
* import { buildChain } from './src/wkf/callAiWithFallback.mjs'
|
|
93
|
+
*
|
|
94
|
+
* let providers = { a: { kind: 'claude' }, b: { kind: 'codex' } }
|
|
95
|
+
* console.log(buildChain(providers, { use: 'a', fallback: ['b', 'c'] }))
|
|
96
|
+
* // => { chain: [ { id: 'a', kind: 'claude' }, { id: 'b', kind: 'codex' } ], missing: [ 'c' ] }
|
|
97
|
+
*
|
|
98
|
+
*/
|
|
99
|
+
function buildChain(providers, spec) {
|
|
100
|
+
let names = [get(spec, 'use', '')]
|
|
101
|
+
let fallback = get(spec, 'fallback', null)
|
|
102
|
+
if (isearr(fallback)) {
|
|
103
|
+
names = [...names, ...fallback]
|
|
104
|
+
}
|
|
105
|
+
let chain = []
|
|
106
|
+
let missing = []
|
|
107
|
+
for (let name of names) {
|
|
108
|
+
let entry = get(providers, name, null)
|
|
109
|
+
if (isobj(entry)) {
|
|
110
|
+
chain.push({ id: name, ...entry }) //id一律用名稱, 令游標與事件可讀
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
missing.push(String(name))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { chain, missing }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 呼叫一個AI名額(主模型+自帶遞補鏈),回覆經parse+check驗證後回傳結構化結果
|
|
122
|
+
*
|
|
123
|
+
* 特點:
|
|
124
|
+
* spec.use為主模型名稱、spec.fallback為遞補名稱陣列,依序展開為遞補鏈,名稱查無定義即回報錯誤(fail fast);
|
|
125
|
+
* parse+check接進遞補層之validate——非法回覆視為該家失敗而自動換下一家,不把壞結果帶回來;
|
|
126
|
+
* 預設掛防寫檔前綴(promptPrefix傳空字串可關閉);
|
|
127
|
+
* 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
|
|
128
|
+
*
|
|
129
|
+
* @param {String} prompt 輸入提示詞字串
|
|
130
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
131
|
+
* @param {Object} opt.providers 輸入provider定義表物件(名稱 → dispatchAiFallback條目,條目內含kind、model、keys、exe、provider、config等)
|
|
132
|
+
* @param {Object} opt.spec 輸入名額規格物件{ use:'主模型名稱', fallback:['遞補名稱', ...] }
|
|
133
|
+
* @param {Function} [opt.check=null] 輸入結果檢核函數(json)=>Boolean,預設null代表只要能解析出JSON即通過
|
|
134
|
+
* @param {Function} [opt.parse=extractJsonLoose] 輸入回覆解析函數(stdout)=>Object|null,預設寬鬆JSON抽取
|
|
135
|
+
* @param {Boolean} [opt.rawText=false] 輸入是否以純文字模式運作布林值,true代表不解析JSON(json欄位為修剪後文字、check收文字),預設false
|
|
136
|
+
* @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt前綴字串,預設為防寫檔約束,傳''關閉
|
|
137
|
+
* @param {Number} [opt.timeoutMs=300000] 輸入單次嘗試逾時毫秒正整數,預設300000
|
|
138
|
+
* @param {Number} [opt.budgetMs=null] 輸入整條遞補鏈之時間預算毫秒正整數,預設null代表不限
|
|
139
|
+
* @param {Number} [opt.maxRetries=0] 輸入同家重試次數非負整數,預設0(韌性交給遞補;端點不穩偶發空回之模型可調高令同鍵重試)
|
|
140
|
+
* @param {String} [opt.cwd=process.cwd()] 輸入子進程工作目錄字串,預設process.cwd()
|
|
141
|
+
* @param {Object} [opt.store=null] 輸入游標持久化物件{get,set},預設null代表用行程內記憶體
|
|
142
|
+
* @param {Function} [opt.onEvent=null] 輸入遞補層事件回調函數,預設null
|
|
143
|
+
* @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否取得可用結果布林值)、json(解析後物件,rawText模式下為文字)、providerId(實際使用之名稱)、keyIndex、keyId、ms(總耗時毫秒)、tried(遞補嘗試歷程陣列)、error(錯誤訊息字串),本函數不會reject
|
|
144
|
+
* @example
|
|
145
|
+
* //need cli in system PATH
|
|
146
|
+
*
|
|
147
|
+
* import callAiWithFallback from './src/wkf/callAiWithFallback.mjs'
|
|
148
|
+
*
|
|
149
|
+
* //鍵名須區分到模型並帶上路徑, 詳見dispatchAiFallback.mjs檔頭之id設計規則
|
|
150
|
+
* let providers = {
|
|
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' },
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* let test = async () => {
|
|
156
|
+
*
|
|
157
|
+
* let r = await callAiWithFallback('只回覆JSON: {"a":1}', {
|
|
158
|
+
* providers,
|
|
159
|
+
* spec: { use: 'zen:deepseek-v4-flash-free', fallback: ['claude:sonnet'] },
|
|
160
|
+
* check: (j) => j.a === 1,
|
|
161
|
+
* })
|
|
162
|
+
* console.log(r.ok, r.json, r.providerId)
|
|
163
|
+
* // => true { a: 1 } 'zen:deepseek-v4-flash-free'
|
|
164
|
+
*
|
|
165
|
+
* }
|
|
166
|
+
* await test()
|
|
167
|
+
* .catch((err) => {
|
|
168
|
+
* console.log(err)
|
|
169
|
+
* })
|
|
170
|
+
*
|
|
171
|
+
*/
|
|
172
|
+
async function callAiWithFallback(prompt, opt = {}) {
|
|
173
|
+
let t0 = Date.now()
|
|
174
|
+
|
|
175
|
+
if (!isestr(prompt)) {
|
|
176
|
+
return { ok: false, json: null, error: 'prompt must be a non-empty string', ms: 0, tried: [] }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let providers = get(opt, 'providers', null)
|
|
180
|
+
let spec = get(opt, 'spec', null)
|
|
181
|
+
if (!isobj(providers) || !isobj(spec)) {
|
|
182
|
+
return { ok: false, json: null, error: `no valid provider for spec: ${JSON.stringify(spec)}`, ms: 0, tried: [] }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//buildChain, 名稱查無定義即回報(fail fast), 不讓打錯字的fallback靜默消失
|
|
186
|
+
let { chain, missing } = buildChain(providers, spec)
|
|
187
|
+
if (missing.length > 0) {
|
|
188
|
+
return { ok: false, json: null, error: `unknown provider name(s): ${missing.join(', ')}`, ms: 0, tried: [] }
|
|
189
|
+
}
|
|
190
|
+
if (chain.length === 0) {
|
|
191
|
+
return { ok: false, json: null, error: `no valid provider for spec: ${JSON.stringify(spec)}`, ms: 0, tried: [] }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let rawText = get(opt, 'rawText', false) === true
|
|
195
|
+
let parse = get(opt, 'parse', null)
|
|
196
|
+
if (!isfun(parse)) {
|
|
197
|
+
parse = extractJsonLoose
|
|
198
|
+
}
|
|
199
|
+
let check = get(opt, 'check', null)
|
|
200
|
+
if (!isfun(check)) {
|
|
201
|
+
check = null
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let promptPrefix = get(opt, 'promptPrefix', null)
|
|
205
|
+
if (!isestr(promptPrefix)) {
|
|
206
|
+
promptPrefix = (promptPrefix === '') ? '' : NO_SIDE_EFFECT
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
//validate接進遞補層: 非法回覆=這一家失敗, 遞補層換下一家
|
|
210
|
+
let validate = (stdout) => {
|
|
211
|
+
if (rawText) {
|
|
212
|
+
let s = String(stdout || '').trim()
|
|
213
|
+
if (s === '') {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
return check ? check(s) === true : true
|
|
217
|
+
}
|
|
218
|
+
let j = parse(stdout)
|
|
219
|
+
if (j === null) {
|
|
220
|
+
return false
|
|
221
|
+
}
|
|
222
|
+
return check ? check(j) === true : true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let r = await dispatchAiFallback(promptPrefix + prompt, {
|
|
226
|
+
providers: chain,
|
|
227
|
+
validate,
|
|
228
|
+
timeoutMs: get(opt, 'timeoutMs', null) || 300000,
|
|
229
|
+
budgetMs: get(opt, 'budgetMs', null) || undefined,
|
|
230
|
+
maxRetries: get(opt, 'maxRetries', null) || 0,
|
|
231
|
+
cwd: get(opt, 'cwd', null) || process.cwd(),
|
|
232
|
+
store: get(opt, 'store', null) || undefined,
|
|
233
|
+
onEvent: get(opt, 'onEvent', null) || undefined,
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
//result, 已過validate故此處parse必然成功(同一解析器), 重解析僅為取出物件
|
|
237
|
+
let result = null
|
|
238
|
+
if (r.ok) {
|
|
239
|
+
result = rawText ? String(r.stdout || '').trim() : parse(r.stdout)
|
|
240
|
+
}
|
|
241
|
+
let providerId = get(r, 'providerId', null)
|
|
242
|
+
let keyIndex = get(r, 'keyIndex', null)
|
|
243
|
+
return {
|
|
244
|
+
ok: r.ok && result !== null,
|
|
245
|
+
json: result, //rawText模式下此欄為文字
|
|
246
|
+
providerId,
|
|
247
|
+
keyIndex,
|
|
248
|
+
keyId: (keyIndex === null) ? providerId : `${providerId}#${keyIndex}`,
|
|
249
|
+
ms: Date.now() - t0,
|
|
250
|
+
tried: get(r, 'tried', []),
|
|
251
|
+
error: r.ok ? '' : get(r, 'error', 'unknown error'),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
export default callAiWithFallback
|
|
257
|
+
export { buildChain, NO_SIDE_EFFECT }
|
|
258
|
+
</code></pre>
|
|
259
|
+
</article>
|
|
260
|
+
</section>
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<br class="clear">
|
|
270
|
+
|
|
271
|
+
<footer>
|
|
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.
|
|
273
|
+
</footer>
|
|
274
|
+
|
|
275
|
+
<script>prettyPrint();</script>
|
|
276
|
+
<script src="scripts/polyfill.js"></script>
|
|
277
|
+
<script src="scripts/linenumber.js"></script>
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
</body>
|
|
282
|
+
</html>
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>wkf/extractJsonLoose.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">wkf/extractJsonLoose.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// extractJsonLoose.mjs — 從AI回覆文字中寬鬆抽取JSON(工作流層預設解析器, 可被注入覆寫)
|
|
49
|
+
//
|
|
50
|
+
// 【為何需要】各家CLI的回覆常帶code fence、前後說明文字、ANSI色碼;
|
|
51
|
+
// 直接JSON.parse必炸。本函數做清理後以「括號配對」找出第一個完整
|
|
52
|
+
// 物件或陣列再解析。呼叫端若有更強的解析器(如含截斷搶救), 以opt.parse注入即可。
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 從文字中抽取第一個完整的JSON物件或陣列
|
|
57
|
+
*
|
|
58
|
+
* 特點:
|
|
59
|
+
* 先去除ANSI色碼與code fence標記後嘗試整段解析(最常見情境之最快路徑);
|
|
60
|
+
* 整段非法時自第一個`{`或`[`起以括號配對(跳過字串與跳脫)取得第一個完整片段再解析;
|
|
61
|
+
* 僅接受物件與陣列,純量(字串/數字/布林)回傳null;
|
|
62
|
+
* 括號未閉合(輸出被截斷)或片段非法一律回傳null,不throw
|
|
63
|
+
*
|
|
64
|
+
* @param {String} text 輸入AI回覆文字字串
|
|
65
|
+
* @returns {Object|Array|null} 回傳解析成功之物件或陣列,失敗回傳null
|
|
66
|
+
* @example
|
|
67
|
+
*
|
|
68
|
+
* import extractJsonLoose from './src/wkf/extractJsonLoose.mjs'
|
|
69
|
+
*
|
|
70
|
+
* console.log(extractJsonLoose('{"a":1}'))
|
|
71
|
+
* // => { a: 1 }
|
|
72
|
+
*
|
|
73
|
+
* console.log(extractJsonLoose('說明文字\n```json\n{"a":1}\n```\n後記'))
|
|
74
|
+
* // => { a: 1 }
|
|
75
|
+
*
|
|
76
|
+
* console.log(extractJsonLoose('{"a":1')) //截斷
|
|
77
|
+
* // => null
|
|
78
|
+
*
|
|
79
|
+
* console.log(extractJsonLoose('純文字回覆'))
|
|
80
|
+
* // => null
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
function extractJsonLoose(text) {
|
|
84
|
+
let s = String(text || '')
|
|
85
|
+
|
|
86
|
+
//去ANSI色碼與code fence標記
|
|
87
|
+
s = s.replace(new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g'), '')
|
|
88
|
+
s = s.replace(/```(?:json)?/g, '')
|
|
89
|
+
s = s.trim()
|
|
90
|
+
if (s === '') {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//整段直接解析(最常見情境, 最快路徑)
|
|
95
|
+
try {
|
|
96
|
+
let j = JSON.parse(s)
|
|
97
|
+
if (j !== null && typeof j === 'object') {
|
|
98
|
+
return j
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) { /* 進入括號配對路徑 */ }
|
|
102
|
+
|
|
103
|
+
//括號配對: 自第一個{或[起, 逐字元追蹤深度(跳過字串與跳脫), 取得第一個完整片段
|
|
104
|
+
let start = -1
|
|
105
|
+
for (let i = 0; i < s.length; i++) {
|
|
106
|
+
if (s[i] === '{' || s[i] === '[') {
|
|
107
|
+
start = i
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (start < 0) {
|
|
112
|
+
return null
|
|
113
|
+
}
|
|
114
|
+
let depth = 0
|
|
115
|
+
let inStr = false
|
|
116
|
+
let esc = false
|
|
117
|
+
for (let i = start; i < s.length; i++) {
|
|
118
|
+
let c = s[i]
|
|
119
|
+
if (inStr) {
|
|
120
|
+
if (esc) {
|
|
121
|
+
esc = false
|
|
122
|
+
}
|
|
123
|
+
else if (c === '\\') {
|
|
124
|
+
esc = true
|
|
125
|
+
}
|
|
126
|
+
else if (c === '"') {
|
|
127
|
+
inStr = false
|
|
128
|
+
}
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (c === '"') {
|
|
132
|
+
inStr = true
|
|
133
|
+
}
|
|
134
|
+
else if (c === '{' || c === '[') {
|
|
135
|
+
depth++
|
|
136
|
+
}
|
|
137
|
+
else if (c === '}' || c === ']') {
|
|
138
|
+
depth--
|
|
139
|
+
if (depth === 0) {
|
|
140
|
+
try {
|
|
141
|
+
let j = JSON.parse(s.slice(start, i + 1))
|
|
142
|
+
if (j !== null && typeof j === 'object') {
|
|
143
|
+
return j
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) { /* 片段仍非法, 視為失敗 */ }
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null //括號未閉合(輸出被截斷)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
export default extractJsonLoose
|
|
156
|
+
</code></pre>
|
|
157
|
+
</article>
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<br class="clear">
|
|
168
|
+
|
|
169
|
+
<footer>
|
|
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
|
+
</footer>
|
|
172
|
+
|
|
173
|
+
<script>prettyPrint();</script>
|
|
174
|
+
<script src="scripts/polyfill.js"></script>
|
|
175
|
+
<script src="scripts/linenumber.js"></script>
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|