@goodandready/dsh-moa 0.2.26 ā 0.2.27
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 +30 -0
- package/README.ru.md +30 -0
- package/lib/client.js +194 -0
- package/lib/history.js +5 -0
- package/lib/index.js +36 -0
- package/lib/moa-budget.js +97 -0
- package/lib/moa-candidates.js +90 -22
- package/lib/moa-context.js +70 -0
- package/lib/moa-multi-judge.js +301 -0
- package/lib/moa-prompts.js +3 -0
- package/lib/moa-report.js +124 -0
- package/lib/moa-router.js +102 -0
- package/lib/moa-runner.js +98 -118
- package/lib/moa-stream.js +117 -0
- package/lib/routes.js +58 -0
- package/package.json +1 -1
package/lib/moa-runner.js
CHANGED
|
@@ -3,12 +3,16 @@ import crypto from 'node:crypto'
|
|
|
3
3
|
import { bestEffort } from './best-effort.js'
|
|
4
4
|
import { extractFileBlocks, collectProjectContext, formatProjectContext, isRefinementTask, writeCandidateWorkspace, promoteCandidateWorkspace, cleanMoaWorkspaces, verifyFileSyntax } from './file-workspace.js'
|
|
5
5
|
import { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
|
|
6
|
-
import { recordMoaRun,
|
|
6
|
+
import { recordMoaRun, candidatesForHistory } from './history.js'
|
|
7
7
|
import { createPromotedPreview } from './live-canvas.js'
|
|
8
8
|
import { slotLabel, cleanAdvisoryMessages, isBroadPromptRequiringQuestions, buildQuestionSynthesisPrompt, buildCuratorSynthesisPrompt, buildSynthesisPrompt, buildPeerCritiquePrompt, ROLE_PERSONA_PROMPTS, SYSTEM_ROLE_PROPOSER, ANTIPATTERNS_RUBRIC } from './moa-prompts.js'
|
|
9
9
|
import { parseWinnerIndex, parseRecommendedAssembler, parseMoACommand, stripOrSummarizeCode, formatMoAResponse } from './moa-parser.js'
|
|
10
10
|
import { REFERENCE_SYSTEM_PROMPT, callWithTransientRetry, runReferencesParallel } from './moa-candidates.js'
|
|
11
11
|
import { executeTestGateForCandidates, runCandidateTestGate } from './moa-test-gate.js'
|
|
12
|
+
import { executeMultiJudgePanel, buildCompositeBlockDirectives } from './moa-multi-judge.js'
|
|
13
|
+
import { applyBudgetGuardrails } from './moa-budget.js'
|
|
14
|
+
import { extractPriorTurnBaseline, pruneMultiTurnMessages } from './moa-context.js'
|
|
15
|
+
import { generateMoABenchmarkReport } from './moa-report.js'
|
|
12
16
|
|
|
13
17
|
// Re-exports for consumers & backward compatibility
|
|
14
18
|
export { slotLabel, cleanAdvisoryMessages, isBroadPromptRequiringQuestions, buildQuestionSynthesisPrompt, buildCuratorSynthesisPrompt, buildSynthesisPrompt, ANTIPATTERNS_RUBRIC } from './moa-prompts.js'
|
|
@@ -16,6 +20,7 @@ export { parseWinnerIndex, parseRecommendedAssembler, parseMoACommand, stripOrSu
|
|
|
16
20
|
export { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
|
|
17
21
|
export { REFERENCE_SYSTEM_PROMPT, callWithTransientRetry, runReferencesParallel } from './moa-candidates.js'
|
|
18
22
|
export { executeTestGateForCandidates, runCandidateTestGate } from './moa-test-gate.js'
|
|
23
|
+
export { streamMoATurn } from './moa-stream.js'
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* Executes the full Mixture of Agents pipeline.
|
|
@@ -60,6 +65,38 @@ export async function runMoAPipeline({
|
|
|
60
65
|
const isBlindEvaluation = Boolean(preset?.blind_evaluation), isPeerCritiqueEnabled = Boolean(preset?.peer_critique_enabled)
|
|
61
66
|
const allowCandidateOverride = Boolean(preset?.allow_candidate_override), runId = crypto.randomUUID()
|
|
62
67
|
|
|
68
|
+
// Feature 6: Budget Guardrail check
|
|
69
|
+
const budgetCheck = applyBudgetGuardrails({
|
|
70
|
+
references: referenceModels,
|
|
71
|
+
enabled: preset?.budget_guard_enabled,
|
|
72
|
+
maxBudgetUsd: preset?.max_budget_usd,
|
|
73
|
+
action: preset?.budget_action || 'trim',
|
|
74
|
+
prices,
|
|
75
|
+
promptLength: userPrompt?.length || 1000,
|
|
76
|
+
})
|
|
77
|
+
if (!budgetCheck.allowed) {
|
|
78
|
+
return {
|
|
79
|
+
kind: 'failure',
|
|
80
|
+
content: `š Budget Guardrail Abort: ${budgetCheck.reason}`,
|
|
81
|
+
aggregator: slotLabel(primaryJudge),
|
|
82
|
+
references: [],
|
|
83
|
+
presetName: preset?.name || 'default',
|
|
84
|
+
isRefinement: false,
|
|
85
|
+
isFastMode,
|
|
86
|
+
winningIndex: 0,
|
|
87
|
+
winnerModel: 'none',
|
|
88
|
+
promotedFiles: [],
|
|
89
|
+
usage: { totalTokens: 0, totalCostUsd: 0, candidates: [], aggregator: { totalTokens: 0, costUsd: 0 } },
|
|
90
|
+
skippedFiles: 0,
|
|
91
|
+
skippedList: [],
|
|
92
|
+
durationMs: Date.now() - startTime,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const effectiveReferences = budgetCheck.references
|
|
96
|
+
if (budgetCheck.action === 'trim' && typeof onProgress === 'function') {
|
|
97
|
+
onProgress(`āļø *${budgetCheck.reason}*\n`)
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
// 2. Collect project context for refinement tasks
|
|
64
101
|
const collectedCtx = await collectProjectContext(cwd, 16000)
|
|
65
102
|
const isRefinement = Boolean(collectedCtx?.files?.length > 0 && isRefinementTask(userPrompt, collectedCtx.files))
|
|
@@ -79,23 +116,28 @@ export async function runMoAPipeline({
|
|
|
79
116
|
})
|
|
80
117
|
}
|
|
81
118
|
|
|
82
|
-
// 3. Build prompts &
|
|
119
|
+
// 3. Build prompts & Feature 8 multi-turn context
|
|
120
|
+
const priorTurnBaseline = preset?.multi_turn_enabled !== false ? extractPriorTurnBaseline(messages) : null
|
|
121
|
+
const prunedHistory = preset?.multi_turn_enabled !== false ? pruneMultiTurnMessages(messages) : messages
|
|
122
|
+
|
|
83
123
|
const candidateSystemPrompt = projectContext
|
|
84
124
|
? `${REFERENCE_SYSTEM_PROMPT}\n\n### Current Project Files & Context:\n${projectContext}`
|
|
85
125
|
: REFERENCE_SYSTEM_PROMPT
|
|
86
126
|
|
|
87
127
|
const askClarifyingQuestions = preset?.ask_clarifying_questions !== false
|
|
88
128
|
const needsQuestions = askClarifyingQuestions && isBroadPromptRequiringQuestions(userPrompt, messages)
|
|
129
|
+
const enrichedMessages = [...prunedHistory, { role: 'user', content: userPrompt }]
|
|
89
130
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// 4. Parallel fan-out to candidate models with quorum & transient retry
|
|
131
|
+
// 4. Parallel fan-out to candidate models with temperature gradient & local fallback
|
|
93
132
|
const referenceOutputs = await runReferencesParallel(
|
|
94
|
-
|
|
133
|
+
effectiveReferences,
|
|
95
134
|
enrichedMessages,
|
|
96
135
|
{
|
|
97
136
|
systemPrompt: candidateSystemPrompt,
|
|
98
137
|
temperature: refTemp,
|
|
138
|
+
temperature_gradient_enabled: Boolean(preset?.temperature_gradient_enabled),
|
|
139
|
+
local_fallback_enabled: Boolean(preset?.local_fallback_enabled),
|
|
140
|
+
local_fallback_models: preset?.local_fallback_models || [],
|
|
99
141
|
maxTokens,
|
|
100
142
|
prices,
|
|
101
143
|
timeoutSec: refTimeoutSec,
|
|
@@ -347,10 +389,32 @@ export async function runMoAPipeline({
|
|
|
347
389
|
})
|
|
348
390
|
}
|
|
349
391
|
|
|
392
|
+
// 7c. Feature 2: Multi-Judge Panel & Consensus Voting
|
|
393
|
+
let multiJudgeResult = null
|
|
394
|
+
if (preset?.multi_judge_enabled && successfulRefs.length > 1) {
|
|
395
|
+
multiJudgeResult = await executeMultiJudgePanel({
|
|
396
|
+
judges: (preset.judge_models && preset.judge_models.length > 0) ? preset.judge_models : [primaryJudge],
|
|
397
|
+
candidates: successfulRefs,
|
|
398
|
+
userPrompt,
|
|
399
|
+
callLlm,
|
|
400
|
+
strategy: preset.judge_voting_strategy || 'majority',
|
|
401
|
+
signal,
|
|
402
|
+
onProgress,
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// 7d. Feature 3: Composite Block Merge Directive
|
|
407
|
+
const compositeMergeDirective = (preset?.composite_merge_enabled && successfulRefs.length > 1)
|
|
408
|
+
? buildCompositeBlockDirectives(successfulRefs)
|
|
409
|
+
: null
|
|
410
|
+
|
|
350
411
|
// 8. Synthesis phase via primary judge or fallback chain
|
|
351
412
|
const synthesisPrompt = buildSynthesisPrompt(userPrompt, referenceOutputs, judgeCriteria, {
|
|
352
413
|
curatorSynthesis: isCuratorSynthesis,
|
|
353
414
|
blindEvaluation: isBlindEvaluation,
|
|
415
|
+
priorTurnBaseline,
|
|
416
|
+
compositeMergeDirective,
|
|
417
|
+
consensusReport: multiJudgeResult?.consensusReport,
|
|
354
418
|
})
|
|
355
419
|
let synthesizedText = ''
|
|
356
420
|
let aggUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 }
|
|
@@ -412,7 +476,10 @@ export async function runMoAPipeline({
|
|
|
412
476
|
}
|
|
413
477
|
|
|
414
478
|
// 9. Evaluate winner & promote files
|
|
415
|
-
const winningIndex =
|
|
479
|
+
const winningIndex = (multiJudgeResult?.consensus && multiJudgeResult?.winningCandidateIndex)
|
|
480
|
+
? multiJudgeResult.winningCandidateIndex
|
|
481
|
+
: parseWinnerIndex(synthesizedText, 1, referenceOutputs.length)
|
|
482
|
+
|
|
416
483
|
const recommendedAssembler = isCuratorSynthesis
|
|
417
484
|
? parseRecommendedAssembler(synthesizedText, winningIndex, referenceOutputs.length)
|
|
418
485
|
: null
|
|
@@ -435,13 +502,32 @@ export async function runMoAPipeline({
|
|
|
435
502
|
}
|
|
436
503
|
|
|
437
504
|
const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs)
|
|
438
|
-
|
|
439
505
|
const durationMs = Date.now() - startTime
|
|
440
506
|
const usageSummary = summarizeMoAUsage(referenceOutputs, aggUsage)
|
|
441
507
|
const winningRef = referenceOutputs[winningIndex - 1]
|
|
442
508
|
const winnerModel = winningRef?.label || slotLabel(referenceModels[0])
|
|
443
509
|
const finalAggLabel = slotLabel(chosenJudge)
|
|
444
510
|
|
|
511
|
+
// Feature 9: Benchmark & Post-Mortem PR report
|
|
512
|
+
let benchmarkReport = null
|
|
513
|
+
if (preset?.report_generation_enabled) {
|
|
514
|
+
benchmarkReport = generateMoABenchmarkReport({
|
|
515
|
+
runId,
|
|
516
|
+
timestamp: new Date().toISOString(),
|
|
517
|
+
preset: preset?.name || 'default',
|
|
518
|
+
prompt: userPrompt,
|
|
519
|
+
durationMs,
|
|
520
|
+
usage: usageSummary,
|
|
521
|
+
costUsd: usageSummary.totalCostUsd,
|
|
522
|
+
candidates: referenceOutputs,
|
|
523
|
+
winningIndex,
|
|
524
|
+
winningLabel: winnerModel,
|
|
525
|
+
consensus: multiJudgeResult,
|
|
526
|
+
testGate: referenceOutputs.find((r) => r.testResult)?.testResult || null,
|
|
527
|
+
isComposite: Boolean(preset?.composite_merge_enabled),
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
|
|
445
531
|
try {
|
|
446
532
|
recordMoaRun({
|
|
447
533
|
id: runId,
|
|
@@ -453,6 +539,8 @@ export async function runMoAPipeline({
|
|
|
453
539
|
winnerIndex: winningIndex,
|
|
454
540
|
winnerModel,
|
|
455
541
|
promotedFiles,
|
|
542
|
+
benchmarkReport,
|
|
543
|
+
consensus: multiJudgeResult,
|
|
456
544
|
totalTokens: usageSummary.totalTokens,
|
|
457
545
|
totalCostUsd: usageSummary.totalCostUsd,
|
|
458
546
|
durationMs,
|
|
@@ -476,6 +564,8 @@ export async function runMoAPipeline({
|
|
|
476
564
|
promotedFiles,
|
|
477
565
|
runId,
|
|
478
566
|
allowCandidateOverride,
|
|
567
|
+
benchmarkReport,
|
|
568
|
+
consensus: multiJudgeResult,
|
|
479
569
|
...(livePreview ? { liveCanvas: livePreview } : {}),
|
|
480
570
|
usage: usageSummary,
|
|
481
571
|
skippedFiles: collectedCtx?.skippedFiles || 0,
|
|
@@ -483,113 +573,3 @@ export async function runMoAPipeline({
|
|
|
483
573
|
durationMs,
|
|
484
574
|
}
|
|
485
575
|
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Streams a full MoA turn into chat with live aggregator tokens and progress feedback.
|
|
489
|
-
*/
|
|
490
|
-
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd, prices, historyFilePath, liveCanvas }, options = {}) {
|
|
491
|
-
const signal = options?.signal
|
|
492
|
-
if (signal?.aborted) return
|
|
493
|
-
|
|
494
|
-
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
495
|
-
yield { type: 'text-delta', index: 0, text: 'š§ *Mixture of Agents started...*\n\n' }
|
|
496
|
-
|
|
497
|
-
// Async push-queue for zero-latency live delta streaming
|
|
498
|
-
const queue = []
|
|
499
|
-
let notify = null
|
|
500
|
-
|
|
501
|
-
const pushUpdate = (text) => {
|
|
502
|
-
queue.push({ type: 'text-delta', index: 0, text })
|
|
503
|
-
if (notify) {
|
|
504
|
-
notify()
|
|
505
|
-
notify = null
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
let done = false
|
|
510
|
-
const pipelinePromise = runMoAPipeline({
|
|
511
|
-
userPrompt,
|
|
512
|
-
messages,
|
|
513
|
-
preset: targetPreset,
|
|
514
|
-
callLlm,
|
|
515
|
-
cwd,
|
|
516
|
-
onProgress: pushUpdate,
|
|
517
|
-
onStreamDelta: (delta) => pushUpdate(delta),
|
|
518
|
-
prices,
|
|
519
|
-
historyFilePath,
|
|
520
|
-
liveCanvas,
|
|
521
|
-
signal,
|
|
522
|
-
})
|
|
523
|
-
.catch((err) => ({ error: err }))
|
|
524
|
-
.finally(() => {
|
|
525
|
-
done = true
|
|
526
|
-
if (notify) {
|
|
527
|
-
notify()
|
|
528
|
-
notify = null
|
|
529
|
-
}
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
const startTime = Date.now()
|
|
533
|
-
let lastYieldTime = Date.now()
|
|
534
|
-
|
|
535
|
-
try {
|
|
536
|
-
while (!done || queue.length > 0) {
|
|
537
|
-
if (signal?.aborted) {
|
|
538
|
-
await cleanMoaWorkspaces(cwd)
|
|
539
|
-
return
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
while (queue.length > 0) {
|
|
543
|
-
const item = queue.shift()
|
|
544
|
-
yield item
|
|
545
|
-
lastYieldTime = Date.now()
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (done) break
|
|
549
|
-
|
|
550
|
-
// Wait for next push item or max 2.5s heartbeat
|
|
551
|
-
await Promise.race([
|
|
552
|
-
new Promise((resolve) => { notify = resolve }),
|
|
553
|
-
new Promise((resolve) => { const t = setTimeout(resolve, 2500); t.unref?.(); }),
|
|
554
|
-
])
|
|
555
|
-
|
|
556
|
-
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
557
|
-
if (!done && Date.now() - lastYieldTime >= 3000) {
|
|
558
|
-
yield { type: 'text-delta', index: 0, text: `ā³ *[${elapsedSec}s] Still processing...*\n` }
|
|
559
|
-
lastYieldTime = Date.now()
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const result = await pipelinePromise
|
|
564
|
-
if (signal?.aborted) {
|
|
565
|
-
await cleanMoaWorkspaces(cwd)
|
|
566
|
-
return
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (result?.error) {
|
|
570
|
-
const errText = '\n\nā ļø **Mixture of Agents error**: ' + (result.error?.message || String(result.error))
|
|
571
|
-
yield { type: 'text-delta', index: 0, text: errText }
|
|
572
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
573
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
574
|
-
return
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const formatted = formatMoAResponse({
|
|
578
|
-
moaResult: result,
|
|
579
|
-
presetName: targetPreset?.name || 'default',
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
583
|
-
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
584
|
-
yield {
|
|
585
|
-
type: 'usage',
|
|
586
|
-
usage: {
|
|
587
|
-
inputTokens: result?.usage?.totalTokens || 0,
|
|
588
|
-
outputTokens: Math.round((formatted.length || 0) / 4),
|
|
589
|
-
},
|
|
590
|
-
}
|
|
591
|
-
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
592
|
-
} finally {
|
|
593
|
-
// cleanup
|
|
594
|
-
}
|
|
595
|
-
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-latency Live Streaming Generator for MoA Turns
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { cleanMoaWorkspaces } from './file-workspace.js'
|
|
6
|
+
import { formatMoAResponse } from './moa-parser.js'
|
|
7
|
+
import { runMoAPipeline } from './moa-runner.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Streams a full MoA turn into chat with live aggregator tokens and progress feedback.
|
|
11
|
+
*/
|
|
12
|
+
export async function* streamMoATurn({ targetPreset, userPrompt, messages, callLlm, cwd, prices, historyFilePath, liveCanvas }, options = {}) {
|
|
13
|
+
const signal = options?.signal
|
|
14
|
+
if (signal?.aborted) return
|
|
15
|
+
|
|
16
|
+
yield { type: 'block-start', index: 0, blockType: 'text' }
|
|
17
|
+
yield { type: 'text-delta', index: 0, text: 'š§ *Mixture of Agents started...*\n\n' }
|
|
18
|
+
|
|
19
|
+
// Async push-queue for zero-latency live delta streaming
|
|
20
|
+
const queue = []
|
|
21
|
+
let notify = null
|
|
22
|
+
|
|
23
|
+
const pushUpdate = (text) => {
|
|
24
|
+
queue.push({ type: 'text-delta', index: 0, text })
|
|
25
|
+
if (notify) {
|
|
26
|
+
notify()
|
|
27
|
+
notify = null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let done = false
|
|
32
|
+
const pipelinePromise = runMoAPipeline({
|
|
33
|
+
userPrompt,
|
|
34
|
+
messages,
|
|
35
|
+
preset: targetPreset,
|
|
36
|
+
callLlm,
|
|
37
|
+
cwd,
|
|
38
|
+
onProgress: pushUpdate,
|
|
39
|
+
onStreamDelta: (delta) => pushUpdate(delta),
|
|
40
|
+
prices,
|
|
41
|
+
historyFilePath,
|
|
42
|
+
liveCanvas,
|
|
43
|
+
signal,
|
|
44
|
+
})
|
|
45
|
+
.catch((err) => ({ error: err }))
|
|
46
|
+
.finally(() => {
|
|
47
|
+
done = true
|
|
48
|
+
if (notify) {
|
|
49
|
+
notify()
|
|
50
|
+
notify = null
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const startTime = Date.now()
|
|
55
|
+
let lastYieldTime = Date.now()
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
while (!done || queue.length > 0) {
|
|
59
|
+
if (signal?.aborted) {
|
|
60
|
+
await cleanMoaWorkspaces(cwd)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
while (queue.length > 0) {
|
|
65
|
+
const item = queue.shift()
|
|
66
|
+
yield item
|
|
67
|
+
lastYieldTime = Date.now()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (done) break
|
|
71
|
+
|
|
72
|
+
// Wait for next push item or max 2.5s heartbeat
|
|
73
|
+
await Promise.race([
|
|
74
|
+
new Promise((resolve) => { notify = resolve }),
|
|
75
|
+
new Promise((resolve) => { const t = setTimeout(resolve, 2500); t.unref?.(); }),
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
const elapsedSec = Math.floor((Date.now() - startTime) / 1000)
|
|
79
|
+
if (!done && Date.now() - lastYieldTime >= 3000) {
|
|
80
|
+
yield { type: 'text-delta', index: 0, text: `ā³ *[${elapsedSec}s] Still processing...*\n` }
|
|
81
|
+
lastYieldTime = Date.now()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const result = await pipelinePromise
|
|
86
|
+
if (signal?.aborted) {
|
|
87
|
+
await cleanMoaWorkspaces(cwd)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (result?.error) {
|
|
92
|
+
const errText = '\n\nā ļø **Mixture of Agents error**: ' + (result.error?.message || String(result.error))
|
|
93
|
+
yield { type: 'text-delta', index: 0, text: errText }
|
|
94
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: errText } }
|
|
95
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const formatted = formatMoAResponse({
|
|
100
|
+
moaResult: result,
|
|
101
|
+
presetName: targetPreset?.name || 'default',
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
yield { type: 'text-delta', index: 0, text: '\n---\n\n' + formatted }
|
|
105
|
+
yield { type: 'block-end', index: 0, block: { type: 'text', text: formatted } }
|
|
106
|
+
yield {
|
|
107
|
+
type: 'usage',
|
|
108
|
+
usage: {
|
|
109
|
+
inputTokens: result?.usage?.totalTokens || 0,
|
|
110
|
+
outputTokens: Math.round((formatted.length || 0) / 4),
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
yield { type: 'finish', reason: { kind: 'stop' } }
|
|
114
|
+
} finally {
|
|
115
|
+
// cleanup
|
|
116
|
+
}
|
|
117
|
+
}
|
package/lib/routes.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
import { promoteCandidateWorkspace, readCandidateFiles, computeLineDiff, isSafeRelativePath } from './file-workspace.js'
|
|
12
12
|
import { isSafeWriteRequest } from './updater.js'
|
|
13
13
|
import { runMoAPipeline } from './moa-runner.js'
|
|
14
|
+
import { generateMoABenchmarkReport } from './moa-report.js'
|
|
15
|
+
import { resolvePresetForPrompt } from './moa-router.js'
|
|
14
16
|
|
|
15
17
|
let _modelsCache = { timestamp: 0, models: [] }
|
|
16
18
|
const MODELS_CACHE_TTL = 60 * 1000
|
|
@@ -423,16 +425,72 @@ ctx.effect(() => {
|
|
|
423
425
|
const urlPath = req.url.split('?')[0]
|
|
424
426
|
const parts = urlPath.split('/').filter(Boolean)
|
|
425
427
|
const runId = parts[2] || ''
|
|
428
|
+
const isReport = parts[3] === 'report'
|
|
426
429
|
const run = runId ? getMoaRunById(runId) : null
|
|
427
430
|
if (!run) {
|
|
428
431
|
writeJson(res, 404, { ok: false, error: 'Run not found' })
|
|
429
432
|
return
|
|
430
433
|
}
|
|
434
|
+
if (isReport) {
|
|
435
|
+
const u = new URL(req.url, 'http://127.0.0.1')
|
|
436
|
+
const format = u.searchParams.get('format') || 'md'
|
|
437
|
+
const report = run.benchmarkReport || generateMoABenchmarkReport({
|
|
438
|
+
runId: run.id,
|
|
439
|
+
timestamp: run.timestamp,
|
|
440
|
+
preset: run.preset,
|
|
441
|
+
prompt: run.prompt,
|
|
442
|
+
durationMs: run.durationMs,
|
|
443
|
+
usage: { totalTokens: run.totalTokens },
|
|
444
|
+
costUsd: run.totalCostUsd,
|
|
445
|
+
candidates: run.candidates,
|
|
446
|
+
winningIndex: run.winnerIndex,
|
|
447
|
+
winningLabel: run.winnerModel,
|
|
448
|
+
})
|
|
449
|
+
if (format === 'json') {
|
|
450
|
+
writeJson(res, 200, { ok: true, report: typeof report === 'object' ? (report.json || report) : report })
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
const mdText = typeof report === 'string' ? report : (report.markdown || JSON.stringify(report, null, 2))
|
|
454
|
+
res.writeHead(200, { 'Content-Type': 'text/markdown; charset=utf-8' })
|
|
455
|
+
res.end(mdText)
|
|
456
|
+
return
|
|
457
|
+
}
|
|
431
458
|
writeJson(res, 200, { ok: true, run })
|
|
432
459
|
},
|
|
433
460
|
})
|
|
434
461
|
}, 'dsh-moa: run by id route')
|
|
435
462
|
|
|
463
|
+
// Route: /dsh-moa/route-preset
|
|
464
|
+
ctx.effect(() => {
|
|
465
|
+
return ctx.webServer.register({
|
|
466
|
+
kind: 'exact',
|
|
467
|
+
path: '/dsh-moa/route-preset',
|
|
468
|
+
handler: async (req, res) => {
|
|
469
|
+
if (req.method !== 'POST') {
|
|
470
|
+
writeJson(res, 405, { ok: false, error: 'POST required' })
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
const raw = await readBody(req)
|
|
475
|
+
const body = JSON.parse(raw || '{}')
|
|
476
|
+
const prompt = body?.prompt || ''
|
|
477
|
+
const cfg = live()
|
|
478
|
+
const result = await resolvePresetForPrompt({
|
|
479
|
+
prompt,
|
|
480
|
+
presets: cfg.presets || [],
|
|
481
|
+
defaultPreset: cfg.default_preset || 'default',
|
|
482
|
+
routingModel: cfg.smart_routing_model,
|
|
483
|
+
callLlm,
|
|
484
|
+
enabled: true,
|
|
485
|
+
})
|
|
486
|
+
writeJson(res, 200, { ok: true, ...result })
|
|
487
|
+
} catch (err) {
|
|
488
|
+
writeJson(res, 500, { ok: false, error: err?.message || String(err) })
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
})
|
|
492
|
+
}, 'dsh-moa: route preset endpoint')
|
|
493
|
+
|
|
436
494
|
// Route: /dsh-moa/run
|
|
437
495
|
ctx.effect(() => {
|
|
438
496
|
return ctx.webServer.register({
|