refactor(session): align prompt input types with their schemas (#25178)
This commit is contained in:
@@ -269,7 +269,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
promptSvc.prompt({
|
promptSvc.prompt({
|
||||||
...ctx.payload,
|
...ctx.payload,
|
||||||
sessionID: ctx.params.sessionID,
|
sessionID: ctx.params.sessionID,
|
||||||
} as unknown as SessionPrompt.PromptInput),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).pipe(
|
).pipe(
|
||||||
@@ -288,7 +288,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
yield* Effect.sync(() => {
|
yield* Effect.sync(() => {
|
||||||
bridge.fork(
|
bridge.fork(
|
||||||
promptSvc
|
promptSvc
|
||||||
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput)
|
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID })
|
||||||
.pipe(
|
.pipe(
|
||||||
Effect.catchCause((error) =>
|
Effect.catchCause((error) =>
|
||||||
Effect.sync(() => {
|
Effect.sync(() => {
|
||||||
@@ -309,14 +309,14 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
|||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof CommandPayload.Type
|
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: {
|
const shell = Effect.fn("SessionHttpApi.shell")(function* (ctx: {
|
||||||
params: { sessionID: SessionID }
|
params: { sessionID: SessionID }
|
||||||
payload: typeof ShellPayload.Type
|
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: {
|
const revert = Effect.fn("SessionHttpApi.revert")(function* (ctx: {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|||||||
import { Truncate } from "@/tool/truncate"
|
import { Truncate } from "@/tool/truncate"
|
||||||
import { decodeDataUrl } from "@/util/data-url"
|
import { decodeDataUrl } from "@/util/data-url"
|
||||||
import { Process } from "@/util/process"
|
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 { zod } from "@/util/effect-zod"
|
||||||
import { withStatics } from "@/util/schema"
|
import { withStatics } from "@/util/schema"
|
||||||
import * as EffectLogger from "@opencode-ai/core/effect/logger"
|
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 resolvePromptParts = Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
|
||||||
const ctx = yield* InstanceState.context
|
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 files = ConfigMarkdown.files(template)
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
yield* Effect.forEach(
|
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:": {
|
case "file:": {
|
||||||
log.info("file", { mime: part.mime })
|
log.info("file", { mime: part.mime })
|
||||||
const filepath = fileURLToPath(part.url)
|
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 { read } = yield* registry.named()
|
||||||
const execRead = (args: Parameters<typeof read.execute>[0], extra?: Tool.Context["extra"]) => {
|
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())))
|
.pipe(Effect.onInterrupt(() => Effect.sync(() => controller.abort())))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.mime === "text/plain") {
|
if (mime === "text/plain") {
|
||||||
let offset: number | undefined
|
let offset: number | undefined
|
||||||
let limit: number | undefined
|
let limit: number | undefined
|
||||||
const range = { start: url.searchParams.get("start"), end: url.searchParams.get("end") }
|
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 {
|
} else {
|
||||||
pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
|
pieces.push({ ...part, mime, messageID: info.id, sessionID: input.sessionID })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const error = Cause.squash(exit.cause)
|
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
|
return pieces
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.mime === "application/x-directory") {
|
if (mime === "application/x-directory") {
|
||||||
const args = { filePath: filepath }
|
const args = { filePath: filepath }
|
||||||
const exit = yield* execRead(args).pipe(Effect.exit)
|
const exit = yield* execRead(args).pipe(Effect.exit)
|
||||||
if (Exit.isFailure(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,
|
synthetic: true,
|
||||||
text: exit.value.output,
|
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,
|
sessionID: input.sessionID,
|
||||||
type: "file",
|
type: "file",
|
||||||
url:
|
url:
|
||||||
`data:${part.mime};base64,` +
|
`data:${mime};base64,` +
|
||||||
Buffer.from(yield* fsys.readFile(filepath).pipe(Effect.catch(Effect.die))).toString("base64"),
|
Buffer.from(yield* fsys.readFile(filepath).pipe(Effect.catch(Effect.die))).toString("base64"),
|
||||||
mime: part.mime,
|
mime,
|
||||||
filename: part.filename!,
|
filename: part.filename!,
|
||||||
source: part.source,
|
source: part.source,
|
||||||
},
|
},
|
||||||
@@ -1700,18 +1700,7 @@ export const PromptInput = Schema.Struct({
|
|||||||
]).annotate({ discriminator: "type" }),
|
]).annotate({ discriminator: "type" }),
|
||||||
),
|
),
|
||||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||||
// `z.discriminatedUnion` erases the discriminated members' shapes back to
|
export type PromptInput = Schema.Schema.Type<typeof PromptInput>
|
||||||
// `{}` 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 class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
|
export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
|
||||||
sessionID: SessionID,
|
sessionID: SessionID,
|
||||||
|
|||||||
Reference in New Issue
Block a user