w-dispatch-ai 1.0.36 → 1.0.38

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.
Files changed (64) hide show
  1. package/README.md +26 -7
  2. package/dist/w-dispatch-ai.umd.js +2 -2
  3. package/dist/w-dispatch-ai.umd.js.map +1 -1
  4. package/docs/WDispatchAi.mjs.html +2 -2
  5. package/docs/adapters.mjs.html +2 -2
  6. package/docs/budgetFor.mjs.html +2 -2
  7. package/docs/buildValidator.mjs.html +2 -2
  8. package/docs/castPintOr.mjs.html +2 -2
  9. package/docs/checkTruncation.mjs.html +199 -0
  10. package/docs/describeNonJsonBody.mjs.html +122 -0
  11. package/docs/dfTimeoutMs.mjs.html +2 -2
  12. package/docs/dispatchAi.mjs.html +2 -2
  13. package/docs/dispatchAiFallback.mjs.html +42 -8
  14. package/docs/dispatchAiWkf.mjs.html +2 -2
  15. package/docs/dispatchAntigravity.mjs.html +2 -2
  16. package/docs/dispatchApiOpenaiCompat.mjs.html +124 -31
  17. package/docs/dispatchApiOpenaiResponses.mjs.html +113 -27
  18. package/docs/dispatchApiTypesafeSystemone.mjs.html +42 -17
  19. package/docs/dispatchClaude.mjs.html +2 -2
  20. package/docs/dispatchCodex.mjs.html +2 -2
  21. package/docs/dispatchOpencode.mjs.html +2 -2
  22. package/docs/getCliArgs.mjs.html +2 -2
  23. package/docs/getErrorResult.mjs.html +2 -2
  24. package/docs/getErrorType.mjs.html +9 -5
  25. package/docs/global.html +3159 -1806
  26. package/docs/index.html +2 -2
  27. package/docs/quota_dfQuotaTimeoutMs.mjs.html +2 -2
  28. package/docs/quota_fetchQuotaJson.mjs.html +2 -2
  29. package/docs/quota_fromCodexUsageHttp.mjs.html +2 -2
  30. package/docs/quota_getQuotaAntigravity.mjs.html +2 -2
  31. package/docs/quota_getQuotaClaude.mjs.html +2 -2
  32. package/docs/quota_getQuotaCodex.mjs.html +2 -2
  33. package/docs/quota_readJsonOrNull.mjs.html +2 -2
  34. package/docs/quota_toQuotaLabel.mjs.html +2 -2
  35. package/docs/quota_toQuotaResult.mjs.html +2 -2
  36. package/docs/quota_toQuotaScopedLabel.mjs.html +2 -2
  37. package/docs/quota_toQuotaWindow.mjs.html +2 -2
  38. package/docs/readEnvFile.mjs.html +2 -2
  39. package/docs/resolveProviders.mjs.html +2 -2
  40. package/docs/wkf_callAiWithFallback.mjs.html +64 -20
  41. package/docs/wkf_createFileStore.mjs.html +2 -2
  42. package/docs/wkf_createUsageCounter.mjs.html +2 -2
  43. package/docs/wkf_extractJsonLoose.mjs.html +2 -2
  44. package/docs/wkf_noSideEffectPrefix.mjs.html +2 -2
  45. package/docs/wkf_runFanout.mjs.html +4 -2
  46. package/docs/wkf_runFanoutPipeline.mjs.html +2 -2
  47. package/docs/wkf_runRolePipeline.mjs.html +2 -2
  48. package/docs/wkf_salvageTruncatedArray.mjs.html +13 -2
  49. package/package.json +1 -1
  50. package/src/checkTruncation.mjs +127 -0
  51. package/src/describeNonJsonBody.mjs +50 -0
  52. package/src/dispatchAiFallback.mjs +40 -6
  53. package/src/dispatchApiOpenaiCompat.mjs +122 -29
  54. package/src/dispatchApiOpenaiResponses.mjs +111 -25
  55. package/src/dispatchApiTypesafeSystemone.mjs +40 -15
  56. package/src/getErrorType.mjs +7 -3
  57. package/src/providers.mjs +1 -1
  58. package/src/wkf/callAiWithFallback.mjs +62 -18
  59. package/src/wkf/runFanout.mjs +2 -0
  60. package/src/wkf/salvageTruncatedArray.mjs +11 -0
  61. package/test/tools/fakeServerForApiTest.mjs +87 -2
  62. package/test/unit-dispatchApiOpenaiCompat.test.mjs +207 -1
  63. package/test/unit-dispatchApiOpenaiResponses.test.mjs +74 -5
  64. package/test/unit-dispatchApiTypesafeSystemone.test.mjs +25 -1
