@goodandready/dsh-moa 0.2.78 → 0.2.80

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.
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Blind Embedding Clustering for Zero-LLM Fast Consensus (#312)
3
+ *
4
+ * Clusters candidate responses using local high-dimensional n-gram semantic embeddings.
5
+ * When a dominant cluster exhibits high semantic convergence (cosine similarity >= threshold),
6
+ * the plugin automatically promotes the cluster centroid candidate without invoking
7
+ * the expensive LLM Judge, saving significant tokens and latency.
8
+ */
9
+
10
+ import { logger } from '../logger.js'
11
+ import { promoteCandidateWorkspace, cleanMoaWorkspaces } from '../workspace/fs.js'
12
+ import { createPromotedPreview } from '../live-canvas.js'
13
+ import { recordMoaRunAsync, candidatesForHistory } from '../history.js'
14
+ import { emitEvent, MOA_EVENTS } from '../events.js'
15
+ import { estimateTokenCost } from '../pricing.js'
16
+
17
+ const EMBEDDING_DIMENSIONS = 1024
18
+
19
+ /**
20
+ * 32-bit FNV-1a hash function for strings
21
+ * @param {string} str
22
+ * @returns {number}
23
+ */
24
+ export function fnv1a32(str) {
25
+ let hash = 2166136261
26
+ for (let i = 0; i < str.length; i++) {
27
+ hash ^= str.charCodeAt(i)
28
+ hash = Math.imul(hash, 16777619)
29
+ }
30
+ return hash >>> 0
31
+ }
32
+
33
+ /**
34
+ * Normalizes text/code for embedding extraction.
35
+ * @param {string} text
36
+ * @returns {string}
37
+ */
38
+ export function normalizeText(text = '') {
39
+ if (typeof text !== 'string') return ''
40
+ return text
41
+ .replace(/\/\*[\s\S]*?\*\//g, '')
42
+ .replace(/\/\/.*$/gm, '')
43
+ .replace(/(^|\s)#.*$/gm, '$1')
44
+ .replace(/\r?\n/g, ' ')
45
+ .replace(/[ \t]+/g, ' ')
46
+ .trim()
47
+ .toLowerCase()
48
+ }
49
+
50
+ /**
51
+ * Generates a unit-normalized Float32Array embedding vector from text
52
+ * using word tokens, code operators, and character n-grams.
53
+ * @param {string} text
54
+ * @param {number} [dimensions=1024]
55
+ * @returns {Float32Array}
56
+ */
57
+ export function generateEmbedding(text = '', dimensions = EMBEDDING_DIMENSIONS) {
58
+ const vector = new Float32Array(dimensions)
59
+ const normalized = normalizeText(text)
60
+ if (!normalized) return vector
61
+
62
+ // 1. Tokens including keywords, identifiers, and syntax operators
63
+ const tokens = normalized.match(/[a-z0-9_$]+|===|!==|<=|>=|=>|\+\+|--|&&|\|\||\.\.\.|[{}()[\];,+\-*\/%=<>!&|^~?:.]/g) || []
64
+ const counts = new Map()
65
+
66
+ for (let i = 0; i < tokens.length; i++) {
67
+ const t = tokens[i]
68
+ const h = fnv1a32(t) % dimensions
69
+ counts.set(h, (counts.get(h) || 0) + 1.2)
70
+
71
+ // Token bigrams
72
+ if (i < tokens.length - 1) {
73
+ const bigram = `${t}_${tokens[i + 1]}`
74
+ const h2 = fnv1a32(bigram) % dimensions
75
+ counts.set(h2, (counts.get(h2) || 0) + 0.8)
76
+ }
77
+ }
78
+
79
+ // 2. Character 3-grams for morphology and syntax
80
+ const charSpan = Math.min(normalized.length, 12000)
81
+ for (let i = 0; i <= charSpan - 3; i++) {
82
+ const trigram = normalized.slice(i, i + 3)
83
+ const h3 = fnv1a32(trigram) % dimensions
84
+ counts.set(h3, (counts.get(h3) || 0) + 0.3)
85
+ }
86
+
87
+ // 3. Sublinear TF weighting: 1 + ln(count)
88
+ let sumSq = 0
89
+ for (const [bucket, count] of counts.entries()) {
90
+ const val = 1 + Math.log(count)
91
+ vector[bucket] = val
92
+ sumSq += val * val
93
+ }
94
+
95
+ // 4. L2 Normalization
96
+ if (sumSq > 0) {
97
+ const norm = Math.sqrt(sumSq)
98
+ for (let i = 0; i < dimensions; i++) {
99
+ vector[i] /= norm
100
+ }
101
+ }
102
+
103
+ return vector
104
+ }
105
+
106
+ /**
107
+ * Computes cosine similarity between two unit-normalized vectors.
108
+ * Dot product equals cosine similarity for unit vectors.
109
+ * @param {Float32Array} vecA
110
+ * @param {Float32Array} vecB
111
+ * @returns {number}
112
+ */
113
+ export function computeCosineSimilarity(vecA, vecB) {
114
+ if (!vecA || !vecB || vecA.length !== vecB.length) return 0.0
115
+ let dot = 0
116
+ for (let i = 0; i < vecA.length; i++) {
117
+ dot += vecA[i] * vecB[i]
118
+ }
119
+ const clamped = Math.max(0.0, Math.min(1.0, dot))
120
+ return Number(clamped.toFixed(4))
121
+ }
122
+
123
+ /**
124
+ * Builds pairwise cosine similarity matrix for candidate solutions.
125
+ * @param {Array<object>} candidates
126
+ * @returns {{ vectors: Float32Array[], matrix: number[][] }}
127
+ */
128
+ export function buildSimilarityMatrix(candidates = []) {
129
+ const n = candidates.length
130
+ const vectors = candidates.map((c) => generateEmbedding(c.text || ''))
131
+ const matrix = Array.from({ length: n }, () => new Array(n).fill(0))
132
+
133
+ for (let i = 0; i < n; i++) {
134
+ matrix[i][i] = 1.0
135
+ for (let j = i + 1; j < n; j++) {
136
+ const sim = computeCosineSimilarity(vectors[i], vectors[j])
137
+ matrix[i][j] = sim
138
+ matrix[j][i] = sim
139
+ }
140
+ }
141
+
142
+ return { vectors, matrix }
143
+ }
144
+
145
+ /**
146
+ * Clusters candidate embeddings and determines if consensus was reached.
147
+ * @param {Array<object>} candidates
148
+ * @param {object} [options={}]
149
+ * @returns {object}
150
+ */
151
+ export function clusterCandidateEmbeddings(candidates = [], options = {}) {
152
+ const threshold = typeof options.threshold === 'number' ? options.threshold : 0.88
153
+ const fallbackToJudgeOnSplit = options.fallbackToJudgeOnSplit !== false
154
+ const minMajorityRatio = typeof options.minMajorityRatio === 'number' ? options.minMajorityRatio : 0.60
155
+
156
+ if (!Array.isArray(candidates) || candidates.length < 2) {
157
+ return {
158
+ consensusReached: false,
159
+ threshold,
160
+ clusters: [],
161
+ dominantCluster: null,
162
+ centroidCandidate: null,
163
+ centroidIndex: 0,
164
+ similarityMatrix: [],
165
+ fallbackToJudge: fallbackToJudgeOnSplit,
166
+ reason: 'Insufficient candidates (need at least 2)',
167
+ }
168
+ }
169
+
170
+ const n = candidates.length
171
+ const { vectors, matrix } = buildSimilarityMatrix(candidates)
172
+
173
+ // Threshold clustering: connect candidates with pairwise similarity >= threshold
174
+ const clusters = []
175
+ const assigned = new Set()
176
+
177
+ for (let i = 0; i < n; i++) {
178
+ if (assigned.has(i)) continue
179
+ const clusterMembers = [i]
180
+ assigned.add(i)
181
+
182
+ // Find all peers reachable with similarity >= threshold
183
+ for (let j = 0; j < n; j++) {
184
+ if (assigned.has(j)) continue
185
+ // Qualifies if similarity to cluster seed or average to cluster >= threshold
186
+ const simToSeed = matrix[i][j]
187
+ const avgSim = clusterMembers.reduce((sum, idx) => sum + matrix[j][idx], 0) / clusterMembers.length
188
+ if (simToSeed >= threshold || avgSim >= threshold) {
189
+ clusterMembers.push(j)
190
+ assigned.add(j)
191
+ }
192
+ }
193
+ clusters.push(clusterMembers)
194
+ }
195
+
196
+ // Find dominant cluster (maximum members)
197
+ clusters.sort((a, b) => b.length - a.length)
198
+ const largestCluster = clusters[0] || []
199
+
200
+ // Compute intra-cluster average similarity
201
+ let intraClusterSimSum = 0
202
+ let pairCount = 0
203
+ for (let a = 0; a < largestCluster.length; a++) {
204
+ for (let b = a + 1; b < largestCluster.length; b++) {
205
+ intraClusterSimSum += matrix[largestCluster[a]][largestCluster[b]]
206
+ pairCount++
207
+ }
208
+ }
209
+ const avgIntraSimilarity = pairCount > 0 ? Number((intraClusterSimSum / pairCount).toFixed(4)) : 1.0
210
+
211
+ // Majority condition: cluster has >= required members and high internal similarity
212
+ const requiredCount = Math.max(2, Math.ceil(n * minMajorityRatio))
213
+ const consensusReached = largestCluster.length >= requiredCount && (avgIntraSimilarity >= threshold || largestCluster.length === n)
214
+
215
+ let centroidIndex = -1
216
+ let centroidCandidate = null
217
+
218
+ if (consensusReached) {
219
+ // Select centroid: member with highest average similarity to peers in the dominant cluster
220
+ let bestAvgSim = -1
221
+ for (const idx of largestCluster) {
222
+ let simTotal = 0
223
+ for (const peerIdx of largestCluster) {
224
+ if (idx !== peerIdx) simTotal += matrix[idx][peerIdx]
225
+ }
226
+ const candidateAvg = largestCluster.length > 1 ? simTotal / (largestCluster.length - 1) : 1.0
227
+
228
+ // Favor syntactically valid candidates in tie/near-tie situations
229
+ const hasSyntaxError = candidates[idx].syntaxValidation?.valid === false
230
+ const adjustedScore = candidateAvg - (hasSyntaxError ? 0.05 : 0)
231
+
232
+ if (adjustedScore > bestAvgSim) {
233
+ bestAvgSim = adjustedScore
234
+ centroidIndex = idx
235
+ }
236
+ }
237
+
238
+ centroidCandidate = candidates[centroidIndex] || null
239
+ }
240
+
241
+ const dominantClusterInfo = {
242
+ size: largestCluster.length,
243
+ memberIndices: largestCluster.map((i) => candidates[i].index ?? (i + 1)),
244
+ avgSimilarity: avgIntraSimilarity,
245
+ }
246
+
247
+ return {
248
+ consensusReached,
249
+ threshold,
250
+ dominantCluster: dominantClusterInfo,
251
+ clusters: clusters.map((c, id) => ({
252
+ id,
253
+ size: c.length,
254
+ indices: c.map((i) => candidates[i].index ?? (i + 1)),
255
+ })),
256
+ centroidCandidate,
257
+ centroidIndex: centroidCandidate ? (centroidCandidate.index ?? (centroidIndex + 1)) : 0,
258
+ similarityMatrix: matrix,
259
+ fallbackToJudge: !consensusReached && fallbackToJudgeOnSplit,
260
+ reason: consensusReached
261
+ ? `Blind embedding consensus reached (${largestCluster.length}/${n} models converged with avg similarity ${avgIntraSimilarity} >= ${threshold})`
262
+ : `No dominant embedding cluster converged (largest has ${largestCluster.length}/${n} with avg sim ${avgIntraSimilarity} < ${threshold})`,
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Estimates saved tokens and USD cost from bypassing the judge round.
268
+ * @param {Array<object>} candidates
269
+ * @param {string} userPrompt
270
+ * @param {number} maxTokens
271
+ * @param {object} prices
272
+ * @returns {{ skippedTokens: number, skippedCostUsd: number }}
273
+ */
274
+ export function estimateSkippedJudgeUsage(candidates = [], userPrompt = '', maxTokens = 4096, prices = {}) {
275
+ const candidateChars = candidates.reduce((acc, c) => acc + (c.text?.length || 0), 0)
276
+ const promptChars = userPrompt.length
277
+ const estimatedInputTokens = Math.round((candidateChars + promptChars) / 3.8)
278
+ const estimatedOutputTokens = Math.min(maxTokens, 1500)
279
+ const skippedTokens = estimatedInputTokens + estimatedOutputTokens
280
+
281
+ const costEst = estimateTokenCost({
282
+ provider: 'default',
283
+ model: 'judge',
284
+ inputTokens: estimatedInputTokens,
285
+ outputTokens: estimatedOutputTokens,
286
+ prices,
287
+ })
288
+
289
+ return {
290
+ skippedTokens,
291
+ skippedCostUsd: Number((costEst?.costUsd || 0.005).toFixed(5)),
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Evaluates candidate responses for blind embedding clustering and executes fast zero-LLM consensus if reached.
297
+ * @param {object} params
298
+ * @returns {Promise<{ ok: boolean, consensusReached: boolean, payload?: object, error?: Error, abort?: boolean }>}
299
+ */
300
+ export async function stageEmbeddingClustering({
301
+ successfulRefs = [],
302
+ referenceOutputs = [],
303
+ preset = {},
304
+ userPrompt = '',
305
+ prices = {},
306
+ maxTokens = 4096,
307
+ cwd = process.cwd(),
308
+ runId = '',
309
+ signal = null,
310
+ promoteOpts = {},
311
+ allowCandidateOverride = false,
312
+ isRefinement = false,
313
+ historyFilePath = null,
314
+ startTime = Date.now(),
315
+ collectedCtx = null,
316
+ liveCanvas = null,
317
+ ctx = null,
318
+ }) {
319
+ const enabled = Boolean(preset?.embedding_clustering_enabled || preset?.embeddingClusteringEnabled)
320
+ const threshold = typeof (preset?.embedding_consensus_threshold ?? preset?.embeddingConsensusThreshold) === 'number'
321
+ ? (preset.embedding_consensus_threshold ?? preset.embeddingConsensusThreshold)
322
+ : 0.88
323
+ const fallbackToJudgeOnSplit = (preset?.fallback_to_judge_on_split ?? preset?.fallbackToJudgeOnSplit) !== false
324
+
325
+ if (!enabled || successfulRefs.length < 2) {
326
+ return { ok: true, consensusReached: false }
327
+ }
328
+
329
+ const result = clusterCandidateEmbeddings(successfulRefs, {
330
+ threshold,
331
+ fallbackToJudgeOnSplit,
332
+ })
333
+
334
+ emitEvent(ctx, MOA_EVENTS.EMBEDDING_CLUSTER, {
335
+ runId,
336
+ consensusReached: result.consensusReached,
337
+ threshold,
338
+ dominantCluster: result.dominantCluster,
339
+ clusters: result.clusters,
340
+ centroidIndex: result.centroidIndex,
341
+ fallbackToJudge: result.fallbackToJudge,
342
+ reason: result.reason,
343
+ })
344
+
345
+ if (!result.consensusReached || !result.centroidCandidate) {
346
+ logger.info(`[dsh-moa] Embedding clustering: ${result.reason}. Proceeding to arbitration.`)
347
+ return { ok: true, consensusReached: false, fallbackToJudge: result.fallbackToJudge }
348
+ }
349
+
350
+ const winner = result.centroidCandidate
351
+ logger.info(`[dsh-moa] Blind embedding fast consensus reached! Winner centroid: candidate ${result.centroidIndex}`)
352
+
353
+ // Promote centroid candidate files to project workspace
354
+ let promotedFiles = []
355
+ let promoteErr = null
356
+ if (winner.files?.length > 0) {
357
+ try {
358
+ promotedFiles = await promoteCandidateWorkspace(cwd, winner.index, {
359
+ ...promoteOpts,
360
+ runId,
361
+ keepMoa: allowCandidateOverride,
362
+ })
363
+ } catch (err) {
364
+ promoteErr = err
365
+ }
366
+ } else if (!allowCandidateOverride) {
367
+ await cleanMoaWorkspaces(cwd, { runId })
368
+ }
369
+
370
+ if (promoteErr) {
371
+ if (!allowCandidateOverride) await cleanMoaWorkspaces(cwd, { runId })
372
+ if (signal?.aborted || promoteErr?.message?.includes('aborted')) {
373
+ return { ok: false, consensusReached: true, abort: true, error: new Error('Turn aborted') }
374
+ }
375
+ }
376
+
377
+ const skippedUsage = estimateSkippedJudgeUsage(successfulRefs, userPrompt, maxTokens, prices)
378
+ const livePreview = await createPromotedPreview(liveCanvas, cwd, promotedFiles, referenceOutputs, {
379
+ winningIndex: winner.index,
380
+ })
381
+
382
+ const totalTokens = referenceOutputs.reduce((acc, r) => acc + (r.usage?.totalTokens || 0), 0)
383
+ const totalCostUsd = Number(referenceOutputs.reduce((acc, r) => acc + (r.costUsd || 0), 0).toFixed(5))
384
+ const durationMs = Date.now() - startTime
385
+
386
+ try {
387
+ await recordMoaRunAsync({
388
+ id: runId,
389
+ cwd,
390
+ prompt: userPrompt,
391
+ preset: preset?.name || 'default',
392
+ isRefinement,
393
+ isEarlyConsensus: true,
394
+ isEmbeddingConsensus: true,
395
+ candidates: candidatesForHistory(referenceOutputs),
396
+ aggregator: 'Blind Embedding Consensus (Bypassed)',
397
+ winnerIndex: winner.index,
398
+ winnerModel: winner.label,
399
+ promotedFiles,
400
+ totalTokens,
401
+ totalCostUsd,
402
+ skippedJudgeTokens: skippedUsage.skippedTokens,
403
+ durationMs,
404
+ }, historyFilePath || undefined)
405
+ } catch (histErr) {
406
+ logger.warn('[dsh-moa] Failed to record embedding consensus in history:', histErr?.message || histErr)
407
+ }
408
+
409
+ emitEvent(ctx, MOA_EVENTS.RUN_FINISH, {
410
+ runId,
411
+ durationMs,
412
+ totalTokens,
413
+ totalCostUsd,
414
+ promotedFiles,
415
+ winnerIndex: winner.index,
416
+ isEarlyConsensus: true,
417
+ isEmbeddingConsensus: true,
418
+ })
419
+
420
+ return {
421
+ ok: true,
422
+ consensusReached: true,
423
+ payload: {
424
+ kind: 'synthesis',
425
+ content: winner.text,
426
+ aggregator: 'Blind Embedding Consensus (Bypassed)',
427
+ references: referenceOutputs,
428
+ presetName: preset?.name || 'default',
429
+ isRefinement,
430
+ isEarlyConsensus: true,
431
+ isEmbeddingConsensus: true,
432
+ winningIndex: winner.index,
433
+ winnerModel: winner.label,
434
+ promotedFiles,
435
+ runId,
436
+ embeddingInfo: {
437
+ dominantCluster: result.dominantCluster,
438
+ threshold,
439
+ clusters: result.clusters,
440
+ centroidIndex: result.centroidIndex,
441
+ },
442
+ ...(livePreview ? { liveCanvas: livePreview } : {}),
443
+ usage: {
444
+ totalTokens,
445
+ totalCostUsd,
446
+ skippedJudgeTokens: skippedUsage.skippedTokens,
447
+ skippedJudgeCostUsd: skippedUsage.skippedCostUsd,
448
+ candidates: referenceOutputs.map((r) => ({ label: r.label, usage: r.usage, costUsd: r.costUsd })),
449
+ aggregator: { inputTokens: 0, outputTokens: 0, totalTokens: 0, costUsd: 0 },
450
+ },
451
+ skippedFiles: collectedCtx?.skippedFiles || 0,
452
+ skippedList: collectedCtx?.skippedList || [],
453
+ durationMs,
454
+ },
455
+ }
456
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * DeepSeek Harness Mixture of Agents (MoA) — Cost/Quality Pareto Frontier Slider (#307)
3
+ *
4
+ * Provides reactive calculation of the optimal ensemble trade-off between Cost ($),
5
+ * Quality (Rigorous Consensus), and Latency (seconds) along the Pareto Frontier.
6
+ * Dynamically maps a single slider value (0–100%) to curated model configurations:
7
+ * - 0–25%: Local Free Edge Tier ($0.00 cost, ultra-low latency)
8
+ * - 26–50%: Economic Tier (Low-cost cloud models + deepseek-chat)
9
+ * - 51–75%: Balanced Production Tier (Diverse candidates + deepseek-reasoner / Sonnet)
10
+ * - 76–100%: Ultra-Rigorous Consensus Tier (Flagship ensemble + Multi-Judge panel)
11
+ */
12
+
13
+ import { MOA_EVENTS, emitEvent } from '../events.js'
14
+ import { estimateCandidateRunCost, estimateDownstreamCost } from '../moa-budget.js'
15
+
16
+ export const PARETO_TIERS = [
17
+ {
18
+ maxPos: 25,
19
+ name: 'local_free',
20
+ label: '⚡ 100% Free / Local Edge',
21
+ description: 'Zero API costs. Local models on MiniPC (Qwen 2.5 Coder / Ollama) for maximum speed and privacy.',
22
+ defaultReferences: [
23
+ { provider: 'ollama', model: 'qwen2.5-coder:7b', role_persona: 'proposer' },
24
+ { provider: 'ollama', model: 'deepseek-r1:8b', role_persona: 'challenger' },
25
+ ],
26
+ defaultAggregator: { provider: 'ollama', model: 'qwen2.5-coder:7b' },
27
+ estimatedCostUsd: 0.0,
28
+ estimatedLatencySec: 4,
29
+ multiJudge: false,
30
+ },
31
+ {
32
+ maxPos: 50,
33
+ name: 'economic',
34
+ label: '💡 Economic Tier',
35
+ description: 'Low-cost fast models. Local edge proposers verified by an economical cloud aggregator.',
36
+ defaultReferences: [
37
+ { provider: 'ollama', model: 'qwen2.5-coder:7b', role_persona: 'proposer' },
38
+ { provider: 'deepseek', model: 'deepseek-chat', role_persona: 'challenger' },
39
+ ],
40
+ defaultAggregator: { provider: 'deepseek', model: 'deepseek-chat' },
41
+ estimatedCostUsd: 0.002,
42
+ estimatedLatencySec: 7,
43
+ multiJudge: false,
44
+ },
45
+ {
46
+ maxPos: 75,
47
+ name: 'balanced',
48
+ label: '⚖️ Optimal Balanced Tier',
49
+ description: 'High hypothesis diversity with reasoning models and flagship aggregator.',
50
+ defaultReferences: [
51
+ { provider: 'deepseek', model: 'deepseek-chat', role_persona: 'proposer' },
52
+ { provider: 'deepseek', model: 'deepseek-reasoner', role_persona: 'challenger' },
53
+ { provider: 'ollama', model: 'qwen2.5-coder:7b', role_persona: 'minimalist' },
54
+ ],
55
+ defaultAggregator: { provider: 'deepseek', model: 'deepseek-reasoner' },
56
+ estimatedCostUsd: 0.009,
57
+ estimatedLatencySec: 12,
58
+ multiJudge: false,
59
+ },
60
+ {
61
+ maxPos: 100,
62
+ name: 'ultra_rigorous',
63
+ label: '🏆 Ultra-Rigorous Consensus',
64
+ description: 'Maximum accuracy. Flagship reasoning models with multi-judge panel and adversarial peer critique.',
65
+ defaultReferences: [
66
+ { provider: 'deepseek', model: 'deepseek-reasoner', role_persona: 'proposer' },
67
+ { provider: 'anthropic', model: 'claude-3-5-sonnet', role_persona: 'challenger' },
68
+ { provider: 'openai', model: 'gpt-4o', role_persona: 'adversary' },
69
+ ],
70
+ defaultAggregator: { provider: 'deepseek', model: 'deepseek-reasoner' },
71
+ estimatedCostUsd: 0.038,
72
+ estimatedLatencySec: 22,
73
+ multiJudge: true,
74
+ },
75
+ ]
76
+
77
+ /**
78
+ * Returns the Pareto tier definition for a given slider position (0-100).
79
+ *
80
+ * @param {number} position
81
+ * @returns {object} Tier configuration
82
+ */
83
+ export function getParetoProfile(position = 50) {
84
+ const clamped = Math.max(0, Math.min(100, Number(position) || 0))
85
+ for (const tier of PARETO_TIERS) {
86
+ if (clamped <= tier.maxPos) {
87
+ return { ...tier, position: clamped }
88
+ }
89
+ }
90
+ return { ...PARETO_TIERS[PARETO_TIERS.length - 1], position: clamped }
91
+ }
92
+
93
+ /**
94
+ * Calculates dynamically predicted cost and latency for an ensemble along the Pareto Frontier.
95
+ *
96
+ * @param {number} sliderPos
97
+ * @param {Array<object>} [availableModels]
98
+ * @param {object} [options]
99
+ * @returns {object} Ensemble recommendation
100
+ */
101
+ export function calculateParetoEnsemble(sliderPos = 50, availableModels = [], options = {}) {
102
+ const profile = getParetoProfile(sliderPos)
103
+ const { prices = {}, promptTokens = 1500, expectedOutputTokens = 2048 } = options
104
+
105
+ // Calculate upfront cost
106
+ const candCost = estimateCandidateRunCost(profile.defaultReferences, prices, promptTokens, expectedOutputTokens)
107
+ const downstreamCost = estimateDownstreamCost(
108
+ profile.defaultAggregator,
109
+ profile.multiJudge ? profile.defaultReferences : [],
110
+ profile.defaultReferences.length,
111
+ prices,
112
+ expectedOutputTokens,
113
+ promptTokens
114
+ )
115
+
116
+ const calculatedCostUsd = profile.name === 'local_free'
117
+ ? 0.0
118
+ : Number((candCost.totalEstimatedUsd + downstreamCost).toFixed(5))
119
+
120
+ return {
121
+ tier: profile.name,
122
+ label: profile.label,
123
+ description: profile.description,
124
+ sliderPosition: profile.position,
125
+ estimatedCostUsd: calculatedCostUsd > 0 ? calculatedCostUsd : profile.estimatedCostUsd,
126
+ estimatedLatencySec: profile.estimatedLatencySec,
127
+ recommendedReferences: profile.defaultReferences,
128
+ recommendedAggregator: profile.defaultAggregator,
129
+ multiJudgeEnabled: profile.multiJudge,
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Adapts preset configuration based on Pareto slider position if enabled.
135
+ *
136
+ * @param {object} preset
137
+ * @param {object} [options]
138
+ * @returns {object} Adjusted preset
139
+ */
140
+ export function applyParetoAdjustment(preset, options = {}) {
141
+ if (!preset?.pareto_slider_enabled) return preset
142
+
143
+ const { ctx, runId, onProgress } = options
144
+ const position = preset.pareto_slider_position ?? 50
145
+ const recommendation = calculateParetoEnsemble(position, options.availableModels || [], options)
146
+
147
+ if (typeof onProgress === 'function') {
148
+ onProgress(`📈 *Pareto Frontier*: Active profile ${recommendation.label} (~$${recommendation.estimatedCostUsd}, ~${recommendation.estimatedLatencySec}s)\n`)
149
+ }
150
+
151
+ if (ctx) {
152
+ emitEvent(ctx, MOA_EVENTS.PARETO_ADJUSTED, {
153
+ runId,
154
+ position,
155
+ tier: recommendation.tier,
156
+ estimatedCostUsd: recommendation.estimatedCostUsd,
157
+ estimatedLatencySec: recommendation.estimatedLatencySec,
158
+ })
159
+ }
160
+
161
+ return {
162
+ ...preset,
163
+ reference_models: recommendation.recommendedReferences,
164
+ aggregator: recommendation.recommendedAggregator,
165
+ multi_judge_enabled: recommendation.multiJudgeEnabled || preset.multi_judge_enabled,
166
+ }
167
+ }