@andespindola/brainlink 0.1.0-beta.165 → 0.1.0-beta.166
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/dist/application/analyze-vault.js +1 -1
- package/dist/application/build-context.js +5 -6
- package/dist/application/server/routes.js +7 -1
- package/dist/cli/commands/read-commands.js +1 -1
- package/dist/cli/commands/write-commands.js +1 -1
- package/dist/infrastructure/config.js +13 -2
- package/dist/mcp/tools.js +3 -3
- package/docs/AGENT_USAGE.md +1 -1
- package/docs/QUICKSTART.md +2 -1
- package/package.json +1 -1
|
@@ -50,7 +50,7 @@ export const getExtendedStats = async (vaultPath, agentId) => {
|
|
|
50
50
|
await searchKnowledge(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), agentId, 'hybrid');
|
|
51
51
|
const searchLatency = performance.now() - searchStart;
|
|
52
52
|
const contextStart = performance.now();
|
|
53
|
-
await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid');
|
|
53
|
+
await buildContextPackage(absoluteVaultPath, probeQuery, Math.min(defaults.defaultSearchLimit, 8), defaults.defaultContextTokens, agentId, 'hybrid', undefined, defaults.defaultContextCacheTtlMs);
|
|
54
54
|
const contextLatency = performance.now() - contextStart;
|
|
55
55
|
return {
|
|
56
56
|
stats,
|
|
@@ -5,7 +5,6 @@ import { readContextPack, writeContextPack } from '../infrastructure/context-pac
|
|
|
5
5
|
import { indexStoragePath } from '../infrastructure/file-index.js';
|
|
6
6
|
import { searchVolatileMemory, volatileMemoryStoragePath } from '../infrastructure/volatile-memory.js';
|
|
7
7
|
import { searchKnowledge } from './search-knowledge.js';
|
|
8
|
-
const contextCacheTtlMs = 45_000;
|
|
9
8
|
const contextCacheMaxEntries = 200;
|
|
10
9
|
const contextCache = new Map();
|
|
11
10
|
const readFileSignature = async (path) => {
|
|
@@ -31,7 +30,7 @@ const withCacheMetadata = (context, cache) => ({
|
|
|
31
30
|
...context,
|
|
32
31
|
cache
|
|
33
32
|
});
|
|
34
|
-
const contextCacheGet = (key, dataSignature) => {
|
|
33
|
+
const contextCacheGet = (key, dataSignature, contextCacheTtlMs) => {
|
|
35
34
|
const entry = contextCache.get(key);
|
|
36
35
|
if (!entry) {
|
|
37
36
|
return undefined;
|
|
@@ -70,11 +69,11 @@ const emptyMetrics = (context, totalMs, overrides = {}) => ({
|
|
|
70
69
|
estimatedTokens: estimateSectionTokens(context),
|
|
71
70
|
...overrides
|
|
72
71
|
});
|
|
73
|
-
export const buildContextPackage = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy = 'rag') => {
|
|
72
|
+
export const buildContextPackage = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy = 'rag', contextCacheTtlMs = 120_000) => {
|
|
74
73
|
const totalStart = performance.now();
|
|
75
74
|
const cacheKey = toCacheKey(vaultPath, query, limit, maxTokens, agentId, mode, strategy);
|
|
76
75
|
const dataSignature = await readContextDataSignature(vaultPath);
|
|
77
|
-
const cached = contextCacheGet(cacheKey, dataSignature);
|
|
76
|
+
const cached = contextCacheGet(cacheKey, dataSignature, contextCacheTtlMs);
|
|
78
77
|
if (cached) {
|
|
79
78
|
return cached;
|
|
80
79
|
}
|
|
@@ -164,7 +163,7 @@ export const buildContextPackage = async (vaultPath, query, limit, maxTokens, ag
|
|
|
164
163
|
});
|
|
165
164
|
return contextWithMetrics;
|
|
166
165
|
};
|
|
167
|
-
export const buildContext = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy) => {
|
|
168
|
-
const contextPackage = await buildContextPackage(vaultPath, query, limit, maxTokens, agentId, mode, strategy);
|
|
166
|
+
export const buildContext = async (vaultPath, query, limit, maxTokens, agentId, mode, strategy, contextCacheTtlMs = 120_000) => {
|
|
167
|
+
const contextPackage = await buildContextPackage(vaultPath, query, limit, maxTokens, agentId, mode, strategy, contextCacheTtlMs);
|
|
169
168
|
return contextPackage.content;
|
|
170
169
|
};
|
|
@@ -28,6 +28,11 @@ const readContextStrategy = async (url) => {
|
|
|
28
28
|
const defaults = resolveAgentRuntimeDefaults(config, readAgentQuery(url));
|
|
29
29
|
return sanitizeContextStrategy(url.searchParams.get('strategy'), defaults.defaultContextStrategy);
|
|
30
30
|
};
|
|
31
|
+
const readContextCacheTtlMs = async (url) => {
|
|
32
|
+
const config = await loadBrainlinkConfig();
|
|
33
|
+
const defaults = resolveAgentRuntimeDefaults(config, readAgentQuery(url));
|
|
34
|
+
return defaults.defaultContextCacheTtlMs;
|
|
35
|
+
};
|
|
31
36
|
const hasInvalidSearchMode = (url) => {
|
|
32
37
|
const mode = url.searchParams.get('mode');
|
|
33
38
|
return mode !== null && !['fts', 'semantic', 'hybrid'].includes(mode);
|
|
@@ -381,13 +386,14 @@ export const route = async (request, url, vaultPath) => {
|
|
|
381
386
|
const tokens = parsePositiveInteger(url.searchParams.get('tokens'), 2000);
|
|
382
387
|
const mode = await readSearchMode(url);
|
|
383
388
|
const strategy = await readContextStrategy(url);
|
|
389
|
+
const contextCacheTtlMs = await readContextCacheTtlMs(url);
|
|
384
390
|
if (hasInvalidSearchMode(url)) {
|
|
385
391
|
return createResponse(createJsonResponse({ error: 'Invalid mode. Use fts, semantic or hybrid.' }), 400, contentTypes['.json']);
|
|
386
392
|
}
|
|
387
393
|
if (hasInvalidContextStrategy(url)) {
|
|
388
394
|
return createResponse(createJsonResponse({ error: 'Invalid strategy. Use rag, cag or auto.' }), 400, contentTypes['.json']);
|
|
389
395
|
}
|
|
390
|
-
return createResponse(createJsonResponse(await buildContextPackage(vaultPath, query, limit, tokens, readAgentQuery(url), mode, strategy)), 200, contentTypes['.json']);
|
|
396
|
+
return createResponse(createJsonResponse(await buildContextPackage(vaultPath, query, limit, tokens, readAgentQuery(url), mode, strategy, contextCacheTtlMs)), 200, contentTypes['.json']);
|
|
391
397
|
}
|
|
392
398
|
if (isReadMethod(request) && url.pathname === '/api/links') {
|
|
393
399
|
return createResponse(createJsonResponse({ links: await listLinks(vaultPath, readAgentQuery(url)) }), 200, contentTypes['.json']);
|
|
@@ -69,7 +69,7 @@ export const registerReadCommands = (program) => {
|
|
|
69
69
|
const resolved = await resolveOptions(options);
|
|
70
70
|
const mode = sanitizeSearchMode(options.mode, resolved.defaults.defaultSearchMode);
|
|
71
71
|
const strategy = sanitizeContextStrategy(options.strategy, resolved.defaults.defaultContextStrategy);
|
|
72
|
-
const contextPackage = await buildContextPackage(resolved.vault, query, parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit), parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens), resolved.agent, mode, strategy);
|
|
72
|
+
const contextPackage = await buildContextPackage(resolved.vault, query, parsePositiveInteger(options.limit ?? String(resolved.defaults.defaultSearchLimit), resolved.defaults.defaultSearchLimit), parsePositiveInteger(options.tokens ?? String(resolved.defaults.defaultContextTokens), resolved.defaults.defaultContextTokens), resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs);
|
|
73
73
|
print(options.json, contextPackage, () => contextPackage.content);
|
|
74
74
|
});
|
|
75
75
|
program
|
|
@@ -1135,7 +1135,7 @@ export const registerWriteCommands = (program) => {
|
|
|
1135
1135
|
const policy = await getBootstrapPolicy();
|
|
1136
1136
|
const bootstrapStatus = await getBootstrapSessionStatus(resolved.vault, resolved.agent);
|
|
1137
1137
|
const context = options.query
|
|
1138
|
-
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy)
|
|
1138
|
+
? await buildContextPackage(resolved.vault, options.query, limit, tokens, resolved.agent, mode, strategy, resolved.defaults.defaultContextCacheTtlMs)
|
|
1139
1139
|
: null;
|
|
1140
1140
|
const agentIntegration = options.installAgent === false
|
|
1141
1141
|
? null
|
|
@@ -14,6 +14,7 @@ export const defaultBrainlinkConfig = {
|
|
|
14
14
|
defaultSearchLimit: 10,
|
|
15
15
|
defaultContextTokens: 2000,
|
|
16
16
|
defaultContextStrategy: 'rag',
|
|
17
|
+
defaultContextCacheTtlMs: 120_000,
|
|
17
18
|
embeddingProvider: 'local',
|
|
18
19
|
defaultSearchMode: 'hybrid',
|
|
19
20
|
chunkSize: 1200,
|
|
@@ -90,11 +91,17 @@ const sanitizeAgentProfile = (value) => {
|
|
|
90
91
|
const defaultContextStrategy = typeof value.defaultContextStrategy === 'string' && contextStrategies.has(value.defaultContextStrategy)
|
|
91
92
|
? value.defaultContextStrategy
|
|
92
93
|
: undefined;
|
|
94
|
+
const defaultContextCacheTtlMs = typeof value.defaultContextCacheTtlMs === 'number' &&
|
|
95
|
+
Number.isFinite(value.defaultContextCacheTtlMs) &&
|
|
96
|
+
value.defaultContextCacheTtlMs > 0
|
|
97
|
+
? Math.floor(value.defaultContextCacheTtlMs)
|
|
98
|
+
: undefined;
|
|
93
99
|
const profile = {
|
|
94
100
|
...(defaultSearchLimit ? { defaultSearchLimit } : {}),
|
|
95
101
|
...(defaultContextTokens ? { defaultContextTokens } : {}),
|
|
96
102
|
...(defaultSearchMode ? { defaultSearchMode } : {}),
|
|
97
|
-
...(defaultContextStrategy ? { defaultContextStrategy } : {})
|
|
103
|
+
...(defaultContextStrategy ? { defaultContextStrategy } : {}),
|
|
104
|
+
...(defaultContextCacheTtlMs ? { defaultContextCacheTtlMs } : {})
|
|
98
105
|
};
|
|
99
106
|
return Object.keys(profile).length > 0 ? profile : null;
|
|
100
107
|
};
|
|
@@ -177,6 +184,9 @@ const sanitizeConfig = (value) => ({
|
|
|
177
184
|
? value.defaultContextTokens
|
|
178
185
|
: defaultBrainlinkConfig.defaultContextTokens,
|
|
179
186
|
defaultContextStrategy: sanitizeContextStrategy(value.defaultContextStrategy, defaultBrainlinkConfig.defaultContextStrategy),
|
|
187
|
+
defaultContextCacheTtlMs: typeof value.defaultContextCacheTtlMs === 'number' && Number.isFinite(value.defaultContextCacheTtlMs) && value.defaultContextCacheTtlMs > 0
|
|
188
|
+
? Math.floor(value.defaultContextCacheTtlMs)
|
|
189
|
+
: defaultBrainlinkConfig.defaultContextCacheTtlMs,
|
|
180
190
|
allowedVaults: [...sanitizeAllowedVaults(value.allowedVaults), ...readAllowedVaultsFromEnv()],
|
|
181
191
|
chunkSize: typeof value.chunkSize === 'number' && value.chunkSize > 0 ? value.chunkSize : defaultBrainlinkConfig.chunkSize,
|
|
182
192
|
searchPack: sanitizeSearchPackConfig(value.searchPack),
|
|
@@ -191,7 +201,8 @@ export const resolveAgentRuntimeDefaults = (config, agent) => {
|
|
|
191
201
|
defaultSearchLimit: profile?.defaultSearchLimit ?? config.defaultSearchLimit,
|
|
192
202
|
defaultContextTokens: profile?.defaultContextTokens ?? config.defaultContextTokens,
|
|
193
203
|
defaultSearchMode: profile?.defaultSearchMode ?? config.defaultSearchMode,
|
|
194
|
-
defaultContextStrategy: profile?.defaultContextStrategy ?? config.defaultContextStrategy
|
|
204
|
+
defaultContextStrategy: profile?.defaultContextStrategy ?? config.defaultContextStrategy,
|
|
205
|
+
defaultContextCacheTtlMs: profile?.defaultContextCacheTtlMs ?? config.defaultContextCacheTtlMs
|
|
195
206
|
};
|
|
196
207
|
};
|
|
197
208
|
const mergeConfigLayers = (layers) => layers.reduce((state, config) => ({
|
package/dist/mcp/tools.js
CHANGED
|
@@ -399,7 +399,7 @@ export const contextTool = async (input) => {
|
|
|
399
399
|
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
400
400
|
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
401
401
|
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
402
|
-
const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy);
|
|
402
|
+
const contextPackage = await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
|
|
403
403
|
const contextSession = await touchContextSession(context.vault, context.agent);
|
|
404
404
|
return jsonResult({
|
|
405
405
|
vault: context.vault,
|
|
@@ -718,7 +718,7 @@ export const syncTool = async (input) => {
|
|
|
718
718
|
const strategy = sanitizeContextStrategy(input.strategy, context.defaults.defaultContextStrategy);
|
|
719
719
|
const contextLimit = input.contextLimit ?? context.defaults.defaultSearchLimit;
|
|
720
720
|
const contextTokens = input.contextTokens ?? context.defaults.defaultContextTokens;
|
|
721
|
-
const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy);
|
|
721
|
+
const contextPackage = await buildContextPackage(context.vault, input.contextQuery, contextLimit, contextTokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs);
|
|
722
722
|
const contextSession = await touchContextSession(context.vault, context.agent);
|
|
723
723
|
return jsonResult({
|
|
724
724
|
...response,
|
|
@@ -740,7 +740,7 @@ export const bootstrapTool = async (input) => {
|
|
|
740
740
|
const limit = input.limit ?? context.defaults.defaultSearchLimit;
|
|
741
741
|
const tokens = input.tokens ?? context.defaults.defaultContextTokens;
|
|
742
742
|
const contextPackage = input.query
|
|
743
|
-
? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy)
|
|
743
|
+
? await buildContextPackage(context.vault, input.query, limit, tokens, context.agent, mode, strategy, context.defaults.defaultContextCacheTtlMs)
|
|
744
744
|
: undefined;
|
|
745
745
|
const contextSession = input.query ? await touchContextSession(context.vault, context.agent) : undefined;
|
|
746
746
|
const guidance = stats.documentCount === 0
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -51,7 +51,7 @@ Set `BRAINLINK_HOME` when the whole Brainlink home directory should live somewhe
|
|
|
51
51
|
Use `blink config where` and `blink config doctor` to inspect active paths and effective source.
|
|
52
52
|
|
|
53
53
|
You can also set `defaultAgent` in `brainlink.config.json` / `.brainlink.json` (for example `"defaultAgent": "coding-agent"`). When set, CLI commands and MCP calls reuse it when `--agent`/`agent` is not passed.
|
|
54
|
-
You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit`, `defaultContextTokens` and `
|
|
54
|
+
You can set `agentProfiles` to define per-agent defaults for `defaultSearchMode`, `defaultSearchLimit`, `defaultContextTokens`, `defaultContextStrategy` and `defaultContextCacheTtlMs`.
|
|
55
55
|
You can tune search-pack compression with `searchPack.rowChunkSize`, `searchPack.compressionLevel` and `searchPack.useDictionary`.
|
|
56
56
|
Guardrails for benchmark acceptance are configured with `searchPack.guardrailMinSavingsPercent` and `searchPack.guardrailMaxLatencyRegressionPercent`.
|
|
57
57
|
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -51,7 +51,8 @@ Optional per-agent retrieval defaults in `brainlink.config.json`:
|
|
|
51
51
|
"defaultSearchMode": "semantic",
|
|
52
52
|
"defaultSearchLimit": 8,
|
|
53
53
|
"defaultContextTokens": 2400,
|
|
54
|
-
"defaultContextStrategy": "auto"
|
|
54
|
+
"defaultContextStrategy": "auto",
|
|
55
|
+
"defaultContextCacheTtlMs": 120000
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
}
|
package/package.json
CHANGED