@@ -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#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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#describeNonJsonBody">describeNonJsonBody</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#finOf">finOf</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#judgeTruncated">judgeTruncated</a></li><li><a href="global.html#normalizeFinishReason">normalizeFinishReason</a></li><li><a href="global.html#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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#safeValidate">safeValidate</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -66,6 +66,8 @@ import callAiWithFallback from './callAiWithFallback.mjs'
66
66
  //
67
67
  // 【實測依據(2026-08-10評比)】整合者是本流程的單點故障——端點不穩的模型
68
68
  // (如偶發靜默空回者)當整合者時, 靠spec.fallback遞補或maxRetries調高才能保住整條鏈。
69
+ // 注意推理模型之空回常是推理耗盡輸出上限(REST類以截斷回報且不重試, 見checkTruncation.mjs),
70
+ // 該調的是max_tokens而非maxRetries。
69
71
 
70
72
 
71
73
  /**
@@ -217,7 +219,7 @@ export { defaultIntegratePrompt }
217
219
  <br class="clear">
218
220
 
219
221
  <footer>
220
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 08:47:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
222
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 15:32:02 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
221
223
  </footer>
222
224
 
223
225
  <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#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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#describeNonJsonBody">describeNonJsonBody</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#finOf">finOf</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#judgeTruncated">judgeTruncated</a></li><li><a href="global.html#normalizeFinishReason">normalizeFinishReason</a></li><li><a href="global.html#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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#safeValidate">safeValidate</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -161,7 +161,7 @@ export default runFanoutPipeline
161
161
  <br class="clear">
162
162
 
163
163
  <footer>
164
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 08:47:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
164
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 15:32:02 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
165
165
  </footer>
166
166
 
167
167
  <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#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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#describeNonJsonBody">describeNonJsonBody</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#finOf">finOf</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#judgeTruncated">judgeTruncated</a></li><li><a href="global.html#normalizeFinishReason">normalizeFinishReason</a></li><li><a href="global.html#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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#safeValidate">safeValidate</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -183,7 +183,7 @@ export default runRolePipeline
183
183
  <br class="clear">
184
184
 
185
185
  <footer>
186
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 08:47:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
186
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 15:32:02 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
187
187
  </footer>
188
188
 
189
189
  <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#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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</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#agyPrint">agyPrint</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#collectAdditional">collectAdditional</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#decodeJwtPayload">decodeJwtPayload</a></li><li><a href="global.html#defaultIntegratePrompt">defaultIntegratePrompt</a></li><li><a href="global.html#describeNonJsonBody">describeNonJsonBody</a></li><li><a href="global.html#dfQuotaTimeoutMs">dfQuotaTimeoutMs</a></li><li><a href="global.html#dfTimeoutMs">dfTimeoutMs</a></li><li><a href="global.html#directWindow">directWindow</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#dispatchApiOpenaiResponses">dispatchApiOpenaiResponses</a></li><li><a href="global.html#dispatchApiTypesafeSystemone">dispatchApiTypesafeSystemone</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#extractOutputText">extractOutputText</a></li><li><a href="global.html#fetchQuotaJson">fetchQuotaJson</a></li><li><a href="global.html#finOf">finOf</a></li><li><a href="global.html#firstStr">firstStr</a></li><li><a href="global.html#fromBucket">fromBucket</a></li><li><a href="global.html#fromCodexUsageHttp">fromCodexUsageHttp</a></li><li><a href="global.html#fromLimitItem">fromLimitItem</a></li><li><a href="global.html#fromRpcWindow">fromRpcWindow</a></li><li><a href="global.html#fromTopField">fromTopField</a></li><li><a href="global.html#fromWindow">fromWindow</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#getQuotaAntigravity">getQuotaAntigravity</a></li><li><a href="global.html#getQuotaClaude">getQuotaClaude</a></li><li><a href="global.html#getQuotaCodex">getQuotaCodex</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#judgeTruncated">judgeTruncated</a></li><li><a href="global.html#normalizeFinishReason">normalizeFinishReason</a></li><li><a href="global.html#pickWindow">pickWindow</a></li><li><a href="global.html#readBodyCapped">readBodyCapped</a></li><li><a href="global.html#readEnvFile">readEnvFile</a></li><li><a href="global.html#readJsonOrNull">readJsonOrNull</a></li><li><a href="global.html#redactText">redactText</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#safeValidate">safeValidate</a></li><li><a href="global.html#salvageTruncatedArray">salvageTruncatedArray</a></li><li><a href="global.html#toQuotaLabel">toQuotaLabel</a></li><li><a href="global.html#toQuotaResult">toQuotaResult</a></li><li><a href="global.html#toQuotaScopedLabel">toQuotaScopedLabel</a></li><li><a href="global.html#toQuotaWindow">toQuotaWindow</a></li><li><a href="global.html#viaAppServer">viaAppServer</a></li><li><a href="global.html#viaHttp">viaHttp</a></li><li><a href="global.html#windowToSeconds">windowToSeconds</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -60,6 +60,17 @@
60
60
  //
61
61
  // 【只搶救「掃到結尾仍未閉合」的截斷】陣列有正常閉合但parse失敗屬別種毛病
62
62
  // (非法字元/壞跳脫), 補`]`救不了反而可能拼出語意錯誤的結果, 一律不碰回null。
63
+ //
64
+ // 【與REST截斷判定之銜接(2026-09-24起)】REST文字類轉接器於validate之前判定截斷且預設失敗;
65
+ // 工作流callAi之acceptTruncated預設為「有自訂parse且非rawText」, 故以本工具組成自訂parse注入即同意
66
+ // 接受截斷內容(既有用法不必改), 結果之truncated:true可辨識救回者為半批; 直接呼叫dispatchAiFallback或轉接器、
67
+ // 且把本工具寫在validate裡者須自給acceptTruncated:true(1.0.37起, 否則截斷在validate之前即判失敗;
68
+ // 安裝方實例: w-knowledge-extract 1.0.1之callJson因此失去搶救, 2026-09-24回報)。
69
+ // CLI類拿不到終止訊號, 其截斷只能靠本工具於解析時發現。
70
+ //
71
+ // 【已知限制(2026-09-24複審指出, 讀碼確認)】只記錄頂層「物件」元素之結束(`}`), 故元素非物件之陣列
72
+ // (如["a","b",…)救不回; 自第一個`[`起算, 故外包物件(如{"items":[…)會救回內層陣列(形狀改變,
73
+ // 呼叫端check須能接受), 前言含`[`(如「[注意]」)則誤判為已閉合而回null。
63
74
 
64
75
 
65
76
  /**
@@ -154,7 +165,7 @@ export default salvageTruncatedArray
154
165
  <br class="clear">
155
166
 
156
167
  <footer>
157
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 08:47:53 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
168
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Thu Sep 24 2026 15:32:02 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
158
169
  </footer>
159
170
 
160
171
  <script>prettyPrint();</script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "w-dispatch-ai",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "main": "dist/w-dispatch-ai.umd.js",
5
5
  "dependencies": {
6
6
  "wsemi": "^1.9.3"
@@ -0,0 +1,127 @@
1
+ import get from 'lodash-es/get.js'
2
+ import isnum from 'wsemi/src/isnum.mjs'
3
+ import isestr from 'wsemi/src/isestr.mjs'
4
+
5
+
6
+ // checkTruncation.mjs — REST文字轉接器之截斷裁定與安全驗證(單一來源, compat與responses共用)
7
+ //
8
+ // 【為何需要(2026-09-24實測後之終審方案)】Zen之space-bunny-free帶max_tokens:600時推理階段即耗盡,
9
+ // 回finish_reason:"length"、content:""(空字串), 舊版dispatchApiOpenaiCompat不看finish_reason而回ok:true、
10
+ // stdout為空——截斷成了無人察覺的成功。同期dispatchApiOpenaiResponses則對status≠completed一律失敗,
11
+ // 兩個兄弟轉接器規則不一; 截斷規則若各自手寫, 日後必再分岔, 故收斂於此。
12
+ //
13
+ // 【規則(兩位Opus 5.5獨立複審後之終審結論, 見README之截斷處理段)】
14
+ // ① 截斷一律於validate之前判定, 預設失敗(errorType incomplete)——「validate接受」不代表內容完整:
15
+ // nonempty/min:N/rawText不檢查完整性, 寬鬆JSON解析還會從前言取出錯誤片段, 不可推定為同意;
16
+ // ② 呼叫端明示acceptTruncated:true才放行, 且僅限length(content_filter為政策性過濾, 殘文一律不收);
17
+ // 放行時有validate則交validate裁決, 無validate則直接接受(供轉發型呼叫端), 結果一律標truncated:true;
18
+ // ③ 可見輸出為空(null/空字串/純空白)時無物可收, 一律失敗, 訊息標明推理可能耗盡輸出上限並附reasoning_tokens。
19
+ //
20
+ // 【validate拋錯視同拒絕】呼叫端之validate(或工作流之parse/check)拋錯時, 舊版一路reject至最外層,
21
+ // 違反各函數「不會reject」之承諾; safeValidate統一接住並回傳拋錯訊息供轉接器置入stderr診斷。
22
+
23
+
24
+ //截斷終止原因集合(正規化後); 其餘值(stop、null、各家未知值)一律視為非截斷, 不可誤殺
25
+ let TRUNCATION_REASONS = ['length', 'content_filter']
26
+
27
+
28
+ /**
29
+ * 正規化終止原因字串(去頭尾空白、轉小寫;非字串回傳空字串)
30
+ *
31
+ * @param {*} v 輸入原始終止原因
32
+ * @returns {String} 回傳正規化字串
33
+ * @example
34
+ *
35
+ * import { normalizeFinishReason } from './src/checkTruncation.mjs'
36
+ *
37
+ * console.log(normalizeFinishReason(' LENGTH '), normalizeFinishReason(null))
38
+ * // => 'length' ''
39
+ *
40
+ */
41
+ function normalizeFinishReason(v) {
42
+ return isestr(v) ? v.trim().toLowerCase() : ''
43
+ }
44
+
45
+
46
+ /**
47
+ * 以不拋錯方式執行驗證函式(拋錯視同拒絕)
48
+ *
49
+ * @param {Function} validator 輸入驗證函式(text)=>Boolean
50
+ * @param {String} text 輸入待驗證文字
51
+ * @returns {Object} 回傳物件,內含pass(是否通過布林值)與threw(拋錯訊息字串,未拋錯為空字串)
52
+ * @example
53
+ *
54
+ * import { safeValidate } from './src/checkTruncation.mjs'
55
+ *
56
+ * console.log(safeValidate((s) => s.length > 1, 'ab'))
57
+ * // => { pass: true, threw: '' }
58
+ *
59
+ * console.log(safeValidate(() => { throw new Error('x') }, 'ab'))
60
+ * // => { pass: false, threw: 'x' }
61
+ *
62
+ */
63
+ function safeValidate(validator, text) {
64
+ try {
65
+ return { pass: !!validator(text), threw: '' }
66
+ }
67
+ catch (err) {
68
+ let msg = get(err, 'message', '')
69
+ return { pass: false, threw: isestr(msg) ? msg : String(err) }
70
+ }
71
+ }
72
+
73
+
74
+ /**
75
+ * 裁定一次已截斷之回應應失敗或放行(呼叫端先判定truncated為true才呼叫)
76
+ *
77
+ * @param {Object} o 輸入設定物件
78
+ * @param {String} o.finishReason 輸入正規化後之終止原因字串,'length'才可能放行
79
+ * @param {String|null} o.content 輸入可見輸出文字,null代表無
80
+ * @param {Boolean} [o.acceptTruncated=false] 輸入呼叫端是否明示接受截斷內容布林值
81
+ * @param {Function|null} [o.validator=null] 輸入驗證函式,放行時交其裁決
82
+ * @param {Number|null} [o.reasoningTokens=null] 輸入推理token數,可見輸出為空時附於訊息
83
+ * @param {String} o.label 輸入錯誤訊息主體字串,例如'finish_reason=length'或'status=incomplete (max_output_tokens)'
84
+ * @returns {Object} 回傳物件,內含accept(是否放行布林值)、error(不放行時之錯誤訊息字串)、threw(validate拋錯訊息字串)
85
+ * @example
86
+ *
87
+ * import { judgeTruncated } from './src/checkTruncation.mjs'
88
+ *
89
+ * console.log(judgeTruncated({ finishReason: 'length', content: '', reasoningTokens: 600, label: 'finish_reason=length' }).error)
90
+ * // => 'INCOMPLETE_RESPONSE: finish_reason=length; no visible output (reasoning may have used up the output token limit, reasoning_tokens=600)'
91
+ *
92
+ * console.log(judgeTruncated({ finishReason: 'length', content: '[{"a":1},', acceptTruncated: true, label: 'finish_reason=length' }).accept)
93
+ * // => true
94
+ *
95
+ */
96
+ function judgeTruncated(o) {
97
+ let finishReason = get(o, 'finishReason', '')
98
+ let content = get(o, 'content', null)
99
+ let acceptTruncated = get(o, 'acceptTruncated', false) === true
100
+ let validator = get(o, 'validator', null)
101
+ let reasoningTokens = get(o, 'reasoningTokens', null)
102
+ let base = `INCOMPLETE_RESPONSE: ${get(o, 'label', '')}`
103
+
104
+ //可見輸出為空: 無物可收, 一律失敗並標明可能為推理耗盡
105
+ let visible = isestr(content) ? content.trim() : ''
106
+ if (visible === '') {
107
+ let rt = isnum(reasoningTokens) ? `, reasoning_tokens=${reasoningTokens}` : ''
108
+ return { accept: false, error: `${base}; no visible output (reasoning may have used up the output token limit${rt})`, threw: '' }
109
+ }
110
+
111
+ //content_filter等非length之截斷一律不收; 未明示同意亦不收
112
+ if (finishReason !== 'length' || !acceptTruncated) {
113
+ return { accept: false, error: base, threw: '' }
114
+ }
115
+
116
+ //明示同意: 有validate交其裁決, 無validate直接接受
117
+ if (typeof validator === 'function') {
118
+ let v = safeValidate(validator, content)
119
+ if (!v.pass) {
120
+ return { accept: false, error: `${base}; rejected by validate`, threw: v.threw }
121
+ }
122
+ }
123
+ return { accept: true, error: '', threw: '' }
124
+ }
125
+
126
+
127
+ export { TRUNCATION_REASONS, normalizeFinishReason, safeValidate, judgeTruncated }
@@ -0,0 +1,50 @@
1
+ import get from 'lodash-es/get.js'
2
+ import isestr from 'wsemi/src/isestr.mjs'
3
+
4
+
5
+ // describeNonJsonBody.mjs — REST轉接器「HTTP 200但本體非JSON」之錯誤訊息(單一來源, 三個REST轉接器與遞補層共用)
6
+ //
7
+ // 【為何需要(2026-09-24安裝方建議C2)】舊版各REST轉接器之JSON.parse失敗被吞掉, 與「是JSON但缺欄位」回同一句
8
+ // INVALID_RESPONSE(如missing choices[0].message.content), stderr又只有解碼後之前500字——本體若是壓縮位元組
9
+ // 只剩亂碼, 安裝方為此直接重打才查出是brotli漏標Content-Encoding。本訊息附原始位元組數、前16位元組之hex
10
+ // (gzip以1f8b開頭等特徵可一眼辨認)、content-encoding與content-type, 令此類故障不必重打即可定位。
11
+ //
12
+ // 【分流】本體非JSON屬傳輸或閘道狀態, 同一請求換金鑰必然再敗一次, 遞補層以BODY_NOT_JSON前綴判為與金鑰無關
13
+ // 而整組跳過(安裝方實例: 換第二把金鑰只是以剩餘預算重打至逾時); 「JSON缺欄位」則維持換金鑰——
14
+ // 部分閘道以200回帳號層級之錯誤JSON, 換金鑰可能有效。errorType一律仍為invalid-response(字彙不增)。
15
+
16
+
17
+ //錯誤訊息前綴, dispatchAiFallback據此判定整組跳過
18
+ let BODY_NOT_JSON = 'INVALID_RESPONSE: body is not JSON'
19
+
20
+
21
+ /**
22
+ * 組出「HTTP 200但本體非JSON」之錯誤訊息
23
+ *
24
+ * @param {Uint8Array} bytes 輸入回應本體之原始位元組(未解碼)
25
+ * @param {Headers|Object} headers 輸入回應標頭(fetch之Headers,或具get方法之物件)
26
+ * @returns {String} 回傳錯誤訊息字串,以BODY_NOT_JSON開頭,附位元組數、前16位元組hex、content-encoding與content-type
27
+ * @example
28
+ *
29
+ * import describeNonJsonBody from './src/describeNonJsonBody.mjs'
30
+ *
31
+ * let h = new Headers({ 'content-type': 'application/json' })
32
+ * console.log(describeNonJsonBody(new Uint8Array([0x8b, 0xef, 0x02]), h))
33
+ * // => 'INVALID_RESPONSE: body is not JSON (3 bytes, first bytes 8bef02, content-encoding=none, content-type=application/json)'
34
+ *
35
+ */
36
+ function describeNonJsonBody(bytes, headers) {
37
+ let u8 = (bytes instanceof Uint8Array) ? bytes : new Uint8Array(0)
38
+ let hex = Array.from(u8.subarray(0, 16)).map((b) => b.toString(16).padStart(2, '0')).join('')
39
+ let getH = (k) => {
40
+ let fn = get(headers, 'get', null)
41
+ let v = (typeof fn === 'function') ? headers.get(k) : null
42
+ return isestr(v) ? v : 'none'
43
+ }
44
+ let more = u8.length > 16 ? '...' : ''
45
+ return `${BODY_NOT_JSON} (${u8.length} bytes, first bytes ${hex || '(none)'}${more}, content-encoding=${getH('content-encoding')}, content-type=${getH('content-type')})`
46
+ }
47
+
48
+
49
+ export default describeNonJsonBody
50
+ export { BODY_NOT_JSON }
@@ -10,6 +10,7 @@ import dispatchAi from './dispatchAi.mjs'
10
10
  import getErrorResult from './getErrorResult.mjs'
