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.
@@ -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
+ };