w-dispatch-ai 1.0.7 → 1.0.8
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 +27 -1
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +4 -3
- package/docs/adapters.mjs.html +2 -2
- package/docs/castPintOr.mjs.html +109 -0
- package/docs/dfTimeoutMs.mjs.html +2 -2
- package/docs/dispatchAi.mjs.html +14 -13
- package/docs/dispatchAiFallback.mjs.html +156 -97
- package/docs/dispatchAiWkf.mjs.html +2 -2
- package/docs/dispatchAntigravity.mjs.html +10 -14
- package/docs/dispatchApiOpenaiCompat.mjs.html +50 -64
- package/docs/dispatchClaude.mjs.html +8 -13
- package/docs/dispatchCodex.mjs.html +8 -13
- package/docs/dispatchOpencode.mjs.html +8 -13
- package/docs/getCliArgs.mjs.html +2 -2
- package/docs/getErrorResult.mjs.html +12 -5
- package/docs/getErrorType.mjs.html +171 -0
- package/docs/global.html +1275 -88
- package/docs/index.html +2 -2
- package/docs/resolveProviders.mjs.html +2 -2
- package/docs/wkf_callAiWithFallback.mjs.html +14 -10
- package/docs/wkf_extractJsonLoose.mjs.html +2 -2
- package/docs/wkf_runFanout.mjs.html +8 -8
- package/docs/wkf_runFanoutPipeline.mjs.html +14 -18
- package/docs/wkf_runRolePipeline.mjs.html +5 -4
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +2 -1
- package/src/castPintOr.mjs +37 -0
- package/src/dispatchAi.mjs +12 -11
- package/src/dispatchAiFallback.mjs +154 -95
- package/src/dispatchAntigravity.mjs +8 -12
- package/src/dispatchApiOpenaiCompat.mjs +48 -62
- package/src/dispatchClaude.mjs +6 -11
- package/src/dispatchCodex.mjs +6 -11
- package/src/dispatchOpencode.mjs +6 -11
- package/src/getErrorResult.mjs +10 -3
- package/src/getErrorType.mjs +99 -0
- package/src/wkf/callAiWithFallback.mjs +12 -8
- package/src/wkf/runFanout.mjs +6 -6
- package/src/wkf/runFanoutPipeline.mjs +12 -16
- package/src/wkf/runRolePipeline.mjs +3 -2
- package/test/tools/fakeServerForApiTest.mjs +4 -1
- package/test/unit-callAiWithFallback.test.mjs +24 -0
- package/test/unit-castPintOr.test.mjs +28 -0
- package/test/unit-dispatchAiFallback.test.mjs +137 -0
- package/test/unit-dispatchApiOpenaiCompat.test.mjs +39 -0
- package/test/unit-getErrorResult.test.mjs +13 -1
- package/test/unit-getErrorType.test.mjs +36 -0
- package/test/unit-runFanout.test.mjs +14 -0
- package/test/unit-runRolePipeline.test.mjs +12 -0
|
@@ -289,4 +289,43 @@ describe('dispatchApiOpenaiCompat', function() {
|
|
|
289
289
|
assert.strict.deepEqual(r, rr)
|
|
290
290
|
})
|
|
291
291
|
|
|
292
|
+
it('usage原樣透傳: 成功帶回應之token用量, 網路層與HTTP錯誤為null', async function() {
|
|
293
|
+
let t1 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'echo' })
|
|
294
|
+
let t2 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'err-500' })
|
|
295
|
+
let r = [t1.ok, t1.usage, t2.ok, t2.usage]
|
|
296
|
+
let rr = [true, { prompt_tokens: 3, completion_tokens: 7, total_tokens: 10 }, false, null]
|
|
297
|
+
assert.strict.deepEqual(r, rr)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('usage經遞補層與工作流層流出於結果與tried, CLI路徑無此欄', async function() {
|
|
301
|
+
let tf = await dispatchAiFallback('abc', {
|
|
302
|
+
providers: [{ id: 'us-api', kind: 'api-openai-compat', baseURL: svr.url, model: 'echo', keys: ['sk-good-us'] }],
|
|
303
|
+
})
|
|
304
|
+
let wkf = dispatchAiWkf({
|
|
305
|
+
providers: {
|
|
306
|
+
'us-w-api': { kind: 'api-openai-compat', baseURL: svr.url, model: 'echo', keys: ['sk-good-us2'] },
|
|
307
|
+
},
|
|
308
|
+
defaults: { promptPrefix: '' },
|
|
309
|
+
})
|
|
310
|
+
let tw = await wkf.callAi('abc', { spec: { use: 'us-w-api' } })
|
|
311
|
+
let r = [
|
|
312
|
+
tf.usage.total_tokens,
|
|
313
|
+
tf.tried[0].usage.total_tokens, //tried歷程各項一併帶上, 供加總實際耗用
|
|
314
|
+
tw.usage.total_tokens,
|
|
315
|
+
]
|
|
316
|
+
let rr = [10, 10, 10]
|
|
317
|
+
assert.strict.deepEqual(r, rr)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('errorType機器可讀分類: http/tool-unsupported/validation/timeout, 成功無此欄', async function() {
|
|
321
|
+
let t1 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'err-500' })
|
|
322
|
+
let t2 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'tool-calls' })
|
|
323
|
+
let t3 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'echo', validate: 'min:100000' })
|
|
324
|
+
let t4 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'slow', timeoutMs: 500 })
|
|
325
|
+
let t5 = await dispatchApiOpenaiCompat('abc', { baseURL: svr.url, key: 'sk-good-1', model: 'echo' })
|
|
326
|
+
let r = [t1.errorType, t2.errorType, t3.errorType, t4.errorType, t5.errorType]
|
|
327
|
+
let rr = ['http', 'tool-unsupported', 'validation', 'timeout', undefined]
|
|
328
|
+
assert.strict.deepEqual(r, rr)
|
|
329
|
+
})
|
|
330
|
+
|
|
292
331
|
})
|
|
@@ -12,6 +12,7 @@ describe('getErrorResult', function() {
|
|
|
12
12
|
stderr: '',
|
|
13
13
|
code: null,
|
|
14
14
|
error: 'something wrong',
|
|
15
|
+
errorType: 'params',
|
|
15
16
|
durationMs: 0,
|
|
16
17
|
attempts: 0,
|
|
17
18
|
}
|
|
@@ -20,7 +21,18 @@ describe('getErrorResult', function() {
|
|
|
20
21
|
|
|
21
22
|
it('鍵名與順序固定', function() {
|
|
22
23
|
let r = Object.keys(getErrorResult('abc'))
|
|
23
|
-
let rr = ['ok', 'stdout', 'stderr', 'code', 'error', 'durationMs', 'attempts']
|
|
24
|
+
let rr = ['ok', 'stdout', 'stderr', 'code', 'error', 'errorType', 'durationMs', 'attempts']
|
|
25
|
+
assert.strict.deepEqual(r, rr)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('errorType可指定, 非有效字串回退params', function() {
|
|
29
|
+
let r = [
|
|
30
|
+
getErrorResult('x').errorType,
|
|
31
|
+
getErrorResult('x', 'aborted').errorType,
|
|
32
|
+
getErrorResult('x', null).errorType,
|
|
33
|
+
getErrorResult('x', '').errorType,
|
|
34
|
+
]
|
|
35
|
+
let rr = ['params', 'aborted', 'params', 'params']
|
|
24
36
|
assert.strict.deepEqual(r, rr)
|
|
25
37
|
})
|
|
26
38
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import getErrorType, { attachErrorType } from '../src/getErrorType.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
describe('getErrorType', function() {
|
|
6
|
+
|
|
7
|
+
it('機械可判之類別: timeout/spawn/validation, 其餘歸exec', function() {
|
|
8
|
+
let r = [
|
|
9
|
+
getErrorType({ error: 'TIMEOUT after 300s' }),
|
|
10
|
+
getErrorType({ error: 'spawn my-cli ENOENT' }),
|
|
11
|
+
getErrorType({ error: 'ENAMETOOLONG: spawn error' }),
|
|
12
|
+
getErrorType({ error: 'OUTPUT_VALIDATION_FAILED' }),
|
|
13
|
+
getErrorType({ error: 'Exit code 1' }),
|
|
14
|
+
getErrorType({ error: 'Invalid API key.' }),
|
|
15
|
+
getErrorType({}),
|
|
16
|
+
getErrorType({ error: '' }),
|
|
17
|
+
]
|
|
18
|
+
let rr = ['timeout', 'spawn', 'spawn', 'validation', 'exec', 'exec', 'exec', 'exec']
|
|
19
|
+
assert.strict.deepEqual(r, rr)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('attachErrorType僅對失敗且未帶errorType之結果追加', function() {
|
|
23
|
+
let rOk = { ok: true, stdout: 'abc' }
|
|
24
|
+
let rFail = { ok: false, error: 'TIMEOUT after 10s' }
|
|
25
|
+
let rTyped = { ok: false, error: 'HTTP 429', errorType: 'http' }
|
|
26
|
+
let r = [
|
|
27
|
+
attachErrorType(rOk).errorType,
|
|
28
|
+
attachErrorType(rOk) === rOk, //成功結果原樣回傳
|
|
29
|
+
attachErrorType(rFail).errorType,
|
|
30
|
+
attachErrorType(rTyped).errorType, //已帶有效errorType則不覆寫
|
|
31
|
+
]
|
|
32
|
+
let rr = [undefined, true, 'timeout', 'http']
|
|
33
|
+
assert.strict.deepEqual(r, rr)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
})
|
|
@@ -202,6 +202,20 @@ describe('runFanout', function() {
|
|
|
202
202
|
assert.strict.deepEqual(r, rr)
|
|
203
203
|
})
|
|
204
204
|
|
|
205
|
+
it('meta為保留鍵: 名額與integrate規格掛meta不影響呼叫行為', async function() {
|
|
206
|
+
let t = await runFanout({
|
|
207
|
+
providers,
|
|
208
|
+
task: 'abc',
|
|
209
|
+
agents: [{ use: 'p-a', meta: { stage: 'draft' } }],
|
|
210
|
+
integrate: { use: 'p-int', meta: { stage: 'final' } },
|
|
211
|
+
callOpt,
|
|
212
|
+
})
|
|
213
|
+
//單稿放行, 假CLI收到之args與未掛meta時完全一致(meta未被轉傳成未知旗標)
|
|
214
|
+
let r = [t.ok, t.integrated, t.result.args]
|
|
215
|
+
let rr = [true, false, ['-p', '--dangerously-skip-permissions', '--model', 'sonnet']]
|
|
216
|
+
assert.strict.deepEqual(r, rr)
|
|
217
|
+
})
|
|
218
|
+
|
|
205
219
|
it('defaultIntegratePrompt嵌入候選數與schema', function() {
|
|
206
220
|
let s = defaultIntegratePrompt([{ a: 1 }, { b: 2 }], { schema: '{"x":"..."}' })
|
|
207
221
|
let r = [s.includes('2 個獨立執行'), s.includes('{"x":"..."}'), s.includes('【候選 2】')]
|
|
@@ -160,4 +160,16 @@ describe('runRolePipeline', function() {
|
|
|
160
160
|
assert.strict.deepEqual(r, rr)
|
|
161
161
|
})
|
|
162
162
|
|
|
163
|
+
it('meta為保留鍵: 階段規格掛meta(如draft/audit分類)不影響呼叫行為', async function() {
|
|
164
|
+
let t = await runRolePipeline({
|
|
165
|
+
providers,
|
|
166
|
+
stages: [{ id: 's1', use: 'p-a', prompt: () => 'abc', meta: { stage: 'draft' } }],
|
|
167
|
+
callOpt,
|
|
168
|
+
})
|
|
169
|
+
//假CLI收到之args與未掛meta時完全一致(meta未被轉傳成未知旗標)
|
|
170
|
+
let r = [t.ok, t.result.args, t.result.stdin]
|
|
171
|
+
let rr = [true, ['-p', '--dangerously-skip-permissions', '--model', 'sonnet'], 'abc']
|
|
172
|
+
assert.strict.deepEqual(r, rr)
|
|
173
|
+
})
|
|
174
|
+
|
|
163
175
|
})
|