Răsfoiți Sursa

fix: adjust resolve parts so that when messages with multiple @ references occur, the tool calls are properly ordered

Aiden Cline 3 săptămâni în urmă
părinte
comite
612b656d36

+ 9 - 1
packages/opencode/src/session/processor.ts

@@ -172,6 +172,14 @@ export namespace SessionProcessor {
                 case "tool-result": {
                   const match = toolcalls[value.toolCallId]
                   if (match && match.state.status === "running") {
+                    const attachments = value.output.attachments?.map(
+                      (attachment: Omit<MessageV2.FilePart, "id" | "messageID" | "sessionID">) => ({
+                        ...attachment,
+                        id: Identifier.ascending("part"),
+                        messageID: match.messageID,
+                        sessionID: match.sessionID,
+                      }),
+                    )
                     await Session.updatePart({
                       ...match,
                       state: {
@@ -184,7 +192,7 @@ export namespace SessionProcessor {
                           start: match.state.time.start,
                           end: Date.now(),
                         },
-                        attachments: value.output.attachments,
+                        attachments,
                       },
                     })
 

+ 43 - 26
packages/opencode/src/session/prompt.ts

@@ -187,13 +187,17 @@ export namespace SessionPrompt {
         text: template,
       },
     ]
-    const files = ConfigMarkdown.files(template)
+    const matches = ConfigMarkdown.files(template)
     const seen = new Set<string>()
-    await Promise.all(
-      files.map(async (match) => {
-        const name = match[1]
-        if (seen.has(name)) return
+    const names = matches
+      .map((match) => match[1])
+      .filter((name) => {
+        if (seen.has(name)) return false
         seen.add(name)
+        return true
+      })
+    const resolved = await Promise.all(
+      names.map(async (name) => {
         const filepath = name.startsWith("~/")
           ? path.join(os.homedir(), name.slice(2))
           : path.resolve(Instance.worktree, name)
@@ -201,33 +205,34 @@ export namespace SessionPrompt {
         const stats = await fs.stat(filepath).catch(() => undefined)
         if (!stats) {
           const agent = await Agent.get(name)
-          if (agent) {
-            parts.push({
-              type: "agent",
-              name: agent.name,
-            })
-          }
-          return
+          if (!agent) return undefined
+          return {
+            type: "agent",
+            name: agent.name,
+          } satisfies PromptInput["parts"][number]
         }
 
         if (stats.isDirectory()) {
-          parts.push({
+          return {
             type: "file",
             url: `file://${filepath}`,
             filename: name,
             mime: "application/x-directory",
-          })
-          return
+          } satisfies PromptInput["parts"][number]
         }
 
-        parts.push({
+        return {
           type: "file",
           url: `file://${filepath}`,
           filename: name,
           mime: "text/plain",
-        })
+        } satisfies PromptInput["parts"][number]
       }),
     )
+    for (const item of resolved) {
+      if (!item) continue
+      parts.push(item)
+    }
     return parts
   }
 
@@ -424,6 +429,12 @@ export namespace SessionPrompt {
         assistantMessage.time.completed = Date.now()
         await Session.updateMessage(assistantMessage)
         if (result && part.state.status === "running") {
+          const attachments = result.attachments?.map((attachment) => ({
+            ...attachment,
+            id: Identifier.ascending("part"),
+            messageID: assistantMessage.id,
+            sessionID: assistantMessage.sessionID,
+          }))
           await Session.updatePart({
             ...part,
             state: {
@@ -432,7 +443,7 @@ export namespace SessionPrompt {
               title: result.title,
               metadata: result.metadata,
               output: result.output,
-              attachments: result.attachments,
+              attachments,
               time: {
                 ...part.state.time,
                 end: Date.now(),
@@ -771,16 +782,13 @@ export namespace SessionPrompt {
         )
 
         const textParts: string[] = []
-        const attachments: MessageV2.FilePart[] = []
+        const attachments: Omit<MessageV2.FilePart, "id" | "messageID" | "sessionID">[] = []
 
         for (const contentItem of result.content) {
           if (contentItem.type === "text") {
             textParts.push(contentItem.text)
           } else if (contentItem.type === "image") {
             attachments.push({
-              id: Identifier.ascending("part"),
-              sessionID: input.session.id,
-              messageID: input.processor.message.id,
               type: "file",
               mime: contentItem.mimeType,
               url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
@@ -792,9 +800,6 @@ export namespace SessionPrompt {
             }
             if (resource.blob) {
               attachments.push({
-                id: Identifier.ascending("part"),
-                sessionID: input.session.id,
-                messageID: input.processor.message.id,
                 type: "file",
                 mime: resource.mimeType ?? "application/octet-stream",
                 url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
@@ -1043,6 +1048,7 @@ export namespace SessionPrompt {
                       pieces.push(
                         ...result.attachments.map((attachment) => ({
                           ...attachment,
+                          id: Identifier.ascending("part"),
                           synthetic: true,
                           filename: attachment.filename ?? part.filename,
                           messageID: info.id,
@@ -1180,7 +1186,18 @@ export namespace SessionPrompt {
           },
         ]
       }),
-    ).then((x) => x.flat())
+    )
+      .then((x) => x.flat())
+      .then((drafts) =>
+        drafts.map(
+          (part): MessageV2.Part => ({
+            ...part,
+            id: Identifier.ascending("part"),
+            messageID: info.id,
+            sessionID: input.sessionID,
+          }),
+        ),
+      )
 
     await Plugin.trigger(
       "chat.message",

+ 7 - 1
packages/opencode/src/tool/batch.ts

@@ -77,6 +77,12 @@ export const BatchTool = Tool.define("batch", async () => {
           })
 
           const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
+          const attachments = result.attachments?.map((attachment) => ({
+            ...attachment,
+            id: Identifier.ascending("part"),
+            messageID: ctx.messageID,
+            sessionID: ctx.sessionID,
+          }))
 
           await Session.updatePart({
             id: partID,
@@ -91,7 +97,7 @@ export const BatchTool = Tool.define("batch", async () => {
               output: result.output,
               title: result.title,
               metadata: result.metadata,
-              attachments: result.attachments,
+              attachments,
               time: {
                 start: callStartTime,
                 end: Date.now(),

+ 0 - 4
packages/opencode/src/tool/read.ts

@@ -6,7 +6,6 @@ import { LSP } from "../lsp"
 import { FileTime } from "../file/time"
 import DESCRIPTION from "./read.txt"
 import { Instance } from "../project/instance"
-import { Identifier } from "../id/id"
 import { assertExternalDirectory } from "./external-directory"
 import { InstructionPrompt } from "../session/instruction"
 
@@ -79,9 +78,6 @@ export const ReadTool = Tool.define("read", {
         },
         attachments: [
           {
-            id: Identifier.ascending("part"),
-            sessionID: ctx.sessionID,
-            messageID: ctx.messageID,
             type: "file",
             mime,
             url: `data:${mime};base64,${Buffer.from(await file.bytes()).toString("base64")}`,

+ 1 - 1
packages/opencode/src/tool/tool.ts

@@ -36,7 +36,7 @@ export namespace Tool {
         title: string
         metadata: M
         output: string
-        attachments?: MessageV2.FilePart[]
+        attachments?: Omit<MessageV2.FilePart, "id" | "sessionID" | "messageID">[]
       }>
       formatValidationError?(error: z.ZodError): string
     }>