1f7eb0ac59db8eb33a1af9a7ce97823002c7480c
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/AGENTS.md b/AGENTS.md
2index 4dc50c1befccb890aa3c2ed984e264b2622629f1..f3f312530f39d8b8b594ae48461a768a3624dcaa 100644
3--- a/AGENTS.md
4+++ b/AGENTS.md
5@@ -12,11 +12,12 @@ This package provides a policy plugin for Pi. It combines host-specific precheck
6
7 The package root and `./pi` export the Pi extension. `src/index.ts` re-exports `./pi/index` for Pi extension loading.
8
9+Your general goal is to keep this code simple. Don't add things you're not asked for. If given a choice, do less.
10+
11 ## Policy flow
12
13 Pi evaluates session Bash overrides and static permissions before deterministic rules. It parses valid Bash into command nodes before evaluation. Unparsed Bash does not receive a deterministic allow and falls back to LLM review.
14
15-
16diff --git a/README.md b/README.md
17index db5cdb5ab274ba918fbd9442a1a5272202a66200..3f4f9b2f631e412146fb4f8aeae87730f9af58bf 100644
18--- a/README.md
19+++ b/README.md
20@@ -36,12 +36,15 @@ Use a Pi-configured model. This uses Pi's provider transport and authentication,
21 "kind": "pi",
22 "provider": "openai-codex",
23 "model": "gpt-5.6-luna",
24- "reasoningEffort": "minimal"
25+ "reasoningEffort": "minimal",
26+ "promptCacheKey": "policy-engine-v1"
27 }
28 }
29 ```
30
31-Pi reviewers accept `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. The selected Pi model determines the supported levels.
32diff --git a/src/core/config.ts b/src/core/config.ts
33index 790311e607c628b013eb3c241dc78ab18cfe6fff..45a6b36099f7c3d2879f377d5267f96a00396756 100644
34--- a/src/core/config.ts
35+++ b/src/core/config.ts
36@@ -51,5 +51,15 @@ export function parseReviewerConfig(value: unknown): ReviewerConfig {
37
38 const provider = stringValue(reviewer.provider)
39 if (!provider) throw new Error("reviewer.provider must be a non-empty string")
40- return { kind: "pi", provider, model, reasoningEffort: reasoningEffort(reviewer.reasoningEffort) }
41+ const promptCacheKey = stringValue(reviewer.promptCacheKey)
42+ if (reviewer.promptCacheKey !== undefined && !promptCacheKey) {
43+ throw new Error("reviewer.promptCacheKey must be a non-empty string")
44+ }
45+ return {
46+ kind: "pi",
47+ provider,
48+ model,
49+ reasoningEffort: reasoningEffort(reviewer.reasoningEffort),
50+ promptCacheKey,
51+ }
52 }
53diff --git a/src/core/review.ts b/src/core/review.ts
54index 0747e1b142d86dc4ff924481e4bf81f9fc71634d..b3261fafc4db3cfca84028a8986b7f6e28b12118 100644
55--- a/src/core/review.ts
56+++ b/src/core/review.ts
57@@ -73,7 +73,7 @@ export function createPolicyReviewer(
58 ]
59 const rawResponse = config.kind === "pi"
60 ? await callPiReviewer(config, messages, 512, context)
61- : await callLlamaReviewer(config, messages, 512)
62+ : await callLlamaReviewer(config, messages, 1024)
63 const decision = parseLLMResponse(rawResponse)
64 if (decision) return { decision, rawResponse }
65 return {
66diff --git a/src/core/types.ts b/src/core/types.ts
67index e48ccb34888889ddc4f4ceddba5697ee887c7667..c0767d0178e75f99f85bf3e9f890c72bcd054ebf 100644
68--- a/src/core/types.ts
69+++ b/src/core/types.ts
70@@ -55,6 +55,7 @@ export type ReviewerConfig =
71 provider: string
72 model: string
73 reasoningEffort?: PiReasoningEffort
74+ promptCacheKey?: string
75 }
76 | {
77 kind: "llama.cpp"
78diff --git a/src/pi/index.ts b/src/pi/index.ts
79index 0e1145053d48ec697657c9eaea14eb774628b151..576f69b4c4e4fa545db36ec39be5b7c8b84cc434 100644
80--- a/src/pi/index.ts
81+++ b/src/pi/index.ts
82@@ -52,7 +52,17 @@ function bypassesPiPolicy(toolName: string): boolean {
83 }
84
85 function piReviewerCaller(ctx: ExtensionContext): PiReviewerCaller {
86diff --git a/test/core/llm.test.ts b/test/core/llm.test.ts
87index 59caf94801e0bb1b281ce419c410d4e1a0280a46..2232e168e34ab222a4b055bcf06521c757f35c75 100644
88--- a/test/core/llm.test.ts
89+++ b/test/core/llm.test.ts
90@@ -9,6 +9,7 @@ const baseUrl = process.env.POLICY_EVAL_BASE_URL?.trim()
91 const provider = process.env.POLICY_EVAL_PROVIDER?.trim() ?? "openai-codex"
92 const model = process.env.POLICY_EVAL_MODEL?.trim()
93 const reasoningEffort = process.env.POLICY_EVAL_REASONING_EFFORT?.trim() ?? "minimal"
94+const promptCacheKey = process.env.POLICY_EVAL_PROMPT_CACHE_KEY?.trim() ?? "policy-engine-evaluation-v1"
95
96 function piReasoningEffort(value: string): PiReasoningEffort {
97 if (value === "none" || value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max") {
98@@ -40,9 +41,16 @@ async function createReviewer(): Promise<PolicyReviewer | undefined> {
99 const piModel = runtime.getModel(provider, model)
100 if (!piModel) throw new Error(`Pi model is unavailable: ${provider}/${model}`)
101 const effort = piReasoningEffort(reasoningEffort)
102+ const usesOpenAIPromptCache = piModel.api === "openai-codex-responses" || piModel.api === "openai-responses"
103
104 return createPolicyReviewer(
105- { kind: "pi", provider, model, reasoningEffort: effort },
106+ {
107+ kind: "pi",
108+ provider,
109+ model,
110+ reasoningEffort: effort,
111+ ...(usesOpenAIPromptCache && { promptCacheKey }),
112+ },
113 async (_config, messages, _maxTokens, context) => {
114 const systemPrompt = messages.find((message) => message.role === "system")?.content
115 const userMessages: UserMessage[] = messages
116@@ -55,7 +63,11 @@ async function createReviewer(): Promise<PolicyReviewer | undefined> {
117 const response = await runtime.complete(
118 piModel,
119 { systemPrompt, messages: userMessages },
120- { signal: context.signal, reasoningEffort: effort },
121+ {
122+ signal: context.signal,
123+ reasoningEffort: effort,
124+ ...(usesOpenAIPromptCache && { sessionId: promptCacheKey }),
125+ },
126 )
127 if (response.stopReason !== "stop") {
128 throw new Error(response.errorMessage ?? `Pi reviewer stopped: ${response.stopReason}`)
129diff --git a/test/core/review.test.ts b/test/core/review.test.ts
130deleted file mode 100644
131index 2cfc4a5cad2ac2d5a1376ddf6f43c62712cf442b..0000000000000000000000000000000000000000
132--- a/test/core/review.test.ts
133+++ /dev/null
134@@ -1,14 +0,0 @@
135-import { expect, test } from "bun:test"
136-import { llmPolicyPrompt } from "../../src/core/rules"
137-
138-test("inserts external-directory permissions before the decision instructions", () => {
139- const prompt = llmPolicyPrompt(["/tmp/pi/*"])
140-
141- expect(prompt).toContain("- \"/tmp/pi/*\"")
142- expect(prompt.indexOf("- \"/tmp/pi/*\"")).toBeLessThan(prompt.indexOf("Never ask because"))
143- expect(prompt).not.toContain("<<EXTERNAL_DIRECTORIES>>")
144-})
145-
146-test("renders a placeholder list when no external directories are configured", () => {
147- expect(llmPolicyPrompt()).toContain("- No additional directories.")
148-})
149diff --git a/test/pi/extension.test.ts b/test/pi/extension.test.ts
150deleted file mode 100644
151index f791a24b825a843fc82ef8cc76caa0f9c1f04bfb..0000000000000000000000000000000000000000
152--- a/test/pi/extension.test.ts
153+++ /dev/null
154@@ -1,311 +0,0 @@
155-import { afterEach, beforeEach, describe, expect, test } from "bun:test"
156-import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
157-import { tmpdir } from "node:os"
158-import { join } from "node:path"
159-import PolicyEngine from "../../src/pi/index"
160-import { loadPiPolicyConfig } from "../../src/pi/config"
161-import { matchesExternalDirectory } from "../../src/pi/static-permissions"
162-
163-const originalAgentDirectory = process.env.PI_CODING_AGENT_DIR
164-const originalKey = process.env.OPENAI_API_KEY
165-const originalFetch = globalThis.fetch
166-let tempDir: string
167-
168-afterEach(() => {
169- globalThis.fetch = originalFetch
170- rmSync(tempDir, { recursive: true, force: true })
171- if (originalAgentDirectory === undefined) delete process.env.PI_CODING_AGENT_DIR
172- else process.env.PI_CODING_AGENT_DIR = originalAgentDirectory
173- if (originalKey === undefined) delete process.env.OPENAI_API_KEY
174- else process.env.OPENAI_API_KEY = originalKey
175-})
176-
177-beforeEach(() => {
178- tempDir = mkdtempSync(join(tmpdir(), "policy-engine-"))
179- process.env.PI_CODING_AGENT_DIR = tempDir
180- process.env.OPENAI_API_KEY = "test-key"
181-})
182-
183-type ToolCallHandler = (event: { toolName: string; input: Record<string, unknown> }, ctx: any) => Promise<unknown>
184-type SessionStartHandler = (event: { reason: string }, ctx: any) => unknown
185-type CommandHandler = (args: string, ctx: any) => Promise<void>
186-type SessionEntry = { customType: string; data: unknown }
187-
188-function loadExtension() {
189- let toolCall: ToolCallHandler | undefined
190- let sessionStart: SessionStartHandler | undefined
191- let allowBash: CommandHandler | undefined
192- let skipPermissions: CommandHandler | undefined
193- const entries: SessionEntry[] = []
194- const events: { name: string; data: unknown }[] = []
195- PolicyEngine({
196- on(event: string, callback: ToolCallHandler | SessionStartHandler) {
197- if (event === "tool_call") toolCall = callback as ToolCallHandler
198- if (event === "session_start") sessionStart = callback as SessionStartHandler
199- },
200- registerCommand(name: string, command: { handler: CommandHandler }) {
201- if (name === "pe-allow-bash") allowBash = command.handler
202- if (name === "pe-skip-permissions") skipPermissions = command.handler
203- },
204- appendEntry(customType: string, data: unknown) {
205- entries.push({ customType, data })
206- },
207- events: { emit(name: string, data: unknown) { events.push({ name, data }) } },
208- } as any)
209- if (!toolCall || !sessionStart || !allowBash || !skipPermissions) throw new Error("policy handlers were not registered")
210- return { toolCall, sessionStart, allowBash, skipPermissions, entries, events }
211-}
212-
213-function context(
214- hasUI = false,
215- confirm: (title: string, message: string) => Promise<boolean> = async () => false,
216- cwd = tempDir,
217-) {
218- return {
219- hasUI,
220- model: undefined,
221- modelRegistry: {
222- find(provider: string, id: string) {
223- return { provider, id }
224- },
225- async complete() {
226- return {
227- stopReason: "stop",
228- content: [{ type: "text", text: '{"decision":"ask","reason":"reviewed","category":"uncertain"}' }],
229- }
230- },
231- },
232- cwd,
233- sessionManager: { getSessionId: () => "test-session", getBranch: () => [] },
234- ui: { confirm, notify() {} },
235- }
236-}
237-
238-describe("Pi policy extension", () => {
239- test("merges project configuration over global configuration", async () => {
240- writeFileSync(join(tempDir, "policy-engine.json"), JSON.stringify({
241- tools: { bash: "deny", global_tool: "allow" },
242- externalDirectories: ["/global"],
243- }))
244- const projectDirectory = join(tempDir, "project")
245- mkdirSync(join(projectDirectory, ".pi"), { recursive: true })
246- writeFileSync(join(projectDirectory, ".pi", "policy-engine.json"), JSON.stringify({
247- tools: { bash: "allow", project_tool: "deny" },
248- externalDirectories: ["/project"],
249- }))
250-
251- expect(loadPiPolicyConfig(projectDirectory)).toMatchObject({
252- tools: { bash: "allow", global_tool: "allow", project_tool: "deny" },
253- externalDirectories: ["/global", "/project"],