Files
oh-my-pi/packages/coding-agent/test/agent-session-force-tool-choice.test.ts
T
can1357 8c323666be feat: added ordered systemPrompt arrays and normalized context prompts
- Converted systemPrompt APIs and state types to ordered `string[]` across agent, AI, and coding-agent surfaces.
- Added `normalizeSystemPrompts` and applied it to context normalization before building provider request payloads.
- Updated AI providers to emit separate normalized prompt blocks/messages instead of a single merged system prompt.
- Removed dedicated `projectPrompt` state and remapped that context into system-context buckets in session, dump, and token accounting.
- Aligned tests and changelogs to pass and assert `systemPrompt` as arrays with ordered prompt semantics.
2026-05-04 15:20:26 +02:00

95 lines
3.2 KiB
TypeScript

import { afterEach, beforeEach, expect, it } from "bun:test";
import * as path from "node:path";
import { Agent, type AgentTool } from "@oh-my-pi/pi-agent-core";
import { getBundledModel } from "@oh-my-pi/pi-ai";
import { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage";
import { convertToLlm } from "@oh-my-pi/pi-coding-agent/session/messages";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { TempDir } from "@oh-my-pi/pi-utils";
import { Type } from "@sinclair/typebox";
class MockAssistantStream extends AssistantMessageEventStream {}
let tempDir: TempDir;
let authStorage: AuthStorage | undefined;
let session: AgentSession;
beforeEach(async () => {
tempDir = TempDir.createSync("@pi-agent-session-force-tool-");
const model = getBundledModel("anthropic", "claude-sonnet-4-5");
if (!model) throw new Error("Expected claude-sonnet-4-5 model to exist");
authStorage = await AuthStorage.create(path.join(tempDir.path(), "testauth.db"));
authStorage.setRuntimeApiKey("anthropic", "test-key");
const modelRegistry = new ModelRegistry(authStorage, path.join(tempDir.path(), "models.yml"));
const settings = Settings.isolated({ "compaction.enabled": false });
const sessionManager = SessionManager.inMemory(tempDir.path());
const bashTool: AgentTool = {
name: "bash",
label: "Bash",
description: "Mock bash tool",
parameters: Type.Object({}),
execute: async () => ({ content: [{ type: "text" as const, text: "ok" }] }),
};
const writeTool: AgentTool = {
name: "write",
label: "Write",
description: "Mock write tool",
parameters: Type.Object({}),
execute: async () => ({ content: [{ type: "text" as const, text: "ok" }] }),
};
const agent = new Agent({
getApiKey: () => "test-key",
initialState: {
model,
systemPrompt: ["Test"],
tools: [bashTool, writeTool],
messages: [],
},
convertToLlm,
streamFn: () => new MockAssistantStream(),
});
session = new AgentSession({
agent,
sessionManager,
settings,
modelRegistry,
toolRegistry: new Map([
[bashTool.name, bashTool],
[writeTool.name, writeTool],
]),
});
});
afterEach(async () => {
await session.dispose();
authStorage?.close();
authStorage = undefined;
tempDir.removeSync();
});
it("forces specific tool, then transitions to none, then clears", () => {
session.setForcedToolChoice("write");
const first = session.nextToolChoice();
const second = session.nextToolChoice();
const third = session.nextToolChoice();
expect(first).toEqual({ type: "tool", name: "write" });
// After the forced call, "none" prevents the loop from making more tool calls
expect(second).toBe("none");
// After "none" is consumed, override clears entirely
expect(third).toBeUndefined();
});
it("throws when forcing a non-active tool", () => {
expect(() => session.setForcedToolChoice("read")).toThrow('Tool "read" is not currently active.');
});