forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodemode.ts
More file actions
77 lines (70 loc) · 2.96 KB
/
Copy pathcodemode.ts
File metadata and controls
77 lines (70 loc) · 2.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
export * as CodeMode from "./codemode"
import { Context, Effect, Layer, Scope } from "effect"
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
import { PermissionV2 } from "./permission"
import { ExecuteTool } from "./tool/execute"
import type { Any, Registration } from "./tool/tool"
import { Wildcard } from "./util/wildcard"
export interface Materialization {
readonly tool?: Any
readonly instructions?: string
}
export interface Interface {
readonly register: (
registrations: ReadonlyArray<Registration & { readonly key: string }>,
) => Effect.Effect<void, never, Scope.Scope>
readonly materialize: (permissions?: PermissionV2.Ruleset) => Effect.Effect<Materialization>
}
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/CodeMode") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const local = new Map<string, Array<{ readonly token: object; readonly registration: ExecuteTool.Registration }>>()
return Service.of({
register: Effect.fn("CodeMode.register")(function* (registrations) {
if (registrations.length === 0) return
yield* Effect.uninterruptible(
Effect.gen(function* () {
const token = {}
for (const registration of registrations)
local.set(registration.key, [
...(local.get(registration.key) ?? []),
{
token,
registration,
},
])
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
for (const registration of registrations) {
const remaining = local.get(registration.key)?.filter((item) => item.token !== token) ?? []
if (remaining.length > 0) local.set(registration.key, remaining)
else local.delete(registration.key)
}
}),
)
}),
)
}),
materialize: Effect.fn("CodeMode.materialize")(function* (permissions) {
const registrations = new Map<string, ExecuteTool.Registration>()
const rules = permissions ?? []
for (const [name, entries] of local) {
const registration = entries.at(-1)?.registration
if (!registration) continue
const rule = rules.findLast((rule) => Wildcard.match(registration.permission, rule.action))
if (rule?.resource === "*" && rule.effect === "deny") continue
registrations.set(name, registration)
}
if (registrations.size === 0) return {}
const executeRule = rules.findLast((rule) => Wildcard.match("execute", rule.action))
if (executeRule?.resource === "*" && executeRule.effect === "deny") return {}
return {
tool: ExecuteTool.create(registrations),
instructions: ExecuteTool.instructions(registrations),
}
}),
})
}),
)
export const node = makeLocationNode({ service: Service, layer, deps: [] })