refactor(core): migrate built-in tools to internal plugins (#34956)
This commit is contained in:
parent
5657cec4f2
commit
88dc960af8
25 changed files with 315 additions and 273 deletions
|
|
@ -41,7 +41,6 @@ import { InstructionContext } from "./instruction-context"
|
||||||
import { SystemContextBuiltIns } from "./system-context/builtins"
|
import { SystemContextBuiltIns } from "./system-context/builtins"
|
||||||
import { SessionContextEntry } from "./session/context-entry"
|
import { SessionContextEntry } from "./session/context-entry"
|
||||||
import { SessionInstructions } from "./session/instructions"
|
import { SessionInstructions } from "./session/instructions"
|
||||||
import { BuiltInTools } from "./tool/builtins"
|
|
||||||
import { McpTool } from "./tool/mcp"
|
import { McpTool } from "./tool/mcp"
|
||||||
import { ReadToolFileSystem } from "./tool/read-filesystem"
|
import { ReadToolFileSystem } from "./tool/read-filesystem"
|
||||||
import { ToolRegistry } from "./tool/registry"
|
import { ToolRegistry } from "./tool/registry"
|
||||||
|
|
@ -89,7 +88,6 @@ export const locationServices = LayerNode.group([
|
||||||
QuestionV2.node,
|
QuestionV2.node,
|
||||||
Generate.node,
|
Generate.node,
|
||||||
ReadToolFileSystem.node,
|
ReadToolFileSystem.node,
|
||||||
BuiltInTools.node,
|
|
||||||
McpTool.node,
|
McpTool.node,
|
||||||
SessionInstructions.node,
|
SessionInstructions.node,
|
||||||
SessionRunnerModel.node,
|
SessionRunnerModel.node,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ import { ConfigProviderPlugin } from "../config/plugin/provider"
|
||||||
import { ConfigReferencePlugin } from "../config/plugin/reference"
|
import { ConfigReferencePlugin } from "../config/plugin/reference"
|
||||||
import { ConfigSkillPlugin } from "../config/plugin/skill"
|
import { ConfigSkillPlugin } from "../config/plugin/skill"
|
||||||
import { EventV2 } from "../event"
|
import { EventV2 } from "../event"
|
||||||
|
import { FileMutation } from "../file-mutation"
|
||||||
import { FileSystem } from "../filesystem"
|
import { FileSystem } from "../filesystem"
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
|
import { Image } from "../image"
|
||||||
import { Integration } from "../integration"
|
import { Integration } from "../integration"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { LocationMutation } from "../location-mutation"
|
import { LocationMutation } from "../location-mutation"
|
||||||
|
|
@ -26,8 +28,11 @@ import { Npm } from "../npm"
|
||||||
import { PluginV2 } from "../plugin"
|
import { PluginV2 } from "../plugin"
|
||||||
import { PluginRuntime } from "../plugin/runtime"
|
import { PluginRuntime } from "../plugin/runtime"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
|
import { QuestionV2 } from "../question"
|
||||||
import { Reference } from "../reference"
|
import { Reference } from "../reference"
|
||||||
import { Ripgrep } from "../ripgrep"
|
import { Ripgrep } from "../ripgrep"
|
||||||
|
import { SessionInstructions } from "../session/instructions"
|
||||||
|
import { SessionTodo } from "../session/todo"
|
||||||
import { Shell } from "../shell"
|
import { Shell } from "../shell"
|
||||||
import { SkillV2 } from "../skill"
|
import { SkillV2 } from "../skill"
|
||||||
import { State } from "../state"
|
import { State } from "../state"
|
||||||
|
|
@ -41,9 +46,20 @@ import { ProviderPlugins } from "./provider"
|
||||||
import { SdkPlugins } from "./sdk"
|
import { SdkPlugins } from "./sdk"
|
||||||
import { SkillPlugin } from "./skill"
|
import { SkillPlugin } from "./skill"
|
||||||
import { VariantPlugin } from "./variant"
|
import { VariantPlugin } from "./variant"
|
||||||
|
import { ApplyPatchTool } from "../tool/apply-patch"
|
||||||
|
import { EditTool } from "../tool/edit"
|
||||||
import { GlobTool } from "../tool/glob"
|
import { GlobTool } from "../tool/glob"
|
||||||
|
import { GrepTool } from "../tool/grep"
|
||||||
|
import { QuestionTool } from "../tool/question"
|
||||||
|
import { ReadTool } from "../tool/read"
|
||||||
|
import { ReadToolFileSystem } from "../tool/read-filesystem"
|
||||||
import { ShellTool } from "../tool/shell"
|
import { ShellTool } from "../tool/shell"
|
||||||
|
import { SkillTool } from "../tool/skill"
|
||||||
import { SubagentTool } from "../tool/subagent"
|
import { SubagentTool } from "../tool/subagent"
|
||||||
|
import { TodoWriteTool } from "../tool/todowrite"
|
||||||
|
import { WebFetchTool } from "../tool/webfetch"
|
||||||
|
import { WebSearchTool } from "../tool/websearch"
|
||||||
|
import { WriteTool } from "../tool/write"
|
||||||
|
|
||||||
export type Requirements =
|
export type Requirements =
|
||||||
| AgentV2.Service
|
| AgentV2.Service
|
||||||
|
|
@ -51,10 +67,12 @@ export type Requirements =
|
||||||
| CommandV2.Service
|
| CommandV2.Service
|
||||||
| Config.Service
|
| Config.Service
|
||||||
| EventV2.Service
|
| EventV2.Service
|
||||||
|
| FileMutation.Service
|
||||||
| FileSystem.Service
|
| FileSystem.Service
|
||||||
| FSUtil.Service
|
| FSUtil.Service
|
||||||
| Global.Service
|
| Global.Service
|
||||||
| HttpClient.HttpClient
|
| HttpClient.HttpClient
|
||||||
|
| Image.Service
|
||||||
| Integration.Service
|
| Integration.Service
|
||||||
| Location.Service
|
| Location.Service
|
||||||
| LocationMutation.Service
|
| LocationMutation.Service
|
||||||
|
|
@ -62,11 +80,16 @@ export type Requirements =
|
||||||
| Npm.Service
|
| Npm.Service
|
||||||
| PermissionV2.Service
|
| PermissionV2.Service
|
||||||
| PluginRuntime.Service
|
| PluginRuntime.Service
|
||||||
|
| QuestionV2.Service
|
||||||
|
| ReadToolFileSystem.Service
|
||||||
| Reference.Service
|
| Reference.Service
|
||||||
| Ripgrep.Service
|
| Ripgrep.Service
|
||||||
|
| SessionInstructions.Service
|
||||||
|
| SessionTodo.Service
|
||||||
| Shell.Service
|
| Shell.Service
|
||||||
| SkillV2.Service
|
| SkillV2.Service
|
||||||
| Tools.Service
|
| Tools.Service
|
||||||
|
| WebSearchTool.ConfigService
|
||||||
|
|
||||||
export interface Plugin<R = never> {
|
export interface Plugin<R = never> {
|
||||||
readonly id: string
|
readonly id: string
|
||||||
|
|
@ -96,13 +119,20 @@ const layer = Layer.effectDiscard(
|
||||||
Context.make(Global.Service, yield* Global.Service),
|
Context.make(Global.Service, yield* Global.Service),
|
||||||
Context.make(HttpClient.HttpClient, yield* HttpClient.HttpClient),
|
Context.make(HttpClient.HttpClient, yield* HttpClient.HttpClient),
|
||||||
Context.make(LocationMutation.Service, yield* LocationMutation.Service),
|
Context.make(LocationMutation.Service, yield* LocationMutation.Service),
|
||||||
|
Context.make(FileMutation.Service, yield* FileMutation.Service),
|
||||||
|
Context.make(Image.Service, yield* Image.Service),
|
||||||
Context.make(PermissionV2.Service, yield* PermissionV2.Service),
|
Context.make(PermissionV2.Service, yield* PermissionV2.Service),
|
||||||
|
Context.make(QuestionV2.Service, yield* QuestionV2.Service),
|
||||||
|
Context.make(ReadToolFileSystem.Service, yield* ReadToolFileSystem.Service),
|
||||||
|
Context.make(SessionInstructions.Service, yield* SessionInstructions.Service),
|
||||||
|
Context.make(SessionTodo.Service, yield* SessionTodo.Service),
|
||||||
Context.make(SkillV2.Service, yield* SkillV2.Service),
|
Context.make(SkillV2.Service, yield* SkillV2.Service),
|
||||||
Context.make(Reference.Service, yield* Reference.Service),
|
Context.make(Reference.Service, yield* Reference.Service),
|
||||||
Context.make(Ripgrep.Service, yield* Ripgrep.Service),
|
Context.make(Ripgrep.Service, yield* Ripgrep.Service),
|
||||||
Context.make(Shell.Service, yield* Shell.Service),
|
Context.make(Shell.Service, yield* Shell.Service),
|
||||||
Context.make(Tools.Service, yield* Tools.Service),
|
Context.make(Tools.Service, yield* Tools.Service),
|
||||||
Context.make(PluginRuntime.Service, yield* PluginRuntime.Service),
|
Context.make(PluginRuntime.Service, yield* PluginRuntime.Service),
|
||||||
|
Context.make(WebSearchTool.ConfigService, yield* WebSearchTool.ConfigService),
|
||||||
)
|
)
|
||||||
const add = (input: Plugin<Requirements | Scope.Scope>) =>
|
const add = (input: Plugin<Requirements | Scope.Scope>) =>
|
||||||
plugin.add(PluginV2.ID.make(input.id), (context: PluginContext) =>
|
plugin.add(PluginV2.ID.make(input.id), (context: PluginContext) =>
|
||||||
|
|
@ -117,9 +147,19 @@ const layer = Layer.effectDiscard(
|
||||||
yield* add(SkillPlugin.Plugin)
|
yield* add(SkillPlugin.Plugin)
|
||||||
yield* add(ModelsDevPlugin)
|
yield* add(ModelsDevPlugin)
|
||||||
yield* add(ConfigExternalPlugin.Plugin)
|
yield* add(ConfigExternalPlugin.Plugin)
|
||||||
|
yield* add(ApplyPatchTool.Plugin)
|
||||||
|
yield* add(EditTool.Plugin)
|
||||||
yield* add(GlobTool.Plugin)
|
yield* add(GlobTool.Plugin)
|
||||||
|
yield* add(GrepTool.Plugin)
|
||||||
|
yield* add(QuestionTool.Plugin)
|
||||||
|
yield* add(ReadTool.Plugin)
|
||||||
yield* add(ShellTool.Plugin)
|
yield* add(ShellTool.Plugin)
|
||||||
|
yield* add(SkillTool.Plugin)
|
||||||
yield* add(SubagentTool.Plugin)
|
yield* add(SubagentTool.Plugin)
|
||||||
|
yield* add(TodoWriteTool.Plugin)
|
||||||
|
yield* add(WebFetchTool.Plugin)
|
||||||
|
yield* add(WebSearchTool.Plugin)
|
||||||
|
yield* add(WriteTool.Plugin)
|
||||||
yield* add(ConfigAgentPlugin.Plugin)
|
yield* add(ConfigAgentPlugin.Plugin)
|
||||||
yield* add(ConfigCommandPlugin.Plugin)
|
yield* add(ConfigCommandPlugin.Plugin)
|
||||||
yield* add(ConfigSkillPlugin.Plugin)
|
yield* add(ConfigSkillPlugin.Plugin)
|
||||||
|
|
@ -145,6 +185,8 @@ export const node = makeLocationNode({
|
||||||
Config.node,
|
Config.node,
|
||||||
Location.node,
|
Location.node,
|
||||||
LocationMutation.node,
|
LocationMutation.node,
|
||||||
|
FileMutation.node,
|
||||||
|
Image.node,
|
||||||
ModelsDev.node,
|
ModelsDev.node,
|
||||||
Npm.node,
|
Npm.node,
|
||||||
EventV2.node,
|
EventV2.node,
|
||||||
|
|
@ -153,6 +195,10 @@ export const node = makeLocationNode({
|
||||||
Global.node,
|
Global.node,
|
||||||
httpClient,
|
httpClient,
|
||||||
PermissionV2.node,
|
PermissionV2.node,
|
||||||
|
QuestionV2.node,
|
||||||
|
ReadToolFileSystem.node,
|
||||||
|
SessionInstructions.node,
|
||||||
|
SessionTodo.node,
|
||||||
SkillV2.node,
|
SkillV2.node,
|
||||||
Reference.node,
|
Reference.node,
|
||||||
Ripgrep.node,
|
Ripgrep.node,
|
||||||
|
|
@ -160,5 +206,6 @@ export const node = makeLocationNode({
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
PluginRuntime.node,
|
PluginRuntime.node,
|
||||||
SdkPlugins.node,
|
SdkPlugins.node,
|
||||||
|
WebSearchTool.configNode,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
export * as ApplyPatchTool from "./apply-patch"
|
export * as ApplyPatchTool from "./apply-patch"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
||||||
import { createTwoFilesPatch, diffLines } from "diff"
|
import { createTwoFilesPatch, diffLines } from "diff"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FileMutation } from "../file-mutation"
|
import { FileMutation } from "../file-mutation"
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { LocationMutation } from "../location-mutation"
|
import { LocationMutation } from "../location-mutation"
|
||||||
import { Patch } from "../patch"
|
import { Patch } from "../patch"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "apply_patch"
|
export const name = "apply_patch"
|
||||||
|
|
||||||
|
|
@ -56,15 +54,15 @@ type Prepared =
|
||||||
readonly after: string
|
readonly after: string
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-apply-patch-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("ApplyPatchTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const mutation = yield* LocationMutation.Service
|
const mutation = yield* LocationMutation.Service
|
||||||
const files = yield* FileMutation.Service
|
const files = yield* FileMutation.Service
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.withPermission(
|
[name]: Tool.withPermission(
|
||||||
Tool.make({
|
Tool.make({
|
||||||
|
|
@ -194,13 +192,7 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/apply-patch",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
|
|
||||||
})
|
|
||||||
|
|
||||||
function patchFile(change: Prepared): typeof FileDiff.Info.Type {
|
function patchFile(change: Prepared): typeof FileDiff.Info.Type {
|
||||||
const counts = diffLines(change.before, change.after).reduce(
|
const counts = diffLines(change.before, change.after).reduce(
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
export * as BuiltInTools from "./builtins"
|
|
||||||
|
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { Context, Layer } from "effect"
|
|
||||||
import { ApplyPatchTool } from "./apply-patch"
|
|
||||||
import { EditTool } from "./edit"
|
|
||||||
import { GrepTool } from "./grep"
|
|
||||||
import { QuestionTool } from "./question"
|
|
||||||
import { ReadTool } from "./read"
|
|
||||||
import { ReadToolFileSystem } from "./read-filesystem"
|
|
||||||
import { SkillTool } from "./skill"
|
|
||||||
import { TodoWriteTool } from "./todowrite"
|
|
||||||
import { WebFetchTool } from "./webfetch"
|
|
||||||
import { WebSearchTool } from "./websearch"
|
|
||||||
import { WriteTool } from "./write"
|
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Record<string, never>>()("@opencode/v2/BuiltInTools") {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Composes only the shipped Location-scoped built-in tool transforms.
|
|
||||||
* Each tool retains its implementation and focused tests independently. Dynamic
|
|
||||||
* MCP and plugin tools later use separate scoped canonical registrations, while
|
|
||||||
* provider/model filtering belongs to a future materialization phase rather
|
|
||||||
* than this static list. The caller intentionally supplies shared Location
|
|
||||||
* services once to this merged set.
|
|
||||||
*
|
|
||||||
* TODO: Port the remaining launch-follow-up leaves deliberately: edit fuzzy
|
|
||||||
* parity, task, LSP,
|
|
||||||
* repo_clone, repo_overview, plan_exit, and Rune/code mode. Keep MCP and plugin
|
|
||||||
* transforms separate from this static built-in list.
|
|
||||||
*/
|
|
||||||
const layer = Layer.succeed(Service, Service.of({}))
|
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
service: Service,
|
|
||||||
layer,
|
|
||||||
deps: [
|
|
||||||
ApplyPatchTool.node,
|
|
||||||
EditTool.node,
|
|
||||||
GrepTool.node,
|
|
||||||
QuestionTool.node,
|
|
||||||
ReadTool.node,
|
|
||||||
ReadToolFileSystem.node,
|
|
||||||
SkillTool.node,
|
|
||||||
TodoWriteTool.node,
|
|
||||||
WebFetchTool.node,
|
|
||||||
WebSearchTool.node,
|
|
||||||
WebSearchTool.configNode,
|
|
||||||
WriteTool.node,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
@ -6,18 +6,16 @@
|
||||||
*/
|
*/
|
||||||
export * as EditTool from "./edit"
|
export * as EditTool from "./edit"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
||||||
import { createTwoFilesPatch, diffLines } from "diff"
|
import { createTwoFilesPatch, diffLines } from "diff"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FileMutation } from "../file-mutation"
|
import { FileMutation } from "../file-mutation"
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { LocationMutation } from "../location-mutation"
|
import { LocationMutation } from "../location-mutation"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "edit"
|
export const name = "edit"
|
||||||
|
|
||||||
|
|
@ -87,15 +85,15 @@ export const toModelOutput = (output: Output, oldString: string, newString: stri
|
||||||
// TODO: Add snapshots / undo after design exists.
|
// TODO: Add snapshots / undo after design exists.
|
||||||
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-edit-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("EditTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const mutation = yield* LocationMutation.Service
|
const mutation = yield* LocationMutation.Service
|
||||||
const files = yield* FileMutation.Service
|
const files = yield* FileMutation.Service
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.withPermission(
|
[name]: Tool.withPermission(
|
||||||
Tool.make({
|
Tool.make({
|
||||||
|
|
@ -214,10 +212,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/edit",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
export * as GrepTool from "./grep"
|
export * as GrepTool from "./grep"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FileSystem } from "../filesystem"
|
import { FileSystem } from "../filesystem"
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { Location } from "../location"
|
import { Location } from "../location"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { Ripgrep } from "../ripgrep"
|
import { Ripgrep } from "../ripgrep"
|
||||||
import { RelativePath } from "../schema"
|
import { RelativePath } from "../schema"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "grep"
|
export const name = "grep"
|
||||||
|
|
||||||
|
|
@ -50,15 +48,15 @@ export const toModelOutput = (output: ModelOutput) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Grep leaf that defaults its filesystem root to the active Location. */
|
/** Grep leaf that defaults its filesystem root to the active Location. */
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-grep-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("GrepTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const ripgrep = yield* Ripgrep.Service
|
const ripgrep = yield* Ripgrep.Service
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description:
|
description:
|
||||||
|
|
@ -128,10 +126,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/grep",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, FSUtil.node, Ripgrep.node, Location.node, PermissionV2.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
export * as QuestionTool from "./question"
|
export * as QuestionTool from "./question"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { QuestionV2 } from "../question"
|
import { QuestionV2 } from "../question"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "question"
|
export const name = "question"
|
||||||
|
|
||||||
|
|
@ -44,13 +42,13 @@ export const toModelOutput = (
|
||||||
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
return `User has answered your questions: ${formatted}. You can now continue with the user's answers in mind.`
|
||||||
}
|
}
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-question-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("QuestionTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const question = yield* QuestionV2.Service
|
const question = yield* QuestionV2.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description,
|
description,
|
||||||
|
|
@ -85,10 +83,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/question",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, PermissionV2.node, QuestionV2.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
export * as ReadTool from "./read"
|
export * as ReadTool from "./read"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { dirname } from "path"
|
import { dirname } from "path"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FileSystem } from "../filesystem"
|
import { FileSystem } from "../filesystem"
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { Image } from "../image"
|
import { Image } from "../image"
|
||||||
|
|
@ -13,9 +13,7 @@ import { PermissionV2 } from "../permission"
|
||||||
import { SessionInstructions } from "../session/instructions"
|
import { SessionInstructions } from "../session/instructions"
|
||||||
import { AbsolutePath } from "../schema"
|
import { AbsolutePath } from "../schema"
|
||||||
import { ReadToolFileSystem } from "./read-filesystem"
|
import { ReadToolFileSystem } from "./read-filesystem"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "read"
|
export const name = "read"
|
||||||
const FILENAME = "AGENTS.md"
|
const FILENAME = "AGENTS.md"
|
||||||
|
|
@ -32,9 +30,9 @@ const LocationInput = Schema.Struct({
|
||||||
const Input = LocationInput
|
const Input = LocationInput
|
||||||
const Output = Schema.Union([FileSystem.Content, ReadToolFileSystem.TextPage, ReadToolFileSystem.ListPage])
|
const Output = Schema.Union([FileSystem.Content, ReadToolFileSystem.TextPage, ReadToolFileSystem.ListPage])
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-read-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("ReadTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const reader = yield* ReadToolFileSystem.Service
|
const reader = yield* ReadToolFileSystem.Service
|
||||||
const mutation = yield* LocationMutation.Service
|
const mutation = yield* LocationMutation.Service
|
||||||
const image = yield* Image.Service
|
const image = yield* Image.Service
|
||||||
|
|
@ -43,7 +41,7 @@ const layer = Layer.effectDiscard(
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description:
|
description:
|
||||||
|
|
@ -111,7 +109,10 @@ const layer = Layer.effectDiscard(
|
||||||
const candidates = discovered.map(FSUtil.resolve).filter((file) => dirname(file) !== root)
|
const candidates = discovered.map(FSUtil.resolve).filter((file) => dirname(file) !== root)
|
||||||
if (candidates.length === 0) return
|
if (candidates.length === 0) return
|
||||||
yield* sessionInstructions.load({ sessionID: context.sessionID, paths: candidates })
|
yield* sessionInstructions.load({ sessionID: context.sessionID, paths: candidates })
|
||||||
}).pipe(Effect.catch(() => Effect.void), Effect.catchDefect(() => Effect.void))
|
}).pipe(
|
||||||
|
Effect.catch(() => Effect.void),
|
||||||
|
Effect.catchDefect(() => Effect.void),
|
||||||
|
)
|
||||||
if ("encoding" in content && content.encoding === "base64" && SUPPORTED_IMAGE_MIMES.has(content.mime)) {
|
if ("encoding" in content && content.encoding === "base64" && SUPPORTED_IMAGE_MIMES.has(content.mime)) {
|
||||||
return yield* image
|
return yield* image
|
||||||
.normalize(resource, { ...content, encoding: "base64" })
|
.normalize(resource, { ...content, encoding: "base64" })
|
||||||
|
|
@ -137,19 +138,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/read",
|
|
||||||
layer,
|
|
||||||
deps: [
|
|
||||||
ToolRegistry.node,
|
|
||||||
ReadToolFileSystem.node,
|
|
||||||
LocationMutation.node,
|
|
||||||
Image.node,
|
|
||||||
PermissionV2.node,
|
|
||||||
SessionInstructions.node,
|
|
||||||
FSUtil.node,
|
|
||||||
Location.node,
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
export * as SkillTool from "./skill"
|
export * as SkillTool from "./skill"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FSUtil } from "../fs-util"
|
import { FSUtil } from "../fs-util"
|
||||||
import { SkillV2 } from "../skill"
|
import { SkillV2 } from "../skill"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "skill"
|
export const name = "skill"
|
||||||
const FILE_LIMIT = 10
|
const FILE_LIMIT = 10
|
||||||
|
|
@ -54,13 +52,13 @@ export const toModelOutput = (skill: SkillV2.Info, files: ReadonlyArray<string>)
|
||||||
const unableToLoad = (name: string, error?: unknown) =>
|
const unableToLoad = (name: string, error?: unknown) =>
|
||||||
new ToolFailure({ message: `Unable to load skill ${name}`, error })
|
new ToolFailure({ message: `Unable to load skill ${name}`, error })
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-skill-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("SkillTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const fs = yield* FSUtil.Service
|
const fs = yield* FSUtil.Service
|
||||||
const skills = yield* SkillV2.Service
|
const skills = yield* SkillV2.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description,
|
description,
|
||||||
|
|
@ -100,10 +98,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/skill",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, FSUtil.node, SkillV2.node, PermissionV2.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
export * as TodoWriteTool from "./todowrite"
|
export * as TodoWriteTool from "./todowrite"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { SessionTodo } from "../session/todo"
|
import { SessionTodo } from "../session/todo"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "todowrite"
|
export const name = "todowrite"
|
||||||
|
|
||||||
|
|
@ -22,13 +20,13 @@ export type Output = typeof Output.Type
|
||||||
|
|
||||||
export const toModelOutput = (output: Output) => JSON.stringify(output.todos, null, 2)
|
export const toModelOutput = (output: Output) => JSON.stringify(output.todos, null, 2)
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-todowrite-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("TodoWriteTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const todos = yield* SessionTodo.Service
|
const todos = yield* SessionTodo.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description:
|
description:
|
||||||
|
|
@ -53,10 +51,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/todowrite",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, PermissionV2.node, SessionTodo.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
export * as WebFetchTool from "./webfetch"
|
export * as WebFetchTool from "./webfetch"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Duration, Effect, Layer, Schema } from "effect"
|
import { Duration, Effect, Schema } from "effect"
|
||||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||||
import { Parser } from "htmlparser2"
|
import { Parser } from "htmlparser2"
|
||||||
import TurndownService from "turndown"
|
import TurndownService from "turndown"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { LayerNodePlatform } from "../effect/app-node-platform"
|
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { collectBoundedResponseBody } from "./http-body"
|
import { collectBoundedResponseBody } from "./http-body"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "webfetch"
|
export const name = "webfetch"
|
||||||
export const MAX_RESPONSE_BYTES = 5 * 1024 * 1024
|
export const MAX_RESPONSE_BYTES = 5 * 1024 * 1024
|
||||||
|
|
@ -115,13 +112,13 @@ const convert = (content: string, contentType: string, format: Format) => {
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-webfetch-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("WebFetchTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const http = yield* HttpClient.HttpClient
|
const http = yield* HttpClient.HttpClient
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description,
|
description,
|
||||||
|
|
@ -178,13 +175,7 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/webfetch",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient],
|
|
||||||
})
|
|
||||||
|
|
||||||
export function extractTextFromHTML(html: string) {
|
export function extractTextFromHTML(html: string) {
|
||||||
let text = ""
|
let text = ""
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
export * as WebSearchTool from "./websearch"
|
export * as WebSearchTool from "./websearch"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Context, Duration, Effect, Layer, Schema } from "effect"
|
import { Context, Duration, Effect, Layer, Schema } from "effect"
|
||||||
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
import { makeLocationNode } from "../effect/app-node"
|
||||||
import { LayerNodePlatform } from "../effect/app-node-platform"
|
|
||||||
import { truthy } from "../flag/flag"
|
import { truthy } from "../flag/flag"
|
||||||
import { InstallationVersion } from "../installation/version"
|
import { InstallationVersion } from "../installation/version"
|
||||||
import { PositiveInt } from "../schema"
|
import { PositiveInt } from "../schema"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
import { collectBoundedResponseBody } from "./http-body"
|
import { collectBoundedResponseBody } from "./http-body"
|
||||||
import { checksum } from "../util/encode"
|
import { checksum } from "../util/encode"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
|
|
||||||
export const name = "websearch"
|
export const name = "websearch"
|
||||||
export const NO_RESULTS = "No search results found. Please try a different query."
|
export const NO_RESULTS = "No search results found. Please try a different query."
|
||||||
|
|
@ -189,14 +187,14 @@ const Output = Schema.Struct({
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-websearch-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("WebSearchTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const http = yield* HttpClient.HttpClient
|
const http = yield* HttpClient.HttpClient
|
||||||
const config = yield* ConfigService
|
const config = yield* ConfigService
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.make({
|
[name]: Tool.make({
|
||||||
description,
|
description,
|
||||||
|
|
@ -251,10 +249,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/websearch",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, PermissionV2.node, LayerNodePlatform.httpClient, configNode],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,13 @@
|
||||||
*/
|
*/
|
||||||
export * as WriteTool from "./write"
|
export * as WriteTool from "./write"
|
||||||
|
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
import { ToolFailure } from "@opencode-ai/llm"
|
import { ToolFailure } from "@opencode-ai/llm"
|
||||||
import { Effect, Layer, Schema } from "effect"
|
import { Effect, Schema } from "effect"
|
||||||
import { makeLocationNode } from "../effect/app-node"
|
|
||||||
import { FileMutation } from "../file-mutation"
|
import { FileMutation } from "../file-mutation"
|
||||||
import { LocationMutation } from "../location-mutation"
|
import { LocationMutation } from "../location-mutation"
|
||||||
import { PermissionV2 } from "../permission"
|
import { PermissionV2 } from "../permission"
|
||||||
import { ToolRegistry } from "./registry"
|
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Tools } from "./tools"
|
|
||||||
|
|
||||||
export const name = "write"
|
export const name = "write"
|
||||||
|
|
||||||
|
|
@ -44,14 +42,14 @@ export const toModelOutput = (output: Output) =>
|
||||||
// TODO: Add snapshots / undo after design exists.
|
// TODO: Add snapshots / undo after design exists.
|
||||||
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
// TODO: Add LSP notification and diagnostics after V2 LSP runtime exists.
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
export const Plugin = {
|
||||||
Effect.gen(function* () {
|
id: "core-write-tool",
|
||||||
const tools = yield* Tools.Service
|
effect: Effect.fn("WriteTool.Plugin")(function* (ctx: PluginContext) {
|
||||||
const mutation = yield* LocationMutation.Service
|
const mutation = yield* LocationMutation.Service
|
||||||
const files = yield* FileMutation.Service
|
const files = yield* FileMutation.Service
|
||||||
const permission = yield* PermissionV2.Service
|
const permission = yield* PermissionV2.Service
|
||||||
|
|
||||||
yield* tools
|
yield* ctx.tool
|
||||||
.register({
|
.register({
|
||||||
[name]: Tool.withPermission(
|
[name]: Tool.withPermission(
|
||||||
Tool.make({
|
Tool.make({
|
||||||
|
|
@ -92,10 +90,4 @@ const layer = Layer.effectDiscard(
|
||||||
})
|
})
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
export const node = makeLocationNode({
|
|
||||||
name: "tool/write",
|
|
||||||
layer,
|
|
||||||
deps: [ToolRegistry.node, LocationMutation.node, FileMutation.node, PermissionV2.node],
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ import { AgentV2 } from "@opencode-ai/core/agent"
|
||||||
import type { PermissionV2 } from "@opencode-ai/core/permission"
|
import type { PermissionV2 } from "@opencode-ai/core/permission"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { Effect } from "effect"
|
import { Tools } from "@opencode-ai/core/tool/tools"
|
||||||
|
import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||||
|
import { Effect, type Scope } from "effect"
|
||||||
|
|
||||||
export const toolIdentity = {
|
export const toolIdentity = {
|
||||||
agent: AgentV2.ID.make("build"),
|
agent: AgentV2.ID.make("build"),
|
||||||
|
|
@ -34,6 +36,29 @@ export function waitForTool(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a core tool plugin's tools against the real registry without booting the
|
||||||
|
* full plugin host. Only the tool domain is live; focused tool tests exercise
|
||||||
|
* registration, materialization, and settlement through the same path production uses.
|
||||||
|
*/
|
||||||
|
export const registerToolPlugin = <R>(plugin: {
|
||||||
|
readonly id: string
|
||||||
|
readonly effect: (context: PluginContext) => Effect.Effect<void, never, R>
|
||||||
|
}): Effect.Effect<void, never, R | Tools.Service | Scope.Scope> =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const tools = yield* Tools.Service
|
||||||
|
const context: Pick<PluginContext, "tool"> = {
|
||||||
|
tool: {
|
||||||
|
register: tools.register,
|
||||||
|
execute: {
|
||||||
|
before: () => Effect.die("registerToolPlugin does not support tool hooks"),
|
||||||
|
after: () => Effect.die("registerToolPlugin does not support tool hooks"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
yield* plugin.effect(context as PluginContext)
|
||||||
|
})
|
||||||
|
|
||||||
export const settleTool = (registry: ToolRegistry.Interface, input: ToolRegistry.ExecuteInput, model = testModel) =>
|
export const settleTool = (registry: ToolRegistry.Interface, input: ToolRegistry.ExecuteInput, model = testModel) =>
|
||||||
registry.materialize({ model }).pipe(Effect.flatMap((materialized) => materialized.settle(input)))
|
registry.materialize({ model }).pipe(Effect.flatMap((materialized) => materialized.settle(input)))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,25 @@ describe("LocationServiceMap", () => {
|
||||||
const catalog = yield* Catalog.Service
|
const catalog = yield* Catalog.Service
|
||||||
yield* catalog.transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
yield* catalog.transform((editor) => editor.provider.update(ProviderV2.ID.make("test"), () => {}))
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
yield* waitForTool(registry, "glob")
|
// Tool plugins register during the forked PluginInternal boot; wait for
|
||||||
yield* waitForTool(registry, "shell")
|
// every expected tool rather than relying on batch ordering.
|
||||||
yield* waitForTool(registry, "subagent")
|
yield* Effect.forEach(
|
||||||
|
[
|
||||||
|
"edit",
|
||||||
|
"glob",
|
||||||
|
"grep",
|
||||||
|
"question",
|
||||||
|
"read",
|
||||||
|
"shell",
|
||||||
|
"skill",
|
||||||
|
"subagent",
|
||||||
|
"todowrite",
|
||||||
|
"webfetch",
|
||||||
|
"websearch",
|
||||||
|
"write",
|
||||||
|
],
|
||||||
|
(name) => waitForTool(registry, name),
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
providers: yield* catalog.provider.all(),
|
providers: yield* catalog.provider.all(),
|
||||||
tools: yield* toolDefinitions(registry),
|
tools: yield* toolDefinitions(registry),
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,24 @@ import { ToolHooks } from "@opencode-ai/core/tool/hooks"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { tempLocationLayer } from "./fixture/location"
|
import { tempLocationLayer } from "./fixture/location"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { settleTool, testModel } from "./lib/tool"
|
import { registerToolPlugin, settleTool, testModel } from "./lib/tool"
|
||||||
|
|
||||||
|
const readToolNode = makeLocationNode({
|
||||||
|
name: "test/read-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(ReadTool.Plugin)),
|
||||||
|
deps: [
|
||||||
|
ToolRegistry.toolsNode,
|
||||||
|
ReadToolFileSystem.node,
|
||||||
|
LocationMutation.node,
|
||||||
|
Image.node,
|
||||||
|
PermissionV2.node,
|
||||||
|
SessionInstructions.node,
|
||||||
|
FSUtil.node,
|
||||||
|
Location.node,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
const projects = Layer.succeed(
|
const projects = Layer.succeed(
|
||||||
ProjectV2.Service,
|
ProjectV2.Service,
|
||||||
|
|
@ -69,7 +85,7 @@ const testLayer = AppNodeBuilder.build(
|
||||||
FSUtil.node,
|
FSUtil.node,
|
||||||
LocationMutation.node,
|
LocationMutation.node,
|
||||||
ReadToolFileSystem.node,
|
ReadToolFileSystem.node,
|
||||||
ReadTool.node,
|
readToolNode,
|
||||||
ToolRegistry.node,
|
ToolRegistry.node,
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
ToolHooks.node,
|
ToolHooks.node,
|
||||||
|
|
@ -156,7 +172,9 @@ describe("SessionInstructions", () => {
|
||||||
expect(firstInjected[0]!.text).toBe(
|
expect(firstInjected[0]!.text).toBe(
|
||||||
`Instructions from: ${deepPath}\ndeep-instructions\n\nInstructions from: ${subPath}\nsub-instructions`,
|
`Instructions from: ${deepPath}\ndeep-instructions\n\nInstructions from: ${subPath}\nsub-instructions`,
|
||||||
)
|
)
|
||||||
expect(firstInjected[0]!.description).toBe(`Loaded ${path.relative(dir, deepPath)}, ${path.relative(dir, subPath)}`)
|
expect(firstInjected[0]!.description).toBe(
|
||||||
|
`Loaded ${path.relative(dir, deepPath)}, ${path.relative(dir, subPath)}`,
|
||||||
|
)
|
||||||
// The synthetic's metadata carries the durable dedup ledger.
|
// The synthetic's metadata carries the durable dedup ledger.
|
||||||
expect(firstInjected[0]!.metadata).toEqual({ instruction: { paths: [deepPath, subPath] } })
|
expect(firstInjected[0]!.metadata).toEqual({ instruction: { paths: [deepPath, subPath] } })
|
||||||
expect(firstInjected[0]!.text).not.toContain("root-instructions")
|
expect(firstInjected[0]!.text).not.toContain("root-instructions")
|
||||||
|
|
@ -192,47 +210,49 @@ describe("SessionInstructions", () => {
|
||||||
// Seed the durable history with a prior synthetic that already claims sub's AGENTS.md
|
// Seed the durable history with a prior synthetic that already claims sub's AGENTS.md
|
||||||
// via the instruction metadata ledger.
|
// via the instruction metadata ledger.
|
||||||
yield* seedSynthetic(sessionID, [subPath])
|
yield* seedSynthetic(sessionID, [subPath])
|
||||||
expect((yield* synthetics(sessionID))).toHaveLength(1)
|
expect(yield* synthetics(sessionID)).toHaveLength(1)
|
||||||
|
|
||||||
yield* settleTool(registry, readCall(sessionID, "call-sub", "sub/file.txt"))
|
yield* settleTool(registry, readCall(sessionID, "call-sub", "sub/file.txt"))
|
||||||
|
|
||||||
// The durable claim on the prior synthetic prevents re-injection; no new synthetic.
|
// The durable claim on the prior synthetic prevents re-injection; no new synthetic.
|
||||||
expect((yield* synthetics(sessionID))).toHaveLength(1)
|
expect(yield* synthetics(sessionID)).toHaveLength(1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("discovers AGENTS.md on a directory listing, including the listed directory's own, and dedups with a later file read", () =>
|
it.effect(
|
||||||
Effect.gen(function* () {
|
"discovers AGENTS.md on a directory listing, including the listed directory's own, and dedups with a later file read",
|
||||||
const location = yield* Location.Service
|
() =>
|
||||||
const dir = location.directory
|
Effect.gen(function* () {
|
||||||
const rootPath = path.resolve(dir, "AGENTS.md")
|
const location = yield* Location.Service
|
||||||
const pkgPath = path.resolve(dir, "packages", "foo", "AGENTS.md")
|
const dir = location.directory
|
||||||
yield* mkdir(path.resolve(dir, "packages", "foo"))
|
const rootPath = path.resolve(dir, "AGENTS.md")
|
||||||
yield* writeAgents(rootPath, "root-instructions")
|
const pkgPath = path.resolve(dir, "packages", "foo", "AGENTS.md")
|
||||||
yield* writeAgents(pkgPath, "pkg-instructions")
|
yield* mkdir(path.resolve(dir, "packages", "foo"))
|
||||||
yield* Effect.promise(() => fs.writeFile(path.resolve(dir, "packages", "foo", "file.txt"), "content"))
|
yield* writeAgents(rootPath, "root-instructions")
|
||||||
|
yield* writeAgents(pkgPath, "pkg-instructions")
|
||||||
|
yield* Effect.promise(() => fs.writeFile(path.resolve(dir, "packages", "foo", "file.txt"), "content"))
|
||||||
|
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const registry = yield* ToolRegistry.Service
|
const registry = yield* ToolRegistry.Service
|
||||||
const sessionID = (yield* session.create({ location: Location.Ref.make({ directory: dir }) })).id
|
const sessionID = (yield* session.create({ location: Location.Ref.make({ directory: dir }) })).id
|
||||||
|
|
||||||
// Listing packages/foo/ discovers its own AGENTS.md, walking up to but excluding
|
// Listing packages/foo/ discovers its own AGENTS.md, walking up to but excluding
|
||||||
// the Location root (already supplied by the core/instructions baseline).
|
// the Location root (already supplied by the core/instructions baseline).
|
||||||
yield* settleTool(registry, readCall(sessionID, "call-list", "packages/foo"))
|
yield* settleTool(registry, readCall(sessionID, "call-list", "packages/foo"))
|
||||||
|
|
||||||
const firstInjected = yield* synthetics(sessionID)
|
const firstInjected = yield* synthetics(sessionID)
|
||||||
expect(firstInjected).toHaveLength(1)
|
expect(firstInjected).toHaveLength(1)
|
||||||
expect(firstInjected[0]!.text).toBe(`Instructions from: ${pkgPath}\npkg-instructions`)
|
expect(firstInjected[0]!.text).toBe(`Instructions from: ${pkgPath}\npkg-instructions`)
|
||||||
expect(firstInjected[0]!.description).toBe(`Loaded ${path.relative(dir, pkgPath)}`)
|
expect(firstInjected[0]!.description).toBe(`Loaded ${path.relative(dir, pkgPath)}`)
|
||||||
expect(firstInjected[0]!.metadata).toEqual({ instruction: { paths: [pkgPath] } })
|
expect(firstInjected[0]!.metadata).toEqual({ instruction: { paths: [pkgPath] } })
|
||||||
expect(firstInjected[0]!.text).not.toContain("root-instructions")
|
expect(firstInjected[0]!.text).not.toContain("root-instructions")
|
||||||
|
|
||||||
// A subsequent file read under the listed directory is a dedup: pkg's AGENTS.md is
|
// A subsequent file read under the listed directory is a dedup: pkg's AGENTS.md is
|
||||||
// already injected for this session, so nothing new is emitted.
|
// already injected for this session, so nothing new is emitted.
|
||||||
yield* settleTool(registry, readCall(sessionID, "call-file", "packages/foo/file.txt"))
|
yield* settleTool(registry, readCall(sessionID, "call-file", "packages/foo/file.txt"))
|
||||||
|
|
||||||
expect((yield* synthetics(sessionID))).toHaveLength(1)
|
expect(yield* synthetics(sessionID)).toHaveLength(1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("listing the Location root directory injects no instructions", () =>
|
it.effect("listing the Location root directory injects no instructions", () =>
|
||||||
|
|
@ -253,7 +273,7 @@ describe("SessionInstructions", () => {
|
||||||
// dropped by the dirname filter, and up() only walks upward so nested dirs are unseen.
|
// dropped by the dirname filter, and up() only walks upward so nested dirs are unseen.
|
||||||
yield* settleTool(registry, readCall(sessionID, "call-root-list", "."))
|
yield* settleTool(registry, readCall(sessionID, "call-root-list", "."))
|
||||||
|
|
||||||
expect((yield* synthetics(sessionID))).toHaveLength(0)
|
expect(yield* synthetics(sessionID)).toHaveLength(0)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,15 @@ import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { ApplyPatchTool } from "@opencode-ai/core/tool/apply-patch"
|
import { ApplyPatchTool } from "@opencode-ai/core/tool/apply-patch"
|
||||||
import { location } from "./fixture/location"
|
import { location } from "./fixture/location"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const applyPatchToolNode = makeLocationNode({
|
||||||
|
name: "test/apply-patch-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(ApplyPatchTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_apply_patch_tool_test")
|
const sessionID = SessionV2.ID.make("ses_apply_patch_tool_test")
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
|
|
@ -101,7 +108,7 @@ const withTool = <A, E, R>(directory: string, body: (registry: ToolRegistry.Inte
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
LocationMutation.node,
|
LocationMutation.node,
|
||||||
FileMutation.node,
|
FileMutation.node,
|
||||||
ApplyPatchTool.node,
|
applyPatchToolNode,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
[FSUtil.node, filesystem],
|
[FSUtil.node, filesystem],
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,15 @@ import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { EditTool } from "@opencode-ai/core/tool/edit"
|
import { EditTool } from "@opencode-ai/core/tool/edit"
|
||||||
import { location } from "./fixture/location"
|
import { location } from "./fixture/location"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const editToolNode = makeLocationNode({
|
||||||
|
name: "test/edit-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(EditTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, LocationMutation.node, FileMutation.node, FSUtil.node, PermissionV2.node],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_edit_tool_test")
|
const sessionID = SessionV2.ID.make("ses_edit_tool_test")
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
|
|
@ -91,7 +98,7 @@ const withTool = <A, E, R>(directory: string, body: (registry: ToolRegistry.Inte
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
LocationMutation.node,
|
LocationMutation.node,
|
||||||
FileMutation.node,
|
FileMutation.node,
|
||||||
EditTool.node,
|
editToolNode,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
[FSUtil.node, filesystem],
|
[FSUtil.node, filesystem],
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { QuestionTool } from "@opencode-ai/core/tool/question"
|
import { QuestionTool } from "@opencode-ai/core/tool/question"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_question_tool_test")
|
const sessionID = SessionV2.ID.make("ses_question_tool_test")
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
|
|
@ -43,8 +44,14 @@ const question = Layer.succeed(
|
||||||
list: () => Effect.die("unused"),
|
list: () => Effect.die("unused"),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
const questionToolNode = makeLocationNode({
|
||||||
|
name: "test/question-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(QuestionTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, PermissionV2.node, QuestionV2.node],
|
||||||
|
})
|
||||||
|
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, QuestionTool.node]), [
|
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, questionToolNode]), [
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
[QuestionV2.node, question],
|
[QuestionV2.node, question],
|
||||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,25 @@ import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { ReadTool } from "@opencode-ai/core/tool/read"
|
import { ReadTool } from "@opencode-ai/core/tool/read"
|
||||||
import { ReadToolFileSystem } from "@opencode-ai/core/tool/read-filesystem"
|
import { ReadToolFileSystem } from "@opencode-ai/core/tool/read-filesystem"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
|
import { SessionInstructions } from "@opencode-ai/core/session/instructions"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const readToolNode = makeLocationNode({
|
||||||
|
name: "test/read-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(ReadTool.Plugin)),
|
||||||
|
deps: [
|
||||||
|
ToolRegistry.toolsNode,
|
||||||
|
ReadToolFileSystem.node,
|
||||||
|
LocationMutation.node,
|
||||||
|
Image.node,
|
||||||
|
PermissionV2.node,
|
||||||
|
SessionInstructions.node,
|
||||||
|
FSUtil.node,
|
||||||
|
Location.node,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
const missingPath = "__missing_read_target__.txt"
|
const missingPath = "__missing_read_target__.txt"
|
||||||
|
|
@ -130,7 +147,7 @@ const unavailableImage = Layer.succeed(
|
||||||
Image.Service.of({ normalize: () => Effect.fail(new Image.ResizerUnavailableError()) }),
|
Image.Service.of({ normalize: () => Effect.fail(new Image.ResizerUnavailableError()) }),
|
||||||
)
|
)
|
||||||
const readLayer = (imageLayer: Layer.Layer<Image.Service>) =>
|
const readLayer = (imageLayer: Layer.Layer<Image.Service>) =>
|
||||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, ReadTool.node]), [
|
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, readToolNode]), [
|
||||||
[ReadToolFileSystem.node, reader],
|
[ReadToolFileSystem.node, reader],
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
[Config.node, config],
|
[Config.node, config],
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,15 @@ import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
import { it } from "./lib/effect"
|
import { it } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
|
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||||
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const skillToolNode = makeLocationNode({
|
||||||
|
name: "test/skill-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(SkillTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, FSUtil.node, SkillV2.node, PermissionV2.node],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_skill_tool_test")
|
const sessionID = SessionV2.ID.make("ses_skill_tool_test")
|
||||||
|
|
||||||
|
|
@ -66,7 +74,7 @@ describe("SkillTool", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const skillToolLayer = AppNodeBuilder.build(
|
const skillToolLayer = AppNodeBuilder.build(
|
||||||
LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, SkillTool.node]),
|
LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, skillToolNode]),
|
||||||
[
|
[
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
[SkillV2.node, skills],
|
[SkillV2.node, skills],
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,14 @@ import { TodoWriteTool } from "@opencode-ai/core/tool/todowrite"
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const todoWriteToolNode = makeLocationNode({
|
||||||
|
name: "test/todowrite-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(TodoWriteTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, PermissionV2.node, SessionTodo.node],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_todowrite_tool_test")
|
const sessionID = SessionV2.ID.make("ses_todowrite_tool_test")
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
|
|
@ -43,7 +50,7 @@ const it = testEffect(
|
||||||
SessionTodo.node,
|
SessionTodo.node,
|
||||||
ToolRegistry.node,
|
ToolRegistry.node,
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
TodoWriteTool.node,
|
todoWriteToolNode,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,15 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { WebFetchTool } from "@opencode-ai/core/tool/webfetch"
|
import { WebFetchTool } from "@opencode-ai/core/tool/webfetch"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const webFetchToolNode = makeLocationNode({
|
||||||
|
name: "test/webfetch-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(WebFetchTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, PermissionV2.node, LayerNodePlatform.httpClient],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_webfetch_test")
|
const sessionID = SessionV2.ID.make("ses_webfetch_test")
|
||||||
const requests: Array<{ readonly url: string; readonly headers: Record<string, string> }> = []
|
const requests: Array<{ readonly url: string; readonly headers: Record<string, string> }> = []
|
||||||
|
|
@ -40,7 +47,7 @@ const permission = Layer.succeed(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const toolLayer = (replacements: LayerNode.Replacements = []) =>
|
const toolLayer = (replacements: LayerNode.Replacements = []) =>
|
||||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, WebFetchTool.node]), [
|
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, webFetchToolNode]), [
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||||
...replacements,
|
...replacements,
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,15 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||||
import { WebSearchTool } from "@opencode-ai/core/tool/websearch"
|
import { WebSearchTool } from "@opencode-ai/core/tool/websearch"
|
||||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const webSearchToolNode = makeLocationNode({
|
||||||
|
name: "test/websearch-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(WebSearchTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, PermissionV2.node, LayerNodePlatform.httpClient, WebSearchTool.configNode],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_websearch_test")
|
const sessionID = SessionV2.ID.make("ses_websearch_test")
|
||||||
const payload = (text: string) =>
|
const payload = (text: string) =>
|
||||||
|
|
@ -125,7 +132,7 @@ const websearchConfig = Layer.succeed(
|
||||||
)
|
)
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
AppNodeBuilder.build(
|
AppNodeBuilder.build(
|
||||||
LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, WebSearchTool.configNode, WebSearchTool.node]),
|
LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, WebSearchTool.configNode, webSearchToolNode]),
|
||||||
[
|
[
|
||||||
[PermissionV2.node, permission],
|
[PermissionV2.node, permission],
|
||||||
[LayerNodePlatform.httpClient, http],
|
[LayerNodePlatform.httpClient, http],
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,15 @@ import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||||
import { WriteTool } from "@opencode-ai/core/tool/write"
|
import { WriteTool } from "@opencode-ai/core/tool/write"
|
||||||
import { location } from "./fixture/location"
|
import { location } from "./fixture/location"
|
||||||
import { tmpdir } from "./fixture/tmpdir"
|
import { tmpdir } from "./fixture/tmpdir"
|
||||||
|
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||||
import { testEffect } from "./lib/effect"
|
import { testEffect } from "./lib/effect"
|
||||||
import { toolIdentity, executeTool, settleTool, toolDefinitions } from "./lib/tool"
|
import { toolIdentity, executeTool, registerToolPlugin, settleTool, toolDefinitions } from "./lib/tool"
|
||||||
|
|
||||||
|
const writeToolNode = makeLocationNode({
|
||||||
|
name: "test/write-tool-plugin",
|
||||||
|
layer: Layer.effectDiscard(registerToolPlugin(WriteTool.Plugin)),
|
||||||
|
deps: [ToolRegistry.toolsNode, LocationMutation.node, FileMutation.node, PermissionV2.node],
|
||||||
|
})
|
||||||
|
|
||||||
const sessionID = SessionV2.ID.make("ses_write_tool_test")
|
const sessionID = SessionV2.ID.make("ses_write_tool_test")
|
||||||
const assertions: PermissionV2.AssertInput[] = []
|
const assertions: PermissionV2.AssertInput[] = []
|
||||||
|
|
@ -75,7 +82,7 @@ const withTool = <A, E, R>(directory: string, body: (registry: ToolRegistry.Inte
|
||||||
ToolRegistry.toolsNode,
|
ToolRegistry.toolsNode,
|
||||||
LocationMutation.node,
|
LocationMutation.node,
|
||||||
FileMutation.node,
|
FileMutation.node,
|
||||||
WriteTool.node,
|
writeToolNode,
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
[FSUtil.node, filesystem],
|
[FSUtil.node, filesystem],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue