Skip to content

Commit 5dfe86d

Browse files
authored
refactor(truncation): effectify TruncateService, delete Scheduler (anomalyco#17957)
1 parent 4b4dd2b commit 5dfe86d

40 files changed

Lines changed: 406 additions & 483 deletions

packages/opencode/src/agent/agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ModelID, ProviderID } from "../provider/schema"
55
import { generateObject, streamObject, type ModelMessage } from "ai"
66
import { SystemPrompt } from "../session/system"
77
import { Instance } from "../project/instance"
8-
import { Truncate } from "../tool/truncation"
8+
import { Truncate } from "../tool/truncate"
99
import { Auth } from "../auth"
1010
import { ProviderTransform } from "../provider/transform"
1111

@@ -14,7 +14,7 @@ import PROMPT_COMPACTION from "./prompt/compaction.txt"
1414
import PROMPT_EXPLORE from "./prompt/explore.txt"
1515
import PROMPT_SUMMARY from "./prompt/summary.txt"
1616
import PROMPT_TITLE from "./prompt/title.txt"
17-
import { PermissionNext } from "@/permission/next"
17+
import { PermissionNext } from "@/permission"
1818
import { mergeDeep, pipe, sortBy, values } from "remeda"
1919
import { Global } from "@/global"
2020
import path from "path"

packages/opencode/src/cli/cmd/debug/agent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { MessageV2 } from "../../../session/message-v2"
77
import { MessageID, PartID } from "../../../session/schema"
88
import { ToolRegistry } from "../../../tool/registry"
99
import { Instance } from "../../../project/instance"
10-
import { PermissionNext } from "../../../permission/next"
10+
import { PermissionNext } from "../../../permission"
1111
import { iife } from "../../../util/iife"
1212
import { bootstrap } from "../../bootstrap"
1313
import { cmd } from "../cmd"

packages/opencode/src/cli/cmd/run.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { createOpencodeClient, type Message, type OpencodeClient, type ToolPart
1111
import { Server } from "../../server/server"
1212
import { Provider } from "../../provider/provider"
1313
import { Agent } from "../../agent/agent"
14-
import { PermissionNext } from "../../permission/next"
14+
import { PermissionNext } from "../../permission"
1515
import { Tool } from "../../tool/tool"
1616
import { GlobTool } from "../../tool/glob"
1717
import { GrepTool } from "../../tool/grep"

packages/opencode/src/effect/instances.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FileService } from "@/file"
33
import { FileTimeService } from "@/file/time"
44
import { FileWatcherService } from "@/file/watcher"
55
import { FormatService } from "@/format"
6-
import { PermissionService } from "@/permission/service"
6+
import { PermissionEffect } from "@/permission/service"
77
import { Instance } from "@/project/instance"
88
import { VcsService } from "@/project/vcs"
99
import { ProviderAuthService } from "@/provider/auth-service"
@@ -17,7 +17,7 @@ export { InstanceContext } from "./instance-context"
1717