11
11
  import castPintOr from './castPintOr.mjs'
12
12
  import dfTimeoutMs from './dfTimeoutMs.mjs'
13
+ import { BODY_NOT_JSON } from './describeNonJsonBody.mjs'
13
14
 
14
15
 
15
16
  // dispatchAiFallback.mjs — 多供應商自動遞補層
@@ -17,8 +18,14 @@ import dfTimeoutMs from './dfTimeoutMs.mjs'
17
18
  // 【兩層策略】群組之間依providers宣告順序(優先序), 群組之內(keys多把)以游標輪替(額度均攤)。
18
19
  //
19
20
  // 【失敗分流】只分兩路:
20
- // 與金鑰無關之失敗(TIMEOUT/ENOENT/參數錯誤/驗證失敗/未知kind) → 整組跳過——
21
+ // 與金鑰無關之失敗(TIMEOUT/ENOENT/參數錯誤/驗證失敗/未知kind/截斷/工具不支援/本體非JSON) → 整組跳過——
21
22
  // 同組各金鑰共用同一exe與model, 換金鑰必然再敗一次, 純屬空耗;
23
+ // 本體非JSON(REST之HTTP 200但無法解析, 見describeNonJsonBody.mjs)屬傳輸或閘道狀態, 2026-09-24依安裝方實例納入
24
+ // (換第二把金鑰只是以剩餘預算重打至逾時); 「JSON缺欄位」不在此列, 仍換金鑰(部分閘道以200回帳號層級錯誤);
25
+ // 截斷(結果之truncated為true, 僅REST文字類可判, 見checkTruncation.mjs)與工具不支援(TOOL_CALLS_UNSUPPORTED)
26
+ // 皆屬模型對同一請求之產出性質, 2026-09-24起納入(前者由複審指出同模型換金鑰再截斷一次;
27
+ // 後者之既有測試標題即寫「不逐把空耗」而斷言卻為逐把換金鑰, 一併更正);
28
+ // REST之status=failed(服務回錯)不屬截斷, 維持換金鑰;
22
29
  // 其餘失敗(含額度上限/金鑰無效/服務回錯等一切未分類者) → 換組內下一把, 不記憶、不停用。
