smartcontext-proxy 0.1.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.
Files changed (166) hide show
  1. package/PLAN.md +406 -0
  2. package/PROGRESS.md +60 -0
  3. package/README.md +99 -0
  4. package/SPEC.md +915 -0
  5. package/adapters/openclaw/embedding.d.ts +8 -0
  6. package/adapters/openclaw/embedding.js +16 -0
  7. package/adapters/openclaw/embedding.ts +15 -0
  8. package/adapters/openclaw/index.d.ts +18 -0
  9. package/adapters/openclaw/index.js +42 -0
  10. package/adapters/openclaw/index.ts +43 -0
  11. package/adapters/openclaw/session-importer.d.ts +22 -0
  12. package/adapters/openclaw/session-importer.js +99 -0
  13. package/adapters/openclaw/session-importer.ts +105 -0
  14. package/adapters/openclaw/storage.d.ts +26 -0
  15. package/adapters/openclaw/storage.js +177 -0
  16. package/adapters/openclaw/storage.ts +183 -0
  17. package/dist/adapters/openclaw/embedding.d.ts +8 -0
  18. package/dist/adapters/openclaw/embedding.js +16 -0
  19. package/dist/adapters/openclaw/index.d.ts +18 -0
  20. package/dist/adapters/openclaw/index.js +42 -0
  21. package/dist/adapters/openclaw/session-importer.d.ts +22 -0
  22. package/dist/adapters/openclaw/session-importer.js +99 -0
  23. package/dist/adapters/openclaw/storage.d.ts +26 -0
  24. package/dist/adapters/openclaw/storage.js +177 -0
  25. package/dist/config/auto-detect.d.ts +3 -0
  26. package/dist/config/auto-detect.js +48 -0
  27. package/dist/config/defaults.d.ts +2 -0
  28. package/dist/config/defaults.js +28 -0
  29. package/dist/config/schema.d.ts +30 -0
  30. package/dist/config/schema.js +3 -0
  31. package/dist/context/budget.d.ts +25 -0
  32. package/dist/context/budget.js +85 -0
  33. package/dist/context/canonical.d.ts +39 -0
  34. package/dist/context/canonical.js +12 -0
  35. package/dist/context/chunker.d.ts +9 -0
  36. package/dist/context/chunker.js +148 -0
  37. package/dist/context/optimizer.d.ts +31 -0
  38. package/dist/context/optimizer.js +163 -0
  39. package/dist/context/retriever.d.ts +29 -0
  40. package/dist/context/retriever.js +103 -0
  41. package/dist/daemon/process.d.ts +6 -0
  42. package/dist/daemon/process.js +76 -0
  43. package/dist/daemon/service.d.ts +2 -0
  44. package/dist/daemon/service.js +99 -0
  45. package/dist/embedding/ollama.d.ts +11 -0
  46. package/dist/embedding/ollama.js +72 -0
  47. package/dist/embedding/types.d.ts +6 -0
  48. package/dist/embedding/types.js +3 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +190 -0
  51. package/dist/metrics/collector.d.ts +43 -0
  52. package/dist/metrics/collector.js +72 -0
  53. package/dist/providers/anthropic.d.ts +15 -0
  54. package/dist/providers/anthropic.js +109 -0
  55. package/dist/providers/google.d.ts +13 -0
  56. package/dist/providers/google.js +40 -0
  57. package/dist/providers/ollama.d.ts +13 -0
  58. package/dist/providers/ollama.js +82 -0
  59. package/dist/providers/openai.d.ts +15 -0
  60. package/dist/providers/openai.js +115 -0
  61. package/dist/providers/types.d.ts +18 -0
  62. package/dist/providers/types.js +3 -0
  63. package/dist/proxy/router.d.ts +12 -0
  64. package/dist/proxy/router.js +46 -0
  65. package/dist/proxy/server.d.ts +25 -0
  66. package/dist/proxy/server.js +265 -0
  67. package/dist/proxy/stream.d.ts +8 -0
  68. package/dist/proxy/stream.js +32 -0
  69. package/dist/src/config/auto-detect.d.ts +3 -0
  70. package/dist/src/config/auto-detect.js +48 -0
  71. package/dist/src/config/defaults.d.ts +2 -0
  72. package/dist/src/config/defaults.js +28 -0
  73. package/dist/src/config/schema.d.ts +30 -0
  74. package/dist/src/config/schema.js +3 -0
  75. package/dist/src/context/budget.d.ts +25 -0
  76. package/dist/src/context/budget.js +85 -0
  77. package/dist/src/context/canonical.d.ts +39 -0
  78. package/dist/src/context/canonical.js +12 -0
  79. package/dist/src/context/chunker.d.ts +9 -0
  80. package/dist/src/context/chunker.js +148 -0
  81. package/dist/src/context/optimizer.d.ts +31 -0
  82. package/dist/src/context/optimizer.js +163 -0
  83. package/dist/src/context/retriever.d.ts +29 -0
  84. package/dist/src/context/retriever.js +103 -0
  85. package/dist/src/daemon/process.d.ts +6 -0
  86. package/dist/src/daemon/process.js +76 -0
  87. package/dist/src/daemon/service.d.ts +2 -0
  88. package/dist/src/daemon/service.js +99 -0
  89. package/dist/src/embedding/ollama.d.ts +11 -0
  90. package/dist/src/embedding/ollama.js +72 -0
  91. package/dist/src/embedding/types.d.ts +6 -0
  92. package/dist/src/embedding/types.js +3 -0
  93. package/dist/src/index.d.ts +2 -0
  94. package/dist/src/index.js +190 -0
  95. package/dist/src/metrics/collector.d.ts +43 -0
  96. package/dist/src/metrics/collector.js +72 -0
  97. package/dist/src/providers/anthropic.d.ts +15 -0
  98. package/dist/src/providers/anthropic.js +109 -0
  99. package/dist/src/providers/google.d.ts +13 -0
  100. package/dist/src/providers/google.js +40 -0
  101. package/dist/src/providers/ollama.d.ts +13 -0
  102. package/dist/src/providers/ollama.js +82 -0
  103. package/dist/src/providers/openai.d.ts +15 -0
  104. package/dist/src/providers/openai.js +115 -0
  105. package/dist/src/providers/types.d.ts +18 -0
  106. package/dist/src/providers/types.js +3 -0
  107. package/dist/src/proxy/router.d.ts +12 -0
  108. package/dist/src/proxy/router.js +46 -0
  109. package/dist/src/proxy/server.d.ts +25 -0
  110. package/dist/src/proxy/server.js +265 -0
  111. package/dist/src/proxy/stream.d.ts +8 -0
  112. package/dist/src/proxy/stream.js +32 -0
  113. package/dist/src/storage/lancedb.d.ts +21 -0
  114. package/dist/src/storage/lancedb.js +158 -0
  115. package/dist/src/storage/types.d.ts +52 -0
  116. package/dist/src/storage/types.js +3 -0
  117. package/dist/src/test/context.test.d.ts +1 -0
  118. package/dist/src/test/context.test.js +141 -0
  119. package/dist/src/test/dashboard.test.d.ts +1 -0
  120. package/dist/src/test/dashboard.test.js +85 -0
  121. package/dist/src/test/proxy.test.d.ts +1 -0
  122. package/dist/src/test/proxy.test.js +188 -0
  123. package/dist/src/ui/dashboard.d.ts +2 -0
  124. package/dist/src/ui/dashboard.js +183 -0
  125. package/dist/storage/lancedb.d.ts +21 -0
  126. package/dist/storage/lancedb.js +158 -0
  127. package/dist/storage/types.d.ts +52 -0
  128. package/dist/storage/types.js +3 -0
  129. package/dist/test/context.test.d.ts +1 -0
  130. package/dist/test/context.test.js +141 -0
  131. package/dist/test/dashboard.test.d.ts +1 -0
  132. package/dist/test/dashboard.test.js +85 -0
  133. package/dist/test/proxy.test.d.ts +1 -0
  134. package/dist/test/proxy.test.js +188 -0
  135. package/dist/ui/dashboard.d.ts +2 -0
  136. package/dist/ui/dashboard.js +183 -0
  137. package/package.json +38 -0
  138. package/src/config/auto-detect.ts +51 -0
  139. package/src/config/defaults.ts +26 -0
  140. package/src/config/schema.ts +33 -0
  141. package/src/context/budget.ts +126 -0
  142. package/src/context/canonical.ts +50 -0
  143. package/src/context/chunker.ts +165 -0
  144. package/src/context/optimizer.ts +201 -0
  145. package/src/context/retriever.ts +123 -0
  146. package/src/daemon/process.ts +70 -0
  147. package/src/daemon/service.ts +103 -0
  148. package/src/embedding/ollama.ts +68 -0
  149. package/src/embedding/types.ts +6 -0
  150. package/src/index.ts +176 -0
  151. package/src/metrics/collector.ts +114 -0
  152. package/src/providers/anthropic.ts +117 -0
  153. package/src/providers/google.ts +42 -0
  154. package/src/providers/ollama.ts +87 -0
  155. package/src/providers/openai.ts +127 -0
  156. package/src/providers/types.ts +20 -0
  157. package/src/proxy/router.ts +48 -0
  158. package/src/proxy/server.ts +315 -0
  159. package/src/proxy/stream.ts +39 -0
  160. package/src/storage/lancedb.ts +169 -0
  161. package/src/storage/types.ts +47 -0
  162. package/src/test/context.test.ts +165 -0
  163. package/src/test/dashboard.test.ts +94 -0
  164. package/src/test/proxy.test.ts +218 -0
  165. package/src/ui/dashboard.ts +184 -0
  166. package/tsconfig.json +18 -0
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProxyServer = void 0;
7
+ const node_http_1 = __importDefault(require("node:http"));
8
+ const node_https_1 = __importDefault(require("node:https"));
9
+ const node_url_1 = require("node:url");
10
+ const router_js_1 = require("./router.js");
11
+ const stream_js_1 = require("./stream.js");
12
+ const optimizer_js_1 = require("../context/optimizer.js");
13
+ const collector_js_1 = require("../metrics/collector.js");
14
+ const chunker_js_1 = require("../context/chunker.js");
15
+ const canonical_js_1 = require("../context/canonical.js");
16
+ const dashboard_js_1 = require("../ui/dashboard.js");
17
+ class ProxyServer {
18
+ server;
19
+ router;
20
+ config;
21
+ requestCount = 0;
22
+ optimizer = null;
23
+ metrics = new collector_js_1.MetricsCollector();
24
+ paused = false;
25
+ constructor(config, embedding, storage) {
26
+ this.config = config;
27
+ this.router = new router_js_1.Router(config);
28
+ this.server = node_http_1.default.createServer((req, res) => this.handleRequest(req, res));
29
+ if (embedding && storage) {
30
+ this.optimizer = new optimizer_js_1.ContextOptimizer(embedding, storage, config.context);
31
+ }
32
+ }
33
+ async start() {
34
+ const { port, host } = this.config.proxy;
35
+ return new Promise((resolve) => {
36
+ this.server.listen(port, host, () => resolve());
37
+ });
38
+ }
39
+ async stop() {
40
+ return new Promise((resolve) => {
41
+ this.server.close(() => resolve());
42
+ });
43
+ }
44
+ getProviderNames() {
45
+ return this.router.getProviderNames();
46
+ }
47
+ getMetrics() {
48
+ return this.metrics;
49
+ }
50
+ setPaused(paused) {
51
+ this.paused = paused;
52
+ }
53
+ isPaused() {
54
+ return this.paused;
55
+ }
56
+ async handleRequest(req, res) {
57
+ const path = req.url || '/';
58
+ const method = req.method || 'GET';
59
+ // Dashboard (root path)
60
+ if (path === '/' && method === 'GET') {
61
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
62
+ res.end((0, dashboard_js_1.renderDashboard)(this.metrics, this.paused));
63
+ return;
64
+ }
65
+ // Health check
66
+ if (path === '/health') {
67
+ res.writeHead(200, { 'Content-Type': 'application/json' });
68
+ res.end(JSON.stringify({
69
+ ok: true,
70
+ requests: this.requestCount,
71
+ paused: this.paused,
72
+ mode: this.optimizer ? 'optimizing' : 'transparent',
73
+ }));
74
+ return;
75
+ }
76
+ // Internal API endpoints (/_sc/*)
77
+ if (path.startsWith('/_sc/')) {
78
+ await this.handleApiRequest(path, method, req, res);
79
+ return;
80
+ }
81
+ // Only handle POST to /v1/{provider}/*
82
+ if (method !== 'POST') {
83
+ res.writeHead(405, { 'Content-Type': 'application/json' });
84
+ res.end(JSON.stringify({ error: 'Method not allowed' }));
85
+ return;
86
+ }
87
+ const route = this.router.resolve(path);
88
+ if (!route) {
89
+ res.writeHead(404, { 'Content-Type': 'application/json' });
90
+ res.end(JSON.stringify({ error: `Unknown provider path: ${path}` }));
91
+ return;
92
+ }
93
+ try {
94
+ this.requestCount++;
95
+ await this.proxyRequest(req, res, route.adapter, path);
96
+ }
97
+ catch (err) {
98
+ const message = err instanceof Error ? err.message : 'Internal proxy error';
99
+ this.log('error', `Proxy error: ${message}`);
100
+ if (!res.headersSent) {
101
+ res.writeHead(502, { 'Content-Type': 'application/json' });
102
+ res.end(JSON.stringify({ error: message }));
103
+ }
104
+ }
105
+ }
106
+ async handleApiRequest(path, method, req, res) {
107
+ res.setHeader('Content-Type', 'application/json');
108
+ switch (path) {
109
+ case '/_sc/status':
110
+ res.end(JSON.stringify({
111
+ state: this.paused ? 'paused' : 'running',
112
+ uptime: this.metrics.getUptime(),
113
+ requests: this.requestCount,
114
+ mode: this.optimizer ? 'optimizing' : 'transparent',
115
+ }));
116
+ break;
117
+ case '/_sc/stats':
118
+ res.end(JSON.stringify(this.metrics.getStats()));
119
+ break;
120
+ case '/_sc/feed':
121
+ res.end(JSON.stringify(this.metrics.getRecent(50)));
122
+ break;
123
+ case '/_sc/pause':
124
+ this.paused = true;
125
+ res.end(JSON.stringify({ ok: true, state: 'paused' }));
126
+ break;
127
+ case '/_sc/resume':
128
+ this.paused = false;
129
+ res.end(JSON.stringify({ ok: true, state: 'running' }));
130
+ break;
131
+ default:
132
+ res.writeHead(404);
133
+ res.end(JSON.stringify({ error: `Unknown API path: ${path}` }));
134
+ }
135
+ }
136
+ async proxyRequest(clientReq, clientRes, adapter, path) {
137
+ const startTime = Date.now();
138
+ const bodyBuf = await this.readBody(clientReq);
139
+ const body = JSON.parse(bodyBuf.toString());
140
+ const headers = {};
141
+ for (const [key, val] of Object.entries(clientReq.headers)) {
142
+ if (typeof val === 'string')
143
+ headers[key] = val;
144
+ }
145
+ const canonical = adapter.parseRequest(body, headers);
146
+ const originalTokens = (0, chunker_js_1.estimateTokens)(canonical.systemPrompt || '') +
147
+ canonical.messages.reduce((sum, m) => sum + (0, chunker_js_1.estimateTokens)((0, canonical_js_1.getTextContent)(m)), 0);
148
+ let forwardBody;
149
+ let optimizedTokens = originalTokens;
150
+ let savingsPercent = 0;
151
+ let chunksRetrieved = 0;
152
+ let topScore = 0;
153
+ let passThrough = true;
154
+ let reason;
155
+ // Context optimization (if available and not paused)
156
+ if (this.optimizer && !this.paused) {
157
+ try {
158
+ const result = await this.optimizer.optimize(canonical);
159
+ passThrough = result.passThrough;
160
+ reason = result.reason;
161
+ if (!result.passThrough) {
162
+ // Use optimized context
163
+ canonical.messages = result.optimizedMessages;
164
+ if (result.systemPrompt !== undefined) {
165
+ canonical.systemPrompt = result.systemPrompt;
166
+ }
167
+ optimizedTokens = result.packed.optimizedTokens;
168
+ savingsPercent = result.packed.savingsPercent;
169
+ }
170
+ if (result.retrieval) {
171
+ chunksRetrieved = result.retrieval.chunks.length;
172
+ topScore = result.retrieval.topScore;
173
+ }
174
+ }
175
+ catch (err) {
176
+ // Graceful degradation: optimization failed, forward original
177
+ this.log('error', `Optimization failed, passing through: ${err}`);
178
+ passThrough = true;
179
+ reason = `optimization error: ${err}`;
180
+ }
181
+ }
182
+ // Serialize for forwarding
183
+ if (!passThrough) {
184
+ forwardBody = JSON.stringify(adapter.serializeRequest(canonical));
185
+ }
186
+ else {
187
+ forwardBody = JSON.stringify(body);
188
+ }
189
+ const forwardUrl = new node_url_1.URL(adapter.forwardUrl(path));
190
+ const apiKey = canonical.providerAuth || this.config.providers[adapter.name]?.apiKey || '';
191
+ const forwardHeaders = {
192
+ 'Content-Type': adapter.contentType(),
193
+ ...adapter.authHeaders(apiKey),
194
+ };
195
+ if (headers['anthropic-version'])
196
+ forwardHeaders['anthropic-version'] = headers['anthropic-version'];
197
+ if (headers['anthropic-beta'])
198
+ forwardHeaders['anthropic-beta'] = headers['anthropic-beta'];
199
+ forwardHeaders['Content-Length'] = Buffer.byteLength(forwardBody).toString();
200
+ const latencyOverhead = Date.now() - startTime;
201
+ const savingsStr = passThrough ? 'pass' : `-${savingsPercent}%`;
202
+ this.log('info', `#${this.requestCount} ${adapter.name}/${canonical.model} ` +
203
+ `${originalTokens}→${optimizedTokens} ${savingsStr} ` +
204
+ `${canonical.stream ? 'stream' : 'sync'} ${latencyOverhead}ms`);
205
+ // Forward to provider
206
+ const transport = forwardUrl.protocol === 'https:' ? node_https_1.default : node_http_1.default;
207
+ const providerRes = await new Promise((resolve, reject) => {
208
+ const proxyReq = transport.request(forwardUrl, { method: 'POST', headers: forwardHeaders }, resolve);
209
+ proxyReq.on('error', reject);
210
+ proxyReq.write(forwardBody);
211
+ proxyReq.end();
212
+ });
213
+ // Add debug headers if enabled
214
+ if (this.config.logging.debug_headers && !passThrough) {
215
+ providerRes.headers['x-smartcontext-savings'] = `${savingsPercent}%`;
216
+ providerRes.headers['x-smartcontext-original-tokens'] = String(originalTokens);
217
+ providerRes.headers['x-smartcontext-optimized-tokens'] = String(optimizedTokens);
218
+ providerRes.headers['x-smartcontext-chunks'] = String(chunksRetrieved);
219
+ providerRes.headers['x-smartcontext-latency-ms'] = String(latencyOverhead);
220
+ providerRes.headers['x-smartcontext-mode'] = this.paused ? 'paused' : 'optimized';
221
+ }
222
+ // Stream response back
223
+ const responseBuffer = await (0, stream_js_1.streamResponse)(providerRes, clientRes);
224
+ // Record metrics
225
+ this.metrics.record({
226
+ id: this.requestCount,
227
+ timestamp: Date.now(),
228
+ provider: adapter.name,
229
+ model: canonical.model,
230
+ streaming: canonical.stream,
231
+ originalTokens,
232
+ optimizedTokens,
233
+ savingsPercent,
234
+ latencyOverheadMs: latencyOverhead,
235
+ chunksRetrieved,
236
+ topScore,
237
+ passThrough,
238
+ reason,
239
+ });
240
+ // Async post-indexing (don't block response)
241
+ if (this.optimizer && !passThrough) {
242
+ const sessionId = canonical.rawHeaders['x-smartcontext-session'] || `auto-${this.requestCount}`;
243
+ this.optimizer.indexExchange(canonical.messages, sessionId).catch((err) => {
244
+ this.log('error', `Post-indexing failed: ${err}`);
245
+ });
246
+ }
247
+ }
248
+ readBody(req) {
249
+ return new Promise((resolve, reject) => {
250
+ const chunks = [];
251
+ req.on('data', (chunk) => chunks.push(chunk));
252
+ req.on('end', () => resolve(Buffer.concat(chunks)));
253
+ req.on('error', reject);
254
+ });
255
+ }
256
+ log(level, message) {
257
+ const timestamp = new Date().toISOString().slice(11, 23);
258
+ const prefix = level === 'error' ? '✗' : '→';
259
+ if (level === 'error' || this.config.logging.level !== 'error') {
260
+ console.log(`[${timestamp}] ${prefix} ${message}`);
261
+ }
262
+ }
263
+ }
264
+ exports.ProxyServer = ProxyServer;
265
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1,8 @@
1
+ import { IncomingMessage } from 'node:http';
2
+ import type { ServerResponse } from 'node:http';
3
+ /**
4
+ * Stream SSE response from provider to client byte-by-byte.
5
+ * Zero buffering — passes through as fast as possible.
6
+ * Returns the full buffered response body for post-indexing.
7
+ */
8
+ export declare function streamResponse(providerRes: IncomingMessage, clientRes: ServerResponse): Promise<Buffer>;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamResponse = streamResponse;
4
+ /**
5
+ * Stream SSE response from provider to client byte-by-byte.
6
+ * Zero buffering — passes through as fast as possible.
7
+ * Returns the full buffered response body for post-indexing.
8
+ */
9
+ async function streamResponse(providerRes, clientRes) {
10
+ return new Promise((resolve, reject) => {
11
+ const chunks = [];
12
+ // Copy status and headers
13
+ clientRes.writeHead(providerRes.statusCode || 200, providerRes.headers);
14
+ providerRes.on('data', (chunk) => {
15
+ chunks.push(chunk);
16
+ clientRes.write(chunk);
17
+ });
18
+ providerRes.on('end', () => {
19
+ clientRes.end();
20
+ resolve(Buffer.concat(chunks));
21
+ });
22
+ providerRes.on('error', (err) => {
23
+ clientRes.end();
24
+ reject(err);
25
+ });
26
+ // Handle client disconnect
27
+ clientRes.on('close', () => {
28
+ providerRes.destroy();
29
+ });
30
+ });
31
+ }
32
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1,3 @@
1
+ import type { ProviderConfig, SmartContextConfig } from './schema.js';
2
+ export declare function detectProviders(): Record<string, ProviderConfig>;
3
+ export declare function buildConfig(overrides?: Partial<SmartContextConfig>): SmartContextConfig;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectProviders = detectProviders;
4
+ exports.buildConfig = buildConfig;
5
+ const defaults_js_1 = require("./defaults.js");
6
+ const PROVIDER_ENV_MAP = {
7
+ anthropic: {
8
+ envKey: 'ANTHROPIC_API_KEY',
9
+ baseUrl: 'https://api.anthropic.com',
10
+ },
11
+ openai: {
12
+ envKey: 'OPENAI_API_KEY',
13
+ baseUrl: 'https://api.openai.com',
14
+ },
15
+ google: {
16
+ envKey: 'GOOGLE_API_KEY',
17
+ baseUrl: 'https://generativelanguage.googleapis.com',
18
+ },
19
+ openrouter: {
20
+ envKey: 'OPENROUTER_API_KEY',
21
+ baseUrl: 'https://openrouter.ai/api',
22
+ },
23
+ };
24
+ function detectProviders() {
25
+ const providers = {};
26
+ for (const [name, { envKey, baseUrl }] of Object.entries(PROVIDER_ENV_MAP)) {
27
+ const apiKey = process.env[envKey];
28
+ if (apiKey) {
29
+ providers[name] = { apiKey, baseUrl };
30
+ }
31
+ }
32
+ // Ollama: detect by host, no API key needed
33
+ const ollamaHost = process.env['OLLAMA_HOST'] || 'http://localhost:11434';
34
+ providers['ollama'] = { apiKey: '', baseUrl: ollamaHost };
35
+ return providers;
36
+ }
37
+ function buildConfig(overrides) {
38
+ const providers = detectProviders();
39
+ return {
40
+ ...defaults_js_1.DEFAULT_CONFIG,
41
+ providers,
42
+ ...overrides,
43
+ proxy: { ...defaults_js_1.DEFAULT_CONFIG.proxy, ...overrides?.proxy },
44
+ context: { ...defaults_js_1.DEFAULT_CONFIG.context, ...overrides?.context },
45
+ logging: { ...defaults_js_1.DEFAULT_CONFIG.logging, ...overrides?.logging },
46
+ };
47
+ }
48
+ //# sourceMappingURL=auto-detect.js.map
@@ -0,0 +1,2 @@
1
+ import type { SmartContextConfig } from './schema.js';
2
+ export declare const DEFAULT_CONFIG: SmartContextConfig;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.DEFAULT_CONFIG = {
5
+ proxy: {
6
+ port: 4800,
7
+ host: '127.0.0.1',
8
+ },
9
+ providers: {},
10
+ context: {
11
+ tier1_exchanges: 3,
12
+ tier2_max_chunks: 10,
13
+ tier2_min_score: 0.55,
14
+ tier3_token_reserve: 500,
15
+ recency_boost: 0.15,
16
+ filepath_boost: 0.20,
17
+ dedup_threshold: 0.92,
18
+ confidence_gate: 0.55,
19
+ response_reserve_tokens: 8192,
20
+ },
21
+ logging: {
22
+ level: 'info',
23
+ raw_logs: true,
24
+ metrics: true,
25
+ debug_headers: false,
26
+ },
27
+ };
28
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1,30 @@
1
+ export interface SmartContextConfig {
2
+ proxy: {
3
+ port: number;
4
+ host: string;
5
+ };
6
+ providers: Record<string, ProviderConfig>;
7
+ context: ContextConfig;
8
+ logging: LoggingConfig;
9
+ }
10
+ export interface ProviderConfig {
11
+ apiKey: string;
12
+ baseUrl?: string;
13
+ }
14
+ export interface ContextConfig {
15
+ tier1_exchanges: number;
16
+ tier2_max_chunks: number;
17
+ tier2_min_score: number;
18
+ tier3_token_reserve: number;
19
+ recency_boost: number;
20
+ filepath_boost: number;
21
+ dedup_threshold: number;
22
+ confidence_gate: number;
23
+ response_reserve_tokens: number;
24
+ }
25
+ export interface LoggingConfig {
26
+ level: 'error' | 'warn' | 'info' | 'debug';
27
+ raw_logs: boolean;
28
+ metrics: boolean;
29
+ debug_headers: boolean;
30
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,25 @@
1
+ import type { ScoredChunk } from '../storage/types.js';
2
+ import type { CanonicalMessage } from './canonical.js';
3
+ export interface BudgetAllocation {
4
+ systemPromptTokens: number;
5
+ tier1Tokens: number;
6
+ tier2Budget: number;
7
+ tier3Reserve: number;
8
+ responseReserve: number;
9
+ totalAvailable: number;
10
+ }
11
+ export interface PackedContext {
12
+ systemPrompt?: string;
13
+ tier1Messages: CanonicalMessage[];
14
+ tier2Chunks: ScoredChunk[];
15
+ tier3Summary?: string;
16
+ allocation: BudgetAllocation;
17
+ originalTokens: number;
18
+ optimizedTokens: number;
19
+ savingsPercent: number;
20
+ }
21
+ export declare function getModelContextLimit(model: string): number;
22
+ /**
23
+ * Allocate token budget across tiers and pack context.
24
+ */
25
+ export declare function packContext(systemPrompt: string | undefined, messages: CanonicalMessage[], retrievedChunks: ScoredChunk[], model: string, tier1Exchanges: number, tier3Reserve: number, responseReserve: number): PackedContext;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getModelContextLimit = getModelContextLimit;
4
+ exports.packContext = packContext;
5
+ const canonical_js_1 = require("./canonical.js");
6
+ const chunker_js_1 = require("./chunker.js");
7
+ /** Known model context window sizes */
8
+ const MODEL_CONTEXT_LIMITS = {
9
+ 'claude-opus-4-6': 200000,
10
+ 'claude-sonnet-4-6': 200000,
11
+ 'claude-haiku-4-5-20251001': 200000,
12
+ 'claude-3-5-sonnet-20241022': 200000,
13
+ 'gpt-4o': 128000,
14
+ 'gpt-4o-mini': 128000,
15
+ 'gpt-4-turbo': 128000,
16
+ 'o1': 200000,
17
+ 'o1-mini': 128000,
18
+ };
19
+ const DEFAULT_CONTEXT_LIMIT = 128000;
20
+ function getModelContextLimit(model) {
21
+ // Check exact match
22
+ if (MODEL_CONTEXT_LIMITS[model])
23
+ return MODEL_CONTEXT_LIMITS[model];
24
+ // Check prefix match
25
+ for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
26
+ if (model.startsWith(key))
27
+ return limit;
28
+ }
29
+ return DEFAULT_CONTEXT_LIMIT;
30
+ }
31
+ /**
32
+ * Allocate token budget across tiers and pack context.
33
+ */
34
+ function packContext(systemPrompt, messages, retrievedChunks, model, tier1Exchanges, tier3Reserve, responseReserve) {
35
+ const contextLimit = getModelContextLimit(model);
36
+ // Calculate original tokens
37
+ const originalTokens = (0, chunker_js_1.estimateTokens)(systemPrompt || '') +
38
+ messages.reduce((sum, m) => sum + (0, chunker_js_1.estimateTokens)((0, canonical_js_1.getTextContent)(m)), 0);
39
+ const systemPromptTokens = (0, chunker_js_1.estimateTokens)(systemPrompt || '');
40
+ // Extract Tier 1: last N exchanges (user+assistant pairs)
41
+ const tier1Messages = [];
42
+ let exchangeCount = 0;
43
+ for (let i = messages.length - 1; i >= 0 && exchangeCount < tier1Exchanges; i--) {
44
+ tier1Messages.unshift(messages[i]);
45
+ if (messages[i].role === 'user')
46
+ exchangeCount++;
47
+ }
48
+ const tier1Tokens = tier1Messages.reduce((sum, m) => sum + (0, chunker_js_1.estimateTokens)((0, canonical_js_1.getTextContent)(m)), 0);
49
+ // Calculate available budget for Tier 2
50
+ const totalAvailable = contextLimit - systemPromptTokens - responseReserve;
51
+ const tier2Budget = Math.max(0, totalAvailable - tier1Tokens - tier3Reserve);
52
+ // Pack Tier 2 chunks greedily by score
53
+ const tier2Chunks = [];
54
+ let tier2Used = 0;
55
+ for (const chunk of retrievedChunks) {
56
+ if (tier2Used + chunk.metadata.tokenCount <= tier2Budget) {
57
+ tier2Chunks.push(chunk);
58
+ tier2Used += chunk.metadata.tokenCount;
59
+ }
60
+ }
61
+ // Tier 3: summary placeholder (will be filled by summary system later)
62
+ const tier3Summary = undefined;
63
+ const optimizedTokens = systemPromptTokens + tier1Tokens + tier2Used;
64
+ const allocation = {
65
+ systemPromptTokens,
66
+ tier1Tokens,
67
+ tier2Budget,
68
+ tier3Reserve,
69
+ responseReserve,
70
+ totalAvailable,
71
+ };
72
+ return {
73
+ systemPrompt,
74
+ tier1Messages,
75
+ tier2Chunks,
76
+ tier3Summary,
77
+ allocation,
78
+ originalTokens,
79
+ optimizedTokens,
80
+ savingsPercent: originalTokens > 0
81
+ ? Math.round((1 - optimizedTokens / originalTokens) * 100)
82
+ : 0,
83
+ };
84
+ }
85
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1,39 @@
1
+ export interface ContentBlock {
2
+ type: 'text' | 'image' | 'tool_use' | 'tool_result';
3
+ text?: string;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface CanonicalMessage {
7
+ role: 'system' | 'user' | 'assistant' | 'tool';
8
+ content: string | ContentBlock[];
9
+ timestamp?: number;
10
+ metadata?: {
11
+ provider?: string;
12
+ model?: string;
13
+ tokens?: number;
14
+ files?: string[];
15
+ tools?: string[];
16
+ sessionId?: string;
17
+ };
18
+ }
19
+ export interface CanonicalRequest {
20
+ messages: CanonicalMessage[];
21
+ systemPrompt?: string;
22
+ model: string;
23
+ stream: boolean;
24
+ maxTokens?: number;
25
+ temperature?: number;
26
+ tools?: unknown[];
27
+ rawHeaders: Record<string, string>;
28
+ providerAuth: string;
29
+ }
30
+ export interface CanonicalResponse {
31
+ content: string | ContentBlock[];
32
+ model: string;
33
+ stopReason?: string;
34
+ usage?: {
35
+ inputTokens: number;
36
+ outputTokens: number;
37
+ };
38
+ }
39
+ export declare function getTextContent(msg: CanonicalMessage): string;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTextContent = getTextContent;
4
+ function getTextContent(msg) {
5
+ if (typeof msg.content === 'string')
6
+ return msg.content;
7
+ return msg.content
8
+ .filter((b) => b.type === 'text' && b.text)
9
+ .map((b) => b.text)
10
+ .join('\n');
11
+ }
12
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1,9 @@
1
+ import type { CanonicalMessage } from './canonical.js';
2
+ import type { Chunk } from '../storage/types.js';
3
+ export declare function estimateTokens(text: string): number;
4
+ /**
5
+ * Chunk a conversation into indexable units.
6
+ * Each chunk = one user-assistant exchange pair.
7
+ * Long responses are split at paragraph boundaries.
8
+ */
9
+ export declare function chunkConversation(messages: CanonicalMessage[], sessionId: string, baseTimestamp?: number): Chunk[];