lynkr 2.0.0 → 3.0.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.
- package/README.md +226 -15
- package/docs/index.md +230 -11
- package/install.sh +260 -0
- package/package.json +4 -3
- package/src/clients/databricks.js +158 -0
- package/src/clients/routing.js +13 -1
- package/src/config/index.js +68 -1
- package/src/db/index.js +118 -0
- package/src/memory/extractor.js +350 -0
- package/src/memory/index.js +55 -0
- package/src/memory/retriever.js +266 -0
- package/src/memory/search.js +239 -0
- package/src/memory/store.js +411 -0
- package/src/memory/surprise.js +306 -0
- package/src/memory/tools.js +348 -0
- package/src/orchestrator/index.js +170 -0
- package/test/llamacpp-integration.test.js +686 -0
- package/test/memory/extractor.test.js +360 -0
- package/test/memory/retriever.test.js +583 -0
- package/test/memory/search.test.js +389 -0
- package/test/memory/store.test.js +312 -0
- package/test/memory/surprise.test.js +300 -0
- package/test/memory-performance.test.js +472 -0
- package/test/openai-integration.test.js +681 -0
|
@@ -1123,6 +1123,52 @@ async function runAgentLoop({
|
|
|
1123
1123
|
);
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
1126
|
+
// === MEMORY RETRIEVAL (Titans-inspired long-term memory) ===
|
|
1127
|
+
if (config.memory?.enabled !== false && steps === 1) {
|
|
1128
|
+
try {
|
|
1129
|
+
const memoryRetriever = require('../memory/retriever');
|
|
1130
|
+
|
|
1131
|
+
// Get last user message for query
|
|
1132
|
+
const lastUserMessage = cleanPayload.messages
|
|
1133
|
+
?.filter(m => m.role === 'user')
|
|
1134
|
+
?.pop();
|
|
1135
|
+
|
|
1136
|
+
if (lastUserMessage) {
|
|
1137
|
+
const query = memoryRetriever.extractQueryFromMessage(lastUserMessage);
|
|
1138
|
+
|
|
1139
|
+
if (query) {
|
|
1140
|
+
const relevantMemories = memoryRetriever.retrieveRelevantMemories(query, {
|
|
1141
|
+
limit: config.memory.retrievalLimit ?? 5,
|
|
1142
|
+
sessionId: session?.id,
|
|
1143
|
+
includeGlobal: config.memory.includeGlobalMemories !== false,
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
if (relevantMemories.length > 0) {
|
|
1147
|
+
logger.debug({
|
|
1148
|
+
sessionId: session?.id ?? null,
|
|
1149
|
+
memoriesRetrieved: relevantMemories.length,
|
|
1150
|
+
}, 'Injecting long-term memories into context');
|
|
1151
|
+
|
|
1152
|
+
// Inject memories into system prompt
|
|
1153
|
+
const injectedSystem = memoryRetriever.injectMemoriesIntoSystem(
|
|
1154
|
+
cleanPayload.system,
|
|
1155
|
+
relevantMemories,
|
|
1156
|
+
config.memory.injectionFormat ?? 'system'
|
|
1157
|
+
);
|
|
1158
|
+
|
|
1159
|
+
if (typeof injectedSystem === 'string') {
|
|
1160
|
+
cleanPayload.system = injectedSystem;
|
|
1161
|
+
} else if (injectedSystem.system) {
|
|
1162
|
+
cleanPayload.system = injectedSystem.system;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
logger.warn({ err, sessionId: session?.id }, 'Memory retrieval failed, continuing without memories');
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1126
1172
|
const databricksResponse = await invokeModel(cleanPayload);
|
|
1127
1173
|
|
|
1128
1174
|
// Handle streaming responses (pass through without buffering)
|
|
@@ -1870,6 +1916,106 @@ async function runAgentLoop({
|
|
|
1870
1916
|
hasToolUse: anthropicPayload.content?.some(c => c.type === 'tool_use')
|
|
1871
1917
|
}, "=== CONVERTED ANTHROPIC RESPONSE ===");
|
|
1872
1918
|
|
|
1919
|
+
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
1920
|
+
} else if (actualProvider === "openai") {
|
|
1921
|
+
const { convertOpenRouterResponseToAnthropic } = require("../clients/openrouter-utils");
|
|
1922
|
+
|
|
1923
|
+
// Validate OpenAI response has choices array before conversion
|
|
1924
|
+
if (!databricksResponse.json?.choices?.length) {
|
|
1925
|
+
logger.warn({
|
|
1926
|
+
json: databricksResponse.json,
|
|
1927
|
+
status: databricksResponse.status
|
|
1928
|
+
}, "OpenAI response missing choices array");
|
|
1929
|
+
|
|
1930
|
+
appendTurnToSession(session, {
|
|
1931
|
+
role: "assistant",
|
|
1932
|
+
type: "error",
|
|
1933
|
+
status: databricksResponse.status,
|
|
1934
|
+
content: databricksResponse.json,
|
|
1935
|
+
metadata: { termination: "malformed_response" },
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
const response = buildErrorResponse(databricksResponse);
|
|
1939
|
+
return {
|
|
1940
|
+
response,
|
|
1941
|
+
steps,
|
|
1942
|
+
durationMs: Date.now() - start,
|
|
1943
|
+
terminationReason: response.terminationReason,
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// Log OpenAI raw response
|
|
1948
|
+
logger.info({
|
|
1949
|
+
hasChoices: !!databricksResponse.json?.choices,
|
|
1950
|
+
choiceCount: databricksResponse.json?.choices?.length || 0,
|
|
1951
|
+
hasToolCalls: !!databricksResponse.json?.choices?.[0]?.message?.tool_calls,
|
|
1952
|
+
toolCallCount: databricksResponse.json?.choices?.[0]?.message?.tool_calls?.length || 0,
|
|
1953
|
+
finishReason: databricksResponse.json?.choices?.[0]?.finish_reason
|
|
1954
|
+
}, "=== OPENAI RAW RESPONSE ===");
|
|
1955
|
+
|
|
1956
|
+
// Convert OpenAI format to Anthropic format (reuse OpenRouter utility)
|
|
1957
|
+
anthropicPayload = convertOpenRouterResponseToAnthropic(
|
|
1958
|
+
databricksResponse.json,
|
|
1959
|
+
requestedModel,
|
|
1960
|
+
);
|
|
1961
|
+
|
|
1962
|
+
logger.info({
|
|
1963
|
+
contentBlocks: anthropicPayload.content?.length || 0,
|
|
1964
|
+
contentTypes: anthropicPayload.content?.map(c => c.type) || [],
|
|
1965
|
+
stopReason: anthropicPayload.stop_reason,
|
|
1966
|
+
hasToolUse: anthropicPayload.content?.some(c => c.type === 'tool_use')
|
|
1967
|
+
}, "=== CONVERTED ANTHROPIC RESPONSE (OpenAI) ===");
|
|
1968
|
+
|
|
1969
|
+
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
1970
|
+
} else if (actualProvider === "llamacpp") {
|
|
1971
|
+
const { convertOpenRouterResponseToAnthropic } = require("../clients/openrouter-utils");
|
|
1972
|
+
|
|
1973
|
+
// Validate llama.cpp response has choices array before conversion
|
|
1974
|
+
if (!databricksResponse.json?.choices?.length) {
|
|
1975
|
+
logger.warn({
|
|
1976
|
+
json: databricksResponse.json,
|
|
1977
|
+
status: databricksResponse.status
|
|
1978
|
+
}, "llama.cpp response missing choices array");
|
|
1979
|
+
|
|
1980
|
+
appendTurnToSession(session, {
|
|
1981
|
+
role: "assistant",
|
|
1982
|
+
type: "error",
|
|
1983
|
+
status: databricksResponse.status,
|
|
1984
|
+
content: databricksResponse.json,
|
|
1985
|
+
metadata: { termination: "malformed_response" },
|
|
1986
|
+
});
|
|
1987
|
+
|
|
1988
|
+
const response = buildErrorResponse(databricksResponse);
|
|
1989
|
+
return {
|
|
1990
|
+
response,
|
|
1991
|
+
steps,
|
|
1992
|
+
durationMs: Date.now() - start,
|
|
1993
|
+
terminationReason: response.terminationReason,
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Log llama.cpp raw response
|
|
1998
|
+
logger.info({
|
|
1999
|
+
hasChoices: !!databricksResponse.json?.choices,
|
|
2000
|
+
choiceCount: databricksResponse.json?.choices?.length || 0,
|
|
2001
|
+
hasToolCalls: !!databricksResponse.json?.choices?.[0]?.message?.tool_calls,
|
|
2002
|
+
toolCallCount: databricksResponse.json?.choices?.[0]?.message?.tool_calls?.length || 0,
|
|
2003
|
+
finishReason: databricksResponse.json?.choices?.[0]?.finish_reason
|
|
2004
|
+
}, "=== LLAMA.CPP RAW RESPONSE ===");
|
|
2005
|
+
|
|
2006
|
+
// Convert llama.cpp format to Anthropic format (reuse OpenRouter utility)
|
|
2007
|
+
anthropicPayload = convertOpenRouterResponseToAnthropic(
|
|
2008
|
+
databricksResponse.json,
|
|
2009
|
+
requestedModel,
|
|
2010
|
+
);
|
|
2011
|
+
|
|
2012
|
+
logger.info({
|
|
2013
|
+
contentBlocks: anthropicPayload.content?.length || 0,
|
|
2014
|
+
contentTypes: anthropicPayload.content?.map(c => c.type) || [],
|
|
2015
|
+
stopReason: anthropicPayload.stop_reason,
|
|
2016
|
+
hasToolUse: anthropicPayload.content?.some(c => c.type === 'tool_use')
|
|
2017
|
+
}, "=== CONVERTED ANTHROPIC RESPONSE (llama.cpp) ===");
|
|
2018
|
+
|
|
1873
2019
|
anthropicPayload.content = policy.sanitiseContent(anthropicPayload.content);
|
|
1874
2020
|
} else {
|
|
1875
2021
|
anthropicPayload = toAnthropicResponse(
|
|
@@ -2140,6 +2286,30 @@ async function runAgentLoop({
|
|
|
2140
2286
|
}
|
|
2141
2287
|
}
|
|
2142
2288
|
|
|
2289
|
+
// === MEMORY EXTRACTION (Titans-inspired long-term memory) ===
|
|
2290
|
+
if (config.memory?.enabled !== false && config.memory?.extraction?.enabled !== false) {
|
|
2291
|
+
setImmediate(async () => {
|
|
2292
|
+
try {
|
|
2293
|
+
const memoryExtractor = require('../memory/extractor');
|
|
2294
|
+
|
|
2295
|
+
const extractedMemories = await memoryExtractor.extractMemories(
|
|
2296
|
+
anthropicPayload,
|
|
2297
|
+
cleanPayload.messages,
|
|
2298
|
+
{ sessionId: session?.id }
|
|
2299
|
+
);
|
|
2300
|
+
|
|
2301
|
+
if (extractedMemories.length > 0) {
|
|
2302
|
+
logger.debug({
|
|
2303
|
+
sessionId: session?.id,
|
|
2304
|
+
memoriesExtracted: extractedMemories.length,
|
|
2305
|
+
}, 'Extracted and stored long-term memories');
|
|
2306
|
+
}
|
|
2307
|
+
} catch (err) {
|
|
2308
|
+
logger.warn({ err, sessionId: session?.id }, 'Memory extraction failed');
|
|
2309
|
+
}
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2143
2313
|
logger.info(
|
|
2144
2314
|
{
|
|
2145
2315
|
sessionId: session?.id ?? null,
|