23
30
  // 不可把正確性建立在「錯誤分類器必須窮盡」之上——實測各家額度/金鑰錯誤訊息
24
31
  // 含中文(无效的令牌)與無特徵字串(UnknownError), 正則涵蓋不了; 而額度視窗有5小時滾動、
@@ -174,6 +181,17 @@ function reorderByCooling(providers, state, cooldownMs, saveState) {
174
181
  }
175
182
 
176
183
 
184
+ /**
185
+ * 取結果之截斷資訊供tried各項記錄(REST文字類轉接器才帶, 其餘kind回空物件)
186
+ *
187
+ * @param {Object} r 輸入dispatchAi結果物件
188
+ * @returns {Object} 回傳物件,含truncated與finishReason,或空物件
189
+ */
190
+ function finOf(r) {
191
+ return (get(r, 'truncated', undefined) !== undefined) ? { truncated: r.truncated, finishReason: r.finishReason } : {}
192
+ }
193
+
194
+
177
195
  /**
178
196
  * 判斷失敗結果是否與「哪一把金鑰」無關(換組內金鑰必然再敗, 應整組跳過)
179
197
  *
@@ -209,6 +227,21 @@ function isKeyIndependentFail(r) {
209
227
  return true
210
228
  }
211
229
 
230
+ //截斷, 同模型同請求換金鑰必然再截斷(依機械旗標truncated判定, 不依errorType——REST之status=failed亦為incomplete但非截斷)
231
+ if (get(r, 'truncated', false) === true) {
232
+ return true
233
+ }
234
+
235
+ //模型回工具呼叫而api類不支援, 屬模型對同一請求之產出性質, 換金鑰仍是同樣產出
236
+ if (error.indexOf('TOOL_CALLS_UNSUPPORTED') === 0) {
237
+ return true
238
+ }
239
+
240
+ //HTTP 200但本體非JSON, 屬傳輸或閘道狀態, 換金鑰必然再敗(「JSON缺欄位」不在此列, 仍換金鑰; 見describeNonJsonBody.mjs)
241
+ if (error.indexOf(BODY_NOT_JSON) === 0) {
242
+ return true
243
+ }
244
+
212
245
  //kind無效, 屬條目設定錯誤
213
246
  if (error.indexOf('unknown ai kind') === 0) {
214
247
  return true
@@ -225,7 +258,7 @@ function isKeyIndependentFail(r) {
225
258
  * providers陣列順序即優先序,排前面的先用;
226
259
  * 條目本身即該次調用之opt(除id與keys外原樣透傳對應轉接器),與dispatchAi「條目直接當opt」同一約定;
227
260
  * 條目給予keys(多把金鑰)時以游標輪替,某把失敗自動換下一把,全數失敗才遞補下一組;
228
- * 與金鑰無關之失敗(逾時/執行檔不存在/參數錯誤/輸出未過驗證/未知kind)直接整組跳過,不逐把空耗;
261
+ * 與金鑰無關之失敗(逾時/執行檔不存在/參數錯誤/輸出未過驗證/未知kind/截斷/工具不支援/HTTP 200但本體非JSON)直接整組跳過,不逐把空耗;
229
262
  * 跨次執行僅記憶游標(經store注入持久化),不設金鑰停用清單——額度視窗形態多樣(5小時滾動/逐時/逐日),
230
263
  * 停用會把已恢復的金鑰閒置,而重探的代價僅一次快速失敗;
231
264
  * 本函數不會reject,一律以結果物件之ok與error欄位回報成敗
@@ -246,8 +279,9 @@ function isKeyIndependentFail(r) {
246
279
  * @param {Function} [opt.onEvent=null] 輸入事件回調函數(ev)=>{},ev.type可為'try'、'ok'、'next-key'、'skip-group'、'budget-out'、'aborted'、'cooled'(冷卻觸發,帶error與cooldownMs,僅cooldownMs>0時出現);失敗事件(next-key/skip-group)另帶errorType、stdout(被拒回覆)與stderr(錯誤輸出)供診斷,後兩者於失敗路徑已由轉接器截斷;回調拋出例外不影響主流程,預設null
247
280
  * @param {Number} [opt.timeoutMs=300000] 輸入各attempt共用之逾時毫秒正整數,條目可覆寫,全套件統一預設300000
248
281
  * @param {String|Function} [opt.validate=undefined] 輸入各attempt共用之stdout驗證規則,條目可覆寫,預設undefined
282
+ * @param {Boolean} [opt.acceptTruncated=false] 輸入是否接受REST文字類轉接器回報之截斷內容布林值(原樣轉傳轉接器),1.0.37起截斷於validate之前判失敗,validate內含搶救策略者須給true,預設false
249
283
  * @param {Number} [opt.maxRetries=0] 輸入各attempt共用之同家重試次數非負整數,韌性建議交給換家而非重試同一家,預設0
250
- * @returns {Promise} 回傳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類無此欄;本函數不會reject
284
+ * @returns {Promise} 回傳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
251
285
  * @example
252
286
  * //need opencode, claude, codex cli in system PATH
253
287
  *
@@ -443,7 +477,7 @@ async function dispatchAiFallback(prompt, opt = {}) {
443
477
  saveState()
444
478
  }
445
479
  emit({ type: 'ok', providerId: id, keyIndex, keyId, durationMs: r.durationMs })
446
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'ok', durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
480
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'ok', durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}), ...finOf(r) })
447
481
  return { ...r, providerId: id, keyIndex, kind, model, tried }
448
482
  }
449
483
 
@@ -474,14 +508,14 @@ async function dispatchAiFallback(prompt, opt = {}) {
474
508
 
475
509
  //與金鑰無關, 整組跳過
476
510
  emit({ type: 'skip-group', providerId: id, keyIndex, keyId, error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr })
477
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'skip-group', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
511
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'skip-group', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}), ...finOf(r) })
478
512
  skipGroup = true
479
513
  }
480
514
  else {
481
515
 
482
516
  //其餘(含額度上限/金鑰無效/未分類), 換組內下一把, 不記憶不停用
483
517
  emit({ type: 'next-key', providerId: id, keyIndex, keyId, error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr })
484
- tried.push({ providerId: id, keyIndex, keyId, outcome: 'next-key', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}) })
518
+ tried.push({ providerId: id, keyIndex, keyId, outcome: 'next-key', error: r.error, errorType: r.errorType, stdout: r.stdout, stderr: r.stderr, durationMs: r.durationMs, ...(r.usage !== undefined ? { usage: r.usage } : {}), ...finOf(r) })
485
519
  }
486
520
 
487
521
  }