Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ export namespace SessionPrompt {
)
let executionError: Error | undefined
const taskAgent = await Agent.get(task.agent)
const toolLogger = Log.create({ service: "tool" })
const taskCtx: Tool.Context = {
agent: task.agent,
messageID: assistantMessage.id,
Expand All @@ -400,6 +401,9 @@ export namespace SessionPrompt {
ruleset: PermissionNext.merge(taskAgent.permission, session.permission ?? []),
})
},
log(level, message, extra) {
toolLogger[level](message, extra)
},
}
const result = await taskTool.execute(taskArgs, taskCtx).catch((error) => {
executionError = error
Expand Down Expand Up @@ -640,6 +644,7 @@ export namespace SessionPrompt {
}) {
using _ = log.time("resolveTools")
const tools: Record<string, AITool> = {}
const toolLogger = Log.create({ service: "tool" })

const context = (args: any, options: ToolCallOptions): Tool.Context => ({
sessionID: input.session.id,
Expand Down Expand Up @@ -673,6 +678,9 @@ export namespace SessionPrompt {
ruleset: PermissionNext.merge(input.agent.permission, input.session.permission ?? []),
})
},
log(level, message, extra) {
toolLogger[level](message, extra)
},
})

for (const item of await ToolRegistry.tools(input.model.providerID)) {
Expand Down Expand Up @@ -884,6 +892,7 @@ export namespace SessionPrompt {
return pieces
}
const url = new URL(part.url)
const toolLogger = Log.create({ service: "tool" })
switch (url.protocol) {
case "data:":
if (part.mime === "text/plain") {
Expand Down Expand Up @@ -983,6 +992,9 @@ export namespace SessionPrompt {
extra: { bypassCwdCheck: true, model },
metadata: async () => {},
ask: async () => {},
log(level, message, extra) {
toolLogger[level](message, extra)
},
}
const result = await t.execute(args, readCtx)
pieces.push({
Expand Down Expand Up @@ -1044,6 +1056,9 @@ export namespace SessionPrompt {
extra: { bypassCwdCheck: true },
metadata: async () => {},
ask: async () => {},
log(level, message, extra) {
toolLogger[level](message, extra)
},
}
const result = await ListTool.init().then((t) => t.execute(args, listCtx))
return [
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/tool/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export namespace Tool {
extra?: { [key: string]: any }
metadata(input: { title?: string; metadata?: M }): void
ask(input: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">): Promise<void>
log(level: "debug" | "info" | "warn" | "error", message: string, extra?: Record<string, unknown>): void
}
export interface Info<Parameters extends z.ZodType = z.ZodType, M extends Metadata = Metadata> {
id: string
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/test/tool/bash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const ctx = {
abort: AbortSignal.any([]),
metadata: () => {},
ask: async () => {},
log: () => {},
}

const projectRoot = path.join(__dirname, "../..")
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/test/tool/grep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ctx = {
abort: AbortSignal.any([]),
metadata: () => {},
ask: async () => {},
log: () => {},
}

const projectRoot = path.join(__dirname, "../..")
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/test/tool/patch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ctx = {
abort: AbortSignal.any([]),
metadata: () => {},
ask: async () => {},
log: () => {},
}

const patchTool = await PatchTool.init()
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/test/tool/read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ctx = {
abort: AbortSignal.any([]),
metadata: () => {},
ask: async () => {},
log: () => {},
}

describe("tool.read external_directory permission", () => {
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ToolContext = {
messageID: string
agent: string
abort: AbortSignal
log(level: "debug" | "info" | "warn" | "error", message: string, extra?: Record<string, unknown>): void
}

export function tool<Args extends z.ZodRawShape>(input: {
Expand Down
36 changes: 34 additions & 2 deletions packages/web/src/content/docs/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,12 @@ export const MyPlugin = async (ctx) => {

```js title=".opencode/plugin/example.js"
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
console.log("Plugin initialized!")
// Use client.app.log() instead of console.log to avoid leaking output
await client.app.log({
service: "my-plugin",
level: "info",
message: "Plugin initialized",
})

return {
// Hook implementations go here
Expand Down Expand Up @@ -290,7 +295,11 @@ Your custom tools will be available to opencode alongside built-in tools.

### Logging

Use `client.app.log()` instead of `console.log` for structured logging:
Use structured logging instead of `console.log` to avoid output leaking to stdout.

#### In plugins

Use `client.app.log()` for logging in plugin initialization and hooks:

```ts title=".opencode/plugin/my-plugin.ts"
export const MyPlugin = async ({ client }) => {
Expand All @@ -303,6 +312,29 @@ export const MyPlugin = async ({ client }) => {
}
```

#### In custom tools

Use `ctx.log()` for logging in custom tool implementations:

```ts title=".opencode/plugin/custom-tool.ts"
import { type Plugin, tool } from "@opencode-ai/plugin"

export const CustomToolPlugin: Plugin = async (ctx) => {
return {
tool: {
mytool: tool({
description: "A tool that logs",
args: { name: tool.schema.string() },
async execute(args, ctx) {
ctx.log("info", "Tool executed", { name: args.name })
return `Hello ${args.name}!`
},
}),
},
}
}
```

Levels: `debug`, `info`, `warn`, `error`. See [SDK documentation](https://opencode.ai/docs/sdk) for details.

---
Expand Down