w-dispatch-ai 1.0.21 → 1.0.23

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 (82) hide show
  1. package/README.md +627 -569
  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 +12 -4
  5. package/docs/adapters.mjs.html +5 -3
  6. package/docs/budgetFor.mjs.html +2 -2
  7. package/docs/buildValidator.mjs.html +166 -0
  8. package/docs/castPintOr.mjs.html +2 -2
  9. package/docs/dfTimeoutMs.mjs.html +2 -2
  10. package/docs/dispatchAi.mjs.html +2 -2
  11. package/docs/dispatchAiFallback.mjs.html +2 -2
  12. package/docs/dispatchAiWkf.mjs.html +2 -2
  13. package/docs/dispatchAntigravity.mjs.html +2 -2
  14. package/docs/dispatchApiOpenaiCompat.mjs.html +4 -68
  15. package/docs/dispatchApiOpenaiResponses.mjs.html +498 -0
  16. package/docs/dispatchClaude.mjs.html +2 -2
  17. package/docs/dispatchCodex.mjs.html +2 -2
  18. package/docs/dispatchOpencode.mjs.html +2 -2
  19. package/docs/getCliArgs.mjs.html +2 -2
  20. package/docs/getErrorResult.mjs.html +2 -2
  21. package/docs/getErrorType.mjs.html +5 -3
  22. package/docs/global.html +11790 -3425
  23. package/docs/index.html +2 -2
  24. package/docs/quota_dfQuotaTimeoutMs.mjs.html +99 -0
  25. package/docs/quota_fetchQuotaJson.mjs.html +348 -0
  26. package/docs/quota_fromCodexUsageHttp.mjs.html +335 -0
  27. package/docs/quota_getQuotaAntigravity.mjs.html +463 -0
  28. package/docs/quota_getQuotaClaude.mjs.html +457 -0
  29. package/docs/quota_getQuotaCodex.mjs.html +515 -0
  30. package/docs/quota_readJsonOrNull.mjs.html +113 -0
  31. package/docs/quota_toQuotaLabel.mjs.html +146 -0
  32. package/docs/quota_toQuotaResult.mjs.html +214 -0
  33. package/docs/quota_toQuotaScopedLabel.mjs.html +117 -0
  34. package/docs/quota_toQuotaWindow.mjs.html +239 -0
  35. package/docs/readEnvFile.mjs.html +2 -2
  36. package/docs/resolveProviders.mjs.html +3 -3
  37. package/docs/wkf_callAiWithFallback.mjs.html +2 -2
  38. package/docs/wkf_createFileStore.mjs.html +2 -2
  39. package/docs/wkf_createUsageCounter.mjs.html +2 -2
  40. package/docs/wkf_extractJsonLoose.mjs.html +2 -2
  41. package/docs/wkf_noSideEffectPrefix.mjs.html +2 -2
  42. package/docs/wkf_runFanout.mjs.html +2 -2
  43. package/docs/wkf_runFanoutPipeline.mjs.html +2 -2
  44. package/docs/wkf_runRolePipeline.mjs.html +2 -2
  45. package/docs/wkf_salvageTruncatedArray.mjs.html +2 -2
  46. package/g.mjs +2 -2
  47. package/package.json +2 -2
  48. package/src/WDispatchAi.mjs +10 -2
  49. package/src/adapters.mjs +3 -1
  50. package/src/buildValidator.mjs +94 -0
  51. package/src/dispatchApiOpenaiCompat.mjs +2 -66
  52. package/src/dispatchApiOpenaiResponses.mjs +426 -0
  53. package/src/getErrorType.mjs +3 -1
  54. package/src/providers.mjs +303 -213
  55. package/src/quota/dfQuotaTimeoutMs.mjs +27 -0
  56. package/src/quota/fetchQuotaJson.mjs +276 -0
  57. package/src/quota/fromCodexUsageHttp.mjs +263 -0
  58. package/src/quota/getQuotaAntigravity.mjs +391 -0
  59. package/src/quota/getQuotaClaude.mjs +385 -0
  60. package/src/quota/getQuotaCodex.mjs +443 -0
  61. package/src/quota/readJsonOrNull.mjs +41 -0
  62. package/src/quota/toQuotaLabel.mjs +74 -0
  63. package/src/quota/toQuotaResult.mjs +142 -0
  64. package/src/quota/toQuotaScopedLabel.mjs +45 -0
  65. package/src/quota/toQuotaWindow.mjs +167 -0
  66. package/src/resolveProviders.mjs +1 -1
  67. package/test/tools/fakeCliForTest.mjs +4 -3
  68. package/test/tools/fakeServerForApiTest.mjs +108 -3
  69. package/test/tools/fakeServerForQuotaTest.mjs +171 -0
  70. package/test/unit-WDispatchAi.test.mjs +19 -6
  71. package/test/unit-adapters.test.mjs +5 -3
  72. package/test/unit-dispatchAi.test.mjs +1 -1
  73. package/test/unit-dispatchApiOpenaiResponses.test.mjs +198 -0
  74. package/test/unit-fetchQuotaJson.test.mjs +70 -0
  75. package/test/unit-fromCodexUsageHttp.test.mjs +76 -0
  76. package/test/unit-getQuotaAntigravity.test.mjs +123 -0
  77. package/test/unit-getQuotaClaude.test.mjs +137 -0
  78. package/test/unit-getQuotaCodex.test.mjs +191 -0
  79. package/test/unit-readJsonOrNull.test.mjs +44 -0
  80. package/test/unit-toQuotaLabel.test.mjs +43 -0
  81. package/test/unit-toQuotaResult.test.mjs +53 -0
  82. package/test/unit-toQuotaWindow.test.mjs +73 -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#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>
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#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>
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 Wed Sep 02 2026 18:01:49 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Sep 05 2026 22:03:08 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,99 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>quota/dfQuotaTimeoutMs.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#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#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>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">quota/dfQuotaTimeoutMs.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>// dfQuotaTimeoutMs.mjs — 額度查詢類函數統一之預設逾時毫秒(單一來源)
49
+ //
50
+ // 【為何不沿用dfTimeoutMs】dfTimeoutMs(300000)是為agent型CLI之單次推論而設,
51
+ // 該類任務實測可達116秒故須寬放; 額度查詢是單次HTTP或一次唯讀CLI呼叫,
52
+ // 給5分鐘只會讓斷網或DNS異常的呼叫端空等5分鐘。
53
+ //
54
+ // 【為何取20000】HTTP查詢正常在1秒內; codex app-server握手加兩次讀取實測約2秒;
55
+ // 留給TLS握手、跨海延遲與冷啟動充裕餘裕, 又不至於讓失敗案例卡住呼叫端。
56
+ // agy之print模式實測5~8秒(含啟動、認證、兩個後端往返), 其轉接器另以較寬之預設覆寫, 見getQuotaAntigravity。
57
+
58
+
59
+ /**
60
+ * 額度查詢類函數統一之預設逾時毫秒
61
+ *
62
+ * @returns {Number} 回傳預設逾時毫秒整數20000(20秒)
63
+ * @example
64
+ *
65
+ * import dfQuotaTimeoutMs from './src/quota/dfQuotaTimeoutMs.mjs'
66
+ *
67
+ * console.log(dfQuotaTimeoutMs)
68
+ * // => 20000
69
+ *
70
+ */
71
+ let dfQuotaTimeoutMs = 20000
72
+
73
+
74
+ export default dfQuotaTimeoutMs
75
+ </code></pre>
76
+ </article>
77
+ </section>
78
+
79
+
80
+
81
+
82
+
83
+
84
+ </div>
85
+
86
+ <br class="clear">
87
+
88
+ <footer>
89
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Sep 05 2026 22:03:07 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
90
+ </footer>
91
+
92
+ <script>prettyPrint();</script>
93
+ <script src="scripts/polyfill.js"></script>
94
+ <script src="scripts/linenumber.js"></script>
95
+
96
+
97
+
98
+ </body>
99
+ </html>
@@ -0,0 +1,348 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>quota/fetchQuotaJson.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#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#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>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">quota/fetchQuotaJson.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import isestr from 'wsemi/src/isestr.mjs'
50
+ import iseobj from 'wsemi/src/iseobj.mjs'
51
+ import isearr from 'wsemi/src/isearr.mjs'
52
+ import castPintOr from '../castPintOr.mjs'
53
+ import dfQuotaTimeoutMs from './dfQuotaTimeoutMs.mjs'
54
+
55
+
56
+ // fetchQuotaJson.mjs — 各額度查詢端點共用之HTTP請求並解析JSON
57
+ //
58
+ // 【為何自寫而不引HTTP套件】本套件相依僅wsemi一項, 額度查詢只需單次請求,
59
+ // Node 18+已內建fetch, 引入額外相依不划算。
60
+ //
61
+ // 【為何逾時走AbortController且涵蓋讀取本文】fetch本身無逾時參數; 且fetch resolve只代表收到標頭,
62
+ // 本文仍可能無限期慢流, 故計時器須持續到本文讀完才解除。
63
+ //
64
+ // 【為何錯誤要分類】呼叫端對「token過期(auth)」與「暫時性網路失敗(network/timeout)」
65
+ // 之處置完全不同, 只給錯誤訊息字串則呼叫端須解析文字判斷。
66
+ //
67
+ // 【為何有redact與maxBodyBytes】錯誤訊息會夾帶回應本文片段, 若對端把請求標頭回顯(除錯頁、代理錯誤頁),
68
+ // 權杖或帳號ID便會流入log; 呼叫端把機密值交給redact, 進訊息前一律替換。本文大小上限則防止
69
+ // 異常回應(例如導向到整頁HTML或無限串流)撐爆記憶體; 額度端點正常回應不到10KB, 1MB綽綽有餘。
70
+ // 兩者皆參考同類工具codex-reset-checker之做法。
71
+
72
+
73
+ //回應本文預設上限位元組
74
+ let DEFAULT_MAX_BODY_BYTES = 1048576
75
+
76
+
77
+ //遮蔽替代文字; 短於此長度之機密值不遮(避免把常見短字串誤遮成無法閱讀)
78
+ let REDACT_MASK = '[REDACTED]'
79
+ let REDACT_MIN_LEN = 6
80
+
81
+
82
+ /**
83
+ * 將文字中之機密值替換為遮蔽字
84
+ *
85
+ * @param {String} text 輸入文字
86
+ * @param {Array} redact 輸入機密值字串陣列
87
+ * @returns {String} 回傳替換後文字
88
+ */
89
+ function redactText(text, redact) {
90
+ let s = String(text)
91
+ for (let v of redact) {
92
+ if (isestr(v) &amp;&amp; v.length >= REDACT_MIN_LEN) {
93
+ s = s.split(v).join(REDACT_MASK)
94
+ }
95
+ }
96
+ return s
97
+ }
98
+
99
+
100
+ /**
101
+ * 讀取回應本文, 超過上限即中止
102
+ *
103
+ * @param {Object} r 輸入fetch之Response
104
+ * @param {Number} maxBytes 輸入上限位元組
105
+ * @returns {Promise} 回傳物件{ok, text, bytes}, 超限時ok為false且bytes為已讀位元組(或content-length)
106
+ */
107
+ async function readBodyCapped(r, maxBytes) {
108
+
109
+ //content-length, 先擋明顯超限者, 不必開始讀
110
+ let cl = Number(r.headers.get('content-length') || 0)
111
+ if (cl > maxBytes) {
112
+ return { ok: false, text: '', bytes: cl }
113
+ }
114
+
115
+ //無串流介面時退回text(), 讀完再量
116
+ let body = r.body
117
+ if (!body || typeof body.getReader !== 'function') {
118
+ let t = await r.text()
119
+ let n = Buffer.byteLength(t)
120
+ if (n > maxBytes) {
121
+ return { ok: false, text: '', bytes: n }
122
+ }
123
+ return { ok: true, text: t, bytes: n }
124
+ }
125
+
126
+ //串流讀取, 邊讀邊量, 超限即取消
127
+ let reader = body.getReader()
128
+ let chunks = []
129
+ let total = 0
130
+ while (true) {
131
+ let { done, value } = await reader.read()
132
+ if (done) {
133
+ break
134
+ }
135
+ total += value.byteLength
136
+ if (total > maxBytes) {
137
+ try {
138
+ await reader.cancel()
139
+ }
140
+ catch (err) {}
141
+ return { ok: false, text: '', bytes: total }
142
+ }
143
+ chunks.push(Buffer.from(value))
144
+ }
145
+ return { ok: true, text: Buffer.concat(chunks).toString('utf8'), bytes: total }
146
+ }
147
+
148
+
149
+ /**
150
+ * 各額度查詢端點共用之HTTP請求並解析JSON
151
+ *
152
+ * 本函數不會reject,一律以結果物件之ok與error欄位回報成敗,
153
+ * 並將失敗歸入機器可讀之errorType,令呼叫端無須解析錯誤訊息字串即可分流處置
154
+ *
155
+ * @param {String} url 輸入查詢端點網址字串
156
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
157
+ * @param {String} [opt.method='GET'] 輸入HTTP方法字串,預設'GET'
158
+ * @param {Object|String} [opt.body=null] 輸入請求內容,給物件時序列化為JSON並自動補Content-Type,給字串時原樣送出,預設null代表無內容
159
+ * @param {Object} [opt.headers={}] 輸入額外HTTP標頭物件,預設{}
160
+ * @param {Number} [opt.timeoutMs=20000] 輸入逾時毫秒正整數,涵蓋連線至本文讀完,逾時將中止連線,預設20000
161
+ * @param {Number} [opt.maxBodyBytes=1048576] 輸入回應本文上限位元組正整數,超過即中止並回errorType 'toolarge',預設1048576
162
+ * @param {Array} [opt.redact=[]] 輸入須自錯誤訊息中遮蔽之機密值字串陣列(例如權杖、帳號ID),長度不足6者不處理,預設[]
163
+ * @returns {Promise} 回傳Promise,resolve回傳結果物件,內含ok(是否成功布林值)、status(HTTP狀態碼整數,未連上為0)、data(解析後之JSON物件,失敗為null)、error(錯誤訊息字串,成功時為空字串,已套用redact)、errorType(錯誤類別字串:params/auth/forbidden/ratelimit/http/parse/timeout/network/toolarge)、durationMs(耗時毫秒),本函數不會reject
164
+ * @example
165
+ * //need network
166
+ *
167
+ * import fetchQuotaJson from './src/quota/fetchQuotaJson.mjs'
168
+ *
169
+ * let test = async () => {
170
+ * let r = await fetchQuotaJson('https://api.anthropic.com/api/oauth/usage', {
171
+ * headers: { Authorization: 'Bearer xxx' },
172
+ * })
173
+ * console.log(r.ok, r.status)
174
+ * // => false 401
175
+ * }
176
+ * test()
177
+ *
178
+ */
179
+ async function fetchQuotaJson(url, opt = {}) {
180
+
181
+ let t0 = Date.now()
182
+
183
+ //rs, 各分支共用之結果骨架
184
+ let rs = (ok, status, data, error, errorType) => {
185
+ return {
186
+ ok,
187
+ status,
188
+ data,
189
+ error,
190
+ errorType,
191
+ durationMs: Date.now() - t0,
192
+ }
193
+ }
194
+
195
+ //check url
196
+ if (!isestr(url)) {
197
+ return rs(false, 0, null, 'url must be a non-empty string', 'params')
198
+ }
199
+
200
+ //headers, 非物件回退空物件
201
+ let headers = get(opt, 'headers', null)
202
+ if (!iseobj(headers)) {
203
+ headers = {}
204
+ }
205
+
206
+ //method, 無效回退GET
207
+ let method = get(opt, 'method', null)
208
+ if (!isestr(method)) {
209
+ method = 'GET'
210
+ }
211
+
212
+ //body, 物件即序列化為JSON並補Content-Type, 字串原樣送出
213
+ let body = get(opt, 'body', null)
214
+ if (iseobj(body)) {
215
+ body = JSON.stringify(body)
216
+ if (!isestr(get(headers, 'Content-Type', null))) {
217
+ headers = {
218
+ ...headers,
219
+ 'Content-Type': 'application/json',
220
+ }
221
+ }
222
+ }
223
+ else if (!isestr(body)) {
224
+ body = null
225
+ }
226
+
227
+ //timeoutMs
228
+ let timeoutMs = castPintOr(get(opt, 'timeoutMs', null), dfQuotaTimeoutMs)
229
+
230
+ //maxBodyBytes
231
+ let maxBodyBytes = castPintOr(get(opt, 'maxBodyBytes', null), DEFAULT_MAX_BODY_BYTES)
232
+
233
+ //redact
234
+ let redact = get(opt, 'redact', null)
235
+ if (!isearr(redact)) {
236
+ redact = []
237
+ }
238
+
239
+ //snip, 錯誤訊息用之本文片段: 先遮蔽再截短, 避免機密值被截半後漏出
240
+ let snip = (text) => redactText(text, redact).slice(0, 300)
241
+
242
+ //ac, 逾時中止連線; 計時器持續至本文讀完
243
+ let ac = new AbortController()
244
+ let idTimer = setTimeout(() => {
245
+ ac.abort()
246
+ }, timeoutMs)
247
+
248
+ let r = null
249
+ let text = ''
250
+ try {
251
+
252
+ //fetch, resolve僅代表收到標頭
253
+ try {
254
+ r = await fetch(url, {
255
+ method,
256
+ headers,
257
+ body,
258
+ signal: ac.signal,
259
+ })
260
+ }
261
+ catch (err) {
262
+ if (get(err, 'name', '') === 'AbortError') {
263
+ return rs(false, 0, null, `request timeout over ${timeoutMs}ms`, 'timeout')
264
+ }
265
+ return rs(false, 0, null, `network error: ${redactText(get(err, 'message', 'unknown'), redact)}`, 'network')
266
+ }
267
+
268
+ //body, 先取原文再解析, 令非JSON之錯誤頁能原樣回報而非只說解析失敗
269
+ try {
270
+ let rb = await readBodyCapped(r, maxBodyBytes)
271
+ if (!rb.ok) {
272
+ ac.abort()
273
+ return rs(false, r.status, null, `response body too large: ${rb.bytes} bytes over limit ${maxBodyBytes}`, 'toolarge')
274
+ }
275
+ text = rb.text
276
+ }
277
+ catch (err) {
278
+ if (get(err, 'name', '') === 'AbortError') {
279
+ return rs(false, r.status, null, `request timeout over ${timeoutMs}ms while reading body`, 'timeout')
280
+ }
281
+ return rs(false, r.status, null, `read body error: ${redactText(get(err, 'message', 'unknown'), redact)}`, 'network')
282
+ }
283
+
284
+ }
285
+ finally {
286
+ clearTimeout(idTimer)
287
+ }
288
+
289
+ //auth, 401代表憑證本身不被接受(過期或撤銷)。此處不給處置指引——各家正確處置不同
290
+ //(Claude/Codex之CLI會於執行時自行刷新, 存檔之refresh token仍有效, 貿然指引「重新登入」是多餘的), 由各轉接器補述
291
+ if (r.status === 401) {
292
+ return rs(false, r.status, null, `unauthorized(401), credential rejected (expired or revoked): ${snip(text)}`, 'auth')
293
+ }
294
+
295
+ //forbidden, 403代表憑證有效但此帳號無權存取該資源, 與401分流(401重新登入可解, 403重新登入一百次也一樣)
296
+ if (r.status === 403) {
297
+ return rs(false, r.status, null, `forbidden(403), credential valid but this account has no access to the resource (plan does not include it, or the service no longer serves this account tier): ${snip(text)}`, 'forbidden')
298
+ }
299
+
300
+ //ratelimit, 429為端點自身之查詢頻率限制, 與訂閱額度用罄無關
301
+ if (r.status === 429) {
302
+ return rs(false, r.status, null, `rate limited(429) by usage endpoint: ${snip(text)}`, 'ratelimit')
303
+ }
304
+
305
+ //http
306
+ if (!r.ok) {
307
+ return rs(false, r.status, null, `http error(${r.status}): ${snip(text)}`, 'http')
308
+ }
309
+
310
+ //data
311
+ let data = null
312
+ try {
313
+ data = JSON.parse(text)
314
+ }
315
+ catch (err) {
316
+ return rs(false, r.status, null, `invalid json: ${snip(text)}`, 'parse')
317
+ }
318
+
319
+ return rs(true, r.status, data, '', '')
320
+ }
321
+
322
+
323
+ export default fetchQuotaJson
324
+ </code></pre>
325
+ </article>
326
+ </section>
327
+
328
+
329
+
330
+
331
+
332
+
333
+ </div>
334
+
335
+ <br class="clear">
336
+
337
+ <footer>
338
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sat Sep 05 2026 22:03:08 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
339
+ </footer>
340
+
341
+ <script>prettyPrint();</script>
342
+ <script src="scripts/polyfill.js"></script>
343
+ <script src="scripts/linenumber.js"></script>
344
+
345
+
346
+
347
+ </body>
348
+ </html>