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 result = yield* Effect.promise(() => def.execute(args as any, pluginCtx))
|
||||||
const output = typeof result === "string" ? result : result.output
|
const output = typeof result === "string" ? result : result.output
|
||||||
const metadata = typeof result === "string" ? {} : (result.metadata ?? {})
|
const metadata = typeof result === "string" ? {} : (result.metadata ?? {})
|
||||||
|
const attachments = typeof result === "string" ? undefined : result.attachments
|
||||||
const info = yield* agent.get(toolCtx.agent)
|
const info = yield* agent.get(toolCtx.agent)
|
||||||
const out = yield* truncate.output(output, {}, info)
|
const out = yield* truncate.output(output, {}, info)
|
||||||
return {
|
return {
|
||||||
title: "",
|
title: typeof result === "string" ? "" : (result.title ?? ""),
|
||||||
output: out.truncated ? out.content : output,
|
output: out.truncated ? out.content : output,
|
||||||
|
attachments,
|
||||||
metadata: {
|
metadata: {
|
||||||
...metadata,
|
...metadata,
|
||||||
truncated: out.truncated,
|
truncated: out.truncated,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { pathToFileURL } from "url"
|
|||||||
import { Effect, Layer, Result, Schema } from "effect"
|
import { Effect, Layer, Result, Schema } from "effect"
|
||||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||||
import { ToolRegistry } from "@/tool/registry"
|
import { ToolRegistry } from "@/tool/registry"
|
||||||
|
import { Tool } from "@/tool/tool"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
@@ -29,6 +30,7 @@ import { InstanceState } from "@/effect/instance-state"
|
|||||||
import { Reference } from "@/reference/reference"
|
import { Reference } from "@/reference/reference"
|
||||||
import { ProviderID, ModelID } from "@/provider/schema"
|
import { ProviderID, ModelID } from "@/provider/schema"
|
||||||
import { ToolJsonSchema } from "@/tool/json-schema"
|
import { ToolJsonSchema } from "@/tool/json-schema"
|
||||||
|
import { MessageID, SessionID } from "@/session/schema"
|
||||||
|
|
||||||
const node = CrossSpawnSpawner.defaultLayer
|
const node = CrossSpawnSpawner.defaultLayer
|
||||||
const originalExperimentalScout = Flag.OPENCODE_EXPERIMENTAL_SCOUT
|
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", () =>
|
it.instance("loads legacy JSON-schema-shaped custom tools with wire schema", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const test = yield* TestInstance
|
const test = yield* TestInstance
|
||||||
|
|||||||
@@ -27,7 +27,21 @@ type AskInput = {
|
|||||||
metadata: { [key: string]: any }
|
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: {
|
export function tool<Args extends z.ZodRawShape>(input: {
|
||||||
description: string
|
description: string
|
||||||
|
|||||||
Reference in New Issue
Block a user