Files
OmniRoute/tests/unit/chatcore-memory-skills-injection.test.ts
Diego Rodrigues de Sa e Souza 871832820f fix(memory): enable agent memory save via MCP tools + builtin stream guard (#10887)
Merged — clean single-commit extraction from #9115's genuinely new content (see PR body for the full extraction rationale: 66-commit branch, only 1 commit matched the stated scope). typecheck/file-size/changelog gates clean, 19/19 unit + 14/14 integration tests passing.
2026-08-20 17:28:37 -03:00

245 lines
9.4 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
// Isolated DATA_DIR set BEFORE importing anything that touches the DB
// (injectMemoryAndSkills -> getMemorySettings / retrieveMemories / injectSkills).
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-mem-skills-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const { getSkillsProviderForFormat, injectMemoryAndSkills } =
await import("../../open-sse/handlers/chatCore/memorySkillsInjection.ts");
const { FORMATS } = await import("../../open-sse/translator/formats.ts");
const core = await import("../../src/lib/db/core.ts");
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
// ─── getSkillsProviderForFormat (pure switch) ────────────────────────────────
test("getSkillsProviderForFormat maps CLAUDE -> anthropic", () => {
assert.equal(getSkillsProviderForFormat(FORMATS.CLAUDE), "anthropic");
});
test("getSkillsProviderForFormat maps GEMINI -> google", () => {
assert.equal(getSkillsProviderForFormat(FORMATS.GEMINI), "google");
});
test("getSkillsProviderForFormat maps OPENAI and any unknown format -> openai (default)", () => {
assert.equal(getSkillsProviderForFormat(FORMATS.OPENAI), "openai");
// any other / unknown format falls through to the default branch
assert.equal(getSkillsProviderForFormat("removed-google-cli"), "openai");
assert.equal(getSkillsProviderForFormat("codex"), "openai");
assert.equal(getSkillsProviderForFormat("totally-unknown"), "openai");
assert.equal(getSkillsProviderForFormat(""), "openai");
});
// ─── injectMemoryAndSkills ───────────────────────────────────────────────────
test("injectMemoryAndSkills with memoryOwnerId=null skips both branches and returns the body unchanged", async () => {
// memoryOwnerId is null -> memorySettings stays null -> the memory guard is false
// (no getMemorySettings/retrieveMemories) AND the skills guard (memorySettings?.skillsEnabled)
// is false. The body is returned verbatim with memorySettings=null.
const body: Record<string, unknown> = {
model: "gpt-4o",
messages: [{ role: "user", content: "hello world" }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: null,
provider: "openai",
effectiveModel: "gpt-4o",
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.OPENAI,
backgroundReason: null,
log: null,
});
assert.equal(result.memorySettings, null, "memorySettings is null when no owner is provided");
// body is returned as-is (same reference, no injection happened)
assert.equal(result.body, body);
assert.deepEqual(result.body.messages, [{ role: "user", content: "hello world" }]);
assert.equal("tools" in result.body, false, "no skills were injected");
});
test("injectMemoryAndSkills with an empty DB resolves settings, finds nothing to inject, returns body unchanged", async () => {
// memoryOwnerId is set -> getMemorySettings() resolves DB defaults (enabled, skillsEnabled).
// The body has NO `messages` array (only `input`), so shouldInjectMemory() returns false and
// the memory-retrieval branch is skipped. The skills branch runs injectSkills(), but the
// empty DB registry has no skills, so mergedTools.length == existingTools.length and the body
// is NOT cloned/mutated. This exercises the realistic "nothing to inject" path end-to-end.
const log = {
debug: (..._args: unknown[]) => {
/* swallow */
},
};
const body: Record<string, unknown> = {
model: "gpt-4o",
input: [{ role: "user", content: "no messages array here" }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: "owner-empty-db",
provider: "openai",
effectiveModel: "gpt-4o",
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.OPENAI,
backgroundReason: null,
log,
});
// memorySettings was resolved (defaults) — it is a real object, not null.
assert.ok(result.memorySettings, "memorySettings resolved from DB defaults");
// PRD-2026-06-19: memory is now OFF by default (skills still default on).
assert.equal(result.memorySettings.enabled, false);
assert.equal(result.memorySettings.skillsEnabled, true);
// No skills in the empty registry -> body returned unchanged (same reference).
assert.equal(result.body, body);
assert.equal("tools" in result.body, false, "no skills injected from an empty registry");
});
test("injectMemoryAndSkills resolves cleanly for a CLAUDE-format body with no owner (provider-mapping path)", async () => {
// Characterizes the no-owner short-circuit for a non-OpenAI source format. Nothing is
// injected; the function just returns the body untouched and memorySettings=null.
const body: Record<string, unknown> = {
model: "claude-3-5-sonnet",
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: null,
provider: "claude",
effectiveModel: "claude-3-5-sonnet",
sourceFormat: FORMATS.CLAUDE,
targetFormat: FORMATS.CLAUDE,
backgroundReason: "background-task",
log: null,
});
assert.equal(result.memorySettings, null);
assert.equal(result.body, body);
});
test("injectMemoryAndSkills injects memory tools when memory is enabled", async () => {
const { updateSettings } = await import("../../src/lib/db/settings.ts");
const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
const { MEMORY_BUILTIN_TOOL_NAMES } = await import("../../src/lib/skills/memoryBuiltins.ts");
await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000 });
invalidateMemorySettingsCache();
const body: Record<string, unknown> = {
model: "gpt-4o",
messages: [{ role: "user", content: "hello" }],
tools: [{ type: "function", function: { name: "some_client_tool", description: "x" } }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: "owner-mem-on",
provider: "openai",
effectiveModel: "gpt-4o",
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.OPENAI,
backgroundReason: null,
log: { debug: () => {} },
});
assert.equal(result.memorySettings?.enabled, true);
const toolNames = (result.body.tools as { function?: { name?: string }; name?: string }[]).map(
(tool) => tool.function?.name ?? tool.name
);
for (const memoryTool of MEMORY_BUILTIN_TOOL_NAMES) {
assert.ok(
toolNames.includes(memoryTool),
`expected ${memoryTool} to be injected into body.tools`
);
}
assert.ok(toolNames.includes("some_client_tool"), "client tools are preserved");
invalidateMemorySettingsCache();
});
test("injectMemoryAndSkills does not inject server memory tools for stream requests", async () => {
const { updateSettings } = await import("../../src/lib/db/settings.ts");
const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
const { MEMORY_BUILTIN_TOOL_NAMES } = await import("../../src/lib/skills/memoryBuiltins.ts");
await updateSettings({ memoryEnabled: true, memoryMaxTokens: 2000 });
invalidateMemorySettingsCache();
const body: Record<string, unknown> = {
model: "gpt-4o",
stream: true,
messages: [{ role: "user", content: "hello" }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: "owner-stream",
provider: "openai",
effectiveModel: "gpt-4o",
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.OPENAI,
backgroundReason: null,
log: { debug: () => {} },
});
assert.equal(result.memorySettings?.enabled, true);
const tools = (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? [];
const toolNames = tools.map((tool) => tool.function?.name ?? tool.name);
for (const memoryTool of MEMORY_BUILTIN_TOOL_NAMES) {
assert.equal(
toolNames.includes(memoryTool),
false,
`expected ${memoryTool} to be absent for stream requests (client-side MCP path)`
);
}
invalidateMemorySettingsCache();
});
test("injectMemoryAndSkills does not inject memory tools when memory is disabled", async () => {
const { updateSettings } = await import("../../src/lib/db/settings.ts");
const { invalidateMemorySettingsCache } = await import("../../src/lib/memory/settings.ts");
const { MEMORY_BUILTIN_TOOL_NAMES } = await import("../../src/lib/skills/memoryBuiltins.ts");
await updateSettings({ memoryEnabled: false });
invalidateMemorySettingsCache();
const body: Record<string, unknown> = {
model: "gpt-4o",
messages: [{ role: "user", content: "hello" }],
};
const result = await injectMemoryAndSkills({
body,
memoryOwnerId: "owner-mem-off",
provider: "openai",
effectiveModel: "gpt-4o",
sourceFormat: FORMATS.OPENAI,
targetFormat: FORMATS.OPENAI,
backgroundReason: null,
log: { debug: () => {} },
});
const tools = (result.body.tools as { function?: { name?: string }; name?: string }[] | undefined) ?? [];
const toolNames = tools.map((tool) => tool.function?.name ?? tool.name);
for (const memoryTool of MEMORY_BUILTIN_TOOL_NAMES) {
assert.equal(
toolNames.includes(memoryTool),
false,
`expected ${memoryTool} to be absent when memory is disabled`
);
}
invalidateMemorySettingsCache();
});