milens 0.6.1 → 0.6.3
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.md +157 -14
- package/dist/analyzer/config.d.ts.map +1 -1
- package/dist/analyzer/config.js +33 -1
- package/dist/analyzer/config.js.map +1 -1
- package/dist/analyzer/engine.d.ts +1 -0
- package/dist/analyzer/engine.d.ts.map +1 -1
- package/dist/analyzer/engine.js +27 -8
- package/dist/analyzer/engine.js.map +1 -1
- package/dist/analyzer/review.d.ts +23 -0
- package/dist/analyzer/review.d.ts.map +1 -0
- package/dist/analyzer/review.js +143 -0
- package/dist/analyzer/review.js.map +1 -0
- package/dist/analyzer/testplan.d.ts +59 -0
- package/dist/analyzer/testplan.d.ts.map +1 -0
- package/dist/analyzer/testplan.js +218 -0
- package/dist/analyzer/testplan.js.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/gateway/analyzer.d.ts +6 -0
- package/dist/gateway/analyzer.d.ts.map +1 -0
- package/dist/gateway/analyzer.js +218 -0
- package/dist/gateway/analyzer.js.map +1 -0
- package/dist/gateway/cache.d.ts +35 -0
- package/dist/gateway/cache.d.ts.map +1 -0
- package/dist/gateway/cache.js +175 -0
- package/dist/gateway/cache.js.map +1 -0
- package/dist/gateway/config.d.ts +10 -0
- package/dist/gateway/config.d.ts.map +1 -0
- package/dist/gateway/config.js +167 -0
- package/dist/gateway/config.js.map +1 -0
- package/dist/gateway/context-memory.d.ts +68 -0
- package/dist/gateway/context-memory.d.ts.map +1 -0
- package/dist/gateway/context-memory.js +157 -0
- package/dist/gateway/context-memory.js.map +1 -0
- package/dist/gateway/observability.d.ts +83 -0
- package/dist/gateway/observability.d.ts.map +1 -0
- package/dist/gateway/observability.js +152 -0
- package/dist/gateway/observability.js.map +1 -0
- package/dist/gateway/privacy.d.ts +27 -0
- package/dist/gateway/privacy.d.ts.map +1 -0
- package/dist/gateway/privacy.js +139 -0
- package/dist/gateway/privacy.js.map +1 -0
- package/dist/gateway/providers.d.ts +66 -0
- package/dist/gateway/providers.d.ts.map +1 -0
- package/dist/gateway/providers.js +377 -0
- package/dist/gateway/providers.js.map +1 -0
- package/dist/gateway/router.d.ts +18 -0
- package/dist/gateway/router.d.ts.map +1 -0
- package/dist/gateway/router.js +102 -0
- package/dist/gateway/router.js.map +1 -0
- package/dist/gateway/server.d.ts +20 -0
- package/dist/gateway/server.d.ts.map +1 -0
- package/dist/gateway/server.js +387 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/gateway/translator.d.ts +19 -0
- package/dist/gateway/translator.d.ts.map +1 -0
- package/dist/gateway/translator.js +340 -0
- package/dist/gateway/translator.js.map +1 -0
- package/dist/gateway/types.d.ts +215 -0
- package/dist/gateway/types.d.ts.map +1 -0
- package/dist/gateway/types.js +3 -0
- package/dist/gateway/types.js.map +1 -0
- package/dist/parser/extract.d.ts +1 -0
- package/dist/parser/extract.d.ts.map +1 -1
- package/dist/parser/extract.js +8 -0
- package/dist/parser/extract.js.map +1 -1
- package/dist/parser/lang-go.d.ts.map +1 -1
- package/dist/parser/lang-go.js +41 -5
- package/dist/parser/lang-go.js.map +1 -1
- package/dist/parser/lang-java.d.ts.map +1 -1
- package/dist/parser/lang-java.js +1 -0
- package/dist/parser/lang-java.js.map +1 -1
- package/dist/parser/lang-js.d.ts.map +1 -1
- package/dist/parser/lang-js.js +23 -1
- package/dist/parser/lang-js.js.map +1 -1
- package/dist/parser/lang-py.d.ts.map +1 -1
- package/dist/parser/lang-py.js +22 -0
- package/dist/parser/lang-py.js.map +1 -1
- package/dist/parser/lang-ruby.d.ts.map +1 -1
- package/dist/parser/lang-ruby.js +1 -0
- package/dist/parser/lang-ruby.js.map +1 -1
- package/dist/parser/lang-ts.d.ts.map +1 -1
- package/dist/parser/lang-ts.js +62 -1
- package/dist/parser/lang-ts.js.map +1 -1
- package/dist/server/mcp.d.ts.map +1 -1
- package/dist/server/mcp.js +615 -106
- package/dist/server/mcp.js.map +1 -1
- package/dist/skills.js +32 -0
- package/dist/skills.js.map +1 -1
- package/dist/store/db.d.ts +44 -0
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +145 -25
- package/dist/store/db.js.map +1 -1
- package/dist/store/gateway-schema.sql +53 -0
- package/dist/store/schema.sql +33 -0
- package/dist/store/vectors.d.ts +65 -0
- package/dist/store/vectors.d.ts.map +1 -0
- package/dist/store/vectors.js +212 -0
- package/dist/store/vectors.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +9 -0
- package/dist/utils.js.map +1 -0
- package/docs/diagram2.svg +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// ── Message analyzer — detect code symbols, files, and intent from chat messages ──
|
|
2
|
+
// ── Intent classification ──
|
|
3
|
+
const INTENT_PATTERNS = [
|
|
4
|
+
{ pattern: /\b(refactor|restructure|rewrite|reorganize|move|extract|split|merge)\b/i, intent: 'refactor' },
|
|
5
|
+
{ pattern: /\b(test|spec|unit test|integration test|e2e|coverage|mock|stub|assert)\b/i, intent: 'test' },
|
|
6
|
+
{ pattern: /\b(debug|fix|bug|error|issue|crash|broken|wrong|fail|exception|trace)\b/i, intent: 'debug' },
|
|
7
|
+
{ pattern: /\b(review|audit|check|examine|inspect|assess|evaluate|analyze)\b/i, intent: 'review' },
|
|
8
|
+
{ pattern: /\b(explain|describe|what does|how does|why does|understand|walk through)\b/i, intent: 'explain' },
|
|
9
|
+
{ pattern: /\b(docstring|jsdoc|comment|document|annotation|describe the|add doc)\b/i, intent: 'docstring' },
|
|
10
|
+
{ pattern: /\b(generate|create|write|implement|add|build|make|scaffold|new)\b/i, intent: 'generate' },
|
|
11
|
+
];
|
|
12
|
+
// ── File path detection ──
|
|
13
|
+
// Match common source file paths (e.g., src/auth.ts, ./models/user.go, path/to/file.py)
|
|
14
|
+
const FILE_PATH_RE = /(?:^|\s|`|'|"|,|\()([.\/]*(?:[\w-]+\/)+[\w.-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|php|rb|vue|html|css|md|sql|json|yaml|yml|toml|sh|c|cpp|h|hpp|cs|swift|kt))\b/gi;
|
|
15
|
+
// ── Symbol name detection ──
|
|
16
|
+
// camelCase, PascalCase, snake_case identifiers in backticks or after common keywords
|
|
17
|
+
const BACKTICK_SYMBOL_RE = /`([A-Za-z_][\w.]*)`/g;
|
|
18
|
+
const KEYWORD_SYMBOL_RE = /(?:function|class|method|interface|type|enum|struct|const|let|var|def|fn|func|mod|trait|impl)\s+([A-Za-z_]\w*)/gi;
|
|
19
|
+
// ── Code block extraction ──
|
|
20
|
+
const CODE_BLOCK_RE = /```[\w]*\n([\s\S]*?)```/g;
|
|
21
|
+
// ── Main analysis function ──
|
|
22
|
+
export function analyzeMessages(messages) {
|
|
23
|
+
const files = new Set();
|
|
24
|
+
const symbolNames = new Set();
|
|
25
|
+
const codeBlocks = [];
|
|
26
|
+
let intent = 'unknown';
|
|
27
|
+
// Only analyze user messages (recent ones have more weight)
|
|
28
|
+
const userMessages = messages
|
|
29
|
+
.filter(m => m.role === 'user')
|
|
30
|
+
.slice(-5); // Last 5 user messages
|
|
31
|
+
for (const msg of userMessages) {
|
|
32
|
+
const text = typeof msg.content === 'string'
|
|
33
|
+
? msg.content
|
|
34
|
+
: Array.isArray(msg.content)
|
|
35
|
+
? msg.content.filter(p => p.type === 'text').map(p => p.text ?? '').join('\n')
|
|
36
|
+
: '';
|
|
37
|
+
if (!text)
|
|
38
|
+
continue;
|
|
39
|
+
// Detect intent (first match wins, later messages override)
|
|
40
|
+
for (const { pattern, intent: i } of INTENT_PATTERNS) {
|
|
41
|
+
if (pattern.test(text)) {
|
|
42
|
+
intent = i;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Detect file paths
|
|
47
|
+
for (const match of text.matchAll(FILE_PATH_RE)) {
|
|
48
|
+
files.add(match[1].replace(/^\.\//, ''));
|
|
49
|
+
}
|
|
50
|
+
// Detect symbol names in backticks
|
|
51
|
+
for (const match of text.matchAll(BACKTICK_SYMBOL_RE)) {
|
|
52
|
+
const name = match[1];
|
|
53
|
+
// Filter out common non-symbol words and file paths
|
|
54
|
+
if (name.length > 1 && !name.includes('/') && !name.includes('.')) {
|
|
55
|
+
symbolNames.add(name);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Detect symbol names after keywords
|
|
59
|
+
for (const match of text.matchAll(KEYWORD_SYMBOL_RE)) {
|
|
60
|
+
symbolNames.add(match[1]);
|
|
61
|
+
}
|
|
62
|
+
// Extract code blocks
|
|
63
|
+
for (const match of text.matchAll(CODE_BLOCK_RE)) {
|
|
64
|
+
codeBlocks.push(match[1]);
|
|
65
|
+
// Also extract function/class names from code blocks
|
|
66
|
+
for (const km of match[1].matchAll(KEYWORD_SYMBOL_RE)) {
|
|
67
|
+
symbolNames.add(km[1]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
files: [...files],
|
|
73
|
+
symbolNames: [...symbolNames],
|
|
74
|
+
symbols: [], // Populated by lookupSymbols
|
|
75
|
+
intent,
|
|
76
|
+
complexity: 0, // Computed after symbol lookup
|
|
77
|
+
codeBlocks,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ── Symbol lookup — enrich analysis with milens data ──
|
|
81
|
+
export function lookupSymbols(analysis, db) {
|
|
82
|
+
const symbols = [];
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
// Look up each detected symbol name
|
|
85
|
+
for (const name of analysis.symbolNames) {
|
|
86
|
+
const found = db.findSymbolByName(name);
|
|
87
|
+
for (const sym of found) {
|
|
88
|
+
if (!seen.has(sym.id)) {
|
|
89
|
+
seen.add(sym.id);
|
|
90
|
+
symbols.push(sym);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Look up symbols from detected file paths
|
|
95
|
+
for (const file of analysis.files) {
|
|
96
|
+
const fileSymbols = db.getSymbolsByFile(file);
|
|
97
|
+
for (const sym of fileSymbols) {
|
|
98
|
+
if (!seen.has(sym.id)) {
|
|
99
|
+
seen.add(sym.id);
|
|
100
|
+
symbols.push(sym);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Compute complexity based on symbol roles and dependencies
|
|
105
|
+
let complexity = 0;
|
|
106
|
+
for (const sym of symbols) {
|
|
107
|
+
const upstream = db.findUpstream(sym.id, 1);
|
|
108
|
+
const dependentCount = upstream.length;
|
|
109
|
+
switch (sym.role) {
|
|
110
|
+
case 'entrypoint':
|
|
111
|
+
complexity += 10;
|
|
112
|
+
break;
|
|
113
|
+
case 'hub':
|
|
114
|
+
complexity += Math.max(dependentCount, 5);
|
|
115
|
+
break;
|
|
116
|
+
case 'utility':
|
|
117
|
+
complexity += 2;
|
|
118
|
+
break;
|
|
119
|
+
case 'leaf':
|
|
120
|
+
complexity += 1;
|
|
121
|
+
break;
|
|
122
|
+
case 'datatype':
|
|
123
|
+
complexity += 1;
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
complexity += Math.min(dependentCount + 1, 8);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Apply intent multiplier
|
|
131
|
+
const intentWeights = {
|
|
132
|
+
docstring: 0.3,
|
|
133
|
+
explain: 0.3,
|
|
134
|
+
review: 0.5,
|
|
135
|
+
test: 0.7,
|
|
136
|
+
generate: 0.8,
|
|
137
|
+
debug: 1.2,
|
|
138
|
+
refactor: 1.5,
|
|
139
|
+
unknown: 1.0,
|
|
140
|
+
};
|
|
141
|
+
complexity = Math.round(complexity * (intentWeights[analysis.intent] ?? 1.0));
|
|
142
|
+
return {
|
|
143
|
+
...analysis,
|
|
144
|
+
symbols,
|
|
145
|
+
complexity,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// ── Context injection builder ──
|
|
149
|
+
export function buildContextInjection(analysis, db, maxTokens = 2000) {
|
|
150
|
+
if (!analysis.symbols.length)
|
|
151
|
+
return undefined;
|
|
152
|
+
const sections = [];
|
|
153
|
+
let estimatedTokens = 0;
|
|
154
|
+
const TOKEN_PER_CHAR = 0.25; // Rough: 1 token ≈ 4 chars
|
|
155
|
+
// 1. Blast radius warnings (highest priority)
|
|
156
|
+
for (const sym of analysis.symbols) {
|
|
157
|
+
if (estimatedTokens > maxTokens * 0.8)
|
|
158
|
+
break;
|
|
159
|
+
const upstream = db.findUpstream(sym.id, 1);
|
|
160
|
+
if (upstream.length > 5) {
|
|
161
|
+
const warning = `⚠ ${sym.name} [${sym.kind}] has ${upstream.length} direct dependents. Changes may affect: ${upstream.slice(0, 8).map(u => u.symbol.name).join(', ')}${upstream.length > 8 ? '...' : ''}`;
|
|
162
|
+
sections.push(warning);
|
|
163
|
+
estimatedTokens += warning.length * TOKEN_PER_CHAR;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 2. Direct callers
|
|
167
|
+
for (const sym of analysis.symbols.slice(0, 5)) {
|
|
168
|
+
if (estimatedTokens > maxTokens * 0.9)
|
|
169
|
+
break;
|
|
170
|
+
const incoming = db.getIncomingLinks(sym.id);
|
|
171
|
+
const callers = incoming.filter(l => l.type === 'calls' || l.type === 'imports');
|
|
172
|
+
if (callers.length > 0) {
|
|
173
|
+
const callerNames = callers.slice(0, 6).map(l => {
|
|
174
|
+
const from = db.findSymbolById(l.fromId);
|
|
175
|
+
return from ? `${from.name} (${from.filePath})` : l.fromId;
|
|
176
|
+
});
|
|
177
|
+
const line = `${sym.name} is called/imported by: ${callerNames.join(', ')}`;
|
|
178
|
+
sections.push(line);
|
|
179
|
+
estimatedTokens += line.length * TOKEN_PER_CHAR;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 3. Dependencies (what the symbol calls/imports)
|
|
183
|
+
for (const sym of analysis.symbols.slice(0, 3)) {
|
|
184
|
+
if (estimatedTokens > maxTokens)
|
|
185
|
+
break;
|
|
186
|
+
const outgoing = db.getOutgoingLinks(sym.id);
|
|
187
|
+
const deps = outgoing.filter(l => l.type === 'calls' || l.type === 'imports');
|
|
188
|
+
if (deps.length > 0) {
|
|
189
|
+
const depNames = deps.slice(0, 6).map(l => {
|
|
190
|
+
const to = db.findSymbolById(l.toId);
|
|
191
|
+
return to ? to.name : l.toId;
|
|
192
|
+
});
|
|
193
|
+
const line = `${sym.name} depends on: ${depNames.join(', ')}`;
|
|
194
|
+
sections.push(line);
|
|
195
|
+
estimatedTokens += line.length * TOKEN_PER_CHAR;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// 4. File outline (sibling symbols)
|
|
199
|
+
if (analysis.files.length > 0 && estimatedTokens < maxTokens * 0.9) {
|
|
200
|
+
for (const file of analysis.files.slice(0, 2)) {
|
|
201
|
+
const fileSymbols = db.getSymbolsByFile(file);
|
|
202
|
+
if (fileSymbols.length > 0) {
|
|
203
|
+
const outline = fileSymbols
|
|
204
|
+
.filter(s => !s.parentId) // Top-level only
|
|
205
|
+
.slice(0, 10)
|
|
206
|
+
.map(s => ` ${s.exported ? '▸' : '▹'} ${s.name} [${s.kind}]${s.signature ? ` — ${s.signature}` : ''}`)
|
|
207
|
+
.join('\n');
|
|
208
|
+
const section = `File ${file}:\n${outline}`;
|
|
209
|
+
sections.push(section);
|
|
210
|
+
estimatedTokens += section.length * TOKEN_PER_CHAR;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!sections.length)
|
|
215
|
+
return undefined;
|
|
216
|
+
return `[milens code context]\n${sections.join('\n\n')}`;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/gateway/analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ChatRequest, MessageAnalysis, CacheEntry, CacheConfig } from './types.js';
|
|
2
|
+
import type { CodeSymbol } from '../types.js';
|
|
3
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
4
|
+
export declare class SemanticCache {
|
|
5
|
+
private rawDb;
|
|
6
|
+
private config;
|
|
7
|
+
private stmts;
|
|
8
|
+
constructor(rawDb: BetterSqlite3.Database, config: CacheConfig);
|
|
9
|
+
private prepareStatements;
|
|
10
|
+
/** Look up a cached response by AST hash + request hash */
|
|
11
|
+
lookup(req: ChatRequest, analysis?: MessageAnalysis): CacheEntry | null;
|
|
12
|
+
/** Store a response in the cache */
|
|
13
|
+
store(req: ChatRequest, analysis: MessageAnalysis, response: string, meta: {
|
|
14
|
+
model: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
tokensIn: number;
|
|
17
|
+
tokensOut: number;
|
|
18
|
+
}): void;
|
|
19
|
+
/** Compute AST hash — captures code structure, survives renames/reformats */
|
|
20
|
+
computeASTHash(symbols: CodeSymbol[]): string;
|
|
21
|
+
/** Compute request hash — normalized version of the prompt */
|
|
22
|
+
private computeRequestHash;
|
|
23
|
+
/** Evict expired entries + enforce max capacity */
|
|
24
|
+
private ensureCapacity;
|
|
25
|
+
/** Invalidate cache entries related to changed files */
|
|
26
|
+
invalidateForFiles(filePaths: string[]): number;
|
|
27
|
+
/** Get cache statistics */
|
|
28
|
+
getStats(): {
|
|
29
|
+
totalEntries: number;
|
|
30
|
+
totalHits: number;
|
|
31
|
+
totalTokensCached: number;
|
|
32
|
+
totalCostSaved: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/gateway/cache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,aAAa,MAAM,gBAAgB,CAAC;AAEhD,qBAAa,aAAa;IAItB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,MAAM;IAJhB,OAAO,CAAC,KAAK,CAAiD;gBAGpD,KAAK,EAAE,aAAa,CAAC,QAAQ,EAC7B,MAAM,EAAE,WAAW;IAK7B,OAAO,CAAC,iBAAiB;IAoDzB,2DAA2D;IAC3D,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,UAAU,GAAG,IAAI;IA2BvE,oCAAoC;IACpC,KAAK,CACH,GAAG,EAAE,WAAW,EAChB,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC7E,IAAI;IAmBP,6EAA6E;IAC7E,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM;IAe7C,8DAA8D;IAC9D,OAAO,CAAC,kBAAkB;IAgB1B,mDAAmD;IACnD,OAAO,CAAC,cAAc;IAStB,wDAAwD;IACxD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM;IAU/C,2BAA2B;IAC3B,QAAQ,IAAI;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE;CAS3G"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ── Semantic cache — AST-structure-aware caching for LLM responses ──
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
export class SemanticCache {
|
|
4
|
+
rawDb;
|
|
5
|
+
config;
|
|
6
|
+
stmts;
|
|
7
|
+
constructor(rawDb, config) {
|
|
8
|
+
this.rawDb = rawDb;
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.stmts = this.prepareStatements();
|
|
11
|
+
}
|
|
12
|
+
prepareStatements() {
|
|
13
|
+
return {
|
|
14
|
+
lookup: this.rawDb.prepare(`SELECT * FROM gateway_cache
|
|
15
|
+
WHERE ast_hash = ? AND request_hash = ?
|
|
16
|
+
AND datetime(created_at, '+' || ttl_hours || ' hours') > datetime('now')
|
|
17
|
+
LIMIT 1`),
|
|
18
|
+
lookupByAst: this.rawDb.prepare(`SELECT * FROM gateway_cache
|
|
19
|
+
WHERE ast_hash = ?
|
|
20
|
+
AND datetime(created_at, '+' || ttl_hours || ' hours') > datetime('now')
|
|
21
|
+
ORDER BY hit_count DESC LIMIT 1`),
|
|
22
|
+
insert: this.rawDb.prepare(`INSERT OR REPLACE INTO gateway_cache
|
|
23
|
+
(id, ast_hash, request_hash, response, model, provider, tokens_in, tokens_out, cost_estimate, ttl_hours)
|
|
24
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
25
|
+
recordHit: this.rawDb.prepare(`UPDATE gateway_cache SET hit_count = hit_count + 1, last_hit_at = datetime('now') WHERE id = ?`),
|
|
26
|
+
evictExpired: this.rawDb.prepare(`DELETE FROM gateway_cache WHERE datetime(created_at, '+' || ttl_hours || ' hours') <= datetime('now')`),
|
|
27
|
+
evictOldest: this.rawDb.prepare(`DELETE FROM gateway_cache WHERE id IN (
|
|
28
|
+
SELECT id FROM gateway_cache ORDER BY last_hit_at ASC NULLS FIRST, created_at ASC
|
|
29
|
+
LIMIT ?
|
|
30
|
+
)`),
|
|
31
|
+
count: this.rawDb.prepare('SELECT COUNT(*) as c FROM gateway_cache'),
|
|
32
|
+
invalidateByFile: this.rawDb.prepare(`DELETE FROM gateway_cache WHERE ast_hash IN (
|
|
33
|
+
SELECT DISTINCT gc.ast_hash FROM gateway_cache gc
|
|
34
|
+
WHERE gc.id IN (
|
|
35
|
+
SELECT gc2.id FROM gateway_cache gc2
|
|
36
|
+
JOIN json_each(gc2.response) -- Response contains symbol info referencing the file
|
|
37
|
+
)
|
|
38
|
+
)`),
|
|
39
|
+
stats: this.rawDb.prepare(`SELECT
|
|
40
|
+
COUNT(*) as total_entries,
|
|
41
|
+
SUM(hit_count) as total_hits,
|
|
42
|
+
SUM(tokens_in + tokens_out) as total_tokens_cached,
|
|
43
|
+
SUM(cost_estimate * hit_count) as total_cost_saved
|
|
44
|
+
FROM gateway_cache`),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Look up a cached response by AST hash + request hash */
|
|
48
|
+
lookup(req, analysis) {
|
|
49
|
+
if (!this.config.enabled)
|
|
50
|
+
return null;
|
|
51
|
+
const astHash = analysis?.symbols.length
|
|
52
|
+
? this.computeASTHash(analysis.symbols)
|
|
53
|
+
: null;
|
|
54
|
+
const requestHash = this.computeRequestHash(req);
|
|
55
|
+
// Try exact match (ast + request)
|
|
56
|
+
if (astHash) {
|
|
57
|
+
const row = this.stmts.lookup.get(astHash, requestHash);
|
|
58
|
+
if (row) {
|
|
59
|
+
this.stmts.recordHit.run(row.id);
|
|
60
|
+
return rowToCacheEntry(row);
|
|
61
|
+
}
|
|
62
|
+
// Try AST-only match (structural cache hit — same code structure, different prompt wording)
|
|
63
|
+
const astRow = this.stmts.lookupByAst.get(astHash);
|
|
64
|
+
if (astRow) {
|
|
65
|
+
this.stmts.recordHit.run(astRow.id);
|
|
66
|
+
return rowToCacheEntry(astRow);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/** Store a response in the cache */
|
|
72
|
+
store(req, analysis, response, meta) {
|
|
73
|
+
if (!this.config.enabled || !analysis.symbols.length)
|
|
74
|
+
return;
|
|
75
|
+
// Evict if over capacity
|
|
76
|
+
this.ensureCapacity();
|
|
77
|
+
const astHash = this.computeASTHash(analysis.symbols);
|
|
78
|
+
const requestHash = this.computeRequestHash(req);
|
|
79
|
+
const id = createHash('sha256').update(astHash + requestHash).digest('hex').slice(0, 16);
|
|
80
|
+
const costEstimate = estimateCost(meta.tokensIn, meta.tokensOut, meta.model);
|
|
81
|
+
this.stmts.insert.run(id, astHash, requestHash, response, meta.model, meta.provider, meta.tokensIn, meta.tokensOut, costEstimate, this.config.ttlHours);
|
|
82
|
+
}
|
|
83
|
+
/** Compute AST hash — captures code structure, survives renames/reformats */
|
|
84
|
+
computeASTHash(symbols) {
|
|
85
|
+
// Hash based on structural properties, NOT code text or names
|
|
86
|
+
const structure = symbols
|
|
87
|
+
.sort((a, b) => a.filePath.localeCompare(b.filePath) || a.startLine - b.startLine)
|
|
88
|
+
.map(s => {
|
|
89
|
+
// Include: kind, export status, parent relationship, line span size
|
|
90
|
+
const span = s.endLine - s.startLine;
|
|
91
|
+
const parent = s.parentId ? '1' : '0';
|
|
92
|
+
return `${s.kind}:${s.exported ? 'E' : 'I'}:${parent}:${span}`;
|
|
93
|
+
})
|
|
94
|
+
.join('|');
|
|
95
|
+
return createHash('sha256').update(structure).digest('hex').slice(0, 16);
|
|
96
|
+
}
|
|
97
|
+
/** Compute request hash — normalized version of the prompt */
|
|
98
|
+
computeRequestHash(req) {
|
|
99
|
+
// Normalize: lowercase, collapse whitespace, remove timestamps
|
|
100
|
+
const normalized = req.messages
|
|
101
|
+
.filter(m => m.role === 'user')
|
|
102
|
+
.map(m => {
|
|
103
|
+
const text = typeof m.content === 'string' ? m.content
|
|
104
|
+
: Array.isArray(m.content)
|
|
105
|
+
? m.content.filter(p => p.type === 'text').map(p => p.text ?? '').join(' ')
|
|
106
|
+
: '';
|
|
107
|
+
return text.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
108
|
+
})
|
|
109
|
+
.join('\n');
|
|
110
|
+
return createHash('sha256').update(normalized).digest('hex').slice(0, 16);
|
|
111
|
+
}
|
|
112
|
+
/** Evict expired entries + enforce max capacity */
|
|
113
|
+
ensureCapacity() {
|
|
114
|
+
this.stmts.evictExpired.run();
|
|
115
|
+
const count = this.stmts.count.get()?.c ?? 0;
|
|
116
|
+
if (count >= this.config.maxEntries) {
|
|
117
|
+
const toDelete = Math.ceil(this.config.maxEntries * 0.1); // Remove 10%
|
|
118
|
+
this.stmts.evictOldest.run(toDelete);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Invalidate cache entries related to changed files */
|
|
122
|
+
invalidateForFiles(filePaths) {
|
|
123
|
+
// Simple approach: clear all cache when files change
|
|
124
|
+
// (More granular: match ast_hash to symbols in changed files)
|
|
125
|
+
if (filePaths.length > 0) {
|
|
126
|
+
const result = this.rawDb.prepare('DELETE FROM gateway_cache').run();
|
|
127
|
+
return result.changes;
|
|
128
|
+
}
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
/** Get cache statistics */
|
|
132
|
+
getStats() {
|
|
133
|
+
const row = this.stmts.stats.get();
|
|
134
|
+
return {
|
|
135
|
+
totalEntries: row?.total_entries ?? 0,
|
|
136
|
+
totalHits: row?.total_hits ?? 0,
|
|
137
|
+
totalTokensCached: row?.total_tokens_cached ?? 0,
|
|
138
|
+
totalCostSaved: row?.total_cost_saved ?? 0,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function rowToCacheEntry(row) {
|
|
143
|
+
return {
|
|
144
|
+
id: row.id,
|
|
145
|
+
astHash: row.ast_hash,
|
|
146
|
+
requestHash: row.request_hash,
|
|
147
|
+
response: row.response,
|
|
148
|
+
model: row.model,
|
|
149
|
+
provider: row.provider,
|
|
150
|
+
tokensIn: row.tokens_in,
|
|
151
|
+
tokensOut: row.tokens_out,
|
|
152
|
+
costEstimate: row.cost_estimate,
|
|
153
|
+
createdAt: row.created_at,
|
|
154
|
+
hitCount: row.hit_count,
|
|
155
|
+
lastHitAt: row.last_hit_at,
|
|
156
|
+
ttlHours: row.ttl_hours,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Rough cost estimation per 1M tokens (USD)
|
|
160
|
+
const MODEL_COSTS = {
|
|
161
|
+
'gpt-4o': { input: 2.5, output: 10 },
|
|
162
|
+
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
163
|
+
'claude-3-5-sonnet': { input: 3, output: 15 },
|
|
164
|
+
'claude-3-5-haiku': { input: 0.8, output: 4 },
|
|
165
|
+
'gemini-1.5-pro': { input: 1.25, output: 5 },
|
|
166
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.3 },
|
|
167
|
+
};
|
|
168
|
+
function estimateCost(tokensIn, tokensOut, model) {
|
|
169
|
+
// Try exact match then prefix match
|
|
170
|
+
const costs = MODEL_COSTS[model]
|
|
171
|
+
?? Object.entries(MODEL_COSTS).find(([k]) => model.includes(k))?.[1]
|
|
172
|
+
?? { input: 1, output: 3 }; // Default fallback
|
|
173
|
+
return (tokensIn * costs.input + tokensOut * costs.output) / 1_000_000;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/gateway/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GatewayConfig } from './types.js';
|
|
2
|
+
/** Load gateway config from gateway.json, applying defaults for missing fields */
|
|
3
|
+
export declare function loadConfig(projectDir: string): GatewayConfig;
|
|
4
|
+
/** Create a default gateway.json if none exists */
|
|
5
|
+
export declare function initConfig(projectDir: string): string;
|
|
6
|
+
/** Validate config, returning errors */
|
|
7
|
+
export declare function validateConfig(config: GatewayConfig): string[];
|
|
8
|
+
/** Resolve env var references in api keys (${VAR_NAME} → process.env.VAR_NAME) */
|
|
9
|
+
export declare function resolveEnvVars(config: GatewayConfig): GatewayConfig;
|
|
10
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/gateway/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAyF,MAAM,YAAY,CAAC;AA0CvI,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAgB5D;AAED,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAqCrD;AAED,wCAAwC;AACxC,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,EAAE,CA4B9D;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAQnE"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// ── Config — gateway.json loader and validator ──
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const DEFAULT_ROUTING = {
|
|
5
|
+
thresholds: { economy: 5, standard: 20 },
|
|
6
|
+
intentMultipliers: {
|
|
7
|
+
generate: 1.0,
|
|
8
|
+
refactor: 1.5,
|
|
9
|
+
review: 0.8,
|
|
10
|
+
test: 0.7,
|
|
11
|
+
debug: 1.2,
|
|
12
|
+
explain: 0.5,
|
|
13
|
+
docstring: 0.3,
|
|
14
|
+
unknown: 1.0,
|
|
15
|
+
},
|
|
16
|
+
defaultTier: 'standard',
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_CACHE = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
maxEntries: 1000,
|
|
21
|
+
ttlHours: 24,
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_CONTEXT_INJECTION = {
|
|
24
|
+
enabled: true,
|
|
25
|
+
maxTokens: 500,
|
|
26
|
+
includeCallers: true,
|
|
27
|
+
includeDependencies: true,
|
|
28
|
+
includeBlastRadius: true,
|
|
29
|
+
};
|
|
30
|
+
const DEFAULT_CONFIG = {
|
|
31
|
+
port: 4141,
|
|
32
|
+
host: '127.0.0.1',
|
|
33
|
+
providers: [],
|
|
34
|
+
routing: DEFAULT_ROUTING,
|
|
35
|
+
cache: DEFAULT_CACHE,
|
|
36
|
+
contextInjection: DEFAULT_CONTEXT_INJECTION,
|
|
37
|
+
privacy: { rules: [], defaultAction: 'allow' },
|
|
38
|
+
rateLimit: { windowMs: 60000, maxRequests: 60 },
|
|
39
|
+
};
|
|
40
|
+
/** Load gateway config from gateway.json, applying defaults for missing fields */
|
|
41
|
+
export function loadConfig(projectDir) {
|
|
42
|
+
const configPath = join(projectDir, 'gateway.json');
|
|
43
|
+
if (!existsSync(configPath)) {
|
|
44
|
+
return { ...DEFAULT_CONFIG };
|
|
45
|
+
}
|
|
46
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`Invalid JSON in ${configPath}`);
|
|
53
|
+
}
|
|
54
|
+
return mergeConfig(parsed);
|
|
55
|
+
}
|
|
56
|
+
/** Create a default gateway.json if none exists */
|
|
57
|
+
export function initConfig(projectDir) {
|
|
58
|
+
const configPath = join(projectDir, 'gateway.json');
|
|
59
|
+
if (existsSync(configPath)) {
|
|
60
|
+
return configPath;
|
|
61
|
+
}
|
|
62
|
+
const template = {
|
|
63
|
+
$schema: './node_modules/milens/gateway-schema.json',
|
|
64
|
+
port: 4141,
|
|
65
|
+
host: '127.0.0.1',
|
|
66
|
+
providers: [
|
|
67
|
+
{
|
|
68
|
+
id: 'openai-main',
|
|
69
|
+
name: 'OpenAI',
|
|
70
|
+
tier: 'premium',
|
|
71
|
+
format: 'openai',
|
|
72
|
+
endpoint: 'https://api.openai.com/v1',
|
|
73
|
+
apiKey: '${OPENAI_API_KEY}',
|
|
74
|
+
models: ['gpt-4o', 'gpt-4o-mini'],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
routing: DEFAULT_ROUTING,
|
|
78
|
+
cache: DEFAULT_CACHE,
|
|
79
|
+
contextInjection: DEFAULT_CONTEXT_INJECTION,
|
|
80
|
+
privacy: {
|
|
81
|
+
rules: [
|
|
82
|
+
{ pathPattern: '**/.env*', action: 'block', reason: 'Environment files contain secrets' },
|
|
83
|
+
{ pathPattern: '**/secrets/**', action: 'block', reason: 'Secret files' },
|
|
84
|
+
],
|
|
85
|
+
defaultAction: 'allow',
|
|
86
|
+
},
|
|
87
|
+
rateLimit: { windowMs: 60000, maxRequests: 60 },
|
|
88
|
+
};
|
|
89
|
+
writeFileSync(configPath, JSON.stringify(template, null, 2) + '\n', 'utf-8');
|
|
90
|
+
return configPath;
|
|
91
|
+
}
|
|
92
|
+
/** Validate config, returning errors */
|
|
93
|
+
export function validateConfig(config) {
|
|
94
|
+
const errors = [];
|
|
95
|
+
if (config.port < 1 || config.port > 65535) {
|
|
96
|
+
errors.push(`Invalid port: ${config.port}`);
|
|
97
|
+
}
|
|
98
|
+
if (!config.providers.length) {
|
|
99
|
+
errors.push('No providers configured — at least one provider is required');
|
|
100
|
+
}
|
|
101
|
+
for (const p of config.providers) {
|
|
102
|
+
if (!p.id)
|
|
103
|
+
errors.push('Provider missing "id"');
|
|
104
|
+
if (!p.format)
|
|
105
|
+
errors.push(`Provider "${p.id}": missing "format"`);
|
|
106
|
+
if (!p.endpoint)
|
|
107
|
+
errors.push(`Provider "${p.id}": missing "endpoint"`);
|
|
108
|
+
if (!p.apiKey)
|
|
109
|
+
errors.push(`Provider "${p.id}": missing "apiKey"`);
|
|
110
|
+
if (!p.models?.length)
|
|
111
|
+
errors.push(`Provider "${p.id}": missing "models"`);
|
|
112
|
+
// Check for env var references
|
|
113
|
+
if (p.apiKey.startsWith('${') && p.apiKey.endsWith('}')) {
|
|
114
|
+
const envVar = p.apiKey.slice(2, -1);
|
|
115
|
+
if (!process.env[envVar]) {
|
|
116
|
+
errors.push(`Provider "${p.id}": env var ${envVar} is not set`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return errors;
|
|
121
|
+
}
|
|
122
|
+
/** Resolve env var references in api keys (${VAR_NAME} → process.env.VAR_NAME) */
|
|
123
|
+
export function resolveEnvVars(config) {
|
|
124
|
+
return {
|
|
125
|
+
...config,
|
|
126
|
+
providers: config.providers.map(p => ({
|
|
127
|
+
...p,
|
|
128
|
+
apiKey: resolveEnvValue(p.apiKey),
|
|
129
|
+
})),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function resolveEnvValue(value) {
|
|
133
|
+
if (value.startsWith('${') && value.endsWith('}')) {
|
|
134
|
+
const envVar = value.slice(2, -1);
|
|
135
|
+
return process.env[envVar] ?? value;
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
function mergeConfig(raw) {
|
|
140
|
+
return {
|
|
141
|
+
port: typeof raw.port === 'number' ? raw.port : DEFAULT_CONFIG.port,
|
|
142
|
+
host: typeof raw.host === 'string' ? raw.host : DEFAULT_CONFIG.host,
|
|
143
|
+
providers: Array.isArray(raw.providers) ? raw.providers : [],
|
|
144
|
+
routing: {
|
|
145
|
+
...DEFAULT_ROUTING,
|
|
146
|
+
...(typeof raw.routing === 'object' && raw.routing ? raw.routing : {}),
|
|
147
|
+
},
|
|
148
|
+
cache: {
|
|
149
|
+
...DEFAULT_CACHE,
|
|
150
|
+
...(typeof raw.cache === 'object' && raw.cache ? raw.cache : {}),
|
|
151
|
+
},
|
|
152
|
+
contextInjection: {
|
|
153
|
+
...DEFAULT_CONTEXT_INJECTION,
|
|
154
|
+
...(typeof raw.contextInjection === 'object' && raw.contextInjection
|
|
155
|
+
? raw.contextInjection : {}),
|
|
156
|
+
},
|
|
157
|
+
privacy: {
|
|
158
|
+
rules: Array.isArray(raw.privacy?.rules) ? raw.privacy.rules : [],
|
|
159
|
+
defaultAction: raw.privacy?.defaultAction ?? 'allow',
|
|
160
|
+
},
|
|
161
|
+
rateLimit: {
|
|
162
|
+
windowMs: raw.rateLimit?.windowMs ?? 60000,
|
|
163
|
+
maxRequests: raw.rateLimit?.maxRequests ?? 60,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/gateway/config.ts"],"names":[],"mappings":"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"}
|