musubi-sdd 5.7.2 → 5.9.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/README.ja.md +74 -0
- package/README.md +129 -0
- package/bin/musubi-config.js +230 -0
- package/bin/musubi-orchestrate.js +16 -0
- package/bin/musubi-release.js +415 -0
- package/bin/musubi-workflow.js +76 -7
- package/package.json +6 -2
- package/src/ai/advanced-ai.js +877 -0
- package/src/ai/index.js +52 -0
- package/src/enterprise/index.js +40 -0
- package/src/enterprise/multi-tenant.js +913 -0
- package/src/generators/changelog-generator.js +344 -0
- package/src/index.js +28 -0
- package/src/integrations/codegraph-mcp.js +538 -4
- package/src/integrations/enterprise-integrations.js +930 -0
- package/src/managers/package-manager.js +470 -0
- package/src/managers/workflow-mode-manager.js +290 -0
- package/src/managers/workflow.js +110 -12
- package/src/orchestration/builtin-skills.js +303 -0
- package/src/orchestration/index.js +20 -0
- package/src/orchestration/skill-registry.js +3 -0
- package/src/schemas/project-schema.json +296 -0
- package/src/validators/constitution-level-manager.js +387 -0
- package/src/validators/constitutional-validator.js +386 -161
- package/src/validators/project-validator.js +322 -0
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced AI Capabilities Module
|
|
3
|
+
*
|
|
4
|
+
* Phase 6 P1: Multi-Model Orchestration, Context Management, RAG Integration
|
|
5
|
+
*
|
|
6
|
+
* @module ai
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
// ============================================================
|
|
12
|
+
// Model Configuration
|
|
13
|
+
// ============================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Supported AI providers
|
|
17
|
+
*/
|
|
18
|
+
const AIProvider = {
|
|
19
|
+
OPENAI: 'openai',
|
|
20
|
+
ANTHROPIC: 'anthropic',
|
|
21
|
+
AZURE_OPENAI: 'azure-openai',
|
|
22
|
+
GOOGLE: 'google',
|
|
23
|
+
LOCAL: 'local',
|
|
24
|
+
CUSTOM: 'custom',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Task types for model routing
|
|
29
|
+
*/
|
|
30
|
+
const TaskType = {
|
|
31
|
+
CODE_GENERATION: 'code_generation',
|
|
32
|
+
CODE_REVIEW: 'code_review',
|
|
33
|
+
CODE_EXPLANATION: 'code_explanation',
|
|
34
|
+
REQUIREMENTS_ANALYSIS: 'requirements_analysis',
|
|
35
|
+
DESIGN_GENERATION: 'design_generation',
|
|
36
|
+
TEST_GENERATION: 'test_generation',
|
|
37
|
+
DOCUMENTATION: 'documentation',
|
|
38
|
+
REFACTORING: 'refactoring',
|
|
39
|
+
DEBUGGING: 'debugging',
|
|
40
|
+
CHAT: 'chat',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Model capability levels
|
|
45
|
+
*/
|
|
46
|
+
const ModelCapability = {
|
|
47
|
+
BASIC: 'basic',
|
|
48
|
+
STANDARD: 'standard',
|
|
49
|
+
ADVANCED: 'advanced',
|
|
50
|
+
EXPERT: 'expert',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// Model Registry
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Configuration for a single model
|
|
59
|
+
*/
|
|
60
|
+
class ModelConfig {
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
this.id = options.id || `model_${Date.now()}`;
|
|
63
|
+
this.name = options.name || 'Unknown Model';
|
|
64
|
+
this.provider = options.provider || AIProvider.OPENAI;
|
|
65
|
+
this.modelId = options.modelId || 'gpt-4';
|
|
66
|
+
this.capability = options.capability || ModelCapability.STANDARD;
|
|
67
|
+
this.maxTokens = options.maxTokens || 4096;
|
|
68
|
+
this.contextWindow = options.contextWindow || 8192;
|
|
69
|
+
this.costPerInputToken = options.costPerInputToken || 0.00003;
|
|
70
|
+
this.costPerOutputToken = options.costPerOutputToken || 0.00006;
|
|
71
|
+
this.supportedTasks = options.supportedTasks || Object.values(TaskType);
|
|
72
|
+
this.metadata = options.metadata || {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if model supports a task
|
|
77
|
+
*/
|
|
78
|
+
supportsTask(taskType) {
|
|
79
|
+
return this.supportedTasks.includes(taskType);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculate estimated cost for tokens
|
|
84
|
+
*/
|
|
85
|
+
estimateCost(inputTokens, outputTokens) {
|
|
86
|
+
return inputTokens * this.costPerInputToken + outputTokens * this.costPerOutputToken;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
toJSON() {
|
|
90
|
+
return {
|
|
91
|
+
id: this.id,
|
|
92
|
+
name: this.name,
|
|
93
|
+
provider: this.provider,
|
|
94
|
+
modelId: this.modelId,
|
|
95
|
+
capability: this.capability,
|
|
96
|
+
maxTokens: this.maxTokens,
|
|
97
|
+
contextWindow: this.contextWindow,
|
|
98
|
+
costPerInputToken: this.costPerInputToken,
|
|
99
|
+
costPerOutputToken: this.costPerOutputToken,
|
|
100
|
+
supportedTasks: this.supportedTasks,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Registry of available models
|
|
107
|
+
*/
|
|
108
|
+
class ModelRegistry {
|
|
109
|
+
constructor() {
|
|
110
|
+
this.models = new Map();
|
|
111
|
+
this.defaultModels = new Map();
|
|
112
|
+
this._initializeDefaults();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_initializeDefaults() {
|
|
116
|
+
// OpenAI models
|
|
117
|
+
this.register(
|
|
118
|
+
new ModelConfig({
|
|
119
|
+
id: 'gpt-4o',
|
|
120
|
+
name: 'GPT-4o',
|
|
121
|
+
provider: AIProvider.OPENAI,
|
|
122
|
+
modelId: 'gpt-4o',
|
|
123
|
+
capability: ModelCapability.EXPERT,
|
|
124
|
+
contextWindow: 128000,
|
|
125
|
+
maxTokens: 4096,
|
|
126
|
+
costPerInputToken: 0.000005,
|
|
127
|
+
costPerOutputToken: 0.000015,
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
this.register(
|
|
132
|
+
new ModelConfig({
|
|
133
|
+
id: 'gpt-4o-mini',
|
|
134
|
+
name: 'GPT-4o Mini',
|
|
135
|
+
provider: AIProvider.OPENAI,
|
|
136
|
+
modelId: 'gpt-4o-mini',
|
|
137
|
+
capability: ModelCapability.STANDARD,
|
|
138
|
+
contextWindow: 128000,
|
|
139
|
+
maxTokens: 16384,
|
|
140
|
+
costPerInputToken: 0.00000015,
|
|
141
|
+
costPerOutputToken: 0.0000006,
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Anthropic models
|
|
146
|
+
this.register(
|
|
147
|
+
new ModelConfig({
|
|
148
|
+
id: 'claude-3-5-sonnet',
|
|
149
|
+
name: 'Claude 3.5 Sonnet',
|
|
150
|
+
provider: AIProvider.ANTHROPIC,
|
|
151
|
+
modelId: 'claude-3-5-sonnet-20241022',
|
|
152
|
+
capability: ModelCapability.EXPERT,
|
|
153
|
+
contextWindow: 200000,
|
|
154
|
+
maxTokens: 8192,
|
|
155
|
+
costPerInputToken: 0.000003,
|
|
156
|
+
costPerOutputToken: 0.000015,
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
this.register(
|
|
161
|
+
new ModelConfig({
|
|
162
|
+
id: 'claude-3-haiku',
|
|
163
|
+
name: 'Claude 3 Haiku',
|
|
164
|
+
provider: AIProvider.ANTHROPIC,
|
|
165
|
+
modelId: 'claude-3-haiku-20240307',
|
|
166
|
+
capability: ModelCapability.BASIC,
|
|
167
|
+
contextWindow: 200000,
|
|
168
|
+
maxTokens: 4096,
|
|
169
|
+
costPerInputToken: 0.00000025,
|
|
170
|
+
costPerOutputToken: 0.00000125,
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Set defaults per task
|
|
175
|
+
this.setDefaultForTask(TaskType.CODE_GENERATION, 'claude-3-5-sonnet');
|
|
176
|
+
this.setDefaultForTask(TaskType.CODE_REVIEW, 'gpt-4o');
|
|
177
|
+
this.setDefaultForTask(TaskType.CHAT, 'gpt-4o-mini');
|
|
178
|
+
this.setDefaultForTask(TaskType.DOCUMENTATION, 'claude-3-haiku');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Register a model
|
|
183
|
+
*/
|
|
184
|
+
register(model) {
|
|
185
|
+
this.models.set(model.id, model);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get a model by ID
|
|
190
|
+
*/
|
|
191
|
+
get(modelId) {
|
|
192
|
+
return this.models.get(modelId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* List all registered models
|
|
197
|
+
*/
|
|
198
|
+
list() {
|
|
199
|
+
return Array.from(this.models.values());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Set default model for a task type
|
|
204
|
+
*/
|
|
205
|
+
setDefaultForTask(taskType, modelId) {
|
|
206
|
+
this.defaultModels.set(taskType, modelId);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get default model for a task
|
|
211
|
+
*/
|
|
212
|
+
getDefaultForTask(taskType) {
|
|
213
|
+
const modelId = this.defaultModels.get(taskType);
|
|
214
|
+
return modelId ? this.get(modelId) : this.list()[0];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Find models by capability
|
|
219
|
+
*/
|
|
220
|
+
findByCapability(capability) {
|
|
221
|
+
return this.list().filter(m => m.capability === capability);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Find models supporting a task
|
|
226
|
+
*/
|
|
227
|
+
findByTask(taskType) {
|
|
228
|
+
return this.list().filter(m => m.supportsTask(taskType));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================
|
|
233
|
+
// Model Router
|
|
234
|
+
// ============================================================
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Routes tasks to appropriate models
|
|
238
|
+
*/
|
|
239
|
+
class ModelRouter {
|
|
240
|
+
constructor(options = {}) {
|
|
241
|
+
this.registry = options.registry || new ModelRegistry();
|
|
242
|
+
this.routingRules = new Map();
|
|
243
|
+
this.fallbackModel = options.fallbackModel || 'gpt-4o-mini';
|
|
244
|
+
this.costOptimize = options.costOptimize !== false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Add a custom routing rule
|
|
249
|
+
*/
|
|
250
|
+
addRule(condition, modelId) {
|
|
251
|
+
const ruleId = `rule_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
252
|
+
this.routingRules.set(ruleId, { condition, modelId });
|
|
253
|
+
return ruleId;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Remove a routing rule
|
|
258
|
+
*/
|
|
259
|
+
removeRule(ruleId) {
|
|
260
|
+
return this.routingRules.delete(ruleId);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Route a task to the best model
|
|
265
|
+
*/
|
|
266
|
+
route(task) {
|
|
267
|
+
const { taskType, complexity, tokens, preferences } = task;
|
|
268
|
+
|
|
269
|
+
// Check custom rules first
|
|
270
|
+
for (const [, rule] of this.routingRules) {
|
|
271
|
+
if (rule.condition(task)) {
|
|
272
|
+
const model = this.registry.get(rule.modelId);
|
|
273
|
+
if (model) return model;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check preferences
|
|
278
|
+
if (preferences?.modelId) {
|
|
279
|
+
const model = this.registry.get(preferences.modelId);
|
|
280
|
+
if (model && model.supportsTask(taskType)) {
|
|
281
|
+
return model;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Find suitable models
|
|
286
|
+
const candidates = this.registry.findByTask(taskType);
|
|
287
|
+
if (candidates.length === 0) {
|
|
288
|
+
return this.registry.get(this.fallbackModel);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Filter by context window
|
|
292
|
+
const filtered = tokens ? candidates.filter(m => m.contextWindow >= tokens) : candidates;
|
|
293
|
+
|
|
294
|
+
if (filtered.length === 0) {
|
|
295
|
+
// Return largest context window if none fit
|
|
296
|
+
return candidates.sort((a, b) => b.contextWindow - a.contextWindow)[0];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Sort by capability/cost
|
|
300
|
+
const sorted = filtered.sort((a, b) => {
|
|
301
|
+
// Match complexity to capability
|
|
302
|
+
const complexityOrder = { low: 0, medium: 1, high: 2, expert: 3 };
|
|
303
|
+
const capabilityOrder = {
|
|
304
|
+
[ModelCapability.BASIC]: 0,
|
|
305
|
+
[ModelCapability.STANDARD]: 1,
|
|
306
|
+
[ModelCapability.ADVANCED]: 2,
|
|
307
|
+
[ModelCapability.EXPERT]: 3,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const targetLevel = complexityOrder[complexity] ?? 1;
|
|
311
|
+
const aLevel = capabilityOrder[a.capability];
|
|
312
|
+
const bLevel = capabilityOrder[b.capability];
|
|
313
|
+
|
|
314
|
+
// Prefer closest match
|
|
315
|
+
const aDist = Math.abs(aLevel - targetLevel);
|
|
316
|
+
const bDist = Math.abs(bLevel - targetLevel);
|
|
317
|
+
|
|
318
|
+
if (aDist !== bDist) return aDist - bDist;
|
|
319
|
+
|
|
320
|
+
// If equal, prefer cheaper
|
|
321
|
+
if (this.costOptimize) {
|
|
322
|
+
return a.costPerInputToken - b.costPerInputToken;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return 0;
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return sorted[0];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Route multiple tasks
|
|
333
|
+
*/
|
|
334
|
+
routeMany(tasks) {
|
|
335
|
+
return tasks.map(task => ({
|
|
336
|
+
task,
|
|
337
|
+
model: this.route(task),
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================
|
|
343
|
+
// Context Window Manager
|
|
344
|
+
// ============================================================
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Manages context window for large inputs
|
|
348
|
+
*/
|
|
349
|
+
class ContextWindowManager {
|
|
350
|
+
constructor(options = {}) {
|
|
351
|
+
this.defaultChunkSize = options.chunkSize || 4000;
|
|
352
|
+
this.overlapTokens = options.overlap || 200;
|
|
353
|
+
this.tokensPerChar = options.tokensPerChar || 0.25;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Estimate tokens for text
|
|
358
|
+
*/
|
|
359
|
+
estimateTokens(text) {
|
|
360
|
+
if (!text) return 0;
|
|
361
|
+
return Math.ceil(text.length * this.tokensPerChar);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Check if text fits in context window
|
|
366
|
+
*/
|
|
367
|
+
fits(text, contextWindow) {
|
|
368
|
+
return this.estimateTokens(text) <= contextWindow;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Chunk text for processing
|
|
373
|
+
*/
|
|
374
|
+
chunk(text, maxTokens = this.defaultChunkSize) {
|
|
375
|
+
if (!text) return [];
|
|
376
|
+
|
|
377
|
+
const tokens = this.estimateTokens(text);
|
|
378
|
+
if (tokens <= maxTokens) {
|
|
379
|
+
return [{ text, tokens, index: 0, total: 1 }];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const chunks = [];
|
|
383
|
+
const charsPerChunk = Math.floor(maxTokens / this.tokensPerChar);
|
|
384
|
+
const overlapChars = Math.floor(this.overlapTokens / this.tokensPerChar);
|
|
385
|
+
|
|
386
|
+
let start = 0;
|
|
387
|
+
let index = 0;
|
|
388
|
+
|
|
389
|
+
while (start < text.length) {
|
|
390
|
+
const end = Math.min(start + charsPerChunk, text.length);
|
|
391
|
+
const chunkText = text.slice(start, end);
|
|
392
|
+
|
|
393
|
+
chunks.push({
|
|
394
|
+
text: chunkText,
|
|
395
|
+
tokens: this.estimateTokens(chunkText),
|
|
396
|
+
index,
|
|
397
|
+
startOffset: start,
|
|
398
|
+
endOffset: end,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
start = end - overlapChars;
|
|
402
|
+
if (start >= text.length - overlapChars) break;
|
|
403
|
+
index++;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Update total count
|
|
407
|
+
chunks.forEach(c => (c.total = chunks.length));
|
|
408
|
+
|
|
409
|
+
return chunks;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Smart chunk by semantic boundaries
|
|
414
|
+
*/
|
|
415
|
+
chunkSemantic(text, maxTokens = this.defaultChunkSize) {
|
|
416
|
+
if (!text) return [];
|
|
417
|
+
|
|
418
|
+
const tokens = this.estimateTokens(text);
|
|
419
|
+
if (tokens <= maxTokens) {
|
|
420
|
+
return [{ text, tokens, index: 0, total: 1 }];
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const chunks = [];
|
|
424
|
+
const separators = ['\n\n', '\n', '. ', '; ', ', ', ' '];
|
|
425
|
+
|
|
426
|
+
let remaining = text;
|
|
427
|
+
let index = 0;
|
|
428
|
+
let offset = 0;
|
|
429
|
+
|
|
430
|
+
while (remaining.length > 0) {
|
|
431
|
+
const targetChars = Math.floor(maxTokens / this.tokensPerChar);
|
|
432
|
+
|
|
433
|
+
if (this.estimateTokens(remaining) <= maxTokens) {
|
|
434
|
+
chunks.push({
|
|
435
|
+
text: remaining,
|
|
436
|
+
tokens: this.estimateTokens(remaining),
|
|
437
|
+
index,
|
|
438
|
+
startOffset: offset,
|
|
439
|
+
endOffset: offset + remaining.length,
|
|
440
|
+
});
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Find best split point
|
|
445
|
+
let splitIndex = targetChars;
|
|
446
|
+
for (const sep of separators) {
|
|
447
|
+
const lastSep = remaining.lastIndexOf(sep, targetChars);
|
|
448
|
+
if (lastSep > targetChars * 0.5) {
|
|
449
|
+
splitIndex = lastSep + sep.length;
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const chunkText = remaining.slice(0, splitIndex);
|
|
455
|
+
chunks.push({
|
|
456
|
+
text: chunkText,
|
|
457
|
+
tokens: this.estimateTokens(chunkText),
|
|
458
|
+
index,
|
|
459
|
+
startOffset: offset,
|
|
460
|
+
endOffset: offset + splitIndex,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
offset += splitIndex;
|
|
464
|
+
remaining = remaining.slice(splitIndex);
|
|
465
|
+
index++;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
chunks.forEach(c => (c.total = chunks.length));
|
|
469
|
+
return chunks;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Prioritize chunks by relevance
|
|
474
|
+
*/
|
|
475
|
+
prioritize(chunks, query, topK = 5) {
|
|
476
|
+
if (!query || chunks.length <= topK) return chunks;
|
|
477
|
+
|
|
478
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/));
|
|
479
|
+
|
|
480
|
+
const scored = chunks.map(chunk => {
|
|
481
|
+
const words = chunk.text.toLowerCase().split(/\s+/);
|
|
482
|
+
const matches = words.filter(w => queryWords.has(w)).length;
|
|
483
|
+
return { ...chunk, relevance: matches / words.length };
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
return scored.sort((a, b) => b.relevance - a.relevance).slice(0, topK);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ============================================================
|
|
491
|
+
// RAG (Retrieval Augmented Generation) Support
|
|
492
|
+
// ============================================================
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Simple in-memory vector store for code embeddings
|
|
496
|
+
*/
|
|
497
|
+
class CodeVectorStore {
|
|
498
|
+
constructor(options = {}) {
|
|
499
|
+
this.dimensions = options.dimensions || 1536;
|
|
500
|
+
this.documents = new Map();
|
|
501
|
+
this.embeddings = new Map();
|
|
502
|
+
this.metadata = new Map();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Add a document with embedding
|
|
507
|
+
*/
|
|
508
|
+
add(id, document, embedding, meta = {}) {
|
|
509
|
+
this.documents.set(id, document);
|
|
510
|
+
this.embeddings.set(id, embedding);
|
|
511
|
+
this.metadata.set(id, {
|
|
512
|
+
...meta,
|
|
513
|
+
addedAt: new Date().toISOString(),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Remove a document
|
|
519
|
+
*/
|
|
520
|
+
remove(id) {
|
|
521
|
+
this.documents.delete(id);
|
|
522
|
+
this.embeddings.delete(id);
|
|
523
|
+
this.metadata.delete(id);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get a document by ID
|
|
528
|
+
*/
|
|
529
|
+
get(id) {
|
|
530
|
+
return {
|
|
531
|
+
document: this.documents.get(id),
|
|
532
|
+
embedding: this.embeddings.get(id),
|
|
533
|
+
metadata: this.metadata.get(id),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Calculate cosine similarity
|
|
539
|
+
*/
|
|
540
|
+
_cosineSimilarity(a, b) {
|
|
541
|
+
if (!a || !b || a.length !== b.length) return 0;
|
|
542
|
+
|
|
543
|
+
let dotProduct = 0;
|
|
544
|
+
let normA = 0;
|
|
545
|
+
let normB = 0;
|
|
546
|
+
|
|
547
|
+
for (let i = 0; i < a.length; i++) {
|
|
548
|
+
dotProduct += a[i] * b[i];
|
|
549
|
+
normA += a[i] * a[i];
|
|
550
|
+
normB += b[i] * b[i];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
554
|
+
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Search for similar documents
|
|
559
|
+
*/
|
|
560
|
+
search(queryEmbedding, topK = 5, threshold = 0.7) {
|
|
561
|
+
const results = [];
|
|
562
|
+
|
|
563
|
+
for (const [id, embedding] of this.embeddings) {
|
|
564
|
+
const similarity = this._cosineSimilarity(queryEmbedding, embedding);
|
|
565
|
+
if (similarity >= threshold) {
|
|
566
|
+
results.push({
|
|
567
|
+
id,
|
|
568
|
+
document: this.documents.get(id),
|
|
569
|
+
metadata: this.metadata.get(id),
|
|
570
|
+
similarity,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
return results.sort((a, b) => b.similarity - a.similarity).slice(0, topK);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Get store statistics
|
|
580
|
+
*/
|
|
581
|
+
getStats() {
|
|
582
|
+
return {
|
|
583
|
+
documentCount: this.documents.size,
|
|
584
|
+
dimensions: this.dimensions,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Clear the store
|
|
590
|
+
*/
|
|
591
|
+
clear() {
|
|
592
|
+
this.documents.clear();
|
|
593
|
+
this.embeddings.clear();
|
|
594
|
+
this.metadata.clear();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* RAG Pipeline for code knowledge retrieval
|
|
600
|
+
*/
|
|
601
|
+
class RAGPipeline {
|
|
602
|
+
constructor(options = {}) {
|
|
603
|
+
this.vectorStore = options.vectorStore || new CodeVectorStore();
|
|
604
|
+
this.contextManager = options.contextManager || new ContextWindowManager();
|
|
605
|
+
this.embeddingProvider = options.embeddingProvider || null;
|
|
606
|
+
this.topK = options.topK || 5;
|
|
607
|
+
this.threshold = options.threshold || 0.7;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Index code documents
|
|
612
|
+
*/
|
|
613
|
+
async index(documents, embeddingFn) {
|
|
614
|
+
const indexed = [];
|
|
615
|
+
|
|
616
|
+
for (const doc of documents) {
|
|
617
|
+
const embedding = embeddingFn
|
|
618
|
+
? await embeddingFn(doc.content)
|
|
619
|
+
: this._mockEmbedding(doc.content);
|
|
620
|
+
|
|
621
|
+
this.vectorStore.add(doc.id, doc.content, embedding, {
|
|
622
|
+
path: doc.path,
|
|
623
|
+
type: doc.type,
|
|
624
|
+
language: doc.language,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
indexed.push(doc.id);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return { indexed: indexed.length };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Generate mock embedding (for testing)
|
|
635
|
+
*/
|
|
636
|
+
_mockEmbedding(text) {
|
|
637
|
+
const embedding = new Array(this.vectorStore.dimensions).fill(0);
|
|
638
|
+
const words = text.toLowerCase().split(/\s+/);
|
|
639
|
+
|
|
640
|
+
for (let i = 0; i < words.length; i++) {
|
|
641
|
+
const hash = this._simpleHash(words[i]);
|
|
642
|
+
const idx = Math.abs(hash) % this.vectorStore.dimensions;
|
|
643
|
+
embedding[idx] += 1;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Normalize
|
|
647
|
+
const norm = Math.sqrt(embedding.reduce((sum, v) => sum + v * v, 0));
|
|
648
|
+
if (norm > 0) {
|
|
649
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
650
|
+
embedding[i] /= norm;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return embedding;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
_simpleHash(str) {
|
|
658
|
+
let hash = 0;
|
|
659
|
+
for (let i = 0; i < str.length; i++) {
|
|
660
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
661
|
+
hash |= 0;
|
|
662
|
+
}
|
|
663
|
+
return hash;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Retrieve relevant context for a query
|
|
668
|
+
*/
|
|
669
|
+
async retrieve(query, embeddingFn) {
|
|
670
|
+
const queryEmbedding = embeddingFn ? await embeddingFn(query) : this._mockEmbedding(query);
|
|
671
|
+
|
|
672
|
+
return this.vectorStore.search(queryEmbedding, this.topK, this.threshold);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Augment prompt with retrieved context
|
|
677
|
+
*/
|
|
678
|
+
async augment(query, prompt, embeddingFn) {
|
|
679
|
+
const retrieved = await this.retrieve(query, embeddingFn);
|
|
680
|
+
|
|
681
|
+
if (retrieved.length === 0) {
|
|
682
|
+
return { prompt, context: [] };
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const contextParts = retrieved.map(
|
|
686
|
+
r =>
|
|
687
|
+
`### ${r.metadata?.path || r.id}\n\`\`\`${r.metadata?.language || ''}\n${r.document}\n\`\`\``
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
const augmentedPrompt = `## Relevant Code Context\n\n${contextParts.join('\n\n')}\n\n---\n\n${prompt}`;
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
prompt: augmentedPrompt,
|
|
694
|
+
context: retrieved,
|
|
695
|
+
tokens: this.contextManager.estimateTokens(augmentedPrompt),
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Get pipeline stats
|
|
701
|
+
*/
|
|
702
|
+
getStats() {
|
|
703
|
+
return {
|
|
704
|
+
store: this.vectorStore.getStats(),
|
|
705
|
+
topK: this.topK,
|
|
706
|
+
threshold: this.threshold,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// ============================================================
|
|
712
|
+
// AI Session Manager
|
|
713
|
+
// ============================================================
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Manages AI interaction sessions
|
|
717
|
+
*/
|
|
718
|
+
class AISessionManager {
|
|
719
|
+
constructor(options = {}) {
|
|
720
|
+
this.sessions = new Map();
|
|
721
|
+
this.maxHistory = options.maxHistory || 50;
|
|
722
|
+
this.contextManager = options.contextManager || new ContextWindowManager();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Create a new session
|
|
727
|
+
*/
|
|
728
|
+
create(options = {}) {
|
|
729
|
+
const id = options.id || `session_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
730
|
+
|
|
731
|
+
const session = {
|
|
732
|
+
id,
|
|
733
|
+
model: options.model || null,
|
|
734
|
+
history: [],
|
|
735
|
+
context: options.context || {},
|
|
736
|
+
createdAt: new Date().toISOString(),
|
|
737
|
+
updatedAt: new Date().toISOString(),
|
|
738
|
+
totalTokens: 0,
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
this.sessions.set(id, session);
|
|
742
|
+
return session;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Get a session
|
|
747
|
+
*/
|
|
748
|
+
get(sessionId) {
|
|
749
|
+
return this.sessions.get(sessionId);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Add message to session history
|
|
754
|
+
*/
|
|
755
|
+
addMessage(sessionId, role, content, tokens = 0) {
|
|
756
|
+
const session = this.sessions.get(sessionId);
|
|
757
|
+
if (!session) return null;
|
|
758
|
+
|
|
759
|
+
session.history.push({
|
|
760
|
+
role,
|
|
761
|
+
content,
|
|
762
|
+
tokens,
|
|
763
|
+
timestamp: new Date().toISOString(),
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
session.totalTokens += tokens;
|
|
767
|
+
session.updatedAt = new Date().toISOString();
|
|
768
|
+
|
|
769
|
+
// Trim history if needed
|
|
770
|
+
while (session.history.length > this.maxHistory) {
|
|
771
|
+
const removed = session.history.shift();
|
|
772
|
+
session.totalTokens -= removed.tokens;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return session;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Get session history formatted for API
|
|
780
|
+
*/
|
|
781
|
+
getHistory(sessionId, maxTokens) {
|
|
782
|
+
const session = this.sessions.get(sessionId);
|
|
783
|
+
if (!session) return [];
|
|
784
|
+
|
|
785
|
+
if (!maxTokens) return session.history;
|
|
786
|
+
|
|
787
|
+
// Return as many recent messages as fit
|
|
788
|
+
const messages = [];
|
|
789
|
+
let tokens = 0;
|
|
790
|
+
|
|
791
|
+
for (let i = session.history.length - 1; i >= 0; i--) {
|
|
792
|
+
const msg = session.history[i];
|
|
793
|
+
if (tokens + msg.tokens > maxTokens) break;
|
|
794
|
+
messages.unshift(msg);
|
|
795
|
+
tokens += msg.tokens;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return messages;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Clear session history
|
|
803
|
+
*/
|
|
804
|
+
clearHistory(sessionId) {
|
|
805
|
+
const session = this.sessions.get(sessionId);
|
|
806
|
+
if (!session) return false;
|
|
807
|
+
|
|
808
|
+
session.history = [];
|
|
809
|
+
session.totalTokens = 0;
|
|
810
|
+
session.updatedAt = new Date().toISOString();
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Delete a session
|
|
816
|
+
*/
|
|
817
|
+
delete(sessionId) {
|
|
818
|
+
return this.sessions.delete(sessionId);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* List all sessions
|
|
823
|
+
*/
|
|
824
|
+
list() {
|
|
825
|
+
return Array.from(this.sessions.values()).map(s => ({
|
|
826
|
+
id: s.id,
|
|
827
|
+
messageCount: s.history.length,
|
|
828
|
+
totalTokens: s.totalTokens,
|
|
829
|
+
createdAt: s.createdAt,
|
|
830
|
+
updatedAt: s.updatedAt,
|
|
831
|
+
}));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ============================================================
|
|
836
|
+
// Default Instances
|
|
837
|
+
// ============================================================
|
|
838
|
+
|
|
839
|
+
const defaultModelRegistry = new ModelRegistry();
|
|
840
|
+
const defaultModelRouter = new ModelRouter({ registry: defaultModelRegistry });
|
|
841
|
+
const defaultContextManager = new ContextWindowManager();
|
|
842
|
+
const defaultVectorStore = new CodeVectorStore();
|
|
843
|
+
const defaultRAGPipeline = new RAGPipeline({
|
|
844
|
+
vectorStore: defaultVectorStore,
|
|
845
|
+
contextManager: defaultContextManager,
|
|
846
|
+
});
|
|
847
|
+
const defaultSessionManager = new AISessionManager({
|
|
848
|
+
contextManager: defaultContextManager,
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// ============================================================
|
|
852
|
+
// Exports
|
|
853
|
+
// ============================================================
|
|
854
|
+
|
|
855
|
+
module.exports = {
|
|
856
|
+
// Constants
|
|
857
|
+
AIProvider,
|
|
858
|
+
TaskType,
|
|
859
|
+
ModelCapability,
|
|
860
|
+
|
|
861
|
+
// Classes
|
|
862
|
+
ModelConfig,
|
|
863
|
+
ModelRegistry,
|
|
864
|
+
ModelRouter,
|
|
865
|
+
ContextWindowManager,
|
|
866
|
+
CodeVectorStore,
|
|
867
|
+
RAGPipeline,
|
|
868
|
+
AISessionManager,
|
|
869
|
+
|
|
870
|
+
// Default instances
|
|
871
|
+
defaultModelRegistry,
|
|
872
|
+
defaultModelRouter,
|
|
873
|
+
defaultContextManager,
|
|
874
|
+
defaultVectorStore,
|
|
875
|
+
defaultRAGPipeline,
|
|
876
|
+
defaultSessionManager,
|
|
877
|
+
};
|