|
|
@@ -1,12 +1,13 @@
|
|
|
import { describe, expect } from "bun:test"
|
|
|
import { Tool } from "@opencode-ai/core/tool/tool"
|
|
|
import { AgentV2 } from "@opencode-ai/core/agent"
|
|
|
+import type { PermissionV2 } from "@opencode-ai/core/permission"
|
|
|
import { ApplicationTools } from "@opencode-ai/core/tool/application-tools"
|
|
|
import { SessionV2 } from "@opencode-ai/core/session"
|
|
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
|
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
|
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
|
|
-import { executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
|
|
+import { executeTool, settleTool, testModel, toolDefinitions } from "./lib/tool"
|
|
|
import { Cause, Deferred, Effect, Exit, Fiber, Layer, Option, Schema, SchemaGetter, SchemaIssue, Scope } from "effect"
|
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
|
|
@@ -61,17 +62,11 @@ describe("ToolRegistry", () => {
|
|
|
bash: make(),
|
|
|
edit: make("edit"),
|
|
|
write: make("edit"),
|
|
|
- apply_patch: make("edit"),
|
|
|
})
|
|
|
- const names = (rules: Parameters<ToolRegistry.Interface["materialize"]>[0]) =>
|
|
|
- toolDefinitions(service, rules).pipe(Effect.map((definitions) => definitions.map((tool) => tool.name)))
|
|
|
-
|
|
|
- expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual([
|
|
|
- "bash",
|
|
|
- "edit",
|
|
|
- "write",
|
|
|
- "apply_patch",
|
|
|
- ])
|
|
|
+ const names = (permissions: PermissionV2.Ruleset) =>
|
|
|
+ toolDefinitions(service, permissions).pipe(Effect.map((definitions) => definitions.map((tool) => tool.name)))
|
|
|
+
|
|
|
+ expect(yield* names([{ action: "question", resource: "*", effect: "deny" }])).toEqual(["bash", "edit", "write"])
|
|
|
expect(
|
|
|
yield* names([
|
|
|
{ action: "*", resource: "*", effect: "deny" },
|
|
|
@@ -88,6 +83,27 @@ describe("ToolRegistry", () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
+ it.effect("selects one edit tool family for each model", () =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const service = yield* ToolRegistry.Service
|
|
|
+ yield* service.register({
|
|
|
+ read: make(),
|
|
|
+ edit: make("edit"),
|
|
|
+ write: make("edit"),
|
|
|
+ apply_patch: make("edit"),
|
|
|
+ })
|
|
|
+ const names = (model: ToolRegistry.MaterializeInput["model"]) =>
|
|
|
+ service
|
|
|
+ .materialize({ model })
|
|
|
+ .pipe(Effect.map((materialized) => materialized.definitions.map((tool) => tool.name)))
|
|
|
+
|
|
|
+ expect(yield* names({ id: "gpt-5", provider: "openai" })).toEqual(["read", "apply_patch"])
|
|
|
+ expect(yield* names({ id: "gpt-4o", provider: "opencode" })).toEqual(["read", "apply_patch"])
|
|
|
+ expect(yield* names({ id: "computer-use-preview", provider: "openai" })).toEqual(["read", "apply_patch"])
|
|
|
+ expect(yield* names({ id: "claude-sonnet-4", provider: "anthropic" })).toEqual(["read", "edit", "write"])
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
it.effect("keeps permission decoration isolated between registrations", () =>
|
|
|
Effect.gen(function* () {
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
@@ -183,7 +199,7 @@ describe("ToolRegistry", () => {
|
|
|
}),
|
|
|
})
|
|
|
expect(
|
|
|
- yield* service.materialize().pipe(
|
|
|
+ yield* service.materialize({ model: testModel }).pipe(
|
|
|
Effect.flatMap((materialized) =>
|
|
|
materialized.settle({
|
|
|
sessionID,
|
|
|
@@ -201,7 +217,7 @@ describe("ToolRegistry", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
yield* service.register({ echo: make() })
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
const exit = yield* materialized.settle(call("echo", "call-retention-failure")).pipe(Effect.exit)
|
|
|
|
|
|
expect(Exit.isFailure(exit)).toBe(true)
|
|
|
@@ -331,7 +347,7 @@ describe("ToolRegistry", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
yield* service.register({ echo: make() })
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
|
|
|
expect((yield* materialized.settle(call("echo"))).result).toEqual({ type: "text", value: "echo" })
|
|
|
}),
|
|
|
@@ -342,7 +358,7 @@ describe("ToolRegistry", () => {
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
const scope = yield* Scope.make()
|
|
|
yield* service.register({ echo: make() }).pipe(Scope.provide(scope))
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
yield* Scope.close(scope, Exit.void)
|
|
|
|
|
|
expect((yield* materialized.settle(call("echo"))).result).toEqual({
|
|
|
@@ -356,7 +372,7 @@ describe("ToolRegistry", () => {
|
|
|
Effect.gen(function* () {
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
yield* service.register({ first: make(), second: make() })
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
yield* service.register({ first: make() })
|
|
|
|
|
|
expect((yield* materialized.settle(call("first"))).result).toEqual({
|
|
|
@@ -373,7 +389,7 @@ describe("ToolRegistry", () => {
|
|
|
yield* service.register({ echo: make() })
|
|
|
const overlay = yield* Scope.make()
|
|
|
yield* service.register({ echo: make() }).pipe(Scope.provide(overlay))
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
yield* Scope.close(overlay, Exit.void)
|
|
|
|
|
|
expect((yield* materialized.settle(call("echo"))).result).toEqual({
|
|
|
@@ -388,7 +404,7 @@ describe("ToolRegistry", () => {
|
|
|
const applications = yield* ApplicationTools.Service
|
|
|
const service = yield* ToolRegistry.Service
|
|
|
yield* applications.register({ echo: make() })
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
yield* service.register({ echo: make() })
|
|
|
|
|
|
expect((yield* materialized.settle(call("echo"))).result).toEqual({
|
|
|
@@ -405,7 +421,7 @@ describe("ToolRegistry", () => {
|
|
|
yield* applications.register({ echo: make() })
|
|
|
const scope = yield* Scope.make()
|
|
|
yield* service.register({ echo: make() }).pipe(Scope.provide(scope))
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
yield* Scope.close(scope, Exit.void)
|
|
|
|
|
|
expect((yield* materialized.settle(call("echo"))).result).toEqual({
|
|
|
@@ -433,7 +449,7 @@ describe("ToolRegistry", () => {
|
|
|
}),
|
|
|
})
|
|
|
.pipe(Scope.provide(scope))
|
|
|
- const materialized = yield* service.materialize()
|
|
|
+ const materialized = yield* service.materialize({ model: testModel })
|
|
|
const settlement = yield* materialized.settle(call("echo")).pipe(Effect.forkChild)
|
|
|
yield* Deferred.await(started)
|
|
|
yield* Scope.close(scope, Exit.void)
|