refactor(session): align prompt input types with their schemas (#25178)

This commit is contained in:
Kit Langton
2026-04-30 19:28:46 -04:00
committed by GitHub
parent 8805104b8d
commit e3134a2a99
2 changed files with 14 additions and 25 deletions

View File

@@ -269,7 +269,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
promptSvc.prompt({
...ctx.payload,
sessionID: ctx.params.sessionID,
} as unknown as SessionPrompt.PromptInput),
}),
),
),
).pipe(
@@ -288,7 +288,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
yield* Effect.sync(() => {
bridge.fork(
promptSvc
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput)
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID })
.pipe(
Effect.catchCause((error) =>
Effect.sync(() => {
@@ -309,14 +309,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
params: { sessionID: SessionID }
payload: typeof CommandPayload.Type
}) {
return yield* promptSvc.command({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.CommandInput)
return yield* promptSvc.command({ ...ctx.payload, sessionID: ctx.params.sessionID })
})
const shell = Effect.fn("SessionHttpApi.shell")(function* (ctx: {
params: { sessionID: SessionID }
payload: typeof ShellPayload.Type
}) {
return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID } as SessionPrompt.ShellInput)
return yield* promptSvc.shell({ ...ctx.payload, sessionID: ctx.params.sessionID })
})
const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: {

View File

@@ -45,7 +45,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Truncate } from "@/tool/truncate"
import { decodeDataUrl } from "@/util/data-url"
import { Process } from "@/util/process"
import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema } from "effect"
import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import * as EffectLogger from "@opencode-ai/core/effect/logger"
@@ -127,7 +127,7 @@ export const layer = Layer.effect(
const resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
const ctx = yield* InstanceState.context
const parts: PromptInput["parts"] = [{ type: "text", text: template }]
const parts: Types.DeepMutable<PromptInput["parts"]> = [{ type: "text", text: template }]
const files = ConfigMarkdown.files(template)
const seen = new Set<string>()
yield* Effect.forEach(
@@ -1012,7 +1012,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
case "file:": {
log.info("file", { mime: part.mime })
const filepath = fileURLToPath(part.url)
if (yield* fsys.isDir(filepath)) part.mime = "application/x-directory"
const mime = (yield* fsys.isDir(filepath)) ? "application/x-directory" : part.mime
const { read } = yield* registry.named()
const execRead = (args: Parameters<typeof read.execute>[0], extra?: Tool.Context["extra"]) => {
@@ -1031,7 +1031,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
.pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort())))
}
if (part.mime === "text/plain") {
if (mime === "text/plain") {
let offset: number | undefined
let limit: number | undefined
const range = { start: url.searchParams.get("start"), end: url.searchParams.get("end") }
@@ -1089,7 +1089,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
})),
)
} else {
pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
pieces.push({ ...part, mime, messageID: info.id, sessionID: input.sessionID })
}
} else {
const error = Cause.squash(exit.cause)
@@ -1110,7 +1110,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
return pieces
}
if (part.mime === "application/x-directory") {
if (mime === "application/x-directory") {
const args = { filePath: filepath }
const exit = yield* execRead(args).pipe(Effect.exit)
if (Exit.isFailure(exit)) {
@@ -1146,7 +1146,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
synthetic: true,
text: exit.value.output,
},
{ ...part, messageID: info.id, sessionID: input.sessionID },
{ ...part, mime, messageID: info.id, sessionID: input.sessionID },
]
}
@@ -1164,9 +1164,9 @@ NOTE: At any point in time through this workflow you should feel free to ask the
sessionID: input.sessionID,
type: "file",
url:
`data:${part.mime};base64,` +
`data:${mime};base64,` +
Buffer.from(yield* fsys.readFile(filepath).pipe(Effect.catch(Effect.die))).toString("base64"),
mime: part.mime,
mime,
filename: part.filename!,
source: part.source,
},
@@ -1700,18 +1700,7 @@ export const PromptInput = Schema.Struct({
]).annotate({ discriminator: "type" }),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
// `z.discriminatedUnion` erases the discriminated members' shapes back to
// `{}` when walked from the generic `z.ZodType` input. Restore the precise
// `parts` type from the exported Schema input types so callers see a proper
// tagged union.
type PartInputUnion =
| MessageV2.TextPartInput
| MessageV2.FilePartInput
| MessageV2.AgentPartInput
| MessageV2.SubtaskPartInput
export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts"> & {
parts: PartInputUnion[]
}
export type PromptInput = Schema.Schema.Type<typeof PromptInput>
export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
sessionID: SessionID,