Преглед изворни кода

fix: pass image parts to custom commands (#6525)

Co-authored-by: Melih Mucuk <[email protected]>
Melih Mucuk пре 1 месец
родитељ
комит
a38e1701ee

+ 8 - 0
packages/app/src/components/prompt-input.tsx

@@ -1111,6 +1111,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
             agent,
             model: `${model.providerID}/${model.modelID}`,
             variant,
+            parts: images.map((attachment) => ({
+              id: Identifier.ascending("part"),
+              type: "file" as const,
+              mime: attachment.mime,
+              url: attachment.dataUrl,
+              filename: attachment.filename,
+            })),
           })
           .catch((err) => {
             showToast({
@@ -1206,6 +1213,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       filename: attachment.filename,
     }))
 
+
     const messageID = Identifier.ascending("message")
     const textPart = {
       id: Identifier.ascending("part"),

+ 6 - 0
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

@@ -559,6 +559,12 @@ export function Prompt(props: PromptProps) {
         model: `${selectedModel.providerID}/${selectedModel.modelID}`,
         messageID,
         variant,
+        parts: nonTextParts
+          .filter((x) => x.type === "file")
+          .map((x) => ({
+            id: Identifier.ascending("part"),
+            ...x,
+          })),
       })
     } else {
       sdk.client.session.prompt({

+ 17 - 3
packages/opencode/src/session/prompt.ts

@@ -1422,10 +1422,23 @@ export namespace SessionPrompt {
     arguments: z.string(),
     command: z.string(),
     variant: z.string().optional(),
+    parts: z
+      .array(
+        z.discriminatedUnion("type", [
+          MessageV2.FilePart.omit({
+            messageID: true,
+            sessionID: true,
+          }).partial({
+            id: true,
+          }),
+        ]),
+      )
+      .optional(),
   })
   export type CommandInput = z.infer<typeof CommandInput>
   const bashRegex = /!`([^`]+)`/g
-  const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
+  // Match [Image N] as single token, quoted strings, or non-space sequences
+  const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
   const placeholderRegex = /\$(\d+)/g
   const quoteTrimRegex = /^["']|["']$/g
   /**
@@ -1516,6 +1529,7 @@ export namespace SessionPrompt {
       throw error
     }
 
+    const templateParts = await resolvePromptParts(template)
     const parts =
       (agent.mode === "subagent" && command.subtask !== false) || command.subtask === true
         ? [
@@ -1525,10 +1539,10 @@ export namespace SessionPrompt {
               description: command.description ?? "",
               command: input.command,
               // TODO: how can we make task tool accept a more complex input?
-              prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""),
+              prompt: templateParts.find((y) => y.type === "text")?.text ?? "",
             },
           ]
-        : await resolvePromptParts(template)
+        : [...templateParts, ...(input.parts ?? [])]
 
     const result = (await prompt({
       sessionID: input.sessionID,

+ 10 - 0
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -24,6 +24,7 @@ import type {
   ExperimentalResourceListResponses,
   FileListResponses,
   FilePartInput,
+  FilePartSource,
   FileReadResponses,
   FileStatusResponses,
   FindFilesResponses,
@@ -1451,6 +1452,14 @@ export class Session extends HeyApiClient {
       arguments?: string
       command?: string
       variant?: string
+      parts?: Array<{
+        id?: string
+        type: "file"
+        mime: string
+        filename?: string
+        url: string
+        source?: FilePartSource
+      }>
     },
     options?: Options<never, ThrowOnError>,
   ) {
@@ -1467,6 +1476,7 @@ export class Session extends HeyApiClient {
             { in: "body", key: "arguments" },
             { in: "body", key: "command" },
             { in: "body", key: "variant" },
+            { in: "body", key: "parts" },
           ],
         },
       ],

+ 8 - 0
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -3292,6 +3292,14 @@ export type SessionCommandData = {
     arguments: string
     command: string
     variant?: string
+    parts?: Array<{
+      id?: string
+      type: "file"
+      mime: string
+      filename?: string
+      url: string
+      source?: FilePartSource
+    }>
   }
   path: {
     /**

+ 32 - 0
packages/sdk/openapi.json

@@ -2667,6 +2667,38 @@
                   },
                   "variant": {
                     "type": "string"
+                  },
+                  "parts": {
+                    "type": "array",
+                    "items": {
+                      "anyOf": [
+                        {
+                          "type": "object",
+                          "properties": {
+                            "id": {
+                              "type": "string"
+                            },
+                            "type": {
+                              "type": "string",
+                              "const": "file"
+                            },
+                            "mime": {
+                              "type": "string"
+                            },
+                            "filename": {
+                              "type": "string"
+                            },
+                            "url": {
+                              "type": "string"
+                            },
+                            "source": {
+                              "$ref": "#/components/schemas/FilePartSource"
+                            }
+                          },
+                          "required": ["type", "mime", "url"]
+                        }
+                      ]
+                    }
                   }
                 },
                 "required": ["arguments", "command"]