/**
* Layer 1 : Hook Merger Tests for before_agent_start
*
* Validates that modelOverride and providerOverride fields are correctly
* propagated through the hook merger , including priority ordering and
* backward compatibility .
*/
import { beforeEach, describe, expect, it } from "vitest" ;
import { createHookRunner } from "./hooks.js" ;
import { addStaticTestHooks, 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;
};
const expectMergedBeforeAgentStart = async (
hooks: Array<{
pluginId: string;
result: PluginHookBeforeAgentStartResult;
priority?: number;
}>,
expected: Partial<PluginHookBeforeAgentStartResult>,
) => {
const result = await runWithHooks(hooks);
expect(result).toEqual(expect.objectContaining(expected));
return result;
};
const runWithHooks = async (
hooks: Array<{
pluginId: string;
result: PluginHookBeforeAgentStartResult;
priority?: number;
}>,
) => {
addStaticTestHooks(registry, {
hookName: "before_agent_start" ,
hooks,
});
const runner = createHookRunner(registry);
return await runner.runBeforeAgentStart({ prompt: "hello" }, stubCtx);
};
it.each([
[
"returns modelOverride from a single plugin" ,
{ modelOverride: "llama3.3:8b" },
{
modelOverride: "llama3.3:8b" ,
},
],
[
"returns providerOverride from a single plugin" ,
{ providerOverride: "ollama" },
{
providerOverride: "ollama" ,
},
],
[
"returns both modelOverride and providerOverride together" ,
{
modelOverride: "llama3.3:8b" ,
providerOverride: "ollama" ,
},
{
modelOverride: "llama3.3:8b" ,
providerOverride: "ollama" ,
},
],
[
"systemPrompt merges correctly alongside model overrides" ,
{
systemPrompt: "You are a helpful assistant" ,
modelOverride: "llama3.3:8b" ,
providerOverride: "ollama" ,
},
{
systemPrompt: "You are a helpful assistant" ,
modelOverride: "llama3.3:8b" ,
providerOverride: "ollama" ,
},
],
] as const )("%s" , async (_name, hookResult, expected) => {
await expectMergedBeforeAgentStart([{ pluginId: "plugin-a" , result: hookResult }], expected);
});
it("higher-priority plugin wins for modelOverride" , async () => {
const result = await expectMergedBeforeAgentStart(
[
{ pluginId: "low-priority" , result: { modelOverride: "gpt-5.4" }, priority: 1 },
{ pluginId: "high-priority" , result: { modelOverride: "llama3.3:8b" }, priority: 10 },
],
{ modelOverride: "llama3.3:8b" },
);
expect(result?.modelOverride).toBe("llama3.3:8b" );
});
it("lower-priority plugin does not overwrite if it returns undefined" , async () => {
const result = await runWithHooks([
{
pluginId: "high-priority" ,
result: { modelOverride: "llama3.3:8b" , providerOverride: "ollama" },
priority: 10 ,
},
{
pluginId: "low-priority" ,
result: { prependContext: "some context" },
priority: 1 ,
},
]);
// High-priority ran first (priority 10), low-priority ran second (priority 1).
// Low-priority didn't return modelOverride, so ?? falls back to acc's value.
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 () => {
const result = await runWithHooks([
{
pluginId: "plugin-a" ,
result: { prependContext: "context A" , modelOverride: "llama3.3:8b" },
priority: 10 ,
},
{
pluginId: "plugin-b" ,
result: { prependContext: "context B" },
priority: 1 ,
},
]);
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 () => {
const result = await runWithSingleHook({ prependContext: "legacy context" });
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("passes runId through the agent context to hook handlers" , async () => {
const registry = createEmptyPluginRegistry();
let capturedCtx: typeof stubCtx | undefined;
addTestHook({
registry,
pluginId: "ctx-spy" ,
hookName: "before_agent_start" ,
handler: ((_event: unknown, ctx: typeof stubCtx) => {
capturedCtx = ctx;
return {};
}) as PluginHookRegistration["handler" ],
});
const runner = createHookRunner(registry);
await runner.runBeforeAgentStart({ prompt: "test" }, stubCtx);
expect(capturedCtx).toBeDefined();
expect(capturedCtx?.runId).toBe("test-run-id" );
});
});
Messung V0.5 in Prozent C=99 H=100 G=99
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland