Selaa lähdekoodia

fix(core): resolve prompt attachment mime types

Dax Raad 2 kuukautta sitten
vanhempi
commit
850a0dfe7c

+ 0 - 4
packages/client/src/generated/types.ts

@@ -305,7 +305,6 @@ export type SessionsPromptInput = {
       readonly text: string
       readonly files?: ReadonlyArray<{
         readonly uri: string
-        readonly mime: string
         readonly name?: string
         readonly description?: string
         readonly source?: { readonly start: number; readonly end: number; readonly text: string }
@@ -324,7 +323,6 @@ export type SessionsPromptInput = {
       readonly text: string
       readonly files?: ReadonlyArray<{
         readonly uri: string
-        readonly mime: string
         readonly name?: string
         readonly description?: string
         readonly source?: { readonly start: number; readonly end: number; readonly text: string }
@@ -343,7 +341,6 @@ export type SessionsPromptInput = {
       readonly text: string
       readonly files?: ReadonlyArray<{
         readonly uri: string
-        readonly mime: string
         readonly name?: string
         readonly description?: string
         readonly source?: { readonly start: number; readonly end: number; readonly text: string }
@@ -362,7 +359,6 @@ export type SessionsPromptInput = {
       readonly text: string
       readonly files?: ReadonlyArray<{
         readonly uri: string
-        readonly mime: string
         readonly name?: string
         readonly description?: string
         readonly source?: { readonly start: number; readonly end: number; readonly text: string }

+ 20 - 3
packages/core/src/session.ts

@@ -10,6 +10,7 @@ import { ModelV2 } from "./model"
 import { Location } from "./location"
 import { SessionMessage } from "./session/message"
 import { Prompt } from "./session/prompt"
+import { PromptInput } from "@opencode-ai/schema/prompt-input"
 import { EventV2 } from "./event"
 import { Database } from "./database/database"
 import { SessionProjector } from "./session/projector"
@@ -32,6 +33,7 @@ import { SessionInput } from "./session/input"
 import { Snapshot } from "./snapshot"
 import { SessionRevert } from "./session/revert"
 import { Revert } from "@opencode-ai/schema/revert"
+import { FSUtil } from "./fs-util"
 
 export const RevertState = Revert.State
 export type RevertState = Revert.State
@@ -137,7 +139,7 @@ export interface Interface {
   readonly prompt: (input: {
     id?: SessionMessage.ID
     sessionID: SessionSchema.ID
-    prompt: Prompt
+    prompt: PromptInput.Prompt
     delivery?: SessionInput.Delivery
     resume?: boolean
   }) => Effect.Effect<SessionInput.Admitted, NotFoundError | PromptConflictError>
@@ -349,13 +351,14 @@ export const layer = Layer.unwrap(
               Effect.uninterruptible(
                 Effect.gen(function* () {
                   yield* result.get(input.sessionID)
+                  const prompt = resolvePrompt(input.prompt)
                   const messageID = input.id ?? SessionMessage.ID.create()
                   const delivery = input.delivery ?? "steer"
-                  const expected = { sessionID: input.sessionID, messageID, prompt: input.prompt, delivery }
+                  const expected = { sessionID: input.sessionID, messageID, prompt, delivery }
                   const admitted = yield* SessionInput.admit(db, events, {
                     id: messageID,
                     sessionID: input.sessionID,
-                    prompt: input.prompt,
+                    prompt,
                     delivery,
                   }).pipe(
                     Effect.catchDefect((defect) =>
@@ -452,3 +455,17 @@ export const defaultLayer = layer.pipe(
   Layer.provide(ProjectV2.defaultLayer),
   Layer.orDie,
 )
+
+const resolvePrompt = (input: PromptInput.Prompt) =>
+  Prompt.make({
+    text: input.text,
+    agents: input.agents,
+    files: input.files?.map((file) => {
+      const dataMime = file.uri.match(/^data:([^;,]+)[;,]/i)?.[1]
+      const target = URL.canParse(file.uri) ? new URL(file.uri).pathname : (file.name ?? file.uri)
+      return {
+        ...file,
+        mime: dataMime ?? (target.endsWith("/") ? "application/x-directory" : FSUtil.mimeType(target)),
+      }
+    }),
+  })

+ 21 - 0
packages/core/test/session-prompt.test.ts

@@ -173,6 +173,27 @@ describe("SessionV2.prompt", () => {
     }),
   )
 
+  it.effect("resolves attachment MIME before admission", () =>
+    Effect.gen(function* () {
+      yield* setup
+      const session = yield* SessionV2.Service
+
+      const message = yield* session.prompt({
+        sessionID,
+        prompt: {
+          text: "Inspect this image",
+          files: [{ uri: "data:image/png;base64,aGVsbG8=", name: "image.png" }],
+        },
+        resume: false,
+      })
+
+      expect(message.prompt.files).toEqual([
+        { uri: "data:image/png;base64,aGVsbG8=", name: "image.png", mime: "image/png" },
+      ])
+      expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files)
+    }),
+  )
+
   it.effect("streams durable Session events after an aggregate sequence", () =>
     Effect.gen(function* () {
       yield* setup

+ 2 - 2
packages/protocol/src/groups/session.ts

@@ -1,6 +1,6 @@
 import { SessionMessage } from "@opencode-ai/schema/session-message"
 import { SessionInput } from "@opencode-ai/schema/session-input"
-import { Prompt } from "@opencode-ai/schema/prompt"
+import { PromptInput } from "@opencode-ai/schema/prompt-input"
 import { Session } from "@opencode-ai/schema/session"
 import { Project } from "@opencode-ai/schema/project"
 import { AbsolutePath, NonNegativeInt, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
@@ -192,7 +192,7 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
         params: { sessionID: Session.ID },
         payload: Schema.Struct({
           id: SessionMessage.ID.pipe(Schema.optional),
-          prompt: Prompt,
+          prompt: PromptInput.Prompt,
           delivery: SessionInput.Delivery.pipe(Schema.optional),
           resume: Schema.Boolean.pipe(Schema.optional),
         }),

+ 1 - 0
packages/schema/src/index.ts

@@ -24,4 +24,5 @@ export { PtyTicket } from "./pty-ticket"
 export { Question } from "./question"
 export { Workspace } from "./workspace"
 export { Prompt, Source, FileAttachment, AgentAttachment } from "./prompt"
+export { PromptInput } from "./prompt-input"
 export * from "./schema"

+ 26 - 0
packages/schema/src/prompt-input.ts

@@ -0,0 +1,26 @@
+export * as PromptInput from "./prompt-input"
+
+import { Schema } from "effect"
+import { AgentAttachment, Source } from "./prompt"
+import { optional, statics } from "./schema"
+
+export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment> {}
+export const FileAttachment = Schema.Struct({
+  uri: Schema.String,
+  name: Schema.String.pipe(optional),
+  description: Schema.String.pipe(optional),
+  source: Source.pipe(optional),
+})
+  .annotate({ identifier: "PromptInput.FileAttachment" })
+  .pipe(
+    statics((schema) => ({
+      create: (input: FileAttachment) => schema.make(input),
+    })),
+  )
+
+export interface Prompt extends Schema.Schema.Type<typeof Prompt> {}
+export const Prompt = Schema.Struct({
+  text: Schema.String,
+  files: Schema.Array(FileAttachment).pipe(optional),
+  agents: Schema.Array(AgentAttachment).pipe(optional),
+}).annotate({ identifier: "PromptInput" })

+ 2 - 2
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -142,7 +142,7 @@ import type {
   ProjectListResponses,
   ProjectUpdateErrors,
   ProjectUpdateResponses,
-  Prompt,
+  PromptInput,
   ProviderAuthErrors,
   ProviderAuthResponses,
   ProviderListErrors,
@@ -5621,7 +5621,7 @@ export class Session3 extends HeyApiClient {
     parameters: {
       sessionID: string
       id?: string
-      prompt?: Prompt
+      prompt?: PromptInput
       delivery?: "steer" | "queue"
       resume?: boolean
     },

+ 14 - 1
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -2700,6 +2700,12 @@ export type SessionNotFoundError = {
   message: string
 }
 
+export type PromptInput = {
+  text: string
+  files?: Array<PromptInputFileAttachment>
+  agents?: Array<PromptAgentAttachment>
+}
+
 export type ConflictError = {
   _tag: "ConflictError"
   message: string
@@ -3814,6 +3820,13 @@ export type SessionV2Info = {
   revert?: RevertState
 }
 
+export type PromptInputFileAttachment = {
+  uri: string
+  name?: string
+  description?: string
+  source?: PromptSource
+}
+
 export type SessionInputAdmitted = {
   admittedSeq: number
   id: string
@@ -12091,7 +12104,7 @@ export type V2SessionSwitchModelResponse = V2SessionSwitchModelResponses[keyof V
 export type V2SessionPromptData = {
   body: {
     id?: string
-    prompt: Prompt
+    prompt: PromptInput
     delivery?: "steer" | "queue"
     resume?: boolean
   }