registry.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { BashTool } from "./bash"
  2. import { EditTool } from "./edit"
  3. import { GlobTool } from "./glob"
  4. import { GrepTool } from "./grep"
  5. import { ListTool } from "./ls"
  6. import { BatchTool } from "./batch"
  7. import { ReadTool } from "./read"
  8. import { TaskTool } from "./task"
  9. import { TodoWriteTool, TodoReadTool } from "./todo"
  10. import { WebFetchTool } from "./webfetch"
  11. import { WriteTool } from "./write"
  12. import { InvalidTool } from "./invalid"
  13. import type { Agent } from "../agent/agent"
  14. import { Tool } from "./tool"
  15. import { Instance } from "../project/instance"
  16. import { Config } from "../config/config"
  17. import path from "path"
  18. import { type ToolDefinition } from "@opencode-ai/plugin"
  19. import z from "zod"
  20. import { Plugin } from "../plugin"
  21. import { WebSearchTool } from "./websearch"
  22. import { CodeSearchTool } from "./codesearch"
  23. import { Flag } from "@/flag/flag"
  24. export namespace ToolRegistry {
  25. export const state = Instance.state(async () => {
  26. const custom = [] as Tool.Info[]
  27. const glob = new Bun.Glob("tool/*.{js,ts}")
  28. for (const dir of await Config.directories()) {
  29. for await (const match of glob.scan({
  30. cwd: dir,
  31. absolute: true,
  32. followSymlinks: true,
  33. dot: true,
  34. })) {
  35. const namespace = path.basename(match, path.extname(match))
  36. const mod = await import(match)
  37. for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
  38. custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
  39. }
  40. }
  41. }
  42. const plugins = await Plugin.list()
  43. for (const plugin of plugins) {
  44. for (const [id, def] of Object.entries(plugin.tool ?? {})) {
  45. custom.push(fromPlugin(id, def))
  46. }
  47. }
  48. return { custom }
  49. })
  50. function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
  51. return {
  52. id,
  53. init: async () => ({
  54. parameters: z.object(def.args),
  55. description: def.description,
  56. execute: async (args, ctx) => {
  57. const result = await def.execute(args as any, ctx)
  58. return {
  59. title: "",
  60. output: result,
  61. metadata: {},
  62. }
  63. },
  64. }),
  65. }
  66. }
  67. export async function register(tool: Tool.Info) {
  68. const { custom } = await state()
  69. const idx = custom.findIndex((t) => t.id === tool.id)
  70. if (idx >= 0) {
  71. custom.splice(idx, 1, tool)
  72. return
  73. }
  74. custom.push(tool)
  75. }
  76. async function all(): Promise<Tool.Info[]> {
  77. const custom = await state().then((x) => x.custom)
  78. const config = await Config.get()
  79. return [
  80. InvalidTool,
  81. BashTool,
  82. ReadTool,
  83. GlobTool,
  84. GrepTool,
  85. ListTool,
  86. EditTool,
  87. WriteTool,
  88. TaskTool,
  89. WebFetchTool,
  90. TodoWriteTool,
  91. TodoReadTool,
  92. WebSearchTool,
  93. CodeSearchTool,
  94. ...(config.experimental?.batch_tool === true ? [BatchTool] : []),
  95. ...custom,
  96. ]
  97. }
  98. export async function ids() {
  99. return all().then((x) => x.map((t) => t.id))
  100. }
  101. export async function tools(providerID: string, _modelID: string) {
  102. const tools = await all()
  103. const result = await Promise.all(
  104. tools
  105. .filter((t) => {
  106. if (t.id === "codesearch" || t.id === "websearch") return providerID === "opencode"
  107. return true
  108. })
  109. .map(async (t) => ({
  110. id: t.id,
  111. ...(await t.init()),
  112. })),
  113. )
  114. return result
  115. }
  116. export async function enabled(
  117. _providerID: string,
  118. _modelID: string,
  119. agent: Agent.Info,
  120. ): Promise<Record<string, boolean>> {
  121. const result: Record<string, boolean> = {}
  122. if (agent.permission.edit === "deny") {
  123. result["edit"] = false
  124. result["write"] = false
  125. }
  126. if (agent.permission.bash["*"] === "deny" && Object.keys(agent.permission.bash).length === 1) {
  127. result["bash"] = false
  128. }
  129. if (agent.permission.webfetch === "deny") {
  130. result["webfetch"] = false
  131. }
  132. return result
  133. }
  134. }