@goodandready/dsh-moa 0.2.31 → 0.2.32
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/lib/client.js +30 -25
- package/lib/history.js +5 -13
- package/lib/index.js +18 -7
- package/lib/moa-context.js +9 -3
- package/lib/moa-runner.js +3 -5
- package/lib/moa-stream.js +2 -2
- package/lib/pricing.js +5 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -422,20 +422,19 @@ window.__ModuleLoader__.load({
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
const PROVIDER_TITLES = {
|
|
425
|
-
|
|
425
|
+
deepseek: 'DeepSeek',
|
|
426
|
+
openai: 'OpenAI',
|
|
427
|
+
anthropic: 'Anthropic Claude',
|
|
426
428
|
claude: 'Claude (Anthropic)',
|
|
427
|
-
|
|
428
|
-
antigravity: 'Antigravity (Google)',
|
|
429
|
+
google: 'Google Gemini',
|
|
429
430
|
gemini: 'Google Gemini',
|
|
430
|
-
commandcode: 'CommandCode Provider',
|
|
431
|
-
'opencode-go': 'OpenCode-Go',
|
|
432
|
-
'qwen-token-plan': 'Qwen (Token Plan)',
|
|
433
431
|
groq: 'Groq Cloud',
|
|
434
|
-
zai: 'Zhipu GLM',
|
|
435
432
|
ollama: 'Ollama (Local / Cloud)',
|
|
436
|
-
|
|
433
|
+
openrouter: 'OpenRouter',
|
|
434
|
+
qwen: 'Qwen',
|
|
435
|
+
zai: 'Zhipu GLM',
|
|
437
436
|
minimax: 'MiniMax',
|
|
438
|
-
|
|
437
|
+
mistral: 'Mistral AI',
|
|
439
438
|
'deepseek-official': 'DeepSeek Official',
|
|
440
439
|
}
|
|
441
440
|
|
|
@@ -850,7 +849,7 @@ window.__ModuleLoader__.load({
|
|
|
850
849
|
const currentPreset = presets.find((p) => p.name === selectedPresetName) || presets[0] || {
|
|
851
850
|
name: 'default',
|
|
852
851
|
reference_models: [],
|
|
853
|
-
aggregator: { provider: '
|
|
852
|
+
aggregator: { provider: (availableModels[0] && availableModels[0].provider) || '', model: (availableModels[0] && availableModels[0].model) || '' },
|
|
854
853
|
aggregator_temperature: 0.4,
|
|
855
854
|
reference_temperature: 0.6,
|
|
856
855
|
judge_criteria: '',
|
|
@@ -902,8 +901,8 @@ window.__ModuleLoader__.load({
|
|
|
902
901
|
} else {
|
|
903
902
|
while (refs.length < targetCount) {
|
|
904
903
|
const fallback = availableModels[refs.length % (availableModels.length || 1)] || {
|
|
905
|
-
provider: '
|
|
906
|
-
model: '
|
|
904
|
+
provider: '',
|
|
905
|
+
model: '',
|
|
907
906
|
}
|
|
908
907
|
refs.push({ provider: fallback.provider, model: fallback.model })
|
|
909
908
|
}
|
|
@@ -924,8 +923,8 @@ window.__ModuleLoader__.load({
|
|
|
924
923
|
updateCurrentPreset((p) => {
|
|
925
924
|
const refs = [...(p.reference_models || [])]
|
|
926
925
|
const fallback = availableModels[refs.length % (availableModels.length || 1)] || {
|
|
927
|
-
provider: '
|
|
928
|
-
model: '
|
|
926
|
+
provider: '',
|
|
927
|
+
model: '',
|
|
929
928
|
}
|
|
930
929
|
refs.push({ provider: fallback.provider, model: fallback.model, role_persona: 'general' })
|
|
931
930
|
return { ...p, reference_models: refs }
|
|
@@ -945,13 +944,18 @@ window.__ModuleLoader__.load({
|
|
|
945
944
|
name,
|
|
946
945
|
enabled: true,
|
|
947
946
|
ask_clarifying_questions: true,
|
|
948
|
-
reference_models: currentPreset?.reference_models?.length ? structuredClone(currentPreset.reference_models) :
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
947
|
+
reference_models: currentPreset?.reference_models?.length ? structuredClone(currentPreset.reference_models) : (
|
|
948
|
+
availableModels.length >= 2 ? [
|
|
949
|
+
{ provider: availableModels[0].provider, model: availableModels[0].model, role_persona: 'general' },
|
|
950
|
+
{ provider: availableModels[1].provider, model: availableModels[1].model, role_persona: 'general' },
|
|
951
|
+
] : [
|
|
952
|
+
{ provider: '', model: '', role_persona: 'general' },
|
|
953
|
+
{ provider: '', model: '', role_persona: 'general' },
|
|
954
|
+
]
|
|
955
|
+
),
|
|
952
956
|
aggregator: currentPreset?.aggregator?.provider ? structuredClone(currentPreset.aggregator) : {
|
|
953
|
-
provider: '
|
|
954
|
-
model:
|
|
957
|
+
provider: (availableModels[0] && availableModels[0].provider) || '',
|
|
958
|
+
model: (availableModels[0] && availableModels[0].model) || '',
|
|
955
959
|
},
|
|
956
960
|
reference_temperature: 0.6,
|
|
957
961
|
aggregator_temperature: 0.4,
|
|
@@ -1513,15 +1517,16 @@ window.__ModuleLoader__.load({
|
|
|
1513
1517
|
'div',
|
|
1514
1518
|
{ style: { flex: 1 } },
|
|
1515
1519
|
React.createElement(SearchableModelPicker, {
|
|
1516
|
-
|
|
1517
|
-
|
|
1520
|
+
provider: (fb && fb.provider) || '',
|
|
1521
|
+
model: (fb && fb.model) || '',
|
|
1522
|
+
onChange: (prov, mod) => {
|
|
1518
1523
|
updateCurrentPreset((p) => {
|
|
1519
1524
|
const list = [...(p.aggregator_fallbacks || [])]
|
|
1520
|
-
list[fbIdx] =
|
|
1525
|
+
list[fbIdx] = { provider: prov, model: mod }
|
|
1521
1526
|
return { ...p, aggregator_fallbacks: list }
|
|
1522
1527
|
})
|
|
1523
1528
|
},
|
|
1524
|
-
|
|
1529
|
+
availableModels,
|
|
1525
1530
|
t,
|
|
1526
1531
|
})
|
|
1527
1532
|
),
|
|
@@ -1553,7 +1558,7 @@ window.__ModuleLoader__.load({
|
|
|
1553
1558
|
onClick: () => {
|
|
1554
1559
|
updateCurrentPreset((p) => ({
|
|
1555
1560
|
...p,
|
|
1556
|
-
aggregator_fallbacks: [...(p.aggregator_fallbacks || []), { provider: '
|
|
1561
|
+
aggregator_fallbacks: [...(p.aggregator_fallbacks || []), { provider: (availableModels[0] && availableModels[0].provider) || '', model: (availableModels[0] && availableModels[0].model) || '' }],
|
|
1557
1562
|
}))
|
|
1558
1563
|
},
|
|
1559
1564
|
},
|
package/lib/history.js
CHANGED
|
@@ -25,7 +25,8 @@ export function getDefaultHistoryDir() {
|
|
|
25
25
|
return process.env.DSH_HISTORY_DIR
|
|
26
26
|
}
|
|
27
27
|
if (process.env.DSH_HOME) {
|
|
28
|
-
|
|
28
|
+
const trimmed = process.env.DSH_HOME.trim()
|
|
29
|
+
return trimmed.endsWith('.dsh') ? trimmed : path.join(trimmed, '.dsh')
|
|
29
30
|
}
|
|
30
31
|
if (isTestEnvironment()) {
|
|
31
32
|
return path.join(os.tmpdir(), '.dsh-test')
|
|
@@ -40,8 +41,6 @@ export function getDefaultHistoryFile() {
|
|
|
40
41
|
return path.join(getDefaultHistoryDir(), 'moa-history.jsonl')
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
export const DEFAULT_HISTORY_DIR = getDefaultHistoryDir()
|
|
44
|
-
export const DEFAULT_HISTORY_FILE = getDefaultHistoryFile()
|
|
45
44
|
|
|
46
45
|
/** Max history file size before rotation (10 MB) */
|
|
47
46
|
export const MAX_HISTORY_BYTES = 10 * 1024 * 1024
|
|
@@ -53,13 +52,6 @@ export function invalidateHistoryCache() {
|
|
|
53
52
|
_historyCache = null
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
export function getHistoryFilePath(customDir) {
|
|
57
|
-
if (customDir) {
|
|
58
|
-
return path.join(customDir, '.moa-history.jsonl')
|
|
59
|
-
}
|
|
60
|
-
return getDefaultHistoryFile()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
55
|
/**
|
|
64
56
|
* Checks if the history file exceeds the size threshold and rotates it.
|
|
65
57
|
* Moves current file to `${filePath}.1`, removing any older `.1` backup.
|
|
@@ -71,7 +63,7 @@ export function rotateHistoryFileIfNeeded(filePath = getDefaultHistoryFile(), ma
|
|
|
71
63
|
if (stat.size >= maxBytes) {
|
|
72
64
|
const backupPath = `${filePath}.1`
|
|
73
65
|
if (fs.existsSync(backupPath)) {
|
|
74
|
-
try { fs.unlinkSync(backupPath) } catch
|
|
66
|
+
try { fs.unlinkSync(backupPath) } catch { /* backup unlink best-effort */ }
|
|
75
67
|
}
|
|
76
68
|
fs.renameSync(filePath, backupPath)
|
|
77
69
|
invalidateHistoryCache()
|
|
@@ -383,7 +375,7 @@ export function getMoaRunById(runId, filePath = getDefaultHistoryFile()) {
|
|
|
383
375
|
try {
|
|
384
376
|
const item = JSON.parse(lines[i])
|
|
385
377
|
if (item && item.id === runId) return item
|
|
386
|
-
} catch
|
|
378
|
+
} catch { /* corrupt line skipped */ }
|
|
387
379
|
}
|
|
388
380
|
return null
|
|
389
381
|
} catch {
|
|
@@ -412,7 +404,7 @@ export function exportMoaHistory(filePath = getDefaultHistoryFile(), options = {
|
|
|
412
404
|
if (!item) continue
|
|
413
405
|
if (presetFilter && presetFilter !== 'all' && item.preset !== presetFilter) continue
|
|
414
406
|
records.push(item)
|
|
415
|
-
} catch
|
|
407
|
+
} catch { /* corrupt line skipped */ }
|
|
416
408
|
}
|
|
417
409
|
|
|
418
410
|
if (format === 'csv') {
|
package/lib/index.js
CHANGED
|
@@ -25,10 +25,8 @@ try {
|
|
|
25
25
|
// safe fallback
|
|
26
26
|
}
|
|
27
27
|
import path from 'node:path'
|
|
28
|
-
import os from 'node:os'
|
|
29
28
|
import {
|
|
30
29
|
parseMoACommand,
|
|
31
|
-
runMoAPipeline,
|
|
32
30
|
streamMoATurn,
|
|
33
31
|
} from './moa-runner.js'
|
|
34
32
|
|
|
@@ -36,7 +34,7 @@ import { registerMoaRoutes } from './routes.js'
|
|
|
36
34
|
import { resolvePresetForPrompt } from './moa-router.js'
|
|
37
35
|
|
|
38
36
|
import { createLiveCanvasClient } from './live-canvas.js'
|
|
39
|
-
import { registerPluginUpdater
|
|
37
|
+
import { registerPluginUpdater } from './updater.js'
|
|
40
38
|
|
|
41
39
|
export const name = '@goodandready/dsh-moa'
|
|
42
40
|
export { collectProjectContext, formatProjectContext } from './file-workspace.js'
|
|
@@ -313,6 +311,15 @@ export function apply(ctx, config) {
|
|
|
313
311
|
})
|
|
314
312
|
|
|
315
313
|
// Session turn interceptor: route turn to real aggregator model & stream MoA synthesis
|
|
314
|
+
|
|
315
|
+
ctx.effect(() => {
|
|
316
|
+
return () => {
|
|
317
|
+
for (const entry of moaSessionTurns.values()) {
|
|
318
|
+
if (entry?.ttlTimer) clearTimeout(entry.ttlTimer)
|
|
319
|
+
}
|
|
320
|
+
moaSessionTurns.clear()
|
|
321
|
+
}
|
|
322
|
+
}, 'dsh-moa: session turns cleanup')
|
|
316
323
|
const moaPendingTurns = new WeakMap()
|
|
317
324
|
const moaSessionTurns = new Map()
|
|
318
325
|
|
|
@@ -345,7 +352,8 @@ export function apply(ctx, config) {
|
|
|
345
352
|
return entry
|
|
346
353
|
}
|
|
347
354
|
|
|
348
|
-
ctx.
|
|
355
|
+
ctx.effect(() => {
|
|
356
|
+
return ctx.on('agent/pre-step', async (event, next) => {
|
|
349
357
|
const messages = event.messages || []
|
|
350
358
|
const lastUserMsg = [...messages].reverse().find((m) => m && m.role === 'user')
|
|
351
359
|
const userText = typeof lastUserMsg?.content === 'string'
|
|
@@ -404,9 +412,11 @@ export function apply(ctx, config) {
|
|
|
404
412
|
}
|
|
405
413
|
|
|
406
414
|
return next()
|
|
407
|
-
|
|
415
|
+
})
|
|
416
|
+
}, 'dsh-moa: agent pre-step interceptor')
|
|
408
417
|
|
|
409
|
-
ctx.
|
|
418
|
+
ctx.effect(() => {
|
|
419
|
+
return ctx.on('agent/request', async (payload, next) => {
|
|
410
420
|
const resolved = await next()
|
|
411
421
|
const turnContext = (payload.signal ? moaPendingTurns.get(payload.signal) : null)
|
|
412
422
|
|| (payload.session?.id ? getSessionTurn(payload.session.id) : null)
|
|
@@ -419,7 +429,8 @@ export function apply(ctx, config) {
|
|
|
419
429
|
}
|
|
420
430
|
}
|
|
421
431
|
return resolved
|
|
422
|
-
|
|
432
|
+
})
|
|
433
|
+
}, 'dsh-moa: agent request interceptor')
|
|
423
434
|
|
|
424
435
|
ctx.effect(() => {
|
|
425
436
|
return ctx.on('llm/stream', (options, next) => {
|
package/lib/moa-context.js
CHANGED
|
@@ -46,19 +46,25 @@ export function pruneMultiTurnMessages(messages = [], maxHistoryTokens = 3000) {
|
|
|
46
46
|
const nonSys = messages.filter((m) => m && m.role !== 'system')
|
|
47
47
|
if (nonSys.length === 0) return pruned
|
|
48
48
|
|
|
49
|
-
// Always keep the final
|
|
49
|
+
// Always keep up to the final 4 turns (last 2 user + assistant pairs)
|
|
50
50
|
const recentTurns = nonSys.slice(-4)
|
|
51
51
|
|
|
52
|
+
// Calculate dynamic character budget derived from maxHistoryTokens (approx 4 chars per token)
|
|
53
|
+
const totalCharBudget = Math.max(1200, (Number(maxHistoryTokens) || 3000) * 4)
|
|
54
|
+
const maxTurnChars = Math.max(400, Math.floor(totalCharBudget / recentTurns.length))
|
|
55
|
+
const maxHead = Math.floor(maxTurnChars * 0.75)
|
|
56
|
+
const maxTail = Math.max(50, maxTurnChars - maxHead)
|
|
57
|
+
|
|
52
58
|
for (let idx = 0; idx < recentTurns.length; idx++) {
|
|
53
59
|
const msg = recentTurns[idx]
|
|
54
60
|
const isLatest = idx === recentTurns.length - 1
|
|
55
61
|
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
56
62
|
|
|
57
|
-
if (isLatest || content.length
|
|
63
|
+
if (isLatest || content.length <= maxTurnChars) {
|
|
58
64
|
pruned.push(msg)
|
|
59
65
|
} else {
|
|
60
66
|
// Prune long prior assistant or user text to protect candidate context
|
|
61
|
-
const truncated = content.slice(0,
|
|
67
|
+
const truncated = content.slice(0, maxHead) + '\n\n[...context pruned for token efficiency...]\n' + content.slice(-maxTail)
|
|
62
68
|
pruned.push({
|
|
63
69
|
role: msg.role,
|
|
64
70
|
content: truncated,
|
package/lib/moa-runner.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { logger } from './logger.js'
|
|
2
|
-
import path from 'node:path'
|
|
3
2
|
import crypto from 'node:crypto'
|
|
4
|
-
import { bestEffort } from './best-effort.js'
|
|
5
3
|
import { extractFileBlocks, collectProjectContext, formatProjectContext, isRefinementTask, writeCandidateWorkspace, promoteCandidateWorkspace, cleanMoaWorkspaces, verifyFileSyntax } from './file-workspace.js'
|
|
6
4
|
import { estimateTokenCost, summarizeMoAUsage } from './pricing.js'
|
|
7
5
|
import { recordMoaRunAsync, candidatesForHistory, isTestEnvironment } from './history.js'
|
|
8
6
|
import { createPromotedPreview } from './live-canvas.js'
|
|
9
|
-
import { slotLabel,
|
|
10
|
-
import { parseWinnerIndex, parseRecommendedAssembler
|
|
7
|
+
import { slotLabel, isBroadPromptRequiringQuestions, buildQuestionSynthesisPrompt, buildSynthesisPrompt, buildPeerCritiquePrompt } from './moa-prompts.js'
|
|
8
|
+
import { parseWinnerIndex, parseRecommendedAssembler } from './moa-parser.js'
|
|
11
9
|
import { REFERENCE_SYSTEM_PROMPT, callWithTransientRetry, runReferencesParallel } from './moa-candidates.js'
|
|
12
|
-
import { executeTestGateForCandidates
|
|
10
|
+
import { executeTestGateForCandidates } from './moa-test-gate.js'
|
|
13
11
|
import { executeMultiJudgePanel, buildCompositeBlockDirectives } from './moa-multi-judge.js'
|
|
14
12
|
import { applyBudgetGuardrails } from './moa-budget.js'
|
|
15
13
|
import { extractPriorTurnBaseline, pruneMultiTurnMessages } from './moa-context.js'
|
package/lib/moa-stream.js
CHANGED
|
@@ -109,8 +109,8 @@ export async function* streamMoATurn({ targetPreset, userPrompt, messages, callL
|
|
|
109
109
|
yield {
|
|
110
110
|
type: 'usage',
|
|
111
111
|
usage: {
|
|
112
|
-
inputTokens: result?.usage?.totalTokens || 0,
|
|
113
|
-
outputTokens: Math.round((formatted.length || 0) / 4),
|
|
112
|
+
inputTokens: result?.usage?.totalInputTokens ?? (result?.usage?.totalTokens || 0),
|
|
113
|
+
outputTokens: result?.usage?.totalOutputTokens ?? Math.round((formatted.length || 0) / 4),
|
|
114
114
|
},
|
|
115
115
|
}
|
|
116
116
|
yield { type: 'finish', reason: { kind: 'stop' } }
|
package/lib/pricing.js
CHANGED
|
@@ -20,7 +20,7 @@ export function getDefaultCacheFile() {
|
|
|
20
20
|
return join(process.env.DSH_STORAGE_DIR, 'dsh-moa-catalog.json')
|
|
21
21
|
}
|
|
22
22
|
const baseDir = process.env.DSH_HOME
|
|
23
|
-
? join(process.env.DSH_HOME, '.dsh')
|
|
23
|
+
? (process.env.DSH_HOME.trim().endsWith('.dsh') ? process.env.DSH_HOME.trim() : join(process.env.DSH_HOME.trim(), '.dsh'))
|
|
24
24
|
: join(homedir(), '.dsh')
|
|
25
25
|
return join(baseDir, 'storages', 'dsh-moa-catalog.json')
|
|
26
26
|
}
|
|
@@ -315,8 +315,10 @@ export function estimateTokenCost(slot = {}, usage = {}, customPrices = {}, cach
|
|
|
315
315
|
|
|
316
316
|
export function summarizeMoAUsage(referenceOutputs = [], aggUsage = null) {
|
|
317
317
|
const normAgg = aggUsage || { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 }
|
|
318
|
-
const
|
|
318
|
+
const totalInputTokens = (referenceOutputs || []).reduce((acc, r) => acc + (r.usage?.inputTokens || r.usage?.promptTokens || 0), 0) + (normAgg.inputTokens || normAgg.promptTokens || 0)
|
|
319
|
+
const totalOutputTokens = (referenceOutputs || []).reduce((acc, r) => acc + (r.usage?.outputTokens || r.usage?.completionTokens || 0), 0) + (normAgg.outputTokens || normAgg.completionTokens || 0)
|
|
320
|
+
const totalTokens = (referenceOutputs || []).reduce((acc, r) => acc + (r.usage?.totalTokens || 0), 0) + (normAgg.totalTokens || (totalInputTokens + totalOutputTokens))
|
|
319
321
|
const totalCostUsd = Number(((referenceOutputs || []).reduce((acc, r) => acc + (r.costUsd || 0), 0) + normAgg.costUsd).toFixed(5))
|
|
320
322
|
const candidates = (referenceOutputs || []).map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd }))
|
|
321
|
-
return { totalTokens, totalCostUsd, candidates, aggregator: normAgg }
|
|
323
|
+
return { totalTokens, totalInputTokens, totalOutputTokens, totalCostUsd, candidates, aggregator: normAgg }
|
|
322
324
|
}
|