@goodandready/dsh-moa 0.2.3 → 0.2.5
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 +21 -0
- package/docs/README.ru.md +21 -0
- package/lib/client.js +226 -124
- package/lib/file-workspace.js +94 -5
- package/lib/history.js +191 -0
- package/lib/index.js +221 -188
- package/lib/moa-runner.js +216 -87
- package/lib/pricing.js +194 -0
- package/package.json +2 -2
package/lib/history.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MoA Run History & Analytics Module
|
|
3
|
+
* Records every MoA pipeline execution, tracks model win rates and costs,
|
|
4
|
+
* and provides history search and run replay capabilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import os from 'node:os'
|
|
10
|
+
import crypto from 'node:crypto'
|
|
11
|
+
|
|
12
|
+
const DEFAULT_HISTORY_DIR = path.join(os.homedir(), '.dsh')
|
|
13
|
+
const DEFAULT_HISTORY_FILE = path.join(DEFAULT_HISTORY_DIR, 'moa-history.jsonl')
|
|
14
|
+
|
|
15
|
+
export function getHistoryFilePath(customDir) {
|
|
16
|
+
if (customDir) {
|
|
17
|
+
return path.join(customDir, '.moa-history.jsonl')
|
|
18
|
+
}
|
|
19
|
+
return DEFAULT_HISTORY_FILE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Appends a completed MoA run record to the history file.
|
|
24
|
+
*/
|
|
25
|
+
export function recordMoaRun(record, filePath = DEFAULT_HISTORY_FILE) {
|
|
26
|
+
try {
|
|
27
|
+
const dir = path.dirname(filePath)
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const entry = {
|
|
33
|
+
id: record.id || crypto.randomUUID(),
|
|
34
|
+
timestamp: record.timestamp || new Date().toISOString(),
|
|
35
|
+
prompt: record.prompt || '',
|
|
36
|
+
preset: record.preset || 'default',
|
|
37
|
+
isRefinement: Boolean(record.isRefinement),
|
|
38
|
+
candidates: Array.isArray(record.candidates)
|
|
39
|
+
? record.candidates.map((c) => ({
|
|
40
|
+
provider: c.provider || '',
|
|
41
|
+
model: c.model || '',
|
|
42
|
+
filesCount: Array.isArray(c.files) ? c.files.length : (c.filesCount || 0),
|
|
43
|
+
usage: c.usage || { inputTokens: 0, outputTokens: 0 },
|
|
44
|
+
costUsd: Number(c.costUsd || 0),
|
|
45
|
+
}))
|
|
46
|
+
: [],
|
|
47
|
+
aggregator: record.aggregator
|
|
48
|
+
? {
|
|
49
|
+
provider: record.aggregator.provider || '',
|
|
50
|
+
model: record.aggregator.model || '',
|
|
51
|
+
usage: record.aggregator.usage || { inputTokens: 0, outputTokens: 0 },
|
|
52
|
+
costUsd: Number(record.aggregator.costUsd || 0),
|
|
53
|
+
}
|
|
54
|
+
: null,
|
|
55
|
+
winnerIndex: typeof record.winnerIndex === 'number' ? record.winnerIndex : -1,
|
|
56
|
+
winnerModel: record.winnerModel || '',
|
|
57
|
+
promotedFiles: Array.isArray(record.promotedFiles) ? record.promotedFiles : [],
|
|
58
|
+
totalTokens: Number(record.totalTokens || 0),
|
|
59
|
+
totalCostUsd: Number(record.totalCostUsd || 0),
|
|
60
|
+
durationMs: Number(record.durationMs || 0),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const line = JSON.stringify(entry) + '\n'
|
|
64
|
+
fs.appendFileSync(filePath, line, 'utf8')
|
|
65
|
+
return entry
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.warn('[dsh-moa] Failed to record run to history:', err)
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reads history runs with pagination (latest first).
|
|
74
|
+
*/
|
|
75
|
+
export function getMoaHistory(limit = 20, offset = 0, filePath = DEFAULT_HISTORY_FILE) {
|
|
76
|
+
try {
|
|
77
|
+
if (!fs.existsSync(filePath)) {
|
|
78
|
+
return { total: 0, runs: [] }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
82
|
+
const lines = raw.trim().split('\n').filter(Boolean)
|
|
83
|
+
const total = lines.length
|
|
84
|
+
|
|
85
|
+
const sliced = lines
|
|
86
|
+
.slice(Math.max(0, total - offset - limit), total - offset)
|
|
87
|
+
.reverse()
|
|
88
|
+
.map((line) => {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(line)
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
|
|
97
|
+
return { total, runs: sliced }
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.warn('[dsh-moa] Failed to read history:', err)
|
|
100
|
+
return { total: 0, runs: [] }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calculates win-rate leaderboard and model performance statistics across history.
|
|
106
|
+
*/
|
|
107
|
+
export function getMoaLeaderboard(filePath = DEFAULT_HISTORY_FILE) {
|
|
108
|
+
try {
|
|
109
|
+
if (!fs.existsSync(filePath)) {
|
|
110
|
+
return { totalRuns: 0, models: [] }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
114
|
+
const lines = raw.trim().split('\n').filter(Boolean)
|
|
115
|
+
const totalRuns = lines.length
|
|
116
|
+
|
|
117
|
+
const modelStats = new Map()
|
|
118
|
+
|
|
119
|
+
for (const line of lines) {
|
|
120
|
+
try {
|
|
121
|
+
const item = JSON.parse(line)
|
|
122
|
+
if (!item) continue
|
|
123
|
+
|
|
124
|
+
const winner = item.winnerModel ? String(item.winnerModel).trim() : null
|
|
125
|
+
|
|
126
|
+
if (Array.isArray(item.candidates)) {
|
|
127
|
+
for (const cand of item.candidates) {
|
|
128
|
+
const key = cand.provider && cand.model ? `${cand.provider}:${cand.model}` : (cand.model || 'unknown')
|
|
129
|
+
if (!modelStats.has(key)) {
|
|
130
|
+
modelStats.set(key, {
|
|
131
|
+
modelKey: key,
|
|
132
|
+
provider: cand.provider || '',
|
|
133
|
+
model: cand.model || key,
|
|
134
|
+
runs: 0,
|
|
135
|
+
wins: 0,
|
|
136
|
+
totalTokens: 0,
|
|
137
|
+
totalCostUsd: 0,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
const st = modelStats.get(key)
|
|
141
|
+
st.runs += 1
|
|
142
|
+
const inTok = cand.usage?.inputTokens || 0
|
|
143
|
+
const outTok = cand.usage?.outputTokens || 0
|
|
144
|
+
st.totalTokens += inTok + outTok
|
|
145
|
+
st.totalCostUsd += Number(cand.costUsd || 0)
|
|
146
|
+
|
|
147
|
+
if (winner && (winner === key || winner === cand.model || winner.includes(cand.model))) {
|
|
148
|
+
st.wins += 1
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// ignore malformed line
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const models = Array.from(modelStats.values())
|
|
158
|
+
.map((st) => ({
|
|
159
|
+
...st,
|
|
160
|
+
winRate: st.runs > 0 ? Number(((st.wins / st.runs) * 100).toFixed(1)) : 0,
|
|
161
|
+
avgTokens: st.runs > 0 ? Math.round(st.totalTokens / st.runs) : 0,
|
|
162
|
+
avgCostUsd: st.runs > 0 ? Number((st.totalCostUsd / st.runs).toFixed(4)) : 0,
|
|
163
|
+
}))
|
|
164
|
+
.sort((a, b) => b.wins - a.wins || b.winRate - a.winRate)
|
|
165
|
+
|
|
166
|
+
return { totalRuns, models }
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.warn('[dsh-moa] Failed to calculate leaderboard:', err)
|
|
169
|
+
return { totalRuns: 0, models: [] }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves a single history run by ID.
|
|
175
|
+
*/
|
|
176
|
+
export function getMoaRunById(runId, filePath = DEFAULT_HISTORY_FILE) {
|
|
177
|
+
try {
|
|
178
|
+
if (!fs.existsSync(filePath)) return null
|
|
179
|
+
const raw = fs.readFileSync(filePath, 'utf8')
|
|
180
|
+
const lines = raw.trim().split('\n').filter(Boolean)
|
|
181
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
182
|
+
try {
|
|
183
|
+
const item = JSON.parse(lines[i])
|
|
184
|
+
if (item && item.id === runId) return item
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
return null
|
|
188
|
+
} catch {
|
|
189
|
+
return null
|
|
190
|
+
}
|
|
191
|
+
}
|