fix(app): separate provider lifetimes and reactive ownership (#33739)

This commit is contained in:
Luke Parker 2026-06-25 14:42:06 +10:00 committed by GitHub
commit cfd75d62fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 838 additions and 274 deletions

View file

@ -4,7 +4,6 @@ import { base64Encode } from "@opencode-ai/core/util/encode"
import { Binary } from "@opencode-ai/core/util/binary"
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
import { batch, type Accessor } from "solid-js"
import type { FileSelection } from "@/context/file"
import { useServer } from "@/context/server"
import { useTabs } from "@/context/tabs"
import { useServerSync, type ServerSync } from "@/context/server-sync"
@ -21,6 +20,7 @@ import { buildRequestParts } from "./build-request-parts"
import { setCursorPosition } from "./editor-dom"
import { formatServerError } from "@/utils/server-errors"
import { ScopedKey } from "@/utils/server-scope"
import { createPromptSubmissionState } from "./submission-state"
type PendingPrompt = {
abort: AbortController
@ -194,15 +194,6 @@ type PromptSubmitInput = {
onSubmit?: () => void
}
type CommentItem = {
path: string
selection?: FileSelection
comment?: string
commentID?: string
commentOrigin?: "review" | "file"
preview?: string
}
export function createPromptSubmit(input: PromptSubmitInput) {
const navigate = useNavigate()
const sdk = useSDK()
@ -251,9 +242,12 @@ export function createPromptSubmit(input: PromptSubmitInput) {
.catch(() => {})
}
const restoreCommentItems = (items: CommentItem[]) => {
const restoreCommentItems = (
target: ReturnType<ReturnType<typeof usePrompt>["capture"]>,
items: (ContextItem & { key: string })[],
) => {
for (const item of items) {
prompt.context.add({
target.context.add({
type: "file",
path: item.path,
selection: item.selection,
@ -265,15 +259,9 @@ export function createPromptSubmit(input: PromptSubmitInput) {
}
}
const removeCommentItems = (items: { key: string }[]) => {
for (const item of items) {
prompt.context.remove(item.key)
}
}
const clearContext = () => {
for (const item of prompt.context.items()) {
prompt.context.remove(item.key)
const clearContext = (target: ReturnType<ReturnType<typeof usePrompt>["capture"]>) => {
for (const item of target.context.items()) {
target.context.remove(item.key)
}
}
@ -295,7 +283,14 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const handleSubmit = async (event: Event) => {
event.preventDefault()
const currentPrompt = prompt.current()
const target = prompt.capture()
const submission = createPromptSubmissionState({
target,
prompt: target.current(),
context: target.context.items().slice(),
})
const currentPrompt = submission.prompt
const context = submission.context
const text = currentPrompt.map((part) => ("content" in part ? part.content : "")).join("")
const images = input.imageAttachments().slice()
const mode = input.mode()
@ -387,6 +382,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const draftID = search.draftId
if (draftID) tabs.promoteDraft(draftID, { server: server.key, sessionId: session.id })
else navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
submission.retarget(prompt.capture({ dir: base64Encode(sessionDirectory), id: session.id }))
}
}
if (!session) {
@ -402,7 +398,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
providerID: currentModel.provider.id,
}
const agent = currentAgent.name
const context = prompt.context.items().slice()
const draft: FollowupDraft = {
sessionID: session.id,
sessionDirectory,
@ -414,13 +409,16 @@ export function createPromptSubmit(input: PromptSubmitInput) {
}
const clearInput = () => {
prompt.reset()
submission.clear()
input.setMode("normal")
input.setPopover(null)
}
const restoreInput = () => {
prompt.set(currentPrompt, input.promptLength(currentPrompt))
const restored = submission.restore()
if (!restored) return false
restored.target.set(restored.prompt, input.promptLength(restored.prompt))
if (!submission.current(prompt.capture())) return true
input.setMode(mode)
input.setPopover(null)
requestAnimationFrame(() => {
@ -430,11 +428,12 @@ export function createPromptSubmit(input: PromptSubmitInput) {
setCursorPosition(editor, input.promptLength(currentPrompt))
input.queueScroll()
})
return true
}
if (!isNewSession && mode === "normal" && input.shouldQueue?.()) {
input.onQueue?.(draft)
clearContext()
clearContext(submission.target())
clearInput()
return
}
@ -504,7 +503,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
})
}
removeCommentItems(commentItems)
for (const item of commentItems) submission.target().context.remove(item.key)
clearInput()
const waitForWorktree = async () => {
@ -521,8 +520,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
sync().set("session_status", session.id, { type: "idle" })
}
removeOptimisticMessage()
restoreCommentItems(commentItems)
restoreInput()
if (restoreInput()) restoreCommentItems(submission.target(), commentItems)
}
pending.set(pendingKey(session.id), { abort: controller, cleanup })
@ -584,8 +582,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
description: errorMessage(err),
})
removeOptimisticMessage()
restoreCommentItems(commentItems)
restoreInput()
if (restoreInput()) restoreCommentItems(submission.target(), commentItems)
})
}