982fb4cd5a6ef3ddcdcbed467b95a35dbc2e0d51
- Author
- TheEdgeOfRage <git@theedgeofrage.com>
- Committer
- TheEdgeOfRage <git@theedgeofrage.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/src/api.ts b/src/api.ts
2index 6d3c28b2ad7dda5e2bd24b612018ceb47d5685b6..e4ab27852f7174fb20d70d890d49f186a16018cd 100644
3--- a/src/api.ts
4+++ b/src/api.ts
5@@ -7,11 +7,8 @@ export function setCapturedApiKey(key: string) {
6 }
7
8 function resolveApiKey(): string {
9- const key =
10- process.env.ANTHROPIC_API_KEY ??
11- capturedApiKey
12- if (!key) throw new Error("No Anthropic API key available")
13- return key
14+ if (!capturedApiKey) throw new Error("No Anthropic API key available")
15+ return capturedApiKey
16 }
17
18 function resolveModel(): string {
19diff --git a/src/index.ts b/src/index.ts
20new file mode 100644
21index 0000000000000000000000000000000000000000..032c333cc9e562d6078dbcb7e5025fd9c8d06642
22--- /dev/null
23+++ b/src/index.ts
24@@ -0,0 +1,196 @@
25+import type { Plugin, PluginModule } from "@opencode-ai/plugin"
26+import type { Permission } from "@opencode-ai/sdk"
27+import { checkDeterministic } from "./deterministic"
28+import { normalizeRequest, cacheKey } from "./normalizer"
29+import { lookupCache, writeCache } from "./cache"
30+import { callHaiku } from "./haiku"
31+import { setCapturedApiKey, callClaude } from "./api"
32+import { addPermissions, getPermissions } from "./permissions"
33+import { logDecision } from "./logger"
34+import { PERMISSION_KEYWORDS, PERMISSION_EXTRACTION_PROMPT } from "./rules"
35+import type { Decision } from "./types"
36+
37+// v2 PermissionRequest shape (event properties)
38+type PermissionAskedEvent = {
39+ type: "permission.asked"
40+ properties: {
41+ id: string
42+ sessionID: string
43+ permission: string
44+ patterns: string[]
45+ metadata: Record<string, unknown>
46+ always: string[]
47+ tool?: { messageID: string; callID: string }
48+ }
49+}
50+
51+function buildToolInput(
52+ toolType: string,
53+ patterns: string[],
54+ metadata: Record<string, unknown>,
55+): Record<string, unknown> {
56+ const pattern = patterns.join(" ")
57+ switch (toolType) {
58+ case "bash":
59+ return { command: pattern }
60+ case "webfetch":
61+ return { url: patterns[0] ?? "" }
62+ default:
63+ return { ...metadata, pattern }
64+ }
65+}
66+
67+async function extractPermissions(text: string): Promise<string[]> {
68+ if (text.length < 10) return []
69+ const lower = text.toLowerCase()
70+ if (!PERMISSION_KEYWORDS.some((kw) => lower.includes(kw))) return []
71+
72+ try {
73+ const prompt = PERMISSION_EXTRACTION_PROMPT.replace("{user_prompt}", text)
74+ const raw = await callClaude([{ role: "user", content: prompt }], 256)
75+
76+ let arr: unknown
77+ try {
78+ arr = JSON.parse(raw)
79+ } catch {
80+ const m = raw.match(/```(?:json)?\s*(\[.*?\])\s*```/s)
81+ if (m) arr = JSON.parse(m[1])
82+ else {
83+ const m2 = raw.match(/\[.*\]/s)
84+ if (m2) arr = JSON.parse(m2[0])
85+ }
86+ }
87+ if (Array.isArray(arr)) return arr.filter((s): s is string => typeof s === "string")
88+ } catch {
89+ // non-fatal
90+ }
91+ return []
92+}
93+
94+async function runPipeline(
95+ toolType: string,
96+ toolInput: Record<string, unknown>,
97+ sessionId: string,
98+): Promise<{ decision: Decision; source: string; rawResponse?: string; error?: string }> {
99+ const start = performance.now()
100+
101+ // Stage 1: deterministic
102+ const det = checkDeterministic(toolType, toolInput)
103+ if (det) {
104+ logDecision({
105+ toolType,
106+ normalized: normalizeRequest(toolType, toolInput),
107+ decision: det,
108+ source: "deterministic",
109+ timingMs: performance.now() - start,
110+ sessionId,
111+ })
112+ return { decision: det, source: "deterministic" }
113+ }
114+
115+ // Stage 2: cache
116+ const normalized = normalizeRequest(toolType, toolInput)
117+ const key = cacheKey(normalized)
118+ const cached = lookupCache(key)
119+ if (cached) {
120+ logDecision({
121+ toolType,
122+ normalized,
123+ decision: cached,