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.
Files changed (51) hide show
  1. package/README.md +27 -1
  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 +4 -3
  5. package/docs/adapters.mjs.html +2 -2
  6. package/docs/castPintOr.mjs.html +109 -0
  7. package/docs/dfTimeoutMs.mjs.html +2 -2
  8. package/docs/dispatchAi.mjs.html +14 -13
  9. package/docs/dispatchAiFallback.mjs.html +156 -97
  10. package/docs/dispatchAiWkf.mjs.html +2 -2
  11. package/docs/dispatchAntigravity.mjs.html +10 -14
  12. package/docs/dispatchApiOpenaiCompat.mjs.html +50 -64
  13. package/docs/dispatchClaude.mjs.html +8 -13
  14. package/docs/dispatchCodex.mjs.html +8 -13
  15. package/docs/dispatchOpencode.mjs.html +8 -13
  16. package/docs/getCliArgs.mjs.html +2 -2
  17. package/docs/getErrorResult.mjs.html +12 -5
  18. package/docs/getErrorType.mjs.html +171 -0
  19. package/docs/global.html +1275 -88
  20. package/docs/index.html +2 -2
  21. package/docs/resolveProviders.mjs.html +2 -2
  22. package/docs/wkf_callAiWithFallback.mjs.html +14 -10
  23. package/docs/wkf_extractJsonLoose.mjs.html +2 -2
  24. package/docs/wkf_runFanout.mjs.html +8 -8
  25. package/docs/wkf_runFanoutPipeline.mjs.html +14 -18
  26. package/docs/wkf_runRolePipeline.mjs.html +5 -4
  27. package/package.json +1 -1
  28. package/src/WDispatchAi.mjs +2 -1
  29. package/src/castPintOr.mjs +37 -0
  30. package/src/dispatchAi.mjs +12 -11
  31. package/src/dispatchAiFallback.mjs +154 -95
  32. package/src/dispatchAntigravity.mjs +8 -12
  33. package/src/dispatchApiOpenaiCompat.mjs +48 -62
  34. package/src/dispatchClaude.mjs +6 -11
  35. package/src/dispatchCodex.mjs +6 -11
  36. package/src/dispatchOpencode.mjs +6 -11
  37. package/src/getErrorResult.mjs +10 -3
  38. package/src/getErrorType.mjs +99 -0
  39. package/src/wkf/callAiWithFallback.mjs +12 -8
  40. package/src/wkf/runFanout.mjs +6 -6
  41. package/src/wkf/runFanoutPipeline.mjs +12 -16
  42. package/src/wkf/runRolePipeline.mjs +3 -2
  43. package/test/tools/fakeServerForApiTest.mjs +4 -1
  44. package/test/unit-callAiWithFallback.test.mjs +24 -0
  45. package/test/unit-castPintOr.test.mjs +28 -0
  46. package/test/unit-dispatchAiFallback.test.mjs +137 -0
  47. package/test/unit-dispatchApiOpenaiCompat.test.mjs +39 -0
  48. package/test/unit-getErrorResult.test.mjs +13 -1
  49. package/test/unit-getErrorType.test.mjs +36 -0
  50. package/test/unit-runFanout.test.mjs +14 -0
  51. 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
  })