mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-27 03:51:21 -04:00
fix(app): stream running shell tool output (#45106)
This commit is contained in:
@@ -6,6 +6,7 @@ import { LocalProvider } from "@/providers/models/selection"
|
||||
import type { ServerConnection } from "@/runtime/server/registry"
|
||||
import { sessionHref } from "@/shell/routes/session"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import { useServerSDK } from "@/runtime/server/client"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
|
||||
export function SessionUIProvider(
|
||||
@@ -17,6 +18,7 @@ export function SessionUIProvider(
|
||||
const navigate = useNavigate()
|
||||
const params = useParams()
|
||||
const data = useData()
|
||||
const serverSDK = useServerSDK()
|
||||
const tabs = useTabs()
|
||||
const directory = () => props.directory
|
||||
const href = (sessionID: string) => sessionHref(props.server, sessionID)
|
||||
@@ -53,6 +55,7 @@ export function SessionUIProvider(
|
||||
data={sessionUIData()}
|
||||
directory={directory()}
|
||||
sessionID={params.id}
|
||||
shellOutput={(input) => serverSDK.api.shell.output(input)}
|
||||
onNavigateToSession={navigateToSession}
|
||||
onSessionHref={href}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { FileDiffInfo, SessionInfo, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type {
|
||||
FileDiffInfo,
|
||||
SessionInfo,
|
||||
SessionStatus,
|
||||
ShellOutputInput,
|
||||
ShellOutputOutput,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
|
||||
|
||||
@@ -40,6 +46,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
data: Data
|
||||
directory: string
|
||||
sessionID?: string
|
||||
shellOutput?: (input: ShellOutputInput) => Promise<ShellOutputOutput>
|
||||
onNavigateToSession?: NavigateToSessionFn
|
||||
onSessionHref?: SessionHrefFn
|
||||
}) => {
|
||||
@@ -55,6 +62,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
},
|
||||
navigateToSession: props.onNavigateToSession,
|
||||
sessionHref: props.onSessionHref,
|
||||
shellOutput: props.shellOutput,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
createSignal,
|
||||
For,
|
||||
Match,
|
||||
onCleanup,
|
||||
onMount,
|
||||
Show,
|
||||
Switch,
|
||||
@@ -1294,15 +1295,41 @@ ToolRegistry.register({
|
||||
name: "shell",
|
||||
render(props) {
|
||||
const i18n = useI18n()
|
||||
const data = useData()
|
||||
const streaming = () => props.status === "streaming"
|
||||
const pending = () => streaming() || props.status === "running" || props.metadata.status === "running"
|
||||
const sawStreaming = streaming()
|
||||
const [streamed, setStreamed] = createSignal("")
|
||||
createEffect(() => {
|
||||
const id = props.metadata.shellID
|
||||
const shellOutput = data.shellOutput
|
||||
if (typeof id !== "string" || !pending() || !shellOutput) return
|
||||
const directory = data.directory
|
||||
let cursor = 0
|
||||
let loading = false
|
||||
let disposed = false
|
||||
const load = async () => {
|
||||
if (loading) return
|
||||
loading = true
|
||||
const response = await shellOutput({ id, location: { directory }, cursor }).catch(() => undefined)
|
||||
if (disposed) return
|
||||
if (response?.data.output) setStreamed((output) => output + response.data.output)
|
||||
if (response) cursor = response.data.cursor
|
||||
loading = false
|
||||
}
|
||||
void load()
|
||||
const interval = setInterval(() => void load(), 1_000)
|
||||
onCleanup(() => {
|
||||
disposed = true
|
||||
clearInterval(interval)
|
||||
})
|
||||
})
|
||||
const command = () => {
|
||||
if (typeof props.input.command === "string") return props.input.command
|
||||
if (typeof props.metadata.command === "string") return props.metadata.command
|
||||
return ""
|
||||
}
|
||||
const output = createMemo(() => stripAnsi(props.output ?? "").replace(/\r\n?/g, "\n"))
|
||||
const output = createMemo(() => stripAnsi((pending() && streamed()) || props.output || "").replace(/\r\n?/g, "\n"))
|
||||
return (
|
||||
<BasicTool
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user