Răsfoiți Sursa

Revert "fix: ensure that tool attachments arent sent as user messages (#8944)"

This reverts commit 8fd1b92e6e95ec8570e13987e73d42093661aa59.
Aiden Cline 1 lună în urmă
părinte
comite
f5a6a4af7f

+ 22 - 16
packages/opencode/src/session/message-v2.ts

@@ -1,14 +1,7 @@
 import { BusEvent } from "@/bus/bus-event"
 import { BusEvent } from "@/bus/bus-event"
 import z from "zod"
 import z from "zod"
 import { NamedError } from "@opencode-ai/util/error"
 import { NamedError } from "@opencode-ai/util/error"
-import {
-  APICallError,
-  convertToModelMessages,
-  LoadAPIKeyError,
-  type ModelMessage,
-  type ToolSet,
-  type UIMessage,
-} from "ai"
+import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessage, type UIMessage } from "ai"
 import { Identifier } from "../id/id"
 import { Identifier } from "../id/id"
 import { LSP } from "../lsp"
 import { LSP } from "../lsp"
 import { Snapshot } from "@/snapshot"
 import { Snapshot } from "@/snapshot"
@@ -439,7 +432,7 @@ export namespace MessageV2 {
   })
   })
   export type WithParts = z.infer<typeof WithParts>
   export type WithParts = z.infer<typeof WithParts>
 
 
