From bddd69d18b01f3e74b4891cdea44e65db3612cd5 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Thu, 23 Jul 2026 14:29:58 +0800 Subject: [PATCH] refactor(app): route followups through server api --- .../app/src/components/prompt-input/submit.ts | 98 ++++++++++++------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index 203722fc6c..2cd30da3ef 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -20,6 +20,8 @@ import { setCursorPosition } from "./editor-dom" import { formatServerError } from "@/utils/server-errors" import { ScopedKey } from "@/utils/server-scope" import { createPromptSubmissionState } from "./submission-state" +import { normalizeSessionInfo } from "@/utils/session" +import { Event } from "@opencode-ai/schema/event" type PendingPrompt = { abort: AbortController @@ -39,7 +41,7 @@ export type FollowupDraft = { } type FollowupSendInput = { - client: DirectorySDK["client"] + api: DirectorySDK["api"]["session"] serverSync: ServerSync sync: DirectorySync draft: FollowupDraft @@ -81,19 +83,21 @@ export async function sendFollowupDraft(input: FollowupSendInput) { return false } - await input.client.session.command({ + const messageID = Identifier.ascending("message") + await input.api.command({ sessionID: input.draft.sessionID, + id: messageID, command: cmd, arguments: tail.join(" "), agent: input.draft.agent, - model: `${input.draft.model.providerID}/${input.draft.model.modelID}`, - variant: input.draft.variant, - parts: images.map((attachment) => ({ - id: Identifier.ascending("part"), - type: "file" as const, - mime: attachment.mime, - url: attachment.dataUrl, - filename: attachment.filename, + model: { + id: input.draft.model.modelID, + providerID: input.draft.model.providerID, + variant: input.draft.variant, + }, + files: images.map((attachment) => ({ + uri: attachment.dataUrl, + name: attachment.filename, })), }) return true @@ -152,13 +156,36 @@ export async function sendFollowupDraft(input: FollowupSendInput) { return false } - await input.client.session.promptAsync({ + await input.api.prompt({ sessionID: input.draft.sessionID, + id: messageID, agent: input.draft.agent, model: input.draft.model, - messageID, - parts: requestParts, variant: input.draft.variant, + text: requestParts.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"), + files: requestParts.flatMap((part) => { + if (part.type !== "file") return [] + const text = part.source?.text + return [ + { + uri: part.url, + name: part.filename, + mention: text ? { start: text.start, end: text.end, text: text.value } : undefined, + }, + ] + }), + agents: requestParts.flatMap((part) => + part.type === "agent" + ? [ + { + name: part.name, + mention: part.source + ? { start: part.source.start, end: part.source.end, text: part.source.value } + : undefined, + }, + ] + : [], + ), }) return true } catch (err) { @@ -210,6 +237,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { const pendingKey = (sessionID: string) => ScopedKey.from(sdk().scope, sessionID) const errorMessage = (err: unknown) => { + if (err && typeof err === "object" && "message" in err && typeof err.message === "string") return err.message if (err && typeof err === "object" && "data" in err) { const data = (err as { data?: { message?: string } }).data if (data?.message) return data.message @@ -235,9 +263,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { return Promise.resolve() } return sdk() - .client.session.abort({ - sessionID, - }) + .api.session.interrupt({ sessionID }) .catch(() => {}) } @@ -364,9 +390,13 @@ export function createPromptSubmit(input: PromptSubmitInput) { let session = input.info() if (!session && isNewSession) { - const created = await client.session - .create() - .then((x) => x.data ?? undefined) + const created = await sdk() + .api.session.create({ + agent: currentAgent.name, + model: { id: currentModel.id, providerID: currentModel.provider.id, variant }, + location: { directory: sessionDirectory }, + }) + .then(normalizeSessionInfo) .catch((err) => { showToast({ title: language.t("prompt.toast.sessionCreateFailed.title"), @@ -450,12 +480,14 @@ export function createPromptSubmit(input: PromptSubmitInput) { if (mode === "shell") { clearInput() - client.session - .shell({ + const eventID = Event.ID.create() + sdk() + .api.session.shell({ sessionID: session.id, + id: eventID, + command: text, agent, model, - command: text, }) .catch((err) => { showToast({ @@ -473,23 +505,23 @@ export function createPromptSubmit(input: PromptSubmitInput) { const customCommand = sync().data.command.find((c) => c.name === commandName) if (customCommand) { clearInput() - client.session - .command({ + const messageID = Identifier.ascending("message") + serverSync().session.set("session_status", session.id, { type: "busy" }) + sdk() + .api.session.command({ sessionID: session.id, + id: messageID, command: commandName, arguments: args.join(" "), 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, + model: { id: model.modelID, providerID: model.providerID, variant }, + files: images.map((attachment) => ({ + uri: attachment.dataUrl, + name: attachment.filename, })), }) .catch((err) => { + serverSync().session.set("session_status", session.id, { type: "idle" }) showToast({ title: language.t("prompt.toast.commandSendFailed.title"), description: formatServerError(err, language.t, language.t("common.requestFailed")), @@ -573,7 +605,7 @@ export function createPromptSubmit(input: PromptSubmitInput) { } void sendFollowupDraft({ - client, + api: sdk().api.session, sync: sync(), serverSync: serverSync(), draft,