w-dispatch-ai 1.0.11 → 1.0.13
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 +33 -5
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +15 -3
- package/docs/adapters.mjs.html +2 -2
- package/docs/budgetFor.mjs.html +124 -0
- package/docs/castPintOr.mjs.html +2 -2
- package/docs/dfTimeoutMs.mjs.html +2 -2
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +2 -2
- package/docs/dispatchAiWkf.mjs.html +2 -2
- package/docs/dispatchAntigravity.mjs.html +2 -2
- package/docs/dispatchApiOpenaiCompat.mjs.html +2 -2
- 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 +2 -2
- package/docs/global.html +1632 -49
- package/docs/index.html +2 -2
- package/docs/readEnvFile.mjs.html +132 -0
- package/docs/resolveProviders.mjs.html +54 -6
- package/docs/wkf_callAiWithFallback.mjs.html +7 -22
- package/docs/wkf_createFileStore.mjs.html +175 -0
- package/docs/wkf_createUsageCounter.mjs.html +213 -0
- package/docs/wkf_extractJsonLoose.mjs.html +2 -2
- package/docs/wkf_noSideEffectPrefix.mjs.html +121 -0
- package/docs/wkf_runFanout.mjs.html +2 -2
- package/docs/wkf_runFanoutPipeline.mjs.html +2 -2
- package/docs/wkf_runRolePipeline.mjs.html +2 -2
- package/docs/wkf_salvageTruncatedArray.mjs.html +167 -0
- package/package.json +2 -2
- package/src/WDispatchAi.mjs +13 -1
- package/src/budgetFor.mjs +52 -0
- package/src/providers.mjs +4 -3
- package/src/readEnvFile.mjs +60 -0
- package/src/resolveProviders.mjs +52 -4
- package/src/wkf/callAiWithFallback.mjs +5 -20
- package/src/wkf/createFileStore.mjs +103 -0
- package/src/wkf/createUsageCounter.mjs +141 -0
- package/src/wkf/noSideEffectPrefix.mjs +49 -0
- package/src/wkf/salvageTruncatedArray.mjs +95 -0
- package/test/unit-WDispatchAi.test.mjs +9 -3
- package/test/unit-budgetFor.test.mjs +26 -0
- package/test/unit-callAiWithFallback.test.mjs +8 -0
- package/test/unit-createFileStore.test.mjs +66 -0
- package/test/unit-createUsageCounter.test.mjs +76 -0
- package/test/unit-readEnvFile.test.mjs +52 -0
- package/test/unit-resolveProviders.test.mjs +49 -0
- package/test/unit-salvageTruncatedArray.test.mjs +39 -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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#NO_SIDE_EFFECT">NO_SIDE_EFFECT</a></li><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#budgetFor">budgetFor</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#createFileStore">createFileStore</a></li><li><a href="global.html#createUsageCounter">createUsageCounter</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</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 Tue Aug 18 2026
|
|
74
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 15:44:01 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,132 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>readEnvFile.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#NO_SIDE_EFFECT">NO_SIDE_EFFECT</a></li><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#budgetFor">budgetFor</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#createFileStore">createFileStore</a></li><li><a href="global.html#createUsageCounter">createUsageCounter</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">readEnvFile.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import fs from 'fs'
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// readEnvFile.mjs — 讀.env為金鑰來源物件(不污染process.env)
|
|
52
|
+
//
|
|
53
|
+
// 【為何需要】resolveProviders之opt.env本為「不用process.env」而設計——多專案並行時
|
|
54
|
+
// 污染全域環境變數會互相覆蓋金鑰——但「env物件從哪來」套件原無答案, README舊教法
|
|
55
|
+
// process.loadEnvFile('./.env')反而把金鑰塞進process.env, 與該設計目的自相矛盾。
|
|
56
|
+
// 兩個消費端專案已各寫一份解析器且行為不一致(2026-08-18使用端調研), 收斂於此。
|
|
57
|
+
//
|
|
58
|
+
// 【為何讀不到回空物件而非拋錯】金鑰缺失由resolveProviders之skipped機制回報
|
|
59
|
+
// (缺金鑰停用該條目、不中斷), 此處拋錯反而讓「部分供應商無金鑰」升級成整體啟動失敗;
|
|
60
|
+
// 要fail-loud的呼叫端自行檢查回傳(如Object.keys(env).length)。
|
|
61
|
+
//
|
|
62
|
+
// 【只解析KEY=value最簡格式】不引入dotenv: 金鑰為單行純文字, 僅剝除成對之首尾引號,
|
|
63
|
+
// 不做跳脫與多行處理——加解析規則只會製造與其他.env工具的行為差異。
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 讀取.env檔為鍵值物件(金鑰來源, 不寫入process.env)
|
|
68
|
+
*
|
|
69
|
+
* 特點:
|
|
70
|
+
* 只解析`KEY=value`形式(KEY限英數底線),忽略空行、註解與無效行;
|
|
71
|
+
* value修剪空白並剝除成對之首尾引號(單雙引號皆可);
|
|
72
|
+
* 檔案不存在或不可讀一律回空物件不throw(金鑰缺失交由resolveProviders之skipped回報)
|
|
73
|
+
*
|
|
74
|
+
* @param {String} file 輸入.env檔案路徑字串
|
|
75
|
+
* @returns {Object} 回傳鍵值物件(變數名 → 字串值),讀取失敗回空物件
|
|
76
|
+
* @example
|
|
77
|
+
*
|
|
78
|
+
* import readEnvFile from './src/readEnvFile.mjs'
|
|
79
|
+
* import resolveProviders from './src/resolveProviders.mjs'
|
|
80
|
+
* import providersAll from './src/providers.mjs'
|
|
81
|
+
*
|
|
82
|
+
* //金鑰放.env(變數值以逗號分隔多把), 讀成物件交resolveProviders, 不污染process.env
|
|
83
|
+
* let env = readEnvFile('./.env')
|
|
84
|
+
* let { providers, skipped } = resolveProviders(providersAll, { env, pick: ['agnes:agnes-2.5-flash'] })
|
|
85
|
+
*
|
|
86
|
+
*/
|
|
87
|
+
function readEnvFile(file) {
|
|
88
|
+
let out = {}
|
|
89
|
+
let text = ''
|
|
90
|
+
try {
|
|
91
|
+
text = fs.readFileSync(file, 'utf8')
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return out //檔案不存在或不可讀, 回空由skipped機制回報
|
|
95
|
+
}
|
|
96
|
+
for (let line of text.split(/\r?\n/)) {
|
|
97
|
+
let m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*)$/)
|
|
98
|
+
if (!m) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
out[m[1]] = m[2].trim().replace(/^(["'])(.*)\1$/, '$2')
|
|
102
|
+
}
|
|
103
|
+
return out
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export default readEnvFile
|
|
108
|
+
</code></pre>
|
|
109
|
+
</article>
|
|
110
|
+
</section>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<br class="clear">
|
|
120
|
+
|
|
121
|
+
<footer>
|
|
122
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 15:44:01 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
123
|
+
</footer>
|
|
124
|
+
|
|
125
|
+
<script>prettyPrint();</script>
|
|
126
|
+
<script src="scripts/polyfill.js"></script>
|
|
127
|
+
<script src="scripts/linenumber.js"></script>
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|
|
@@ -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#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#NO_SIDE_EFFECT">NO_SIDE_EFFECT</a></li><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#budgetFor">budgetFor</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#createFileStore">createFileStore</a></li><li><a href="global.html#createUsageCounter">createUsageCounter</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -62,6 +62,14 @@ import isearr from 'wsemi/src/isearr.mjs'
|
|
|
62
62
|
//
|
|
63
63
|
// 【自選】opt.pick給id陣列即可只取用部分條目, 且依pick之順序回傳
|
|
64
64
|
// (順序即dispatchAiFallback之優先序); 查無之id列入missing回報。
|
|
65
|
+
//
|
|
66
|
+
// 【後處理單一入口: opt.exes與opt.patch】消費端常需於展開後對條目做兩類覆寫:
|
|
67
|
+
// ①逐kind注入CLI執行檔絕對路徑(Windows排程於session 0執行時PATH可能不含npm
|
|
68
|
+
// 全域目錄, 靠指令名會ENOENT); ②逐id覆寫任意欄位(如各家實測耗時相差近10倍,
|
|
69
|
+
// timeoutMs須逐條指定)。此前只能於呼叫端自行map, 而陣列版(providers)與表格版
|
|
70
|
+
// (table)若各自後處理, 極易只套用其一——使用端殷鑑(2026-08-14): 逐條timeout
|
|
71
|
+
// 只加在陣列版, 工作流走表格版全部落回全域預設而不自知。後處理收進本函數,
|
|
72
|
+
// 兩種輸出同源產出, 結構性杜絕單邊套用。
|
|
65
73
|
|
|
66
74
|
|
|
67
75
|
/**
|
|
@@ -72,30 +80,46 @@ import isearr from 'wsemi/src/isearr.mjs'
|
|
|
72
80
|
* 缺對應環境變數(或值為空)之條目停用並列入skipped,不中斷不throw;
|
|
73
81
|
* 無envVar之條目(訂閱登入態CLI)原樣通過;條目已自帶有效keys者以keys為準;
|
|
74
82
|
* opt.pick可依id自選子集並以pick順序回傳(順序即遞補優先序);
|
|
83
|
+
* opt.exes依kind注入CLI執行檔路徑、opt.patch依id淺合併覆寫欄位——皆於展開後施作,
|
|
84
|
+
* providers與table同源產出故必然一致;
|
|
75
85
|
* 輸入陣列與條目皆不被改動(輸出為淺拷貝)
|
|
76
86
|
*
|
|
77
87
|
* @param {Array} providers 輸入providers條目物件陣列(如providers.mjs之預設匯出)
|
|
78
88
|
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
79
|
-
* @param {Object} [opt.env=process.env] 輸入金鑰來源物件(變數名 → 逗號分隔之金鑰字串),預設process.env
|
|
89
|
+
* @param {Object} [opt.env=process.env] 輸入金鑰來源物件(變數名 → 逗號分隔之金鑰字串),預設process.env。建議以readEnvFile('./.env')讀成物件傳入,不污染process.env
|
|
80
90
|
* @param {Array} [opt.pick=null] 輸入自選id字串陣列,依此順序回傳對應條目,查無之id列入missing,預設null代表全取(依原順序)
|
|
91
|
+
* @param {Object} [opt.exes=null] 輸入kind對執行檔絕對路徑之物件(如{claude:'C:/.../claude.exe'}),命中kind之條目補上exe欄位(條目已自帶exe者不覆寫——條目層設定優先),預設null代表不注入
|
|
92
|
+
* @param {Object} [opt.patch=null] 輸入id對部分欄位之物件(如{'claude:sonnet':{timeoutMs:360000}}),命中id之條目淺合併覆寫,於exes之後施作故可覆寫exe,預設null代表不覆寫
|
|
81
93
|
* @returns {Object} 回傳物件,內含providers(可直接餵dispatchAiFallback之條目陣列)、table(id對條目之物件,可直接餵dispatchAiWkf)、skipped(缺環境變數而停用之{id,envVar}陣列)、missing(pick查無之id字串陣列)
|
|
82
94
|
* @example
|
|
83
95
|
*
|
|
84
96
|
* import resolveProviders from './src/resolveProviders.mjs'
|
|
97
|
+
* import readEnvFile from './src/readEnvFile.mjs'
|
|
85
98
|
* import providersAll from './src/providers.mjs'
|
|
86
99
|
*
|
|
87
|
-
*
|
|
100
|
+
* //金鑰放.env(變數值以逗號分隔多把), 以readEnvFile讀成物件, 不污染process.env
|
|
101
|
+
* let env = readEnvFile('./.env')
|
|
88
102
|
*
|
|
89
103
|
* //全取: envVar展開為keys, 缺環境變數者列入skipped
|
|
90
|
-
* let { providers, table, skipped } = resolveProviders(providersAll)
|
|
104
|
+
* let { providers, table, skipped } = resolveProviders(providersAll, { env })
|
|
91
105
|
* console.log(providers.length, skipped)
|
|
92
106
|
* // => 10 []
|
|
93
107
|
*
|
|
94
108
|
* //自選: 依pick順序回傳(順序即遞補優先序), 可直接餵dispatchAiFallback
|
|
95
|
-
* let r2 = resolveProviders(providersAll, { pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'] })
|
|
109
|
+
* let r2 = resolveProviders(providersAll, { env, pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'] })
|
|
96
110
|
* console.log(r2.providers.map((p) => p.id))
|
|
97
111
|
* // => [ 'agnes:agnes-2.5-flash', 'claude:sonnet' ]
|
|
98
112
|
*
|
|
113
|
+
* //後處理: exes逐kind注入執行檔、patch逐id覆寫欄位, 陣列與table同步生效
|
|
114
|
+
* let r3 = resolveProviders(providersAll, {
|
|
115
|
+
* env,
|
|
116
|
+
* pick: ['agnes:agnes-2.5-flash', 'claude:sonnet'],
|
|
117
|
+
* exes: { claude: 'C:/Users/x/.local/bin/claude.exe' },
|
|
118
|
+
* patch: { 'claude:sonnet': { timeoutMs: 360000 } },
|
|
119
|
+
* })
|
|
120
|
+
* console.log(r3.table['claude:sonnet'].exe, r3.table['claude:sonnet'].timeoutMs)
|
|
121
|
+
* // => 'C:/Users/x/.local/bin/claude.exe' 360000
|
|
122
|
+
*
|
|
99
123
|
* //table可直接餵dispatchAiWkf之providers定義表
|
|
100
124
|
* //let wkf = dispatchAiWkf({ providers: r2.table, defaults: { timeoutMs: 1200000 } })
|
|
101
125
|
*
|
|
@@ -151,6 +175,30 @@ function resolveProviders(providers, opt = {}) {
|
|
|
151
175
|
out.push({ ...omit(entry, ['envVar']), keys })
|
|
152
176
|
}
|
|
153
177
|
|
|
178
|
+
//後處理: exes(逐kind)與patch(逐id), 於table組建「之前」施作——
|
|
179
|
+
//providers與table由同一份out產出, 兩種輸出必然一致(檔頭之單邊套用殷鑑)
|
|
180
|
+
let exes = get(opt, 'exes', null)
|
|
181
|
+
let patch = get(opt, 'patch', null)
|
|
182
|
+
if (isobj(exes) || isobj(patch)) {
|
|
183
|
+
out = out.map((entry) => {
|
|
184
|
+
let e = entry
|
|
185
|
+
|
|
186
|
+
//exes: 條目已自帶exe者不覆寫(條目層設定優先於全域注入)
|
|
187
|
+
let exe = isobj(exes) ? get(exes, get(e, 'kind', ''), null) : null
|
|
188
|
+
if (isestr(exe) && !isestr(get(e, 'exe', null))) {
|
|
189
|
+
e = { ...e, exe }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
//patch: 依id淺合併, 於exes之後施作故可覆寫exe
|
|
193
|
+
let pt = isobj(patch) ? get(patch, get(e, 'id', ''), null) : null
|
|
194
|
+
if (isobj(pt)) {
|
|
195
|
+
e = { ...e, ...pt }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return e
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
154
202
|
//table, id → 條目, 可直接作dispatchAiWkf之providers定義表
|
|
155
203
|
let table = {}
|
|
156
204
|
for (let entry of out) {
|
|
@@ -176,7 +224,7 @@ export default resolveProviders
|
|
|
176
224
|
<br class="clear">
|
|
177
225
|
|
|
178
226
|
<footer>
|
|
179
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026
|
|
227
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 15:44:01 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
180
228
|
</footer>
|
|
181
229
|
|
|
182
230
|
<script>prettyPrint();</script>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li></ul>
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#NO_SIDE_EFFECT">NO_SIDE_EFFECT</a></li><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#budgetFor">budgetFor</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#createFileStore">createFileStore</a></li><li><a href="global.html#createUsageCounter">createUsageCounter</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li></ul>
|
|
33
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -54,6 +54,7 @@ import isobj from 'wsemi/src/isobj.mjs'
|
|
|
54
54
|
import dispatchAiFallback from '../dispatchAiFallback.mjs'
|
|
55
55
|
import dfTimeoutMs from '../dfTimeoutMs.mjs'
|
|
56
56
|
import extractJsonLoose from './extractJsonLoose.mjs'
|
|
57
|
+
import NO_SIDE_EFFECT from './noSideEffectPrefix.mjs'
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
// callAiWithFallback.mjs — 工作流的最小呼叫單元: 一個「AI名額」=主模型+自帶遞補鏈
|
|
@@ -71,15 +72,9 @@ import extractJsonLoose from './extractJsonLoose.mjs'
|
|
|
71
72
|
//
|
|
72
73
|
// 【防寫檔前綴】agentic CLI對cwd隔離免疫(會自行解析專案根目錄寫檔),
|
|
73
74
|
// 故預設在prompt前掛「禁止建檔」約束(實測有效); 不需要時傳promptPrefix:''關閉。
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// opencode與claude有獨立的read/grep工具, 不受「禁止執行指令」約束;
|
|
78
|
-
// 但codex讀檔即是執行shell指令, 舊措辭「禁止執行任何指令」對它等同「禁止讀檔」,
|
|
79
|
-
// 凡需讀專案的任務會得到一句「請貼上檔案內容」而非成果, 且該拒答是合法字串,
|
|
80
|
-
// 會通過驗證被遞補層當成「成功」, 整條fallback鏈就此停住——兜底地位被靜默廢掉。
|
|
81
|
-
// 實測: 舊措辭下codex回「無法讀取該檔案」, 改為下列措辭後正常讀檔作答(11.1s),
|
|
82
|
-
// 防副作用(建檔/改檔/刪檔/改動系統狀態)之本旨不變。
|
|
75
|
+
// 措辭已移至獨立模組wkf/noSideEffectPrefix.mjs(單一來源)——不經本層、
|
|
76
|
+
// 直接呼叫dispatchAiFallback的呼叫端亦應引用同一份, 措辭修正時全體同步;
|
|
77
|
+
// 措辭本身的殷鑑(孤兒檔、codex唯讀豁免之A/B實測)詳見該檔檔頭。
|
|
83
78
|
|
|
84
79
|
|
|
85
80
|
//本層自用之設定鍵, 其餘鍵(timeoutMs/budgetMs/minAttemptMs/maxRetries/cwd/store/onEvent/
|
|
@@ -90,16 +85,6 @@ import extractJsonLoose from './extractJsonLoose.mjs'
|
|
|
90
85
|
let OWN_KEYS = ['providers', 'spec', 'check', 'parse', 'rawText', 'promptPrefix', 'meta']
|
|
91
86
|
|
|
92
87
|
|
|
93
|
-
//預設防寫檔前綴(禁副作用, 但豁免唯讀查閱——codex以shell讀檔, 一律禁指令等同禁讀檔)
|
|
94
|
-
let NO_SIDE_EFFECT = [
|
|
95
|
-
'【執行約束】你只需把結果輸出在回覆內容中。',
|
|
96
|
-
'禁止建立、修改或刪除任何檔案,也不要執行任何會改動磁碟或系統狀態的指令——',
|
|
97
|
-
'呼叫端只讀取你的回覆文字,任何寫入磁碟的動作都不會被採用,只會製造無人讀取的垃圾檔。',
|
|
98
|
-
'唯讀查閱(讀取檔案、搜尋內容、列出目錄)不在此限,需要時請照常使用。',
|
|
99
|
-
'', '',
|
|
100
|
-
].join('\n')
|
|
101
|
-
|
|
102
|
-
|
|
103
88
|
/**
|
|
104
89
|
* 依providers定義表把「名稱規格」展開成dispatchAiFallback的providers陣列
|
|
105
90
|
*
|
|
@@ -152,7 +137,7 @@ function buildChain(providers, spec) {
|
|
|
152
137
|
* @param {Function} [opt.check=null] 輸入結果檢核函數(json)=>Boolean,預設null代表只要能解析出JSON即通過
|
|
153
138
|
* @param {Function} [opt.parse=extractJsonLoose] 輸入回覆解析函數(stdout)=>Object|null,預設寬鬆JSON抽取
|
|
154
139
|
* @param {Boolean} [opt.rawText=false] 輸入是否以純文字模式運作布林值,true代表不解析JSON(json欄位為修剪後文字、check收文字),預設false
|
|
155
|
-
* @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt
|
|
140
|
+
* @param {String} [opt.promptPrefix=防寫檔約束] 輸入prompt前綴字串,預設為防寫檔約束(見wkf/noSideEffectPrefix.mjs),傳''關閉
|
|
156
141
|
* @param {Number} [opt.timeoutMs=300000] 輸入單次嘗試逾時毫秒正整數,全套件統一預設300000
|
|
157
142
|
* @param {Number} [opt.budgetMs=null] 輸入整條遞補鏈之時間預算毫秒正整數,預設null代表不限
|
|
158
143
|
* @param {Number} [opt.minAttemptMs=20000] 輸入搭配budgetMs之開工門檻毫秒正整數,剩餘預算低於此值即不再開工,預設20000
|
|
@@ -289,7 +274,7 @@ export { buildChain, NO_SIDE_EFFECT }
|
|
|
289
274
|
<br class="clear">
|
|
290
275
|
|
|
291
276
|
<footer>
|
|
292
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026
|
|
277
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 15:44:01 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
293
278
|
</footer>
|
|
294
279
|
|
|
295
280
|
<script>prettyPrint();</script>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>wkf/createFileStore.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#NO_SIDE_EFFECT">NO_SIDE_EFFECT</a></li><li><a href="global.html#WDispatchAi">WDispatchAi</a></li><li><a href="global.html#adapters">adapters</a></li><li><a href="global.html#attachErrorType">attachErrorType</a></li><li><a href="global.html#budgetFor">budgetFor</a></li><li><a href="global.html#buildChain">buildChain</a></li><li><a href="global.html#buildValidator">buildValidator</a></li><li><a href="global.html#callAiWithFallback">callAiWithFallback</a></li><li><a href="global.html#callOnce">callOnce</a></li><li><a href="global.html#castPintOr">castPintOr</a></li><li><a href="global.html#createFileStore">createFileStore</a></li><li><a href="global.html#createUsageCounter">createUsageCounter</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#dispatchAi">dispatchAi</a></li><li><a href="global.html#dispatchAiFallback">dispatchAiFallback</a></li><li><a href="global.html#dispatchAiWkf">dispatchAiWkf</a></li><li><a href="global.html#dispatchAntigravity">dispatchAntigravity</a></li><li><a href="global.html#dispatchApiOpenaiCompat">dispatchApiOpenaiCompat</a></li><li><a href="global.html#dispatchClaude">dispatchClaude</a></li><li><a href="global.html#dispatchCodex">dispatchCodex</a></li><li><a href="global.html#dispatchOpencode">dispatchOpencode</a></li><li><a href="global.html#extractJsonLoose">extractJsonLoose</a></li><li><a href="global.html#getCliArgs">getCliArgs</a></li><li><a href="global.html#getErrorResult">getErrorResult</a></li><li><a href="global.html#getErrorType">getErrorType</a></li><li><a href="global.html#initState">initState</a></li><li><a href="global.html#isKeyIndependentFail">isKeyIndependentFail</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#reorderByCooling">reorderByCooling</a></li><li><a href="global.html#resolveProviders">resolveProviders</a></li><li><a href="global.html#runFanout">runFanout</a></li><li><a href="global.html#runFanoutPipeline">runFanoutPipeline</a></li><li><a href="global.html#runRolePipeline">runRolePipeline</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">wkf/createFileStore.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import path from 'path'
|
|
49
|
+
import get from 'lodash-es/get.js'
|
|
50
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
51
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
52
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
53
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
54
|
+
import fsReadJson from 'wsemi/src/fsReadJson.mjs'
|
|
55
|
+
import fsWriteJson from 'wsemi/src/fsWriteJson.mjs'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// createFileStore.mjs — dispatchAiFallback之store的檔案持久化實作
|
|
59
|
+
//
|
|
60
|
+
// 【為何需要】dispatchAiFallback只定義store契約{get,set}, 預設用行程內記憶體——
|
|
61
|
+
// 排程任務每次執行都是獨立行程, 記憶體狀態每次歸零: 金鑰輪替游標永遠從第一把開始,
|
|
62
|
+
// 跨次輪替形同失效、額度無法均攤; 供應商冷卻亦然(上一輪已測得失效者, 本輪照樣先踩)。
|
|
63
|
+
// 每個跨行程消費端都得重寫這份檔案實作, 收斂於此。
|
|
64
|
+
//
|
|
65
|
+
// 【採排除式而非白名單式——本設計最重要的一條】本函數只負責「把套件給的state
|
|
66
|
+
// 原封不動存下、再原封不動拿回來」, 不理解state內有哪些欄位。使用端殷鑑:
|
|
67
|
+
// 曾以白名單只取cursors, 於套件1.0.7新增cooling(供應商冷卻)時被靜默丟棄,
|
|
68
|
+
// cooldownMs形同未啟用——且不會有任何錯誤訊息, 只會「功能沒作用」。
|
|
69
|
+
// 排除式(僅剔除本層自用欄位)使日後state再擴充欄位即自動相容。
|
|
70
|
+
//
|
|
71
|
+
// 【併發假定】與dispatchAiFallback之store文件一致: 假定單行程序列調用,
|
|
72
|
+
// 並行執行同一任務請自行加鎖, 否則兩行程互相覆蓋游標。
|
|
73
|
+
//
|
|
74
|
+
// 【本工廠為同步函數會throw】file與dir皆缺屬設定錯誤, 應於組裝期即失敗(fail fast),
|
|
75
|
+
// 與dispatchAiWkf工廠同一約定; 執行期之get/set則不throw(讀壞回空、寫失敗靜默)。
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 建立dispatchAiFallback之store的檔案持久化(游標與冷卻跨行程有效)
|
|
80
|
+
*
|
|
81
|
+
* 特點:
|
|
82
|
+
* 採排除式passthrough——state原封存還,僅剔除本層自用欄位(預設只有at:人讀的最後更新時間戳),
|
|
83
|
+
* 日後套件擴充state欄位(如cooling)即自動相容,不需改本函數;
|
|
84
|
+
* 檔案不存在或非法JSON時get回空物件(套件視為全新狀態);
|
|
85
|
+
* stamp可注入時間戳函數(排程環境建議注入時區錨定者),寫入時補進at欄位供人工debug
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
88
|
+
* @param {String} [opt.file=null] 輸入狀態檔完整路徑字串,與dir/name二擇一
|
|
89
|
+
* @param {String} [opt.dir=null] 輸入狀態目錄字串,與name組成檔案路徑
|
|
90
|
+
* @param {String} [opt.name='ai-cursor.json'] 輸入狀態檔名字串,預設'ai-cursor.json'
|
|
91
|
+
* @param {Array} [opt.ownKeys=['at']] 輸入本層自用、不屬於套件狀態之欄位名字串陣列,get時剔除set時補回,預設['at']
|
|
92
|
+
* @param {Function} [opt.stamp=ISO時間戳] 輸入時間戳函數()=>String,寫入at欄位用,預設回傳new Date().toISOString()
|
|
93
|
+
* @returns {Object} 回傳store物件,內含get、set(可直接餵dispatchAiFallback之store)與file(狀態檔路徑)
|
|
94
|
+
* @example
|
|
95
|
+
*
|
|
96
|
+
* import createFileStore from './src/wkf/createFileStore.mjs'
|
|
97
|
+
* import dispatchAiFallback from './src/dispatchAiFallback.mjs'
|
|
98
|
+
*
|
|
99
|
+
* let store = createFileStore({ dir: './state' })
|
|
100
|
+
* //let r = await dispatchAiFallback(prompt, { providers, store, cooldownMs: 3600000 })
|
|
101
|
+
*
|
|
102
|
+
*/
|
|
103
|
+
function createFileStore(opt = {}) {
|
|
104
|
+
let file = get(opt, 'file', null)
|
|
105
|
+
if (!isestr(file)) {
|
|
106
|
+
let dir = get(opt, 'dir', null)
|
|
107
|
+
if (!isestr(dir)) {
|
|
108
|
+
throw new Error('createFileStore: file or dir is required')
|
|
109
|
+
}
|
|
110
|
+
let name = get(opt, 'name', null)
|
|
111
|
+
file = path.join(dir, isestr(name) ? name : 'ai-cursor.json')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let ownKeys = get(opt, 'ownKeys', null)
|
|
115
|
+
if (!isearr(ownKeys)) {
|
|
116
|
+
ownKeys = ['at']
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let stamp = get(opt, 'stamp', null)
|
|
120
|
+
if (!isfun(stamp)) {
|
|
121
|
+
stamp = () => new Date().toISOString()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
|
|
126
|
+
file,
|
|
127
|
+
|
|
128
|
+
get: () => {
|
|
129
|
+
let r = fsReadJson(file)
|
|
130
|
+
let j = (get(r, 'error', undefined) !== undefined) ? null : get(r, 'success', null)
|
|
131
|
+
if (!isobj(j)) {
|
|
132
|
+
return {}
|
|
133
|
+
}
|
|
134
|
+
let state = { ...j }
|
|
135
|
+
for (let k of ownKeys) {
|
|
136
|
+
delete state[k]
|
|
137
|
+
}
|
|
138
|
+
return state
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
set: (state) => {
|
|
142
|
+
let out = { ...(isobj(state) ? state : {}), at: stamp() }
|
|
143
|
+
fsWriteJson(file, out, { useFormat: true })
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
export default createFileStore
|
|
151
|
+
</code></pre>
|
|
152
|
+
</article>
|
|
153
|
+
</section>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<br class="clear">
|
|
163
|
+
|
|
164
|
+
<footer>
|
|
165
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Aug 18 2026 15:44:01 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
166
|
+
</footer>
|
|
167
|
+
|
|
168
|
+
<script>prettyPrint();</script>
|
|
169
|
+
<script src="scripts/polyfill.js"></script>
|
|
170
|
+
<script src="scripts/linenumber.js"></script>
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
</body>
|
|
175
|
+
</html>
|