diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 4b081b5b4..9f2e0ba06 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -1,14 +1,7 @@ import { BusEvent } from "@/bus/bus-event" import z from "zod" import { NamedError } from "@opencode-ai/util/error" -import { - APICallError, - convertToModelMessages, - LoadAPIKeyError, - type ModelMessage, - type UIMessage, - type ToolSet, -} from "ai" +import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai" import { Identifier } from "../id/id" import { LSP } from "../lsp" import { Snapshot } from "@/snapshot" @@ -439,7 +432,7 @@ export namespace MessageV2 { }) export type WithParts = z.infer - export function toModelMessage(input: WithParts[], options?: { tools?: ToolSet }): ModelMessage[] { + export function toModelMessage(input: WithParts[]): ModelMessage[] { const result: UIMessage[] = [] for (const msg of input) { @@ -510,14 +503,30 @@ export namespace MessageV2 { }) if (part.type === "tool") { if (part.state.status === "completed") { + if (part.state.attachments?.length) { + result.push({ + id: Identifier.ascending("message"), + role: "user", + parts: [ + { + type: "text", + text: `Tool ${part.tool} returned an attachment:`, + }, + ...part.state.attachments.map((attachment) => ({ + type: "file" as const, + url: attachment.url, + mediaType: attachment.mime, + filename: attachment.filename, + })), + ], + }) + } assistantMessage.parts.push({ type: ("tool-" + part.tool) as `tool-${string}`, state: "output-available", toolCallId: part.callID, input: part.state.input, - output: part.state.time.compacted - ? "[Old tool result content cleared]" - : { output: part.state.output, attachments: part.state.attachments }, + output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output, callProviderMetadata: part.metadata, }) } @@ -556,12 +565,7 @@ export namespace MessageV2 { } } - return convertToModelMessages( - result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")), - { - tools: options?.tools, - }, - ) + return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start"))) } export const stream = fn(Identifier.schema("session"), async function* (sessionID) { diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 663f5660f..8327698fd 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -597,7 +597,7 @@ export namespace SessionPrompt { sessionID, system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())], messages: [ - ...MessageV2.toModelMessage(sessionMessages, { tools }), + ...MessageV2.toModelMessage(sessionMessages), ...(isLastStep ? [ { @@ -716,18 +716,10 @@ export namespace SessionPrompt { ) return result }, - toModelOutput(result: { output: string; attachments?: MessageV2.FilePart[] }) { - if (!result.attachments?.length) return { type: "text", value: result.output } + toModelOutput(result) { return { - type: "content", - value: [ - { type: "text", text: result.output }, - ...result.attachments.map((a) => ({ - type: "media" as const, - data: a.url.slice(a.url.indexOf(",") + 1), - mediaType: a.mime, - })), - ], + type: "text", + value: result.output, } }, }) @@ -814,18 +806,10 @@ export namespace SessionPrompt { content: result.content, // directly return content to preserve ordering when outputting to model } } - item.toModelOutput = (result: { output: string; attachments?: MessageV2.FilePart[] }) => { - if (!result.attachments?.length) return { type: "text", value: result.output } + item.toModelOutput = (result) => { return { - type: "content", - value: [ - { type: "text", text: result.output }, - ...result.attachments.map((a) => ({ - type: "media" as const, - data: a.url.slice(a.url.indexOf(",") + 1), - mediaType: a.mime, - })), - ], + type: "text", + value: result.output, } } tools[key] = item