adaptive-memory-multi-model-router 1.3.1 → 1.4.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,224 @@
1
+ /**
2
+ * Obsidian Vault Integration
3
+ *
4
+ * Stores routing decisions and context as markdown files
5
+ * compatible with Obsidian note-taking app.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+
11
+ export interface VaultConfig {
12
+ path: string;
13
+ autoSave: boolean;
14
+ maxFileAge: number; // days
15
+ }
16
+
17
+ export interface RoutingDecision {
18
+ id: string;
19
+ timestamp: number;
20
+ prompt: string;
21
+ context: any;
22
+ selectedProvider: string;
23
+ selectedModel: string;
24
+ reasoning: string;
25
+ cost: number;
26
+ latency: number;
27
+ }
28
+
29
+ export class ObsidianVault {
30
+ private config: VaultConfig;
31
+ private decisions: RoutingDecision[];
32
+
33
+ constructor(config: Partial<VaultConfig> = {}) {
34
+ this.config = {
35
+ path: config.path || './vault',
36
+ autoSave: config.autoSave !== false,
37
+ maxFileAge: config.maxFileAge || 30
38
+ };
39
+ this.decisions = [];
40
+ this.ensureDirectory();
41
+ }
42
+
43
+ private ensureDirectory() {
44
+ if (!fs.existsSync(this.config.path)) {
45
+ fs.mkdirSync(this.config.path, { recursive: true });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Save a routing decision
51
+ */
52
+ async saveDecision(decision: RoutingDecision): Promise<string> {
53
+ this.decisions.push(decision);
54
+
55
+ const filename = `routing-decision-${decision.id}.md`;
56
+ const filepath = path.join(this.config.path, filename);
57
+ const content = this.formatDecisionAsMarkdown(decision);
58
+
59
+ fs.writeFileSync(filepath, content, 'utf-8');
60
+
61
+ // Update index
62
+ await this.updateIndex();
63
+
64
+ return filepath;
65
+ }
66
+
67
+ /**
68
+ * Format decision as markdown
69
+ */
70
+ private formatDecisionAsMarkdown(decision: RoutingDecision): string {
71
+ const date = new Date(decision.timestamp).toISOString();
72
+
73
+ return `# Routing Decision ${decision.id}
74
+
75
+ **Date:** ${date}
76
+ **Prompt:** ${this.escapeMarkdown(decision.prompt.substring(0, 200))}
77
+
78
+ ## Decision
79
+
80
+ | Field | Value |
81
+ |-------|-------|
82
+ | Provider | ${decision.selectedProvider} |
83
+ | Model | ${decision.selectedModel} |
84
+ | Reasoning | ${this.escapeMarkdown(decision.reasoning)} |
85
+ | Cost | $${decision.cost.toFixed(4)} |
86
+ | Latency | ${decision.latency}ms |
87
+
88
+ ## Context
89
+
90
+ \`\`\`json
91
+ ${JSON.stringify(decision.context, null, 2)}
92
+ \`\`\`
93
+
94
+ ## Prompt (Full)
95
+
96
+ ${decision.prompt}
97
+
98
+ ---
99
+ *Generated by A3M Router*
100
+ `;
101
+ }
102
+
103
+ /**
104
+ * Escape markdown special characters
105
+ */
106
+ private escapeMarkdown(text: string): string {
107
+ return text
108
+ .replace(/\*/g, '\\*')
109
+ .replace(/#/g, '\\#')
110
+ .replace(/\|/g, '\\|')
111
+ .replace(/`/g, '\\`');
112
+ }
113
+
114
+ /**
115
+ * Update the vault index
116
+ */
117
+ private async updateIndex() {
118
+ const indexPath = path.join(this.config.path, 'routing-index.md');
119
+
120
+ const lines = [
121
+ '# Routing Decisions Index',
122
+ '',
123
+ `Last updated: ${new Date().toISOString()}`,
124
+ '',
125
+ `Total decisions: ${this.decisions.length}`,
126
+ '',
127
+ '## Recent Decisions',
128
+ ''
129
+ ];
130
+
131
+ for (const decision of this.decisions.slice(-20).reverse()) {
132
+ const date = new Date(decision.timestamp).toLocaleDateString();
133
+ lines.push(`- [${decision.id}](./routing-decision-${decision.id}.md) - ${date} - ${decision.selectedProvider}/${decision.selectedModel}`);
134
+ }
135
+
136
+ fs.writeFileSync(indexPath, lines.join('\n'), 'utf-8');
137
+ }
138
+
139
+ /**
140
+ * Get recent decisions
141
+ */
142
+ getRecentDecisions(count = 10): RoutingDecision[] {
143
+ return this.decisions.slice(-count).reverse();
144
+ }
145
+
146
+ /**
147
+ * Search decisions
148
+ */
149
+ searchDecisions(query: string): RoutingDecision[] {
150
+ const queryLower = query.toLowerCase();
151
+ return this.decisions.filter(d =>
152
+ d.prompt.toLowerCase().includes(queryLower) ||
153
+ d.selectedProvider.toLowerCase().includes(queryLower) ||
154
+ d.selectedModel.toLowerCase().includes(queryLower)
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Export all decisions
160
+ */
161
+ async exportAll(filepath: string) {
162
+ const content = this.decisions.map(d =>
163
+ this.formatDecisionAsMarkdown(d)
164
+ ).join('\n\n---\n\n');
165
+
166
+ fs.writeFileSync(filepath, content, 'utf-8');
167
+ }
168
+
169
+ /**
170
+ * Get vault statistics
171
+ */
172
+ getStats() {
173
+ return {
174
+ totalDecisions: this.decisions.length,
175
+ vaultPath: this.config.path,
176
+ fileCount: fs.readdirSync(this.config.path).filter(f => f.endsWith('.md')).length,
177
+ totalSize: this.getDirectorySize(this.config.path)
178
+ };
179
+ }
180
+
181
+ private getDirectorySize(dir: string): number {
182
+ let size = 0;
183
+ const files = fs.readdirSync(dir);
184
+
185
+ for (const file of files) {
186
+ const filepath = path.join(dir, file);
187
+ const stat = fs.statSync(filepath);
188
+
189
+ if (stat.isDirectory()) {
190
+ size += this.getDirectorySize(filepath);
191
+ } else {
192
+ size += stat.size;
193
+ }
194
+ }
195
+
196
+ return size;
197
+ }
198
+
199
+ /**
200
+ * Clean old files
201
+ */
202
+ async cleanOldFiles() {
203
+ const maxAge = this.config.maxFileAge * 24 * 60 * 60 * 1000;
204
+ const now = Date.now();
205
+ const files = fs.readdirSync(this.config.path);
206
+ let cleaned = 0;
207
+
208
+ for (const file of files) {
209
+ if (!file.endsWith('.md') || file === 'routing-index.md') continue;
210
+
211
+ const filepath = path.join(this.config.path, file);
212
+ const stat = fs.statSync(filepath);
213
+
214
+ if (now - stat.mtimeMs > maxAge) {
215
+ fs.unlinkSync(filepath);
216
+ cleaned++;
217
+ }
218
+ }
219
+
220
+ return cleaned;
221
+ }
222
+ }
223
+
224
+ export default ObsidianVault;