1818
export type InstanceServices =
1919
| QuestionService
20-
| PermissionService
20+
| PermissionEffect.Service
2121
| ProviderAuthService
2222
| FileWatcherService
2323
| VcsService
@@ -37,7 +37,7 @@ function lookup(_key: string) {
3737
const ctx = Layer.sync(InstanceContext, () => InstanceContext.of(Instance.current))
3838
return Layer.mergeAll(
3939
Layer.fresh(QuestionService.layer),
40-
Layer.fresh(PermissionService.layer),
40+
Layer.fresh(PermissionEffect.layer),
4141
Layer.fresh(ProviderAuthService.layer),
4242
Layer.fresh(FileWatcherService.layer).pipe(Layer.orDie),
4343
Layer.fresh(VcsService.layer),
@@ -67,8 +67,4 @@ export class Instances extends ServiceMap.Service<Instances, LayerMap.LayerMap<s
6767
static get(directory: string): Layer.Layer<InstanceServices, never, Instances> {
6868
return Layer.unwrap(Instances.use((map) => Effect.succeed(map.get(directory))))
6969
}
70-
71-
static invalidate(directory: string): Effect.Effect<void, never, Instances> {
72-
return Instances.use((map) => map.invalidate(directory))
73-
}
7470
}

packages/opencode/src/effect/runtime.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,15 @@ import { AccountService } from "@/account/service"
33
import { AuthService } from "@/auth/service"
44
import { Instances } from "@/effect/instances"
55
import type { InstanceServices } from "@/effect/instances"
6+
import { TruncateEffect } from "@/tool/truncate-effect"
67
import { Instance } from "@/project/instance"
78

89
export const runtime = ManagedRuntime.make(
9-
Layer.mergeAll(AccountService.defaultLayer, Instances.layer).pipe(Layer.provideMerge(AuthService.defaultLayer)),
10+
Layer.mergeAll(
11+
AccountService.defaultLayer, //
12+
TruncateEffect.defaultLayer,
13+
Instances.layer,
14+
).pipe(Layer.provideMerge(AuthService.defaultLayer)),
1015
)
1116

1217
export function runPromiseInstance<A, E>(effect: Effect.Effect<A, E, InstanceServices>) {
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Config } from "@/config/config"
33
import { fn } from "@/util/fn"
44
import { Wildcard } from "@/util/wildcard"
55
import os from "os"
6-
import * as S from "./service"
6+
import { PermissionEffect as S } from "./service"
77

88
export namespace PermissionNext {
99
function expand(pattern: string): string {
@@ -26,7 +26,7 @@ export namespace PermissionNext {
2626
export type Reply = S.Reply
2727
export const Approval = S.Approval
2828
export const Event = S.Event
29-
export const Service = S.PermissionService
29+
export const Service = S.Service
3030
export const RejectedError = S.RejectedError
3131
export const CorrectedError = S.CorrectedError
3232
export const DeniedError = S.DeniedError
@@ -53,16 +53,14 @@ export namespace PermissionNext {
5353
return rulesets.flat()
5454
}
5555

56-
export const ask = fn(S.AskInput, async (input) =>
57-
runPromiseInstance(S.PermissionService.use((service) => service.ask(input))),
58-
)
56+
export const ask = fn(S.AskInput, async (input) => runPromiseInstance(S.Service.use((service) => service.ask(input))))
5957

6058
export const reply = fn(S.ReplyInput, async (input) =>
61-
runPromiseInstance(S.PermissionService.use((service) => service.reply(input))),
59+
runPromiseInstance(S.Service.use((service) => service.reply(input))),
6260
)
6361

6462
export async function list() {
65-
return runPromiseInstance(S.PermissionService.use((service) => service.list()))
63+
return runPromiseInstance(S.Service.use((service) => service.list()))
6664
}
6765

6866
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {

packages/opencode/src/permission/service.ts

Lines changed: 103 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -11,121 +11,128 @@ import { Deferred, Effect, Layer, Schema, ServiceMap } from "effect"
1111
import z from "zod"
1212
import { PermissionID } from "./schema"
1313

14-
const log = Log.create({ service: "permission" })
15-
16-
export const Action = z.enum(["allow", "deny", "ask"]).meta({
17-
ref: "PermissionAction",
18-
})
19-
export type Action = z.infer<typeof Action>
20-
21-
export const Rule = z
22-
.object({
23-
permission: z.string(),
24-
pattern: z.string(),
25-
action: Action,
26-
})
27-
.meta({
28-
ref: "PermissionRule",
29-
})
30-
export type Rule = z.infer<typeof Rule>
31-
32-
export const Ruleset = Rule.array().meta({
33-
ref: "PermissionRuleset",
34-
})
35-
export type Ruleset = z.infer<typeof Ruleset>
36-
37-
export const Request = z
38-
.object({
39-
id: PermissionID.zod,
40-
sessionID: SessionID.zod,
41-
permission: z.string(),
42-
patterns: z.string().array(),
43-
metadata: z.record(z.string(), z.any()),
44-
always: z.string().array(),
45-
tool: z
46-
.object({
47-
messageID: MessageID.zod,
48-
callID: z.string(),
49-
})
50-
.optional(),
14+
export namespace PermissionEffect {
15+
const log = Log.create({ service: "permission" })
16+
17+
export const Action = z.enum(["allow", "deny", "ask"]).meta({
18+
ref: "PermissionAction",
5119
})
52-
.meta({
53-
ref: "PermissionRequest",
20+
export type Action = z.infer<typeof Action>
21+
22+
export const Rule = z
23+
.object({
24+
permission: z.string(),
25+
pattern: z.string(),
26+
action: Action,
27+
})
28+
.meta({
29+
ref: "PermissionRule",
30+
})
31+
export type Rule = z.infer<typeof Rule>
32+
33+
export const Ruleset = Rule.array().meta({
34+
ref: "PermissionRuleset",
5435
})
55-
export type Request = z.infer<typeof Request>
56-
57-
export const Reply = z.enum(["once", "always", "reject"])
58-
export type Reply = z.infer<typeof Reply>
36+
export type Ruleset = z.infer<typeof Ruleset>
5937

60-
export const Approval = z.object({
61-
projectID: ProjectID.zod,
62-
patterns: z.string().array(),
63-
})
64-
65-
export const Event = {
66-
Asked: BusEvent.define("permission.asked", Request),
67-
Replied: BusEvent.define(
68-
"permission.replied",
69-
z.object({
38+
export const Request = z
39+
.object({
40+
id: PermissionID.zod,
7041
sessionID: SessionID.zod,
71-
requestID: PermissionID.zod,
72-
reply: Reply,
73-
}),
74-
),
75-
}
42+
permission: z.string(),
43+
patterns: z.string().array(),
44+
metadata: z.record(z.string(), z.any()),
45+
always: z.string().array(),
46+
tool: z
47+
.object({
48+
messageID: MessageID.zod,
49+
callID: z.string(),
50+
})
51+
.optional(),
52+
})
53+
.meta({
54+
ref: "PermissionRequest",
55+
})
56+
export type Request = z.infer<typeof Request>
57+
58+
export const Reply = z.enum(["once", "always", "reject"])
59+
export type Reply = z.infer<typeof Reply>
60+
61+
export const Approval = z.object({
62+
projectID: ProjectID.zod,
63+
patterns: z.string().array(),
64+
})
7665

77-
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionRejectedError", {}) {
78-
override get message() {
79-
return "The user rejected permission to use this specific tool call."
66+
export const Event = {
67+
Asked: BusEvent.define("permission.asked", Request),
68+
Replied: BusEvent.define(
69+
"permission.replied",
70+
z.object({
71+
sessionID: SessionID.zod,
72+
requestID: PermissionID.zod,
73+
reply: Reply,
74+
}),
75+
),
8076
}
81-
}
8277

83-
export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("PermissionCorrectedError", {
84-
feedback: Schema.String,
85-
}) {
86-
override get message() {
87-
return `The user rejected permission to use this specific tool call with the following feedback: ${this.feedback}`
78+
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionRejectedError", {}) {
79+
override get message() {
80+
return "The user rejected permission to use this specific tool call."
81+
}
8882
}
89-
}
9083

91-
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionDeniedError", {
92-
ruleset: Schema.Any,
93-
}) {
94-
override get message() {
95-
return `The user has specified a rule which prevents you from using this specific tool call. Here are some of the relevant rules ${JSON.stringify(this.ruleset)}`
84+
export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("PermissionCorrectedError", {
85+
feedback: Schema.String,
86+
}) {
87+
override get message() {
88+
return `The user rejected permission to use this specific tool call with the following feedback: ${this.feedback}`
89+
}
9690
}
97-
}
9891

99-
export type PermissionError = DeniedError | RejectedError | CorrectedError
92+
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionDeniedError", {
93+
ruleset: Schema.Any,
94+
}) {
95+
override get message() {
96+
return `The user has specified a rule which prevents you from using this specific tool call. Here are some of the relevant rules ${JSON.stringify(this.ruleset)}`
97+
}
98+
}
10099

101-
interface PendingEntry {
102-
info: Request
103-
deferred: Deferred.Deferred<void, RejectedError | CorrectedError>
104-
}
100+
export type Error = DeniedError | RejectedError | CorrectedError
105101

106-
export const AskInput = Request.partial({ id: true }).extend({
107-
ruleset: Ruleset,
108-
})
102+
export const AskInput = Request.partial({ id: true }).extend({
103+
ruleset: Ruleset,
104+
})
109105

110-
export const ReplyInput = z.object({
111-
requestID: PermissionID.zod,
112-
reply: Reply,
113-
message: z.string().optional(),
114-
})
106+
export const ReplyInput = z.object({
107+
requestID: PermissionID.zod,
108+
reply: Reply,
109+
message: z.string().optional(),
110+
})
115111

116-
export declare namespace PermissionService {
117112
export interface Api {
118-
readonly ask: (input: z.infer<typeof AskInput>) => Effect.Effect<void, PermissionError>
113+
readonly ask: (input: z.infer<typeof AskInput>) => Effect.Effect<void, Error>
119114
readonly reply: (input: z.infer<typeof ReplyInput>) => Effect.Effect<void>
120115
readonly list: () => Effect.Effect<Request[]>
121116
}
122-
}
123117

124-
export class PermissionService extends ServiceMap.Service<PermissionService, PermissionService.Api>()(
125-
"@opencode/PermissionNext",
126-
) {
127-
static readonly layer = Layer.effect(
128-
PermissionService,
118+
interface PendingEntry {
119+
info: Request
120+
deferred: Deferred.Deferred<void, RejectedError | CorrectedError>
121+
}
122+
123+
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
124+
const rules = rulesets.flat()
125+
log.info("evaluate", { permission, pattern, ruleset: rules })
126+
const match = rules.findLast(
127+
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
128+
)
129+
return match ?? { action: "ask", permission, pattern: "*" }
130+
}
131+
132+
export class Service extends ServiceMap.Service<Service, Api>()("@opencode/PermissionNext") {}
133+
134+
export const layer = Layer.effect(
135+
Service,
129136
Effect.gen(function* () {
130137
const { project } = yield* InstanceContext
131138
const row = Database.use((db) =>
@@ -225,27 +232,13 @@ export class PermissionService extends ServiceMap.Service<PermissionService, Per
225232
})
226233
yield* Deferred.succeed(item.deferred, undefined)
227234
}
228-
229-
// TODO: we don't save the permission ruleset to disk yet until there's
230-
// UI to manage it
231-
// db().insert(PermissionTable).values({ projectID: Instance.project.id, data: s.approved })
232-
// .onConflictDoUpdate({ target: PermissionTable.projectID, set: { data: s.approved } }).run()
233235
})
234236

235237
const list = Effect.fn("PermissionService.list")(function* () {
236238
return Array.from(pending.values(), (item) => item.info)
237239
})
238240

239-
return PermissionService.of({ ask, reply, list })
241+
return Service.of({ ask, reply, list })
240242
}),
241243
)
242244
}
243-
244-
export function evaluate(permission: string, pattern: string, ...rulesets: Ruleset[]): Rule {
245-
const merged = rulesets.flat()
246-
log.info("evaluate", { permission, pattern, ruleset: merged })
247-
const match = merged.findLast(
248-
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
249-
)
250-
return match ?? { action: "ask", permission, pattern: "*" }
251-
}

packages/opencode/src/project/bootstrap.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import { Instance } from "./instance"
1010
import { VcsService } from "./vcs"
1111
import { Log } from "@/util/log"
1212
import { ShareNext } from "@/share/share-next"
13-
import { Snapshot } from "../snapshot"
14-
import { Truncate } from "../tool/truncation"
1513
import { runPromiseInstance } from "@/effect/runtime"
1614

1715
export async function InstanceBootstrap() {
@@ -23,8 +21,6 @@ export async function InstanceBootstrap() {
2321
await runPromiseInstance(FileWatcherService.use((service) => service.init()))
2422
File.init()
2523
await runPromiseInstance(VcsService.use((s) => s.init()))
26-
Snapshot.init()
27-
Truncate.init()
2824

2925
Bus.subscribe(Command.Event.Executed, async (payload) => {
3026
if (payload.properties.name === Command.Default.INIT) {

0 commit comments

Comments
 (0)