w-dispatch-ai 1.0.2 → 1.0.3
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 +60 -5
- package/dist/w-dispatch-ai.umd.js +2 -2
- package/dist/w-dispatch-ai.umd.js.map +1 -1
- package/docs/WDispatchAi.mjs.html +8 -4
- package/docs/adapters.mjs.html +9 -7
- package/docs/dispatchAi.mjs.html +2 -2
- package/docs/dispatchAiFallback.mjs.html +2 -2
- package/docs/dispatchAiWkf.mjs.html +172 -0
- package/docs/dispatchAntigravity.mjs.html +2 -2
- package/docs/dispatchApiOpenaiCompat.mjs.html +483 -0
- 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/global.html +5747 -1467
- package/docs/index.html +2 -2
- package/docs/wkf_callAiWithFallback.mjs.html +281 -0
- package/docs/wkf_extractJsonLoose.mjs.html +180 -0
- package/docs/wkf_runFanout.mjs.html +227 -0
- package/docs/wkf_runFanoutPipeline.mjs.html +178 -0
- package/docs/wkf_runRolePipeline.mjs.html +195 -0
- package/g.mjs +11 -2
- package/package.json +1 -1
- package/src/WDispatchAi.mjs +6 -2
- package/src/adapters.mjs +7 -5
- package/src/dispatchAiWkf.mjs +100 -0
- package/src/dispatchApiOpenaiCompat.mjs +411 -0
- package/src/wkf/callAiWithFallback.mjs +209 -0
- package/src/wkf/extractJsonLoose.mjs +108 -0
- package/src/wkf/runFanout.mjs +155 -0
- package/src/wkf/runFanoutPipeline.mjs +106 -0
- package/src/wkf/runRolePipeline.mjs +123 -0
- package/test/tools/fakeServerForApiTest.mjs +137 -0
- package/test/unit-WDispatchAi.test.mjs +13 -6
- package/test/unit-adapters.test.mjs +5 -3
- package/test/unit-callAiWithFallback.test.mjs +146 -0
- package/test/unit-dispatchAi.test.mjs +1 -1
- package/test/unit-dispatchAiWkf.test.mjs +118 -0
- package/test/unit-dispatchApiOpenaiCompat.test.mjs +233 -0
- package/test/unit-extractJsonLoose.test.mjs +78 -0
- package/test/unit-runFanout.test.mjs +166 -0
- package/test/unit-runFanoutPipeline.test.mjs +86 -0
- package/test/unit-runRolePipeline.test.mjs +163 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import runRolePipeline from '../src/wkf/runRolePipeline.mjs'
|
|
3
|
+
import createFakeCli from './tools/fakeCliForTest.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
describe('runRolePipeline', function() {
|
|
7
|
+
|
|
8
|
+
let fake = null
|
|
9
|
+
let providers = null
|
|
10
|
+
let callOpt = { promptPrefix: '' }
|
|
11
|
+
|
|
12
|
+
before(function() {
|
|
13
|
+
fake = createFakeCli('fake-wkf-role')
|
|
14
|
+
providers = {
|
|
15
|
+
'p-a': { kind: 'claude', exe: fake.exe, model: 'sonnet' },
|
|
16
|
+
'p-b': { kind: 'codex', exe: fake.exe, model: 'gpt-5.6-luna' },
|
|
17
|
+
'p-dead': { kind: 'claude', exe: fake.exe, extraArgs: ['--fake-exit=1'] },
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
after(function() {
|
|
22
|
+
if (fake) {
|
|
23
|
+
fake.clean()
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('stages無效時回傳錯誤結果物件', async function() {
|
|
28
|
+
let t = await runRolePipeline({ providers, stages: null, callOpt })
|
|
29
|
+
let r = [t.ok, t.error, t.order]
|
|
30
|
+
let rr = [false, 'stages must be a non-empty array', []]
|
|
31
|
+
assert.strict.deepEqual(r, rr)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('各階段依序執行, ctx傳遞input與prev與results', async function() {
|
|
35
|
+
let ctxSeen = []
|
|
36
|
+
let t = await runRolePipeline({
|
|
37
|
+
providers,
|
|
38
|
+
input: 'INPUT-MARK',
|
|
39
|
+
stages: [
|
|
40
|
+
{
|
|
41
|
+
id: 'draft',
|
|
42
|
+
use: 'p-a',
|
|
43
|
+
prompt: (ctx) => {
|
|
44
|
+
ctxSeen.push(['draft', ctx.input, ctx.prev, ctx.index])
|
|
45
|
+
return `draft on ${ctx.input}`
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'review',
|
|
50
|
+
use: 'p-b',
|
|
51
|
+
prompt: (ctx) => {
|
|
52
|
+
ctxSeen.push(['review', ctx.input, ctx.prev === ctx.results.draft, ctx.index])
|
|
53
|
+
return `review ${ctx.prev.stdin}` //prev為draft階段之echo物件
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
callOpt,
|
|
58
|
+
})
|
|
59
|
+
let r = [
|
|
60
|
+
t.ok,
|
|
61
|
+
t.order,
|
|
62
|
+
t.failedStage,
|
|
63
|
+
ctxSeen,
|
|
64
|
+
t.results.draft.stdin,
|
|
65
|
+
t.results.review.stdin,
|
|
66
|
+
t.result === t.results.review, //最末階段成果即工作流成果
|
|
67
|
+
]
|
|
68
|
+
let rr = [
|
|
69
|
+
true,
|
|
70
|
+
['draft', 'review'],
|
|
71
|
+
null,
|
|
72
|
+
[['draft', 'INPUT-MARK', null, 0], ['review', 'INPUT-MARK', true, 1]],
|
|
73
|
+
'draft on INPUT-MARK',
|
|
74
|
+
'review draft on INPUT-MARK',
|
|
75
|
+
true,
|
|
76
|
+
]
|
|
77
|
+
assert.strict.deepEqual(r, rr)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('某階段失敗即中止, 已完成階段成果保留且failedStage標明斷點', async function() {
|
|
81
|
+
let t = await runRolePipeline({
|
|
82
|
+
providers,
|
|
83
|
+
stages: [
|
|
84
|
+
{ id: 's1', use: 'p-a', prompt: () => 'stage1' },
|
|
85
|
+
{ id: 's2', use: 'p-dead', prompt: () => 'stage2' },
|
|
86
|
+
{ id: 's3', use: 'p-a', prompt: () => 'stage3' },
|
|
87
|
+
],
|
|
88
|
+
callOpt,
|
|
89
|
+
})
|
|
90
|
+
let r = [
|
|
91
|
+
t.ok,
|
|
92
|
+
t.failedStage,
|
|
93
|
+
t.order,
|
|
94
|
+
t.results.s1.stdin,
|
|
95
|
+
t.results.s2,
|
|
96
|
+
t.error.indexOf('stage[s2] failed:') === 0,
|
|
97
|
+
]
|
|
98
|
+
let rr = [false, 's2', ['s1', 's2'], 'stage1', undefined, true]
|
|
99
|
+
assert.strict.deepEqual(r, rr)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('prompt非函數或回傳空值時回報該階段錯誤', async function() {
|
|
103
|
+
let t1 = await runRolePipeline({ providers, stages: [{ id: 'x', use: 'p-a', prompt: 'not-fn' }], callOpt })
|
|
104
|
+
let t2 = await runRolePipeline({ providers, stages: [{ id: 'y', use: 'p-a', prompt: () => '' }], callOpt })
|
|
105
|
+
let r = [
|
|
106
|
+
[t1.ok, t1.failedStage, t1.error],
|
|
107
|
+
[t2.ok, t2.failedStage, t2.error],
|
|
108
|
+
]
|
|
109
|
+
let rr = [
|
|
110
|
+
[false, 'x', 'stage[x].prompt must be a function'],
|
|
111
|
+
[false, 'y', 'stage[y].prompt returned empty'],
|
|
112
|
+
]
|
|
113
|
+
assert.strict.deepEqual(r, rr)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('未給id之階段自動命名stageN', async function() {
|
|
117
|
+
let t = await runRolePipeline({
|
|
118
|
+
providers,
|
|
119
|
+
stages: [{ use: 'p-a', prompt: () => 'abc' }],
|
|
120
|
+
callOpt,
|
|
121
|
+
})
|
|
122
|
+
let r = [t.ok, t.order]
|
|
123
|
+
let rr = [true, ['stage1']]
|
|
124
|
+
assert.strict.deepEqual(r, rr)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('階段可自帶fallback, 主模型失敗自動遞補', async function() {
|
|
128
|
+
let t = await runRolePipeline({
|
|
129
|
+
providers,
|
|
130
|
+
stages: [{ id: 's1', use: 'p-dead', fallback: ['p-b'], prompt: () => 'abc' }],
|
|
131
|
+
callOpt,
|
|
132
|
+
})
|
|
133
|
+
let r = [t.ok, t.stages.s1.providerId]
|
|
134
|
+
let rr = [true, 'p-b']
|
|
135
|
+
assert.strict.deepEqual(r, rr)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('rawText階段之成果為文字並可傳遞至下一階段', async function() {
|
|
139
|
+
let t = await runRolePipeline({
|
|
140
|
+
providers,
|
|
141
|
+
stages: [
|
|
142
|
+
{ id: 's1', use: 'p-a', rawText: true, prompt: () => 'abc' },
|
|
143
|
+
{ id: 's2', use: 'p-b', prompt: (ctx) => `got ${typeof ctx.prev}` },
|
|
144
|
+
],
|
|
145
|
+
callOpt,
|
|
146
|
+
})
|
|
147
|
+
let r = [t.ok, typeof t.results.s1, t.results.s2.stdin]
|
|
148
|
+
let rr = [true, 'string', 'got string']
|
|
149
|
+
assert.strict.deepEqual(r, rr)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('階段check未過視為該階段失敗', async function() {
|
|
153
|
+
let t = await runRolePipeline({
|
|
154
|
+
providers,
|
|
155
|
+
stages: [{ id: 's1', use: 'p-a', prompt: () => 'abc', check: () => false }],
|
|
156
|
+
callOpt,
|
|
157
|
+
})
|
|
158
|
+
let r = [t.ok, t.failedStage]
|
|
159
|
+
let rr = [false, 's1']
|
|
160
|
+
assert.strict.deepEqual(r, rr)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
})
|