LatentCodeCustomize
Plugins and custom tools
Extend LatentCode with JavaScript or TypeScript: react to events, check or change what the agent does, and give it new tools.
Install a plugin
Plugins published to npm install with one command:
latentcode plugin some-latentcode-pluginThis installs the package and adds it to the project's config. Add -g to install it for all projects and -f to replace a version that's already installed. You can also list plugins in latentcode.json, with options:
{
"plugin": [
"some-latentcode-plugin",
["another-plugin", { "channel": "#builds" }],
"./scripts/my-plugin.ts"
]
}Relative paths are resolved from the config file. Start LatentCode with --pure to run without any external plugins, for example to rule one out when something goes wrong. latentcode debug info lists the plugins that are loaded.
Write a plugin
Put a .ts or .js file in .latentcode/plugins/ (project) or ~/.config/latentcode/plugins/ (everywhere). Every export must be a plugin: an async function that receives context and returns the hooks it implements.
export const Guard = async ({ directory, $ }) => {
return {
// Refuse to read secrets, whatever the permission rules say.
"tool.execute.before": async (input, output) => {
if (input.tool === "read" && String(output.args.filePath).includes("secrets/")) {
throw new Error("Reading files in secrets/ is not allowed")
}
},
// Desktop notification when the agent finishes (macOS).
event: async ({ event }) => {
if (event.type === "session.idle") {
await $`osascript -e 'display notification "LatentCode finished" with title "${directory}"'`
}
},
}
}The plugin package, with types for plugins and tools, is installed into each .latentcode folder automatically. The context includes directory and worktree (the project), project, and $ for running shell commands.
Hooks
| Hook | Called | You can |
|---|---|---|
| event | For every event: sessions starting and going idle, messages, permission requests, and more. | React, for example notify or log. |
| tool.execute.before | Before a tool runs, with its arguments. | Change the arguments, or throw to block the call. |
| tool.execute.after | After a tool runs. | Change the title, output or metadata the agent sees. |
| permission.ask | When a permission rule would ask. | Set status to allow, deny or ask. |
| shell.env | Before a shell command runs. | Add environment variables. |
| chat.message | When you send a message. | Change the message and its parts. |
| chat.params | Before each model request. | Change temperature and provider options. |
| chat.headers | Before each model request. | Add HTTP headers. |
| command.execute.before | Before a slash command runs. | Change the parts it sends. |
| config | At startup, with the merged config. | Read the configuration. |
| tool | At startup. | Add tools (below). |
Custom tools
To give the agent a new tool without writing a full plugin, put a file in .latentcode/tools/ or ~/.config/latentcode/tools/:
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "Look up a ticket in the tracker by its key, e.g. PAY-123",
args: {
key: tool.schema.string().describe("Ticket key"),
},
async execute(args, context) {
const res = await fetch(`https://tracker.example.com/api/tickets/${args.key}`, {
headers: { Authorization: `Bearer ${process.env.TRACKER_TOKEN}` },
})
return await res.text()
},
})- A default export is named after the file (
ticket). Named exports are called<file>_<export>, so one file can hold several tools. tool.schemais Zod; describe each argument so the model knows what to pass.- Return a string, or
{ output, title, metadata }. Long output is truncated like any other tool's. contexthas thesessionID,agent,directory,worktree, anabortsignal, andask()to request permission.- The tool can call anything: shell out to a Python or Go script, or call an API as above.
- Plugins can add tools the same way, under a
toolkey in the hooks they return.