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.
@@ -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,