| |
| |
| |
| |
| |
| |
| |
| import { beforeEach, describe, expect, it } from "vitest"; |
| import { createHookRunner } from "./hooks.js"; |
| import { addTestHook, TEST_PLUGIN_AGENT_CTX } from "./hooks.test-helpers.js"; |
| import { createEmptyPluginRegistry, type PluginRegistry } from "./registry.js"; |
| import type { PluginHookBeforeAgentStartResult, PluginHookRegistration } from "./types.js"; |
|
|
| function addBeforeAgentStartHook( |
| registry: PluginRegistry, |
| pluginId: string, |
| handler: () => PluginHookBeforeAgentStartResult | Promise<PluginHookBeforeAgentStartResult>, |
| priority?: number, |
| ) { |
| addTestHook({ |
| registry, |
| pluginId, |
| hookName: "before_agent_start", |
| handler: handler as PluginHookRegistration["handler"], |
| priority, |
| }); |
| } |
|
|
| const stubCtx = TEST_PLUGIN_AGENT_CTX; |
|
|
| describe("before_agent_start hook merger", () => { |
| let registry: PluginRegistry; |
|
|
| beforeEach(() => { |
| registry = createEmptyPluginRegistry(); |
| }); |
|
|
| const runWithSingleHook = async (result: PluginHookBeforeAgentStartResult, priority?: number) => { |
| addBeforeAgentStartHook(registry, "plugin-a", () => result, priority); |
| const runner = createHookRunner(registry); |
| return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
| }; |
|
|
| const expectSingleModelOverride = async (modelOverride: string) => { |
| const result = await runWithSingleHook({ modelOverride }); |
| expect(result?.modelOverride).toBe(modelOverride); |
| return result; |
| }; |
|
|
| it("returns modelOverride from a single plugin", async () => { |
| await expectSingleModelOverride("llama3.3:8b"); |
| }); |
|
|
| it("returns providerOverride from a single plugin", async () => { |
| const result = await runWithSingleHook({ |
| providerOverride: "ollama", |
| }); |
| expect(result?.providerOverride).toBe("ollama"); |
| }); |
|
|
| it("returns both modelOverride and providerOverride together", async () => { |
| addBeforeAgentStartHook(registry, "plugin-a", () => ({ |
| modelOverride: "llama3.3:8b", |
| providerOverride: "ollama", |
| })); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| expect(result?.modelOverride).toBe("llama3.3:8b"); |
| expect(result?.providerOverride).toBe("ollama"); |
| }); |
|
|
| it("higher-priority plugin wins for modelOverride", async () => { |
| addBeforeAgentStartHook(registry, "low-priority", () => ({ modelOverride: "gpt-4o" }), 1); |
| addBeforeAgentStartHook( |
| registry, |
| "high-priority", |
| () => ({ modelOverride: "llama3.3:8b" }), |
| 10, |
| ); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "PII prompt" }, stubCtx); |
|
|
| expect(result?.modelOverride).toBe("llama3.3:8b"); |
| }); |
|
|
| it("lower-priority plugin does not overwrite if it returns undefined", async () => { |
| addBeforeAgentStartHook( |
| registry, |
| "high-priority", |
| () => ({ modelOverride: "llama3.3:8b", providerOverride: "ollama" }), |
| 10, |
| ); |
| addBeforeAgentStartHook( |
| registry, |
| "low-priority", |
| () => ({ prependContext: "some context" }), |
| 1, |
| ); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| |
| |
| expect(result?.modelOverride).toBe("llama3.3:8b"); |
| expect(result?.providerOverride).toBe("ollama"); |
| expect(result?.prependContext).toBe("some context"); |
| }); |
|
|
| it("prependContext still concatenates when modelOverride is present", async () => { |
| addBeforeAgentStartHook( |
| registry, |
| "plugin-a", |
| () => ({ |
| prependContext: "context A", |
| modelOverride: "llama3.3:8b", |
| }), |
| 10, |
| ); |
| addBeforeAgentStartHook( |
| registry, |
| "plugin-b", |
| () => ({ |
| prependContext: "context B", |
| }), |
| 1, |
| ); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| expect(result?.prependContext).toBe("context A\n\ncontext B"); |
| expect(result?.modelOverride).toBe("llama3.3:8b"); |
| }); |
|
|
| it("backward compat: plugin returning only prependContext produces no modelOverride", async () => { |
| addBeforeAgentStartHook(registry, "legacy-plugin", () => ({ |
| prependContext: "legacy context", |
| })); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| expect(result?.prependContext).toBe("legacy context"); |
| expect(result?.modelOverride).toBeUndefined(); |
| expect(result?.providerOverride).toBeUndefined(); |
| }); |
|
|
| it("modelOverride without providerOverride leaves provider undefined", async () => { |
| const result = await expectSingleModelOverride("llama3.3:8b"); |
| expect(result?.providerOverride).toBeUndefined(); |
| }); |
|
|
| it("returns undefined when no hooks are registered", async () => { |
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| expect(result).toBeUndefined(); |
| }); |
|
|
| it("systemPrompt merges correctly alongside model overrides", async () => { |
| addBeforeAgentStartHook(registry, "plugin-a", () => ({ |
| systemPrompt: "You are a helpful assistant", |
| modelOverride: "llama3.3:8b", |
| providerOverride: "ollama", |
| })); |
|
|
| const runner = createHookRunner(registry); |
| const result = await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx); |
|
|
| expect(result?.systemPrompt).toBe("You are a helpful assistant"); |
| expect(result?.modelOverride).toBe("llama3.3:8b"); |
| expect(result?.providerOverride).toBe("ollama"); |
| }); |
| }); |
|
|