custom-tools.mdx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. ---
  2. title: Custom Tools
  3. description: Create tools the LLM can call in opencode.
  4. ---
  5. Custom tools are functions you create that the LLM can call during conversations. They work alongside opencode's [built-in tools](/docs/tools) like `read`, `write`, and `bash`.
  6. ---
  7. ## Creating a tool
  8. Tools are defined as **TypeScript** or **JavaScript** files. However, the tool definition can invoke scripts written in **any language** — TypeScript or JavaScript is only used for the tool definition itself.
  9. ---
  10. ### Location
  11. They can be defined:
  12. - Locally by placing them in the `.opencode/tools/` directory of your project.
  13. - Or globally, by placing them in `~/.config/opencode/tools/`.
  14. ---
  15. ### Structure
  16. The easiest way to create tools is using the `tool()` helper which provides type-safety and validation.
  17. ```ts title=".opencode/tools/database.ts" {1}
  18. import { tool } from "@opencode-ai/plugin"
  19. export default tool({
  20. description: "Query the project database",
  21. args: {
  22. query: tool.schema.string().describe("SQL query to execute"),
  23. },
  24. async execute(args) {
  25. // Your database logic here
  26. return `Executed query: ${args.query}`
  27. },
  28. })
  29. ```
  30. The **filename** becomes the **tool name**. The above creates a `database` tool.
  31. ---
  32. #### Multiple tools per file
  33. You can also export multiple tools from a single file. Each export becomes **a separate tool** with the name **`<filename>_<exportname>`**:
  34. ```ts title=".opencode/tools/math.ts"
  35. import { tool } from "@opencode-ai/plugin"
  36. export const add = tool({
  37. description: "Add two numbers",
  38. args: {
  39. a: tool.schema.number().describe("First number"),
  40. b: tool.schema.number().describe("Second number"),
  41. },
  42. async execute(args) {
  43. return args.a + args.b
  44. },
  45. })
  46. export const multiply = tool({
  47. description: "Multiply two numbers",
  48. args: {
  49. a: tool.schema.number().describe("First number"),
  50. b: tool.schema.number().describe("Second number"),
  51. },
  52. async execute(args) {
  53. return args.a * args.b
  54. },
  55. })
  56. ```
  57. This creates two tools: `math_add` and `math_multiply`.
  58. ---
  59. #### Name collisions with built-in tools
  60. Custom tools are keyed by tool name. If a custom tool uses the same name as a built-in tool, the custom tool takes precedence.
  61. For example, this file replaces the built-in `bash` tool:
  62. ```ts title=".opencode/tools/bash.ts"
  63. import { tool } from "@opencode-ai/plugin"
  64. export default tool({
  65. description: "Restricted bash wrapper",
  66. args: {
  67. command: tool.schema.string(),
  68. },
  69. async execute(args) {
  70. return `blocked: ${args.command}`
  71. },
  72. })
  73. ```
  74. :::note
  75. Prefer unique names unless you intentionally want to replace a built-in tool. If you want to disable a built in tool but not override it, use [permissions](/docs/permissions).
  76. :::
  77. ---
  78. ### Arguments
  79. You can use `tool.schema`, which is just [Zod](https://zod.dev), to define argument types.
  80. ```ts "tool.schema"
  81. args: {
  82. query: tool.schema.string().describe("SQL query to execute")
  83. }
  84. ```
  85. You can also import [Zod](https://zod.dev) directly and return a plain object:
  86. ```ts {6}
  87. import { z } from "zod"
  88. export default {
  89. description: "Tool description",
  90. args: {
  91. param: z.string().describe("Parameter description"),
  92. },
  93. async execute(args, context) {
  94. // Tool implementation
  95. return "result"
  96. },
  97. }
  98. ```
  99. ---
  100. ### Context
  101. Tools receive context about the current session:
  102. ```ts title=".opencode/tools/project.ts" {8}
  103. import { tool } from "@opencode-ai/plugin"
  104. export default tool({
  105. description: "Get project information",
  106. args: {},
  107. async execute(args, context) {
  108. // Access context information
  109. const { agent, sessionID, messageID, directory, worktree } = context
  110. return `Agent: ${agent}, Session: ${sessionID}, Message: ${messageID}, Directory: ${directory}, Worktree: ${worktree}`
  111. },
  112. })
  113. ```
  114. Use `context.directory` for the session working directory.
  115. Use `context.worktree` for the git worktree root.
  116. ---
  117. ## Examples
  118. ### Write a tool in Python
  119. You can write your tools in any language you want. Here's an example that adds two numbers using Python.
  120. First, create the tool as a Python script:
  121. ```python title=".opencode/tools/add.py"
  122. import sys
  123. a = int(sys.argv[1])
  124. b = int(sys.argv[2])
  125. print(a + b)
  126. ```
  127. Then create the tool definition that invokes it:
  128. ```ts title=".opencode/tools/python-add.ts" {10}
  129. import { tool } from "@opencode-ai/plugin"
  130. import path from "path"
  131. export default tool({
  132. description: "Add two numbers using Python",
  133. args: {
  134. a: tool.schema.number().describe("First number"),
  135. b: tool.schema.number().describe("Second number"),
  136. },
  137. async execute(args, context) {
  138. const script = path.join(context.worktree, ".opencode/tools/add.py")
  139. const result = await Bun.$`python3 ${script} ${args.a} ${args.b}`.text()
  140. return result.trim()
  141. },
  142. })
  143. ```
  144. Here we are using the [`Bun.$`](https://bun.com/docs/runtime/shell) utility to run the Python script.