dual-brain 0.1.22 → 0.2.0
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/bin/dual-brain.mjs +676 -265
- package/package.json +16 -2
- package/src/awareness.mjs +343 -0
- package/src/calibration.mjs +148 -0
- package/src/cost-tracker.mjs +184 -0
- package/src/decide.mjs +162 -10
- package/src/dispatch.mjs +40 -2
- package/src/doctor.mjs +716 -1
- package/src/fx.mjs +276 -0
- package/src/intelligence.mjs +423 -0
- package/src/ledger.mjs +196 -0
- package/src/living-docs.mjs +210 -0
- package/src/models.mjs +363 -0
- package/src/pipeline.mjs +367 -8
- package/src/prompt-intel.mjs +325 -0
- package/src/think-engine.mjs +428 -0
package/src/models.mjs
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* models.mjs — Static model intelligence registry for the Dual-Brain Orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Pure in-memory registry of AI model capabilities used by routing modules.
|
|
5
|
+
* No file I/O, no API calls. Updated with each package release.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const REGISTRY_VERSION = '2026-05-15';
|
|
9
|
+
export const REGISTRY_UPDATED = '2026-05-15';
|
|
10
|
+
|
|
11
|
+
export const MODEL_REGISTRY = Object.freeze({
|
|
12
|
+
'claude-opus-4-6': {
|
|
13
|
+
provider: 'anthropic',
|
|
14
|
+
name: 'Claude Opus 4.6',
|
|
15
|
+
tier: 'frontier',
|
|
16
|
+
contextWindow: 200000,
|
|
17
|
+
maxOutput: 32000,
|
|
18
|
+
strengths: ['complex reasoning', 'architecture', 'code review', 'long context', 'multi-step planning'],
|
|
19
|
+
weaknesses: ['speed', 'cost'],
|
|
20
|
+
costTier: 'high',
|
|
21
|
+
bestFor: ['think', 'review', 'architecture', 'complex debugging'],
|
|
22
|
+
speed: 'slow',
|
|
23
|
+
reasoning: true,
|
|
24
|
+
vision: true,
|
|
25
|
+
tools: true,
|
|
26
|
+
agentCapable: true,
|
|
27
|
+
},
|
|
28
|
+
'claude-sonnet-4-6': {
|
|
29
|
+
provider: 'anthropic',
|
|
30
|
+
name: 'Claude Sonnet 4.6',
|
|
31
|
+
tier: 'workhorse',
|
|
32
|
+
contextWindow: 200000,
|
|
33
|
+
maxOutput: 16000,
|
|
34
|
+
strengths: ['balanced speed/quality', 'code generation', 'editing', 'testing'],
|
|
35
|
+
weaknesses: ['less depth on architecture decisions'],
|
|
36
|
+
costTier: 'medium',
|
|
37
|
+
bestFor: ['execute', 'implement', 'test', 'fix'],
|
|
38
|
+
speed: 'medium',
|
|
39
|
+
reasoning: true,
|
|
40
|
+
vision: true,
|
|
41
|
+
tools: true,
|
|
42
|
+
agentCapable: true,
|
|
43
|
+
},
|
|
44
|
+
'claude-haiku-4-5-20251001': {
|
|
45
|
+
provider: 'anthropic',
|
|
46
|
+
name: 'Claude Haiku 4.5',
|
|
47
|
+
tier: 'fast',
|
|
48
|
+
contextWindow: 200000,
|
|
49
|
+
maxOutput: 8192,
|
|
50
|
+
strengths: ['speed', 'low cost', 'simple tasks', 'search', 'classification'],
|
|
51
|
+
weaknesses: ['complex reasoning', 'multi-step tasks'],
|
|
52
|
+
costTier: 'low',
|
|
53
|
+
bestFor: ['search', 'classify', 'simple edits', 'grep'],
|
|
54
|
+
speed: 'fast',
|
|
55
|
+
reasoning: false,
|
|
56
|
+
vision: true,
|
|
57
|
+
tools: true,
|
|
58
|
+
agentCapable: true,
|
|
59
|
+
},
|
|
60
|
+
'gpt-5.5': {
|
|
61
|
+
provider: 'openai',
|
|
62
|
+
name: 'GPT-5.5',
|
|
63
|
+
tier: 'frontier',
|
|
64
|
+
contextWindow: 1050000,
|
|
65
|
+
maxOutput: 128000,
|
|
66
|
+
strengths: ['massive context', 'complex professional work', 'reasoning', 'web search', 'code interpreter'],
|
|
67
|
+
weaknesses: ['cost', 'latency on complex reasoning'],
|
|
68
|
+
costTier: 'premium',
|
|
69
|
+
bestFor: ['think', 'review', 'research', 'long-context analysis'],
|
|
70
|
+
speed: 'medium',
|
|
71
|
+
reasoning: true,
|
|
72
|
+
vision: true,
|
|
73
|
+
tools: true,
|
|
74
|
+
agentCapable: true,
|
|
75
|
+
},
|
|
76
|
+
'o3': {
|
|
77
|
+
provider: 'openai',
|
|
78
|
+
name: 'o3',
|
|
79
|
+
tier: 'reasoning',
|
|
80
|
+
contextWindow: 200000,
|
|
81
|
+
maxOutput: 100000,
|
|
82
|
+
strengths: ['deep reasoning', 'math', 'code', 'science', 'multi-step logic'],
|
|
83
|
+
weaknesses: ['speed', 'cost', 'simple tasks overkill'],
|
|
84
|
+
costTier: 'premium',
|
|
85
|
+
bestFor: ['think', 'complex debugging', 'algorithm design', 'security analysis'],
|
|
86
|
+
speed: 'slow',
|
|
87
|
+
reasoning: true,
|
|
88
|
+
vision: true,
|
|
89
|
+
tools: true,
|
|
90
|
+
agentCapable: true,
|
|
91
|
+
},
|
|
92
|
+
'gpt-4o': {
|
|
93
|
+
provider: 'openai',
|
|
94
|
+
name: 'GPT-4o',
|
|
95
|
+
tier: 'workhorse',
|
|
96
|
+
contextWindow: 128000,
|
|
97
|
+
maxOutput: 16384,
|
|
98
|
+
strengths: ['balanced', 'fast', 'multimodal', 'tool use'],
|
|
99
|
+
weaknesses: ['less depth than frontier models'],
|
|
100
|
+
costTier: 'medium',
|
|
101
|
+
bestFor: ['execute', 'implement', 'chat', 'multimodal'],
|
|
102
|
+
speed: 'fast',
|
|
103
|
+
reasoning: false,
|
|
104
|
+
vision: true,
|
|
105
|
+
tools: true,
|
|
106
|
+
agentCapable: true,
|
|
107
|
+
},
|
|
108
|
+
'gpt-4o-mini': {
|
|
109
|
+
provider: 'openai',
|
|
110
|
+
name: 'GPT-4o Mini',
|
|
111
|
+
tier: 'fast',
|
|
112
|
+
contextWindow: 128000,
|
|
113
|
+
maxOutput: 16384,
|
|
114
|
+
strengths: ['very fast', 'very cheap', 'good enough for simple tasks'],
|
|
115
|
+
weaknesses: ['complex reasoning', 'nuanced code review'],
|
|
116
|
+
costTier: 'low',
|
|
117
|
+
bestFor: ['search', 'classify', 'simple tasks', 'formatting'],
|
|
118
|
+
speed: 'fast',
|
|
119
|
+
reasoning: false,
|
|
120
|
+
vision: true,
|
|
121
|
+
tools: true,
|
|
122
|
+
agentCapable: false,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const COST_TIER_ORDER = ['low', 'medium', 'high', 'premium'];
|
|
127
|
+
const SPEED_ORDER = ['fast', 'medium', 'slow'];
|
|
128
|
+
const TIER_ORDER = ['fast', 'workhorse', 'reasoning', 'frontier'];
|
|
129
|
+
|
|
130
|
+
const MODEL_QUIRKS = {
|
|
131
|
+
'claude-opus-4-6': [
|
|
132
|
+
'Best for architecture decisions',
|
|
133
|
+
'Use for code review when quality matters',
|
|
134
|
+
'Expensive — reserve for high-value tasks',
|
|
135
|
+
],
|
|
136
|
+
'claude-sonnet-4-6': [
|
|
137
|
+
'Best general-purpose worker',
|
|
138
|
+
'Good balance of speed and quality',
|
|
139
|
+
'Use Agent(model: "sonnet") for work dispatch',
|
|
140
|
+
],
|
|
141
|
+
'claude-haiku-4-5-20251001': [
|
|
142
|
+
'Best for fast search and classification',
|
|
143
|
+
'Not reliable for multi-step edits',
|
|
144
|
+
'Use for disposable search agents',
|
|
145
|
+
],
|
|
146
|
+
'gpt-5.5': [
|
|
147
|
+
'Massive 1M+ context window',
|
|
148
|
+
'Good challenger for dual-brain think',
|
|
149
|
+
'Web search and code interpreter available',
|
|
150
|
+
],
|
|
151
|
+
'o3': [
|
|
152
|
+
'Purpose-built for deep reasoning chains',
|
|
153
|
+
'Strong challenger for security analysis',
|
|
154
|
+
'Slow — avoid for quick tasks',
|
|
155
|
+
],
|
|
156
|
+
'gpt-4o': [
|
|
157
|
+
'Fast and multimodal',
|
|
158
|
+
'Solid fallback for execute-tier GPT work',
|
|
159
|
+
'Native tool use support',
|
|
160
|
+
],
|
|
161
|
+
'gpt-4o-mini': [
|
|
162
|
+
'Cheapest GPT option for simple tasks',
|
|
163
|
+
'Not agent-capable — single-shot only',
|
|
164
|
+
'Good for classification and formatting',
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export function getModel(modelId) {
|
|
169
|
+
return MODEL_REGISTRY[modelId] ?? null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function getModelsForTask(taskType, provider = null) {
|
|
173
|
+
const entries = Object.entries(MODEL_REGISTRY);
|
|
174
|
+
|
|
175
|
+
const filtered = entries.filter(([, m]) => {
|
|
176
|
+
if (provider && m.provider !== provider) return false;
|
|
177
|
+
return true;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
filtered.sort(([, a], [, b]) => {
|
|
181
|
+
const aMatch = a.bestFor.includes(taskType) ? 1 : 0;
|
|
182
|
+
const bMatch = b.bestFor.includes(taskType) ? 1 : 0;
|
|
183
|
+
if (bMatch !== aMatch) return bMatch - aMatch;
|
|
184
|
+
|
|
185
|
+
const aTier = TIER_ORDER.indexOf(a.tier);
|
|
186
|
+
const bTier = TIER_ORDER.indexOf(b.tier);
|
|
187
|
+
if (bTier !== aTier) return bTier - aTier;
|
|
188
|
+
|
|
189
|
+
return SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return filtered.map(([id, m]) => ({ id, ...m }));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getBestModel(taskType, constraints = {}) {
|
|
196
|
+
const { provider = null, maxCost = null, preferSpeed = false, requireReasoning = false, minContext = 0 } = constraints;
|
|
197
|
+
|
|
198
|
+
let candidates = getModelsForTask(taskType, provider);
|
|
199
|
+
|
|
200
|
+
if (requireReasoning) {
|
|
201
|
+
candidates = candidates.filter(m => m.reasoning);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (minContext > 0) {
|
|
205
|
+
candidates = candidates.filter(m => m.contextWindow >= minContext);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (maxCost) {
|
|
209
|
+
const maxIdx = COST_TIER_ORDER.indexOf(maxCost);
|
|
210
|
+
if (maxIdx !== -1) {
|
|
211
|
+
candidates = candidates.filter(m => COST_TIER_ORDER.indexOf(m.costTier) <= maxIdx);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (preferSpeed) {
|
|
216
|
+
candidates.sort((a, b) => SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return candidates[0] ?? null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function compareModels(modelA, modelB) {
|
|
223
|
+
const a = MODEL_REGISTRY[modelA];
|
|
224
|
+
const b = MODEL_REGISTRY[modelB];
|
|
225
|
+
|
|
226
|
+
if (!a || !b) {
|
|
227
|
+
return {
|
|
228
|
+
contextAdvantage: 'tie',
|
|
229
|
+
speedAdvantage: 'tie',
|
|
230
|
+
costAdvantage: 'tie',
|
|
231
|
+
reasoningAdvantage: 'tie',
|
|
232
|
+
recommendation: 'One or both model IDs are unknown.',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const ctxAdv = a.contextWindow > b.contextWindow ? 'a'
|
|
237
|
+
: b.contextWindow > a.contextWindow ? 'b'
|
|
238
|
+
: 'tie';
|
|
239
|
+
|
|
240
|
+
const spdA = SPEED_ORDER.indexOf(a.speed);
|
|
241
|
+
const spdB = SPEED_ORDER.indexOf(b.speed);
|
|
242
|
+
const spdAdv = spdA < spdB ? 'a' : spdB < spdA ? 'b' : 'tie';
|
|
243
|
+
|
|
244
|
+
const costA = COST_TIER_ORDER.indexOf(a.costTier);
|
|
245
|
+
const costB = COST_TIER_ORDER.indexOf(b.costTier);
|
|
246
|
+
const costAdv = costA < costB ? 'a' : costB < costA ? 'b' : 'tie';
|
|
247
|
+
|
|
248
|
+
const rsnAdv = (a.reasoning && !b.reasoning) ? 'a'
|
|
249
|
+
: (b.reasoning && !a.reasoning) ? 'b'
|
|
250
|
+
: 'tie';
|
|
251
|
+
|
|
252
|
+
const aAdvantages = [ctxAdv, spdAdv, costAdv, rsnAdv].filter(v => v === 'a').length;
|
|
253
|
+
const bAdvantages = [ctxAdv, spdAdv, costAdv, rsnAdv].filter(v => v === 'b').length;
|
|
254
|
+
|
|
255
|
+
let recommendation;
|
|
256
|
+
if (aAdvantages > bAdvantages) {
|
|
257
|
+
recommendation = `Use ${a.name} for ${a.bestFor.slice(0, 2).join('/')}; ${b.name} as a lighter alternative.`;
|
|
258
|
+
} else if (bAdvantages > aAdvantages) {
|
|
259
|
+
recommendation = `Use ${b.name} for ${b.bestFor.slice(0, 2).join('/')}; ${a.name} as a lighter alternative.`;
|
|
260
|
+
} else {
|
|
261
|
+
recommendation = `${a.name} and ${b.name} are comparable — choose based on provider availability.`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { contextAdvantage: ctxAdv, speedAdvantage: spdAdv, costAdvantage: costAdv, reasoningAdvantage: rsnAdv, recommendation };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function getRegistryAge() {
|
|
268
|
+
const updated = new Date(REGISTRY_UPDATED);
|
|
269
|
+
const now = new Date();
|
|
270
|
+
return Math.floor((now - updated) / (1000 * 60 * 60 * 24));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const COST_SYMBOL = { low: '$', medium: '$$', high: '$$$', premium: '$$$$' };
|
|
274
|
+
|
|
275
|
+
function fmtCtx(n) {
|
|
276
|
+
if (n >= 1000000) return `${(n / 1000000).toFixed(2)}M ctx`;
|
|
277
|
+
return `${Math.round(n / 1000)}K ctx`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function formatModelTable(provider = null) {
|
|
281
|
+
const groups = {};
|
|
282
|
+
for (const [id, m] of Object.entries(MODEL_REGISTRY)) {
|
|
283
|
+
if (provider && m.provider !== provider) continue;
|
|
284
|
+
if (!groups[m.provider]) groups[m.provider] = [];
|
|
285
|
+
groups[m.provider].push({ id, ...m });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const lines = [];
|
|
289
|
+
const providerLabels = { anthropic: 'Claude Models', openai: 'OpenAI Models' };
|
|
290
|
+
const providerOrder = ['anthropic', 'openai'];
|
|
291
|
+
|
|
292
|
+
for (const prov of providerOrder) {
|
|
293
|
+
if (!groups[prov]) continue;
|
|
294
|
+
lines.push(`${providerLabels[prov] ?? prov}:`);
|
|
295
|
+
for (const m of groups[prov]) {
|
|
296
|
+
const displayName = m.provider === 'anthropic' ? m.name.replace(/^Claude /, '') : m.name;
|
|
297
|
+
const name = displayName.padEnd(12);
|
|
298
|
+
const tier = m.tier.padEnd(10);
|
|
299
|
+
const ctx = fmtCtx(m.contextWindow).padEnd(10);
|
|
300
|
+
const speed = m.speed.padEnd(8);
|
|
301
|
+
const cost = (COST_SYMBOL[m.costTier] ?? '?').padEnd(6);
|
|
302
|
+
const tasks = m.bestFor.slice(0, 2).join('/');
|
|
303
|
+
lines.push(` ${name}${tier}${ctx}${speed}${cost}${tasks}`);
|
|
304
|
+
}
|
|
305
|
+
lines.push('');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return lines.join('\n').trimEnd();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getModelQuirks(modelId) {
|
|
312
|
+
return MODEL_QUIRKS[modelId] ?? [];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function suggestModel(intent, risk, complexity, availableProviders = ['anthropic', 'openai']) {
|
|
316
|
+
const intentLower = (intent || '').toLowerCase();
|
|
317
|
+
|
|
318
|
+
const TASK_MAP = {
|
|
319
|
+
think: ['architect', 'design', 'plan', 'review', 'security', 'audit', 'analysis', 'analyze'],
|
|
320
|
+
execute: ['implement', 'edit', 'fix', 'refactor', 'test', 'build', 'write', 'update', 'create'],
|
|
321
|
+
search: ['search', 'find', 'grep', 'look', 'explore', 'classify', 'format', 'list'],
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
let taskType = 'execute';
|
|
325
|
+
for (const [type, keywords] of Object.entries(TASK_MAP)) {
|
|
326
|
+
if (keywords.some(kw => intentLower.includes(kw))) {
|
|
327
|
+
taskType = type;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if ((risk === 'critical' || risk === 'high') && taskType !== 'think') {
|
|
333
|
+
taskType = 'think';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const requireReasoning = complexity === 'complex' || risk === 'critical';
|
|
337
|
+
const preferSpeed = risk === 'low' && complexity === 'simple';
|
|
338
|
+
const maxCost = risk === 'low' && complexity !== 'complex' ? 'medium' : null;
|
|
339
|
+
|
|
340
|
+
const candidates = getModelsForTask(taskType)
|
|
341
|
+
.filter(m => availableProviders.includes(m.provider))
|
|
342
|
+
.filter(m => !requireReasoning || m.reasoning)
|
|
343
|
+
.filter(m => !maxCost || COST_TIER_ORDER.indexOf(m.costTier) <= COST_TIER_ORDER.indexOf(maxCost));
|
|
344
|
+
|
|
345
|
+
if (preferSpeed) {
|
|
346
|
+
candidates.sort((a, b) => SPEED_ORDER.indexOf(a.speed) - SPEED_ORDER.indexOf(b.speed));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const best = candidates[0] ?? null;
|
|
350
|
+
if (!best) {
|
|
351
|
+
return { model: null, reason: 'No suitable model found for the given constraints.', alternatives: [] };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const reason = [
|
|
355
|
+
`Selected ${best.name} for ${taskType}-tier work`,
|
|
356
|
+
risk !== 'low' ? `(${risk} risk)` : null,
|
|
357
|
+
requireReasoning ? '— reasoning required' : null,
|
|
358
|
+
].filter(Boolean).join(' ');
|
|
359
|
+
|
|
360
|
+
const alternatives = candidates.slice(1, 3).map(m => ({ id: m.id, name: m.name }));
|
|
361
|
+
|
|
362
|
+
return { model: best.id, reason, alternatives };
|
|
363
|
+
}
|