add backend fetch

This commit is contained in:
James Long
2026-05-17 14:57:34 -04:00
parent 990a705063
commit 8e0676ebea
3 changed files with 53 additions and 0 deletions
+16
View File
@@ -68,6 +68,7 @@ import { createTuiAttention } from "@/cli/cmd/tui/attention"
import { FormatError, FormatUnknownError } from "@/cli/error"
import { CommandPaletteProvider, useCommandPalette } from "./context/command-palette"
import { OpencodeKeymapProvider, registerOpencodeKeymap, useBindings, useOpencodeKeymap } from "./keymap"
import { DiffViewer } from "./routes/diff"
import type { EventSource } from "./context/sdk"
import { DialogVariant } from "./component/dialog-variant"
@@ -102,6 +103,7 @@ const appBindingCommands = [
"theme.switch",
"theme.switch_mode",
"theme.mode.lock",
"diff.open",
"help.show",
"docs.open",
"app.debug",
@@ -352,6 +354,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
renderer.clearSelection()
}
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
const [diffOpen, setDiffOpen] = createSignal(false)
const [pasteSummaryEnabled, setPasteSummaryEnabled] = createSignal(
kv.get("paste_summary_enabled", !sync.data.config.experimental?.disable_paste_summary),
)
@@ -663,6 +666,16 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
},
category: "System",
},
{
name: "diff.open",
title: "Open diff viewer",
slashName: "diff",
run: () => {
setDiffOpen(true)
dialog.clear()
},
category: "VCS",
},
{
name: "help.show",
title: "Help",
@@ -962,6 +975,9 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
<TuiPluginRuntime.Slot name="app_bottom" />
</box>
<TuiPluginRuntime.Slot name="app" />
<Show when={diffOpen()}>
<DiffViewer onClose={() => setDiffOpen(false)} />
</Show>
</Show>
<StartupLoading ready={ready} />
</box>
@@ -67,6 +67,7 @@ export const Definitions = {
sidebar_toggle: keybind("<leader>b", "Toggle sidebar"),
scrollbar_toggle: keybind("none", "Toggle session scrollbar"),
status_view: keybind("<leader>s", "View status"),
diff_open: keybind("<leader>d", "Open diff viewer"),
session_export: keybind("<leader>x", "Export session to editor"),
session_copy: keybind("none", "Copy session transcript"),
@@ -251,6 +252,7 @@ export const CommandMap = {
sidebar_toggle: "session.sidebar.toggle",
scrollbar_toggle: "session.toggle.scrollbar",
status_view: "opencode.status",
diff_open: "diff.open",
session_export: "session.export",
session_copy: "session.copy",
session_new: "session.new",
@@ -152,6 +152,12 @@ const ScriptActionSchema = z.union([
const ScriptSchema = z.union([z.array(ScriptActionSchema), z.object({ actions: z.array(ScriptActionSchema) })])
const TargetSchema = z.union([z.string(), z.array(z.string()).min(1), z.literal("all")])
const BackendFetchSchema = z.object({
url: z.string(),
method: z.string().optional(),
headers: z.record(z.string(), z.string()).optional(),
body: z.unknown().optional(),
})
const remoteInstances = new Map<string, RemoteInstance>()
function currentBuffer(renderer: CliRenderer): RenderBuffer {
@@ -264,6 +270,27 @@ async function control(options: Options, method: string, pathname: string, body?
throw new Error(typeof data?.error === "string" ? data.error : `Simulation control request failed: ${response.status}`)
}
async function backendFetch(options: Options, input: z.infer<typeof BackendFetchSchema>) {
const running = current(options)
const headers = new Headers(input.headers)
const body =
input.body === undefined ? undefined : typeof input.body === "string" ? input.body : JSON.stringify(input.body)
if (body !== undefined && !headers.has("content-type") && typeof input.body !== "string") headers.set("content-type", "application/json")
const response = await (running.controlFetch ?? fetch)(new URL(input.url, running.controlUrl), {
method: input.method ?? (body === undefined ? "GET" : "POST"),
headers,
body,
})
return {
url: response.url,
status: response.status,
ok: response.ok,
headers: Object.fromEntries(response.headers.entries()),
body: await response.text(),
}
}
async function mcpRequest(url: string, method: string, params: unknown, timeout = 500) {
const response = await fetch(url, {
method: "POST",
@@ -431,6 +458,14 @@ function createServer(options: Options) {
await current(options).harness.renderOnce()
return toolResult(snapshot(options))
})
server.registerTool(
"simulation_backend_fetch",
{
description: "Proxy one fetch request through the simulated backend and return the raw response.",
inputSchema: BackendFetchSchema,
},
async (input) => toolResult(await backendFetch(options, input)),
)
server.registerTool(
"simulation_action_execute",
{