mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-14 08:32:33 +00:00
fix(plugin): preserve tool attachments (#27157)
This commit is contained in:
@@ -160,11 +160,13 @@ export const layer: Layer.Layer<
|
||||
const result = yield* Effect.promise(() => def.execute(args as any, pluginCtx))
|
||||
const output = typeof result === "string" ? result : result.output
|
||||
const metadata = typeof result === "string" ? {} : (result.metadata ?? {})
|
||||
const attachments = typeof result === "string" ? undefined : result.attachments
|
||||
const info = yield* agent.get(toolCtx.agent)
|
||||
const out = yield* truncate.output(output, {}, info)
|
||||
return {
|
||||
title: "",
|
||||
title: typeof result === "string" ? "" : (result.title ?? ""),
|
||||
output: out.truncated ? out.content : output,
|
||||
attachments,
|
||||
metadata: {
|
||||
...metadata,
|
||||
truncated: out.truncated,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { pathToFileURL } from "url"
|
||||
import { Effect, Layer, Result, Schema } from "effect"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Tool } from "@/tool/tool"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
@@ -29,6 +30,7 @@ import { InstanceState } from "@/effect/instance-state"
|
||||
import { Reference } from "@/reference/reference"
|
||||
import { ProviderID, ModelID } from "@/provider/schema"
|
||||
import { ToolJsonSchema } from "@/tool/json-schema"
|
||||
import { MessageID, SessionID } from "@/session/schema"
|
||||
|
||||
const node = CrossSpawnSpawner.defaultLayer
|
||||
const originalExperimentalScout = Flag.OPENCODE_EXPERIMENTAL_SCOUT
|
||||
@@ -193,6 +195,54 @@ describe("tool.registry", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("preserves attachments from structured custom tool results", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
const customTools = path.join(test.directory, ".opencode", "tools")
|
||||
const pluginTool = pathToFileURL(path.resolve(import.meta.dir, "../../../plugin/src/tool.ts")).href
|
||||
yield* Effect.promise(() => fs.mkdir(customTools, { recursive: true }))
|
||||
yield* Effect.promise(() =>
|
||||
Bun.write(
|
||||
path.join(customTools, "image.ts"),
|
||||
[
|
||||
`import { tool } from ${JSON.stringify(pluginTool)}`,
|
||||
"export default tool({",
|
||||
" description: 'image tool',",
|
||||
" args: {},",
|
||||
" execute: async () => ({",
|
||||
" output: 'here is an image',",
|
||||
" attachments: [{ type: 'file', mime: 'image/png', filename: 'picture.png', url: 'data:image/png;base64,AAAA' }],",
|
||||
" }),",
|
||||
"})",
|
||||
"",
|
||||
].join("\n"),
|
||||
),
|
||||
)
|
||||
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const loaded = (yield* registry.all()).find((tool) => tool.id === "image")
|
||||
if (!loaded) throw new Error("custom image tool was not loaded")
|
||||
const agents = yield* Agent.Service
|
||||
const result = yield* loaded.execute(
|
||||
{},
|
||||
{
|
||||
sessionID: SessionID.make("ses_test"),
|
||||
messageID: MessageID.make("msg_test"),
|
||||
agent: (yield* agents.defaultInfo()).name,
|
||||
abort: new AbortController().signal,
|
||||
messages: [],
|
||||
metadata: () => Effect.void,
|
||||
ask: () => Effect.void,
|
||||
} satisfies Tool.Context,
|
||||
)
|
||||
|
||||
expect(result.output).toBe("here is an image")
|
||||
expect(result.attachments).toEqual([
|
||||
{ type: "file", mime: "image/png", filename: "picture.png", url: "data:image/png;base64,AAAA" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () =>
|
||||
Effect.gen(function* () {
|
||||
const test = yield* TestInstance
|
||||
|
||||
@@ -27,7 +27,21 @@ type AskInput = {
|
||||
metadata: { [key: string]: any }
|
||||
}
|
||||
|
||||
export type ToolResult = string | { output: string; metadata?: { [key: string]: any } }
|
||||
export type ToolAttachment = {
|
||||
type: "file"
|
||||
mime: string
|
||||
url: string
|
||||
filename?: string
|
||||
}
|
||||
|
||||
export type ToolResult =
|
||||
| string
|
||||
| {
|
||||
title?: string
|
||||
output: string
|
||||
metadata?: { [key: string]: any }
|
||||
attachments?: ToolAttachment[]
|
||||
}
|
||||
|
||||
export function tool<Args extends z.ZodRawShape>(input: {
|
||||
description: string
|
||||
|
||||
Reference in New Issue
Block a user