-  export function toModelMessage(input: WithParts[], options?: { tools?: ToolSet }): ModelMessage[] {
+  export function toModelMessage(input: WithParts[]): ModelMessage[] {
     const result: UIMessage[] = []
     const result: UIMessage[] = []
 
 
     for (const msg of input) {
     for (const msg of input) {
@@ -510,14 +503,30 @@ export namespace MessageV2 {
             })
             })
           if (part.type === "tool") {
           if (part.type === "tool") {
             if (part.state.status === "completed") {
             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({
               assistantMessage.parts.push({
                 type: ("tool-" + part.tool) as `tool-${string}`,
                 type: ("tool-" + part.tool) as `tool-${string}`,
                 state: "output-available",
                 state: "output-available",
                 toolCallId: part.callID,
                 toolCallId: part.callID,
                 input: part.state.input,
                 input: part.state.input,
-                output: part.state.time.compacted
-                  ? { output: "[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,
                 callProviderMetadata: part.metadata,
               })
               })
             }
             }
@@ -556,10 +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) {
   export const stream = fn(Identifier.schema("session"), async function* (sessionID) {

+ 5 - 33
packages/opencode/src/session/prompt.ts

@@ -597,7 +597,7 @@ export namespace SessionPrompt {
         sessionID,
         sessionID,
         system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())],
         system: [...(await SystemPrompt.environment()), ...(await SystemPrompt.custom())],
         messages: [
         messages: [
-          ...MessageV2.toModelMessage(sessionMessages, { tools }),
+          ...MessageV2.toModelMessage(sessionMessages),
           ...(isLastStep
           ...(isLastStep
             ? [
             ? [
                 {
                 {
@@ -718,22 +718,8 @@ export namespace SessionPrompt {
         },
         },
         toModelOutput(result) {
         toModelOutput(result) {
           return {
           return {
-            type: "content",
-            value: [
-              {
-                type: "text",
-                text: result.output,
-              },
-              ...(result.attachments?.map((attachment: MessageV2.FilePart) => {
-                const base64 = attachment.url.startsWith("data:") ? attachment.url.split(",", 2)[1] : attachment.url
-
-                return {
-                  type: "media",
-                  data: base64,
-                  mediaType: attachment.mime,
-                }
-              }) ?? []),
-            ],
+            type: "text",
+            value: result.output,
           }
           }
         },
         },
       })
       })
@@ -822,22 +808,8 @@ export namespace SessionPrompt {
       }
       }
       item.toModelOutput = (result) => {
       item.toModelOutput = (result) => {
         return {
         return {
-          type: "content",
-          value: [
-            {
-              type: "text",
-              text: result.output,
-            },
-            ...(result.attachments?.map((attachment: MessageV2.FilePart) => {
-              const base64 = attachment.url.startsWith("data:") ? attachment.url.split(",", 2)[1] : attachment.url
-
-              return {
-                type: "media",
-                data: base64,
-                mediaType: attachment.mime,
-              }
-            }) ?? []),
-          ],
+          type: "text",
+          value: result.output,
         }
         }
       }
       }
       tools[key] = item
       tools[key] = item

+ 16 - 40
packages/opencode/test/session/message-v2.test.ts

@@ -1,35 +1,8 @@
 import { describe, expect, test } from "bun:test"
 import { describe, expect, test } from "bun:test"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageV2 } from "../../src/session/message-v2"
-import type { ToolSet } from "ai"
 
 
 const sessionID = "session"
 const sessionID = "session"
 
 
-// Mock tool that transforms output to content format with media support
-function createMockTools(): ToolSet {
-  return {
-    bash: {
-      description: "mock bash tool",
-      inputSchema: { type: "object", properties: {} } as any,
-      toModelOutput(result: { output: string; attachments?: MessageV2.FilePart[] }) {
-        return {
-          type: "content" as const,
-          value: [
-            { type: "text" as const, text: result.output },
-            ...(result.attachments?.map((attachment) => {
-              const base64 = attachment.url.startsWith("data:") ? attachment.url.split(",", 2)[1] : attachment.url
-              return {
-                type: "media" as const,
-                data: base64,
-                mediaType: attachment.mime,
-              }
-            }) ?? []),
-          ],
-        }
-      },
-    },
-  } as ToolSet
-}
-
 function userInfo(id: string): MessageV2.User {
 function userInfo(id: string): MessageV2.User {
   return {
   return {
     id,
     id,
@@ -286,11 +259,23 @@ describe("session.message-v2.toModelMessage", () => {
       },
       },
     ]
     ]
 
 
-    expect(MessageV2.toModelMessage(input, { tools: createMockTools() })).toStrictEqual([
+    expect(MessageV2.toModelMessage(input)).toStrictEqual([
       {
       {
         role: "user",
         role: "user",
         content: [{ type: "text", text: "run tool" }],
         content: [{ type: "text", text: "run tool" }],
       },
       },
+      {
+        role: "user",
+        content: [
+          { type: "text", text: "Tool bash returned an attachment:" },
+          {
+            type: "file",
+            mediaType: "image/png",
+            filename: "attachment.png",
+            data: "https://example.com/attachment.png",
+          },
+        ],
+      },
       {
       {
         role: "assistant",
         role: "assistant",
         content: [
         content: [
@@ -312,13 +297,7 @@ describe("session.message-v2.toModelMessage", () => {
             type: "tool-result",
             type: "tool-result",
             toolCallId: "call-1",
             toolCallId: "call-1",
             toolName: "bash",
             toolName: "bash",
-            output: {
-              type: "content",
-              value: [
-                { type: "text", text: "ok" },
-                { type: "media", data: "https://example.com/attachment.png", mediaType: "image/png" },
-              ],
-            },
+            output: { type: "text", value: "ok" },
             providerOptions: { openai: { tool: "meta" } },
             providerOptions: { openai: { tool: "meta" } },
           },
           },
         ],
         ],
@@ -362,7 +341,7 @@ describe("session.message-v2.toModelMessage", () => {
       },
       },
     ]
     ]
 
 
-    expect(MessageV2.toModelMessage(input, { tools: createMockTools() })).toStrictEqual([
+    expect(MessageV2.toModelMessage(input)).toStrictEqual([
       {
       {
         role: "user",
         role: "user",
         content: [{ type: "text", text: "run tool" }],
         content: [{ type: "text", text: "run tool" }],
@@ -386,10 +365,7 @@ describe("session.message-v2.toModelMessage", () => {
             type: "tool-result",
             type: "tool-result",
             toolCallId: "call-1",
             toolCallId: "call-1",
             toolName: "bash",
             toolName: "bash",
-            output: {
-              type: "content",
-              value: [{ type: "text", text: "[Old tool result content cleared]" }],
-            },
+            output: { type: "text", value: "[Old tool result content cleared]" },
           },
           },
         ],
         ],
       },
       },