w-dispatch-ai 1.0.22 → 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.
- package/README.md +627 -581
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +10 -4
- package/docs/adapters.mjs.html +2 -2
- package/docs/budgetFor.mjs.html +2 -2
- package/docs/buildValidator.mjs.html +2 -2
- 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/dispatchApiOpenaiResponses.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 +10615 -3368
- package/docs/index.html +2 -2
- package/docs/quota_dfQuotaTimeoutMs.mjs.html +99 -0
- package/docs/quota_fetchQuotaJson.mjs.html +348 -0
- package/docs/quota_fromCodexUsageHttp.mjs.html +335 -0
- package/docs/quota_getQuotaAntigravity.mjs.html +463 -0
- package/docs/quota_getQuotaClaude.mjs.html +457 -0
- package/docs/quota_getQuotaCodex.mjs.html +515 -0
- package/docs/quota_readJsonOrNull.mjs.html +113 -0
- package/docs/quota_toQuotaLabel.mjs.html +146 -0
- package/docs/quota_toQuotaResult.mjs.html +214 -0
- package/docs/quota_toQuotaScopedLabel.mjs.html +117 -0
- package/docs/quota_toQuotaWindow.mjs.html +239 -0
- package/docs/readEnvFile.mjs.html +2 -2
- package/docs/resolveProviders.mjs.html +2 -2
- package/docs/wkf_callAiWithFallback.mjs.html +2 -2
- package/docs/wkf_createFileStore.mjs.html +2 -2
- package/docs/wkf_createUsageCounter.mjs.html +2 -2
- package/docs/wkf_extractJsonLoose.mjs.html +2 -2
- package/docs/wkf_noSideEffectPrefix.mjs.html +2 -2
- 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 +2 -2
- package/package.json +2 -2
- package/src/WDispatchAi.mjs +8 -2
- package/src/quota/dfQuotaTimeoutMs.mjs +27 -0
- package/src/quota/fetchQuotaJson.mjs +276 -0
- package/src/quota/fromCodexUsageHttp.mjs +263 -0
- package/src/quota/getQuotaAntigravity.mjs +391 -0
- package/src/quota/getQuotaClaude.mjs +385 -0
- package/src/quota/getQuotaCodex.mjs +443 -0
- package/src/quota/readJsonOrNull.mjs +41 -0
- package/src/quota/toQuotaLabel.mjs +74 -0
- package/src/quota/toQuotaResult.mjs +142 -0
- package/src/quota/toQuotaScopedLabel.mjs +45 -0
- package/src/quota/toQuotaWindow.mjs +167 -0
- package/test/tools/fakeCliForTest.mjs +4 -3
- package/test/tools/fakeServerForQuotaTest.mjs +171 -0
- package/test/unit-WDispatchAi.test.mjs +11 -2
- package/test/unit-fetchQuotaJson.test.mjs +70 -0
- package/test/unit-fromCodexUsageHttp.test.mjs +76 -0
- package/test/unit-getQuotaAntigravity.test.mjs +123 -0
- package/test/unit-getQuotaClaude.test.mjs +137 -0
- package/test/unit-getQuotaCodex.test.mjs +191 -0
- package/test/unit-readJsonOrNull.test.mjs +44 -0
- package/test/unit-toQuotaLabel.test.mjs +43 -0
- package/test/unit-toQuotaResult.test.mjs +53 -0
- package/test/unit-toQuotaWindow.test.mjs +73 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import toQuotaResult from '../src/quota/toQuotaResult.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('toQuotaResult', function() {
|
|
6
|
+
|
|
7
|
+
it('未指定emailWant時不比對(matched為null), ok僅取決於查詢本身', function() {
|
|
8
|
+
let r1 = toQuotaResult('claude', { email: 'a@b.com', windows: [{ key: 'x' }] })
|
|
9
|
+
let r2 = toQuotaResult('claude', { email: 'a@b.com', error: 'boom', errorType: 'http' })
|
|
10
|
+
let r = [[r1.ok, r1.matched, r1.windows.length], [r2.ok, r2.matched, r2.errorType]]
|
|
11
|
+
let rr = [[true, null, 1], [false, null, 'http']]
|
|
12
|
+
assert.strict.deepEqual(r, rr)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('帳號比對去空白且不分大小寫; 不符時ok為false但額度資料仍回傳, error載明本機實際帳號', function() {
|
|
16
|
+
let r1 = toQuotaResult('codex', { email: 'User@Example.com', emailWant: ' user@example.com ', windows: [{ key: 'x' }] })
|
|
17
|
+
let r2 = toQuotaResult('codex', { email: 'a@b.com', emailWant: 'c@d.com', windows: [{ key: 'x' }] })
|
|
18
|
+
let r = [
|
|
19
|
+
[r1.ok, r1.matched],
|
|
20
|
+
[r2.ok, r2.matched, r2.errorType, r2.windows.length, r2.error],
|
|
21
|
+
]
|
|
22
|
+
let rr = [
|
|
23
|
+
[true, true],
|
|
24
|
+
[false, false, 'account', 1, 'local codex login account is [a@b.com], which does not match the requested account [c@d.com]'],
|
|
25
|
+
]
|
|
26
|
+
assert.strict.deepEqual(r, rr)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('指定emailWant但無從取得本機帳號時視為不符並補述原因(errorType為account)', function() {
|
|
30
|
+
let r1 = toQuotaResult('antigravity', { emailWant: 'a@b.com' })
|
|
31
|
+
let r = [r1.ok, r1.matched, r1.errorType, r1.error]
|
|
32
|
+
let rr = [false, false, 'account', 'cannot determine the local antigravity login account, so it is unknown whether it is the requested account [a@b.com]']
|
|
33
|
+
assert.strict.deepEqual(r, rr)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('已有查詢錯誤時不以帳號比對訊息覆蓋之', function() {
|
|
37
|
+
let r1 = toQuotaResult('claude', { emailWant: 'a@b.com', error: 'unauthorized(401)', errorType: 'auth' })
|
|
38
|
+
let r = [r1.ok, r1.matched, r1.error, r1.errorType]
|
|
39
|
+
let rr = [false, false, 'unauthorized(401)', 'auth']
|
|
40
|
+
assert.strict.deepEqual(r, rr)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('回傳鍵名固定且無效輸入回退預設', function() {
|
|
44
|
+
let r1 = toQuotaResult(null, { windows: 'bad', plan: 123, credits: undefined })
|
|
45
|
+
let r = [Object.keys(r1), r1.provider, r1.windows, r1.plan, r1.credits, r1.raw, r1.durationMs]
|
|
46
|
+
let rr = [
|
|
47
|
+
['ok', 'provider', 'email', 'matched', 'plan', 'planTier', 'source', 'windows', 'credits', 'raw', 'error', 'errorType', 'durationMs'],
|
|
48
|
+
'', [], '', null, null, 0,
|
|
49
|
+
]
|
|
50
|
+
assert.strict.deepEqual(r, rr)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import toQuotaWindow from '../src/quota/toQuotaWindow.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('toQuotaWindow', function() {
|
|
6
|
+
|
|
7
|
+
it('正規化基本欄位: label由windowSeconds推導, remainingPercent由usedPercent推得', function() {
|
|
8
|
+
let w = toQuotaWindow({ key: 'five_hour', windowSeconds: 18000, usedPercent: 11 })
|
|
9
|
+
let r = [w.key, w.label, w.windowSeconds, w.usedPercent, w.remainingPercent, w.scope, w.active, w.severity]
|
|
10
|
+
let rr = ['five_hour', '5小時', 18000, 11, 89, '', false, 'normal']
|
|
11
|
+
assert.strict.deepEqual(r, rr)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('usedPercent夾至0~100且未知時remainingPercent同為null(不假造100)', function() {
|
|
15
|
+
let w1 = toQuotaWindow({ usedPercent: 150 })
|
|
16
|
+
let w2 = toQuotaWindow({ usedPercent: -3 })
|
|
17
|
+
let w3 = toQuotaWindow({})
|
|
18
|
+
let r = [[w1.usedPercent, w1.remainingPercent], [w2.usedPercent, w2.remainingPercent], [w3.usedPercent, w3.remainingPercent]]
|
|
19
|
+
let rr = [[100, 0], [0, 100], [null, null]]
|
|
20
|
+
assert.strict.deepEqual(r, rr)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('resetAt接受ISO字串或unix秒數, 一律轉ISO字串並補算resetAfterSeconds', function() {
|
|
24
|
+
let future = Math.floor(Date.now() / 1000) + 3600
|
|
25
|
+
let w1 = toQuotaWindow({ resetAt: future })
|
|
26
|
+
let w2 = toQuotaWindow({ resetAt: '2099-01-01T00:00:00.000Z' })
|
|
27
|
+
let w3 = toQuotaWindow({ resetAt: 'not-a-date' })
|
|
28
|
+
let r = [
|
|
29
|
+
w1.resetAt === new Date(future * 1000).toISOString(),
|
|
30
|
+
w1.resetAfterSeconds >= 3598 && w1.resetAfterSeconds <= 3600,
|
|
31
|
+
w2.resetAt,
|
|
32
|
+
w2.resetAfterSeconds > 0,
|
|
33
|
+
w3.resetAt,
|
|
34
|
+
w3.resetAfterSeconds,
|
|
35
|
+
]
|
|
36
|
+
let rr = [true, true, '2099-01-01T00:00:00.000Z', true, '', null]
|
|
37
|
+
assert.strict.deepEqual(r, rr)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('只給resetAfterSeconds時反推resetAt; 已過期之resetAt其resetAfterSeconds夾為0', function() {
|
|
41
|
+
let w1 = toQuotaWindow({ resetAfterSeconds: 120 })
|
|
42
|
+
let w2 = toQuotaWindow({ resetAt: '2000-01-01T00:00:00.000Z' })
|
|
43
|
+
let dt = Math.round((new Date(w1.resetAt).getTime() - Date.now()) / 1000)
|
|
44
|
+
let r = [dt >= 118 && dt <= 120, w1.resetAfterSeconds, w2.resetAfterSeconds]
|
|
45
|
+
let rr = [true, 120, 0]
|
|
46
|
+
assert.strict.deepEqual(r, rr)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('severity: 供應商有給即用之; 未給則由usedPercent推導(用罄exhausted/其餘normal/未知空字串)——三家對稱', function() {
|
|
50
|
+
let r = [
|
|
51
|
+
toQuotaWindow({ usedPercent: 50, severity: 'warning' }).severity,
|
|
52
|
+
toQuotaWindow({ usedPercent: 50 }).severity,
|
|
53
|
+
toQuotaWindow({ usedPercent: 100 }).severity,
|
|
54
|
+
toQuotaWindow({}).severity,
|
|
55
|
+
]
|
|
56
|
+
let rr = ['warning', 'normal', 'exhausted', '']
|
|
57
|
+
assert.strict.deepEqual(r, rr)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('明給label優先於推導; scope與active原樣保留', function() {
|
|
61
|
+
let w = toQuotaWindow({ windowSeconds: 604800, label: '7天(Fable)', scope: 'Fable', active: true })
|
|
62
|
+
let r = [w.label, w.scope, w.active]
|
|
63
|
+
let rr = ['7天(Fable)', 'Fable', true]
|
|
64
|
+
assert.strict.deepEqual(r, rr)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('回傳鍵名固定', function() {
|
|
68
|
+
let r = Object.keys(toQuotaWindow({}))
|
|
69
|
+
let rr = ['key', 'label', 'windowSeconds', 'usedPercent', 'remainingPercent', 'resetAt', 'resetAfterSeconds', 'scope', 'active', 'severity']
|
|
70
|
+
assert.strict.deepEqual(r, rr)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
})
|