test(runtime): guard pi package contracts
This commit is contained in:
1 parent
c26e7420fb
commit
245586a9aa
6 files changed
+293
-26
No files matched your search
@@ -0,0 +1,144 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { Agent } from "@mariozechner/pi-agent-core";
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
import { loadSkills } from "@mariozechner/pi-coding-agent";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildAgentHooks } from "../../src/main/agent-runtime/agent-hooks";
|
||||
import {
|
||||
createPiAgent,
|
||||
resolveThinkingLevel,
|
||||
} from "../../src/main/worker/runtime/pi-agent-factory";
|
||||
import {
|
||||
createRunScopedBashOptions,
|
||||
createRunScopedCodingTools,
|
||||
} from "../../src/main/worker/runtime/run-tools";
|
||||
import type { WorkerRpcClient } from "../../src/main/worker/entry/rpc";
|
||||
|
||||
function createModel(): Model<"openai-responses"> {
|
||||
return {
|
||||
id: "contract-model",
|
||||
name: "Contract Model",
|
||||
api: "openai-responses",
|
||||
provider: "openai",
|
||||
baseUrl: "https://example.invalid",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 8192,
|
||||
maxTokens: 2048,
|
||||
};
|
||||
}
|
||||
|
||||
function createRpc(): WorkerRpcClient {
|
||||
return {
|
||||
invoke: async () => {
|
||||
throw new Error("Unexpected RPC call in pi runtime contract test.");
|
||||
},
|
||||
} as unknown as WorkerRpcClient;
|
||||
}
|
||||
|
||||
describe("pi runtime integration contract", () => {
|
||||
it("builds the AgentCompany pi Agent boundary with sequential execution and hooks", () => {
|
||||
const agent = createPiAgent({
|
||||
runId: "run-contract",
|
||||
taskSessionId: "session-contract",
|
||||
systemPrompt: "Contract prompt",
|
||||
model: createModel(),
|
||||
tools: [],
|
||||
priorMessages: [],
|
||||
thinkingLevel: resolveThinkingLevel("high"),
|
||||
apiKey: "contract-key",
|
||||
extraHeaders: { "x-contract": "true" },
|
||||
rpc: createRpc(),
|
||||
});
|
||||
|
||||
expect(agent).toBeInstanceOf(Agent);
|
||||
expect(agent.sessionId).toBe("session-contract");
|
||||
expect(agent.toolExecution).toBe("sequential");
|
||||
expect(agent.state.systemPrompt).toBe("Contract prompt");
|
||||
expect(agent.state.thinkingLevel).toBe("high");
|
||||
expect(agent.state.tools).toEqual([]);
|
||||
expect(agent.state.messages).toEqual([]);
|
||||
expect(typeof agent.beforeToolCall).toBe("function");
|
||||
expect(typeof agent.afterToolCall).toBe("function");
|
||||
expect(typeof agent.transformContext).toBe("function");
|
||||
});
|
||||
|
||||
it("keeps the coding-tool surface expected by worker runs", () => {
|
||||
const workspaceDir = mkdtempSync(join(tmpdir(), "agentcompany-pi-tools-"));
|
||||
try {
|
||||
const tools = createRunScopedCodingTools(workspaceDir, { AGENTCOMPANY_CONTRACT: "1" });
|
||||
expect(tools.map((tool) => tool.name)).toEqual(["read", "bash", "edit", "write"]);
|
||||
for (const tool of tools) {
|
||||
expect(typeof tool.execute).toBe("function");
|
||||
}
|
||||
} finally {
|
||||
rmSync(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves run-scoped environment through the bash spawn hook", () => {
|
||||
const options = createRunScopedBashOptions({
|
||||
AGENTCOMPANY_CONTRACT: "1",
|
||||
PATH: "/contract/bin",
|
||||
});
|
||||
const result = options.spawnHook?.({
|
||||
command: "pwd",
|
||||
cwd: "/tmp",
|
||||
env: {
|
||||
HOME: "/home/contract",
|
||||
PATH: "/base/bin",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result?.command).toBe("pwd");
|
||||
expect(result?.cwd).toBe("/tmp");
|
||||
expect(result?.env.HOME).toBe("/home/contract");
|
||||
expect(result?.env.PATH).toBe("/contract/bin");
|
||||
expect(result?.env.AGENTCOMPANY_CONTRACT).toBe("1");
|
||||
});
|
||||
|
||||
it("loads progressive-disclosure skills through the pinned pi runtime", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "agentcompany-pi-skills-"));
|
||||
const skillDir = join(root, "contract-skill");
|
||||
mkdirSync(skillDir);
|
||||
writeFileSync(
|
||||
join(skillDir, "SKILL.md"),
|
||||
[
|
||||
"---",
|
||||
"name: contract-skill",
|
||||
"description: Contract skill loaded by the pinned pi runtime.",
|
||||
"---",
|
||||
"# Contract Skill",
|
||||
"",
|
||||
"Use this skill to prove the runtime loader contract.",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
try {
|
||||
const result = loadSkills({ skillPaths: [skillDir], includeDefaults: false });
|
||||
expect(result.diagnostics).toEqual([]);
|
||||
expect(result.skills).toHaveLength(1);
|
||||
expect(result.skills[0]?.name).toBe("contract-skill");
|
||||
expect(result.skills[0]?.description).toBe("Contract skill loaded by the pinned pi runtime.");
|
||||
expect(result.skills[0]?.filePath).toBe(join(skillDir, "SKILL.md"));
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps the aggregated hook bundle assignable to pi Agent options at runtime", () => {
|
||||
const hooks = buildAgentHooks({ runId: "run-contract", rpc: createRpc() });
|
||||
expect(Object.keys(hooks).sort()).toEqual([
|
||||
"afterToolCall",
|
||||
"beforeToolCall",
|
||||
"transformContext",
|
||||
]);
|
||||
expect(typeof hooks.beforeToolCall).toBe("function");
|
||||
expect(typeof hooks.afterToolCall).toBe("function");
|
||||
expect(typeof hooks.transformContext).toBe("function");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user