refactor(tui): remove legacy sdk client
This commit is contained in:
parent
4c45a5cf23
commit
36f8cb7054
36 changed files with 316 additions and 510 deletions
1
bun.lock
1
bun.lock
|
|
@ -1006,7 +1006,6 @@
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
|
||||||
"@opencode-ai/simulation": "workspace:*",
|
"@opencode-ai/simulation": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
"./context/log": "./src/context/log.tsx",
|
"./context/log": "./src/context/log.tsx",
|
||||||
"./context/project": "./src/context/project.tsx",
|
"./context/project": "./src/context/project.tsx",
|
||||||
"./context/runtime": "./src/context/runtime.tsx",
|
"./context/runtime": "./src/context/runtime.tsx",
|
||||||
"./context/sdk": "./src/context/sdk.tsx",
|
"./context/client": "./src/context/client.tsx",
|
||||||
"./context/theme": "./src/context/theme.tsx",
|
"./context/theme": "./src/context/theme.tsx",
|
||||||
"./context/editor": "./src/context/editor.ts",
|
"./context/editor": "./src/context/editor.ts",
|
||||||
"./context/clipboard": "./src/context/clipboard.tsx",
|
"./context/clipboard": "./src/context/clipboard.tsx",
|
||||||
|
|
@ -55,7 +55,6 @@
|
||||||
"@opencode-ai/client": "workspace:*",
|
"@opencode-ai/client": "workspace:*",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
|
||||||
"@opencode-ai/simulation": "workspace:*",
|
"@opencode-ai/simulation": "workspace:*",
|
||||||
"@opencode-ai/ui": "workspace:*",
|
"@opencode-ai/ui": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import { PluginRouteMissing } from "./component/plugin-route-missing"
|
||||||
import { ProjectProvider, useProject } from "./context/project"
|
import { ProjectProvider, useProject } from "./context/project"
|
||||||
import { EditorContextProvider } from "./context/editor"
|
import { EditorContextProvider } from "./context/editor"
|
||||||
import { useEvent } from "./context/event"
|
import { useEvent } from "./context/event"
|
||||||
import { SDKProvider, useSDK } from "./context/sdk"
|
import { ClientProvider, useClient } from "./context/client"
|
||||||
import { StartupLoading } from "./component/startup-loading"
|
import { StartupLoading } from "./component/startup-loading"
|
||||||
import { Reconnecting } from "./component/reconnecting"
|
import { Reconnecting } from "./component/reconnecting"
|
||||||
import { DataProvider, useData } from "./context/data"
|
import { DataProvider, useData } from "./context/data"
|
||||||
|
|
@ -55,7 +55,6 @@ import { DialogStatus } from "./component/dialog-status"
|
||||||
import { DialogConfig } from "./component/dialog-config"
|
import { DialogConfig } from "./component/dialog-config"
|
||||||
import { DialogDebug } from "./component/dialog-debug"
|
import { DialogDebug } from "./component/dialog-debug"
|
||||||
import { DialogPair, type DialogPairCredentials } from "./component/dialog-pair"
|
import { DialogPair, type DialogPairCredentials } from "./component/dialog-pair"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { DialogThemeList } from "./component/dialog-theme-list"
|
import { DialogThemeList } from "./component/dialog-theme-list"
|
||||||
import { DialogHelp } from "./ui/dialog-help"
|
import { DialogHelp } from "./ui/dialog-help"
|
||||||
import { DialogAgent } from "./component/dialog-agent"
|
import { DialogAgent } from "./component/dialog-agent"
|
||||||
|
|
@ -66,8 +65,6 @@ import { Session } from "./routes/session"
|
||||||
import { PromptHistoryProvider } from "./component/prompt/history"
|
import { PromptHistoryProvider } from "./component/prompt/history"
|
||||||
import { FrecencyProvider } from "./component/prompt/frecency"
|
import { FrecencyProvider } from "./component/prompt/frecency"
|
||||||
import { PromptStashProvider } from "./component/prompt/stash"
|
import { PromptStashProvider } from "./component/prompt/stash"
|
||||||
import { DialogAlert } from "./ui/dialog-alert"
|
|
||||||
import { DialogConfirm } from "./ui/dialog-confirm"
|
|
||||||
import { ToastProvider, useToast } from "./ui/toast"
|
import { ToastProvider, useToast } from "./ui/toast"
|
||||||
import { isDefaultTitle } from "./util/session"
|
import { isDefaultTitle } from "./util/session"
|
||||||
import * as Model from "./util/model"
|
import * as Model from "./util/model"
|
||||||
|
|
@ -198,7 +195,6 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
const endpoint = await reconnectEndpoint(attempt)
|
const endpoint = await reconnectEndpoint(attempt)
|
||||||
const next = { baseUrl: endpoint.url, headers: Service.headers(endpoint) }
|
const next = { baseUrl: endpoint.url, headers: Service.headers(endpoint) }
|
||||||
return {
|
return {
|
||||||
client: createOpencodeClient({ ...next, directory }),
|
|
||||||
api: OpenCode.make(next),
|
api: OpenCode.make(next),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,8 +332,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PluginRuntimeProvider value={pluginRuntime}>
|
<PluginRuntimeProvider value={pluginRuntime}>
|
||||||
<SDKProvider
|
<ClientProvider
|
||||||
client={createOpencodeClient({ ...options, directory })}
|
|
||||||
api={api}
|
api={api}
|
||||||
reconnect={reconnect}
|
reconnect={reconnect}
|
||||||
reload={input.server.reload}
|
reload={input.server.reload}
|
||||||
|
|
@ -377,7 +372,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</PermissionProvider>
|
</PermissionProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</PluginRuntimeProvider>
|
</PluginRuntimeProvider>
|
||||||
</RouteProvider>
|
</RouteProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|
@ -426,7 +421,7 @@ function App(props: {
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const keymap = useOpencodeKeymap()
|
const keymap = useOpencodeKeymap()
|
||||||
const event = useEvent()
|
const event = useEvent()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const themeState = useTheme()
|
const themeState = useTheme()
|
||||||
const { theme, mode, setMode, locked, lock, unlock } = themeState
|
const { theme, mode, setMode, locked, lock, unlock } = themeState
|
||||||
|
|
@ -475,7 +470,7 @@ function App(props: {
|
||||||
route,
|
route,
|
||||||
routes: pluginRuntime.routes,
|
routes: pluginRuntime.routes,
|
||||||
event,
|
event,
|
||||||
sdk,
|
client,
|
||||||
project,
|
project,
|
||||||
data,
|
data,
|
||||||
theme: themeState,
|
theme: themeState,
|
||||||
|
|
@ -585,7 +580,7 @@ function App(props: {
|
||||||
if (continued || !args.continue) return
|
if (continued || !args.continue) return
|
||||||
continued = true
|
continued = true
|
||||||
const location = data.location.default()
|
const location = data.location.default()
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.list({
|
.list({
|
||||||
limit: 1,
|
limit: 1,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
|
|
@ -600,7 +595,7 @@ function App(props: {
|
||||||
route.navigate({ type: "session", sessionID: match })
|
route.navigate({ type: "session", sessionID: match })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.fork({ sessionID: match })
|
.fork({ sessionID: match })
|
||||||
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
|
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
|
|
@ -613,7 +608,7 @@ function App(props: {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (forked || !args.sessionID || !args.fork) return
|
if (forked || !args.sessionID || !args.fork) return
|
||||||
forked = true
|
forked = true
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.fork({ sessionID: args.sessionID })
|
.fork({ sessionID: args.sessionID })
|
||||||
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
|
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
|
|
@ -815,7 +810,7 @@ function App(props: {
|
||||||
},
|
},
|
||||||
category: "System",
|
category: "System",
|
||||||
},
|
},
|
||||||
...(sdk.reload
|
...(client.reload
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
name: "server.reload",
|
name: "server.reload",
|
||||||
|
|
@ -826,7 +821,7 @@ function App(props: {
|
||||||
toast.show({ variant: "info", message: "Reloading server...", duration: 30000 })
|
toast.show({ variant: "info", message: "Reloading server...", duration: 30000 })
|
||||||
// reload resolves once the replacement service is healthy; the
|
// reload resolves once the replacement service is healthy; the
|
||||||
// event stream reattaches through the reconnect loop.
|
// event stream reattaches through the reconnect loop.
|
||||||
await sdk.reload!()
|
await client.reload!()
|
||||||
.then(() => toast.show({ variant: "success", message: "Server reloaded" }))
|
.then(() => toast.show({ variant: "success", message: "Server reloaded" }))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
},
|
},
|
||||||
|
|
@ -1092,45 +1087,6 @@ function App(props: {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
event.on("installation.update-available", async (evt) => {
|
|
||||||
const version = evt.data.version
|
|
||||||
|
|
||||||
const choice = await DialogConfirm.show(
|
|
||||||
dialog,
|
|
||||||
`Update Available`,
|
|
||||||
`A new release v${version} is available. Would you like to update now?`,
|
|
||||||
"later",
|
|
||||||
)
|
|
||||||
|
|
||||||
if (choice !== true) return
|
|
||||||
|
|
||||||
toast.show({
|
|
||||||
variant: "info",
|
|
||||||
message: `Updating to v${version}...`,
|
|
||||||
duration: 30000,
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await sdk.client.global.upgrade({ target: version })
|
|
||||||
|
|
||||||
if (result.error || !result.data?.success) {
|
|
||||||
toast.show({
|
|
||||||
variant: "error",
|
|
||||||
title: "Update Failed",
|
|
||||||
message: "Update failed",
|
|
||||||
duration: 10000,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await DialogAlert.show(
|
|
||||||
dialog,
|
|
||||||
"Update Complete",
|
|
||||||
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
|
|
||||||
)
|
|
||||||
|
|
||||||
void exit()
|
|
||||||
})
|
|
||||||
|
|
||||||
const plugin = createMemo(() => {
|
const plugin = createMemo(() => {
|
||||||
if (!ready()) return
|
if (!ready()) return
|
||||||
if (route.data.type !== "plugin") return
|
if (route.data.type !== "plugin") return
|
||||||
|
|
@ -1148,7 +1104,7 @@ function App(props: {
|
||||||
clearTimeout(reconnectTimer)
|
clearTimeout(reconnectTimer)
|
||||||
reconnectTimer = undefined
|
reconnectTimer = undefined
|
||||||
}
|
}
|
||||||
const status = sdk.connection.status()
|
const status = client.connection.status()
|
||||||
if (status === "connected") {
|
if (status === "connected") {
|
||||||
setShowReconnecting(false)
|
setShowReconnecting(false)
|
||||||
return
|
return
|
||||||
|
|
@ -1211,7 +1167,7 @@ function App(props: {
|
||||||
<StartupLoading ready={ready} />
|
<StartupLoading ready={ready} />
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={showReconnecting()}>
|
<Show when={showReconnecting()}>
|
||||||
<Reconnecting attempt={sdk.connection.attempt()} error={sdk.connection.error()} />
|
<Reconnecting attempt={client.connection.attempt()} error={client.connection.error()} />
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type {
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { useClipboard } from "../context/clipboard"
|
import { useClipboard } from "../context/clipboard"
|
||||||
import { useData } from "../context/data"
|
import { useData } from "../context/data"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useBindings } from "../keymap"
|
import { useBindings } from "../keymap"
|
||||||
import { useDialog } from "../ui/dialog"
|
import { useDialog } from "../ui/dialog"
|
||||||
|
|
@ -102,7 +102,7 @@ function manageConnections(
|
||||||
) {
|
) {
|
||||||
dialog.replace(() => {
|
dialog.replace(() => {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
return (
|
return (
|
||||||
<DialogSelect
|
<DialogSelect
|
||||||
|
|
@ -121,7 +121,7 @@ function manageConnections(
|
||||||
title: `Disconnect ${connection.label}`,
|
title: `Disconnect ${connection.label}`,
|
||||||
value: connection.id,
|
value: connection.id,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
void sdk.api.credential
|
void client.api.credential
|
||||||
.remove({ credentialID: connection.id, location: location(data) })
|
.remove({ credentialID: connection.id, location: location(data) })
|
||||||
.then(() => disconnected(integration.name, data, dialog, toast))
|
.then(() => disconnected(integration.name, data, dialog, toast))
|
||||||
.catch(toast.error)
|
.catch(toast.error)
|
||||||
|
|
@ -172,7 +172,7 @@ function KeyMethod(props: {
|
||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [error, setError] = createSignal<string>()
|
const [error, setError] = createSignal<string>()
|
||||||
|
|
@ -183,7 +183,7 @@ function KeyMethod(props: {
|
||||||
placeholder="API key"
|
placeholder="API key"
|
||||||
onConfirm={(key) => {
|
onConfirm={(key) => {
|
||||||
if (!key) return
|
if (!key) return
|
||||||
void sdk.api.integration
|
void client.api.integration
|
||||||
.connect.key({
|
.connect.key({
|
||||||
integrationID: props.integration.id,
|
integrationID: props.integration.id,
|
||||||
location: location(data),
|
location: location(data),
|
||||||
|
|
@ -218,11 +218,11 @@ function OAuthStarting(props: {
|
||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
void sdk.api.integration
|
void client.api.integration
|
||||||
.connect.oauth({
|
.connect.oauth({
|
||||||
integrationID: props.integration.id,
|
integrationID: props.integration.id,
|
||||||
location: location(data),
|
location: location(data),
|
||||||
|
|
@ -267,7 +267,7 @@ function OAuthAuto(props: {
|
||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const clipboard = useClipboard()
|
const clipboard = useClipboard()
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined
|
let timer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
@ -291,7 +291,7 @@ function OAuthAuto(props: {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const poll = () => {
|
const poll = () => {
|
||||||
void sdk.api.integration
|
void client.api.integration
|
||||||
.attempt.status({ attemptID: props.attempt.attemptID, location: location(data) })
|
.attempt.status({ attemptID: props.attempt.attemptID, location: location(data) })
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const status = result.data
|
const status = result.data
|
||||||
|
|
@ -318,7 +318,7 @@ function OAuthAuto(props: {
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (timer) clearTimeout(timer)
|
if (timer) clearTimeout(timer)
|
||||||
if (settled) return
|
if (settled) return
|
||||||
void sdk.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
void client.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -340,7 +340,7 @@ function OAuthCode(props: {
|
||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [error, setError] = createSignal<string>()
|
const [error, setError] = createSignal<string>()
|
||||||
|
|
@ -348,7 +348,7 @@ function OAuthCode(props: {
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
if (settled) return
|
if (settled) return
|
||||||
void sdk.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
void client.api.integration.attempt.cancel({ attemptID: props.attempt.attemptID, location: location(data) })
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -357,7 +357,7 @@ function OAuthCode(props: {
|
||||||
placeholder="Authorization code"
|
placeholder="Authorization code"
|
||||||
onConfirm={(code) => {
|
onConfirm={(code) => {
|
||||||
if (!code) return
|
if (!code) return
|
||||||
void sdk.api.integration
|
void client.api.integration
|
||||||
.attempt.complete({ attemptID: props.attempt.attemptID, location: location(data), code })
|
.attempt.complete({ attemptID: props.attempt.attemptID, location: location(data), code })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
settled = true
|
settled = true
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { createMemo, createResource, createSignal, onMount, Show } from "solid-j
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
||||||
import { useDialog } from "../ui/dialog"
|
import { useDialog } from "../ui/dialog"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useData } from "../context/data"
|
import { useData } from "../context/data"
|
||||||
import { abbreviateHome } from "../runtime"
|
import { abbreviateHome } from "../runtime"
|
||||||
|
|
@ -35,7 +35,7 @@ type DialogMoveSessionProps = {
|
||||||
|
|
||||||
export function DialogMoveSession(props: DialogMoveSessionProps) {
|
export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const sessionData = useData()
|
const sessionData = useData()
|
||||||
|
|
@ -63,7 +63,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
const [loadedProject] = createResource(
|
const [loadedProject] = createResource(
|
||||||
() => (projectContext.project() === undefined ? props.projectID : undefined),
|
() => (projectContext.project() === undefined ? props.projectID : undefined),
|
||||||
(projectID) =>
|
(projectID) =>
|
||||||
sdk.api.project
|
client.api.project
|
||||||
.current({ location: { directory: projectContext.instance.directory() || paths.cwd } })
|
.current({ location: { directory: projectContext.instance.directory() || paths.cwd } })
|
||||||
.then((project) => (project.id === projectID ? project.directory : undefined))
|
.then((project) => (project.id === projectID ? project.directory : undefined))
|
||||||
.catch(() => undefined),
|
.catch(() => undefined),
|
||||||
|
|
@ -78,11 +78,11 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
async (projectID, info): Promise<ReadonlyArray<ProjectDirectory> | undefined> => {
|
async (projectID, info): Promise<ReadonlyArray<ProjectDirectory> | undefined> => {
|
||||||
try {
|
try {
|
||||||
const location = { directory: projectContext.instance.directory() || paths.cwd }
|
const location = { directory: projectContext.instance.directory() || paths.cwd }
|
||||||
await sdk.api.projectCopy.refresh({
|
await client.api.projectCopy.refresh({
|
||||||
projectID,
|
projectID,
|
||||||
location,
|
location,
|
||||||
})
|
})
|
||||||
const directories = await sdk.api.project.directories({
|
const directories = await client.api.project.directories({
|
||||||
projectID,
|
projectID,
|
||||||
location,
|
location,
|
||||||
})
|
})
|
||||||
|
|
@ -232,7 +232,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
setToDelete(undefined)
|
setToDelete(undefined)
|
||||||
setRemoving(selected.directory)
|
setRemoving(selected.directory)
|
||||||
setWorking(true)
|
setWorking(true)
|
||||||
const error = await sdk.api.projectCopy
|
const error = await client.api.projectCopy
|
||||||
.remove({
|
.remove({
|
||||||
projectID: props.projectID,
|
projectID: props.projectID,
|
||||||
location: { directory: projectContext.instance.directory() || paths.cwd },
|
location: { directory: projectContext.instance.directory() || paths.cwd },
|
||||||
|
|
@ -247,7 +247,9 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
setRemoving(undefined)
|
setRemoving(undefined)
|
||||||
setWorking(false)
|
setWorking(false)
|
||||||
if (isRecord(error) && isRecord(error.data) && error.data.forceRequired === true) {
|
if (isRecord(error) && isRecord(error.data) && error.data.forceRequired === true) {
|
||||||
const status = await sdk.client.vcs.status({ directory: selected.directory }).catch(() => undefined)
|
const status = await client.api.vcs
|
||||||
|
.status({ location: { directory: selected.directory } })
|
||||||
|
.catch(() => undefined)
|
||||||
const choice = await DialogWorkspaceFileChanges.show(dialog, status?.data ?? [], {
|
const choice = await DialogWorkspaceFileChanges.show(dialog, status?.data ?? [], {
|
||||||
title: "Delete working copy?",
|
title: "Delete working copy?",
|
||||||
message: "This working copy has file changes. Do you want to delete it anyway?",
|
message: "This working copy has file changes. Do you want to delete it anyway?",
|
||||||
|
|
@ -257,7 +259,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reopen(selected.directory)
|
reopen(selected.directory)
|
||||||
const forcedError = await sdk.api.projectCopy
|
const forcedError = await client.api.projectCopy
|
||||||
.remove({
|
.remove({
|
||||||
projectID: props.projectID,
|
projectID: props.projectID,
|
||||||
location: { directory: projectContext.instance.directory() || paths.cwd },
|
location: { directory: projectContext.instance.directory() || paths.cwd },
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
|
||||||
import { useTerminalDimensions } from "@opentui/solid"
|
import { useTerminalDimensions } from "@opentui/solid"
|
||||||
import { createMemo, createResource, createSignal, For, Show } from "solid-js"
|
import { createMemo, createResource, createSignal, For, Show } from "solid-js"
|
||||||
import { renderUnicodeCompact } from "uqr"
|
import { renderUnicodeCompact } from "uqr"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useDialog } from "../ui/dialog"
|
import { useDialog } from "../ui/dialog"
|
||||||
import { errorMessage } from "../util/error"
|
import { errorMessage } from "../util/error"
|
||||||
|
|
@ -13,7 +13,7 @@ export type DialogPairCredentials = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
@ -25,7 +25,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||||
dialog.setCentered(true)
|
dialog.setCentered(true)
|
||||||
|
|
||||||
const [server] = createResource(() =>
|
const [server] = createResource(() =>
|
||||||
sdk.api.server
|
client.api.server
|
||||||
.get()
|
.get()
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setLoadError(error)
|
setLoadError(error)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { useData } from "../context/data"
|
||||||
import { Locale } from "../util/locale"
|
import { Locale } from "../util/locale"
|
||||||
import { useProject } from "../context/project"
|
import { useProject } from "../context/project"
|
||||||
import { useTheme } from "../context/theme"
|
import { useTheme } from "../context/theme"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { useLocal } from "../context/local"
|
import { useLocal } from "../context/local"
|
||||||
import { createDebouncedSignal } from "../util/signal"
|
import { createDebouncedSignal } from "../util/signal"
|
||||||
import { useToast } from "../ui/toast"
|
import { useToast } from "../ui/toast"
|
||||||
|
|
@ -23,7 +23,7 @@ export function DialogSessionList() {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const [search, setSearch] = createDebouncedSignal("", 150)
|
const [search, setSearch] = createDebouncedSignal("", 150)
|
||||||
|
|
@ -36,7 +36,7 @@ export function DialogSessionList() {
|
||||||
if (!query) return
|
if (!query) return
|
||||||
const location = data.location.default()
|
const location = data.location.default()
|
||||||
try {
|
try {
|
||||||
const response = await sdk.api.session.list({
|
const response = await client.api.session.list({
|
||||||
search: query,
|
search: query,
|
||||||
limit: 50,
|
limit: 50,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
|
|
@ -140,7 +140,7 @@ export function DialogSessionList() {
|
||||||
setToDelete(option.value)
|
setToDelete(option.value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void sdk.api.session.remove({ sessionID: option.value }).catch((error) => {
|
void client.api.session.remove({ sessionID: option.value }).catch((error) => {
|
||||||
setToDelete(undefined)
|
setToDelete(undefined)
|
||||||
toast.show({
|
toast.show({
|
||||||
message: `Failed to delete session: ${errorMessage(error)}`,
|
message: `Failed to delete session: ${errorMessage(error)}`,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||||
import { type DialogContext, useDialog } from "../ui/dialog"
|
import { type DialogContext, useDialog } from "../ui/dialog"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { useToast } from "../ui/toast"
|
import { useToast } from "../ui/toast"
|
||||||
import { errorMessage } from "../util/error"
|
import { errorMessage } from "../util/error"
|
||||||
|
|
||||||
export function DialogSessionRename(props: { sessionID: string; currentTitle?: string }) {
|
export function DialogSessionRename(props: { sessionID: string; currentTitle?: string }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -17,7 +17,7 @@ export function DialogSessionRename(props: { sessionID: string; currentTitle?: s
|
||||||
onConfirm={(value) => {
|
onConfirm={(value) => {
|
||||||
const title = value.trim()
|
const title = value.trim()
|
||||||
if (!title) return
|
if (!title) return
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.rename({ sessionID: props.sessionID, title })
|
.rename({ sessionID: props.sessionID, title })
|
||||||
.then(() => dialog.clear())
|
.then(() => dialog.clear())
|
||||||
.catch((error) =>
|
.catch((error) =>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { createMemo, createResource } from "solid-js"
|
||||||
import { DialogSelect } from "../ui/dialog-select"
|
import { DialogSelect } from "../ui/dialog-select"
|
||||||
import { useDialog } from "../ui/dialog"
|
import { useDialog } from "../ui/dialog"
|
||||||
import { useProject } from "../context/project"
|
import { useProject } from "../context/project"
|
||||||
import { useSDK } from "../context/sdk"
|
import { useClient } from "../context/client"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
|
||||||
export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
||||||
const [files] = createResource(
|
const [files] = createResource(
|
||||||
() => [store.filter],
|
() => [store.filter],
|
||||||
async () => {
|
async () => {
|
||||||
const result = await sdk.api.file
|
const result = await client.api.file
|
||||||
.find({
|
.find({
|
||||||
query: store.filter,
|
query: store.filter,
|
||||||
type: "file",
|
type: "file",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { createMemo, createResource, createEffect, onMount, onCleanup, Index, Sh
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useEditorContext } from "../../context/editor"
|
import { useEditorContext } from "../../context/editor"
|
||||||
import { useProject } from "../../context/project"
|
import { useProject } from "../../context/project"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { getScrollAcceleration } from "../../util/scroll"
|
import { getScrollAcceleration } from "../../util/scroll"
|
||||||
import { useTuiPaths } from "../../context/runtime"
|
import { useTuiPaths } from "../../context/runtime"
|
||||||
|
|
@ -84,7 +84,7 @@ export function Autocomplete(props: {
|
||||||
promptPartTypeId: () => number
|
promptPartTypeId: () => number
|
||||||
}) {
|
}) {
|
||||||
const editor = useEditorContext()
|
const editor = useEditorContext()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
const slashes = useCommandSlashes()
|
const slashes = useCommandSlashes()
|
||||||
|
|
@ -315,7 +315,7 @@ export function Autocomplete(props: {
|
||||||
if (referenceMatch()) return []
|
if (referenceMatch()) return []
|
||||||
const { lineRange, baseQuery } = extractLineRange(input.query ?? "")
|
const { lineRange, baseQuery } = extractLineRange(input.query ?? "")
|
||||||
|
|
||||||
const result = await sdk.api.file
|
const result = await client.api.file
|
||||||
.find({
|
.find({
|
||||||
query: baseQuery,
|
query: baseQuery,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { EmptyBorder, SplitBorder } from "../../ui/border"
|
||||||
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
|
||||||
import { useClipboard } from "../../context/clipboard"
|
import { useClipboard } from "../../context/clipboard"
|
||||||
import { Spinner } from "../spinner"
|
import { Spinner } from "../spinner"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useRoute } from "../../context/route"
|
import { useRoute } from "../../context/route"
|
||||||
import { useProject } from "../../context/project"
|
import { useProject } from "../../context/project"
|
||||||
import { useEvent } from "../../context/event"
|
import { useEvent } from "../../context/event"
|
||||||
|
|
@ -148,7 +148,7 @@ export function Prompt(props: PromptProps) {
|
||||||
const paths = useTuiPaths()
|
const paths = useTuiPaths()
|
||||||
const terminalEnvironment = useTuiTerminalEnvironment()
|
const terminalEnvironment = useTuiTerminalEnvironment()
|
||||||
const clipboard = useClipboard()
|
const clipboard = useClipboard()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const editor = useEditorContext()
|
const editor = useEditorContext()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
|
|
@ -421,7 +421,7 @@ export function Prompt(props: PromptProps) {
|
||||||
}, 5000)
|
}, 5000)
|
||||||
|
|
||||||
if (store.interrupt >= 2) {
|
if (store.interrupt >= 2) {
|
||||||
void sdk.api.session.interrupt({
|
void client.api.session.interrupt({
|
||||||
sessionID: props.sessionID,
|
sessionID: props.sessionID,
|
||||||
})
|
})
|
||||||
setStore("interrupt", 0)
|
setStore("interrupt", 0)
|
||||||
|
|
@ -440,7 +440,7 @@ export function Prompt(props: PromptProps) {
|
||||||
if (!input.focused) return
|
if (!input.focused) return
|
||||||
if (!props.sessionID) return
|
if (!props.sessionID) return
|
||||||
|
|
||||||
void sdk.api.session.background({
|
void client.api.session.background({
|
||||||
sessionID: props.sessionID,
|
sessionID: props.sessionID,
|
||||||
})
|
})
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
|
|
@ -965,7 +965,7 @@ export function Prompt(props: PromptProps) {
|
||||||
finishMoveProgress = Boolean(move.progress())
|
finishMoveProgress = Boolean(move.progress())
|
||||||
const location = data.location.default()
|
const location = data.location.default()
|
||||||
|
|
||||||
const created = await sdk.api.session
|
const created = await client.api.session
|
||||||
.create({
|
.create({
|
||||||
location: directory ? { directory } : location,
|
location: directory ? { directory } : location,
|
||||||
agent: agent.id,
|
agent: agent.id,
|
||||||
|
|
@ -1009,7 +1009,7 @@ export function Prompt(props: PromptProps) {
|
||||||
|
|
||||||
if (store.mode === "shell") {
|
if (store.mode === "shell") {
|
||||||
move.startSubmit()
|
move.startSubmit()
|
||||||
void sdk.api.session.shell({
|
void client.api.session.shell({
|
||||||
sessionID,
|
sessionID,
|
||||||
command: inputText,
|
command: inputText,
|
||||||
})
|
})
|
||||||
|
|
@ -1028,7 +1028,7 @@ export function Prompt(props: PromptProps) {
|
||||||
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
|
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
|
||||||
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
|
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
|
||||||
|
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.command({
|
.command({
|
||||||
sessionID,
|
sessionID,
|
||||||
command: command.slice(1),
|
command: command.slice(1),
|
||||||
|
|
@ -1048,7 +1048,7 @@ export function Prompt(props: PromptProps) {
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
move.startSubmit()
|
move.startSubmit()
|
||||||
void sdk.api.session.skill({
|
void client.api.session.skill({
|
||||||
sessionID,
|
sessionID,
|
||||||
skill: inputText.split("\n")[0].split(" ")[0].slice(1),
|
skill: inputText.split("\n")[0].split(" ")[0].slice(1),
|
||||||
})
|
})
|
||||||
|
|
@ -1059,20 +1059,20 @@ export function Prompt(props: PromptProps) {
|
||||||
session = data.session.get(sessionID)
|
session = data.session.get(sessionID)
|
||||||
}
|
}
|
||||||
if (session?.agent !== agent.id) {
|
if (session?.agent !== agent.id) {
|
||||||
await sdk.api.session.switchAgent({ sessionID, agent: agent.id })
|
await client.api.session.switchAgent({ sessionID, agent: agent.id })
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
session?.model?.providerID !== selectedModel.providerID ||
|
session?.model?.providerID !== selectedModel.providerID ||
|
||||||
session.model.id !== selectedModel.modelID ||
|
session.model.id !== selectedModel.modelID ||
|
||||||
session.model.variant !== variant
|
session.model.variant !== variant
|
||||||
) {
|
) {
|
||||||
await sdk.api.session.switchModel({
|
await client.api.session.switchModel({
|
||||||
sessionID,
|
sessionID,
|
||||||
model: { providerID: selectedModel.providerID, id: selectedModel.modelID, variant },
|
model: { providerID: selectedModel.providerID, id: selectedModel.modelID, variant },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (session?.revert) {
|
if (session?.revert) {
|
||||||
const error = await sdk.api.session.revert.commit({ sessionID }).then(
|
const error = await client.api.session.revert.commit({ sessionID }).then(
|
||||||
() => undefined,
|
() => undefined,
|
||||||
(error) => error,
|
(error) => error,
|
||||||
)
|
)
|
||||||
|
|
@ -1083,7 +1083,7 @@ export function Prompt(props: PromptProps) {
|
||||||
}
|
}
|
||||||
if (pendingEditorSelection) {
|
if (pendingEditorSelection) {
|
||||||
// Keep editor context hidden while admitting it before the corresponding user prompt.
|
// Keep editor context hidden while admitting it before the corresponding user prompt.
|
||||||
const error = await sdk.api.session
|
const error = await client.api.session
|
||||||
.synthetic({
|
.synthetic({
|
||||||
sessionID,
|
sessionID,
|
||||||
text: formatEditorContext(pendingEditorSelection),
|
text: formatEditorContext(pendingEditorSelection),
|
||||||
|
|
@ -1098,7 +1098,7 @@ export function Prompt(props: PromptProps) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const error = await sdk.api.session
|
const error = await client.api.session
|
||||||
.prompt({
|
.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
text: inputText,
|
text: inputText,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import path from "path"
|
||||||
import { useTuiPaths } from "../../context/runtime"
|
import { useTuiPaths } from "../../context/runtime"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { useDialog } from "../../ui/dialog"
|
import { useDialog } from "../../ui/dialog"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useToast } from "../../ui/toast"
|
import { useToast } from "../../ui/toast"
|
||||||
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
|
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
|
||||||
import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes"
|
import { DialogWorkspaceFileChanges } from "../dialog-workspace-file-changes"
|
||||||
|
|
@ -17,7 +17,7 @@ function moveReminderText(directory: string) {
|
||||||
|
|
||||||
export function usePromptMove(input: { projectID: () => string | undefined; sessionID: () => string | undefined }) {
|
export function usePromptMove(input: { projectID: () => string | undefined; sessionID: () => string | undefined }) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const homeDestination = useHomeSessionDestination()
|
const homeDestination = useHomeSessionDestination()
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
|
|
@ -33,7 +33,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
setCreating(true)
|
setCreating(true)
|
||||||
setProgress("Creating copy")
|
setProgress("Creating copy")
|
||||||
try {
|
try {
|
||||||
const result = await sdk.api.projectCopy.create({
|
const result = await client.api.projectCopy.create({
|
||||||
projectID,
|
projectID,
|
||||||
location: { directory: project.instance.directory() || paths.cwd },
|
location: { directory: project.instance.directory() || paths.cwd },
|
||||||
strategy: "git_worktree",
|
strategy: "git_worktree",
|
||||||
|
|
@ -44,7 +44,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
if (!directory) throw new Error("No project copy directory returned")
|
if (!directory) throw new Error("No project copy directory returned")
|
||||||
|
|
||||||
// Call a location-based route to make sure it's bootstrapped before moving on.
|
// Call a location-based route to make sure it's bootstrapped before moving on.
|
||||||
await sdk.api.location.get({ location: { directory } })
|
await client.api.location.get({ location: { directory } })
|
||||||
|
|
||||||
setProgress("Creating session")
|
setProgress("Creating session")
|
||||||
return directory
|
return directory
|
||||||
|
|
@ -98,7 +98,9 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
|
|
||||||
async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) {
|
async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) {
|
||||||
const session = await resolveSession(sessionID)
|
const session = await resolveSession(sessionID)
|
||||||
const status = await sdk.client.vcs.status({ directory: session?.location.directory }).catch(() => undefined)
|
const status = await client.api.vcs
|
||||||
|
.status({ location: session?.location.directory ? { directory: session.location.directory } : undefined })
|
||||||
|
.catch(() => undefined)
|
||||||
const choice = status?.data?.length ? await DialogWorkspaceFileChanges.show(dialog, status.data) : "no"
|
const choice = status?.data?.length ? await DialogWorkspaceFileChanges.show(dialog, status.data) : "no"
|
||||||
if (!choice) return
|
if (!choice) return
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
|
|
@ -110,8 +112,8 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
}
|
}
|
||||||
setProgress("Moving session")
|
setProgress("Moving session")
|
||||||
try {
|
try {
|
||||||
await sdk.api.session.move({ sessionID, destination: { directory }, moveChanges: choice === "yes" })
|
await client.api.session.move({ sessionID, destination: { directory }, moveChanges: choice === "yes" })
|
||||||
await sdk.api.session
|
await client.api.session
|
||||||
.synthetic({ sessionID, text: moveReminderText(directory), resume: false })
|
.synthetic({ sessionID, text: moveReminderText(directory), resume: false })
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
|
|
@ -129,7 +131,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
if (projectID) return projectID
|
if (projectID) return projectID
|
||||||
const sessionID = input.sessionID()
|
const sessionID = input.sessionID()
|
||||||
if (sessionID) return (await resolveSession(sessionID))?.projectID
|
if (sessionID) return (await resolveSession(sessionID))?.projectID
|
||||||
return sdk.api.project
|
return client.api.project
|
||||||
.current({ location: { directory: project.instance.directory() || paths.cwd } })
|
.current({ location: { directory: project.instance.directory() || paths.cwd } })
|
||||||
.then((project) => project.id)
|
.then((project) => project.id)
|
||||||
.catch(() => undefined)
|
.catch(() => undefined)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
|
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
|
||||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
|
||||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||||
import { onCleanup, onMount } from "solid-js"
|
import { onCleanup, onMount } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createSimpleContext } from "./helper"
|
import { createSimpleContext } from "./helper"
|
||||||
import { useLog } from "./log"
|
import { useLog } from "./log"
|
||||||
|
|
||||||
export type SDKConnectionStatus = "connected" | "connecting" | "reconnecting"
|
export type ClientConnectionStatus = "connected" | "connecting" | "reconnecting"
|
||||||
export type SDKConnectionEvent = {
|
export type ClientConnectionEvent = {
|
||||||
readonly type: "client.connection"
|
readonly type: "client.connection"
|
||||||
readonly created: number
|
readonly created: number
|
||||||
readonly data: {
|
readonly data: {
|
||||||
|
|
@ -17,27 +16,25 @@ export type SDKConnectionEvent = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SDKEventMap = { [Type in OpenCodeEvent["type"]]: Extract<OpenCodeEvent, { type: Type }> }
|
type ClientEventMap = { [Type in OpenCodeEvent["type"]]: Extract<OpenCodeEvent, { type: Type }> }
|
||||||
const connectTimeout = 2_000
|
const connectTimeout = 2_000
|
||||||
const connectionHistoryLimit = 50
|
const connectionHistoryLimit = 50
|
||||||
|
|
||||||
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
export const { use: useClient, provider: ClientProvider } = createSimpleContext({
|
||||||
name: "SDK",
|
name: "Client",
|
||||||
init: (props: {
|
init: (props: {
|
||||||
client: OpencodeClient
|
|
||||||
api: OpenCodeClient
|
api: OpenCodeClient
|
||||||
reconnect?: (attempt: number) => Promise<{ client: OpencodeClient; api: OpenCodeClient }>
|
reconnect?: (attempt: number) => Promise<{ api: OpenCodeClient }>
|
||||||
// Stops and starts the managed service; present only in service mode.
|
// Stops and starts the managed service; present only in service mode.
|
||||||
reload?: () => Promise<void>
|
reload?: () => Promise<void>
|
||||||
}) => {
|
}) => {
|
||||||
const log = useLog({ component: "sdk" })
|
const log = useLog({ component: "client" })
|
||||||
const abort = new AbortController()
|
const abort = new AbortController()
|
||||||
const history: SDKConnectionEvent[] = []
|
const history: ClientConnectionEvent[] = []
|
||||||
let client = props.client
|
|
||||||
let api = props.api
|
let api = props.api
|
||||||
const events = createGlobalEmitter<SDKEventMap>()
|
const events = createGlobalEmitter<ClientEventMap>()
|
||||||
const [connection, setConnection] = createStore<{
|
const [connection, setConnection] = createStore<{
|
||||||
status: SDKConnectionStatus
|
status: ClientConnectionStatus
|
||||||
attempt: number
|
attempt: number
|
||||||
error?: string
|
error?: string
|
||||||
}>({
|
}>({
|
||||||
|
|
@ -46,7 +43,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||||
})
|
})
|
||||||
let stream: AbortController | undefined
|
let stream: AbortController | undefined
|
||||||
|
|
||||||
function record(status: SDKConnectionEvent["data"]["status"], attempt: number, error?: string) {
|
function record(status: ClientConnectionEvent["data"]["status"], attempt: number, error?: string) {
|
||||||
history.push({ type: "client.connection", created: Date.now(), data: { status, attempt, error } })
|
history.push({ type: "client.connection", created: Date.now(), data: { status, attempt, error } })
|
||||||
if (history.length > connectionHistoryLimit) history.shift()
|
if (history.length > connectionHistoryLimit) history.shift()
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +118,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||||
const next = await props.reconnect(attempt).catch(() => undefined)
|
const next = await props.reconnect(attempt).catch(() => undefined)
|
||||||
if (abort.signal.aborted || controller.signal.aborted) return
|
if (abort.signal.aborted || controller.signal.aborted) return
|
||||||
if (next) {
|
if (next) {
|
||||||
client = next.client
|
|
||||||
api = next.api
|
api = next.api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -144,9 +140,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get client() {
|
|
||||||
return client
|
|
||||||
},
|
|
||||||
get api() {
|
get api() {
|
||||||
return api
|
return api
|
||||||
},
|
},
|
||||||
|
|
@ -30,7 +30,7 @@ import type {
|
||||||
import type { Data } from "@opencode-ai/plugin/v2/tui/context"
|
import type { Data } from "@opencode-ai/plugin/v2/tui/context"
|
||||||
import { createStore, produce, reconcile } from "solid-js/store"
|
import { createStore, produce, reconcile } from "solid-js/store"
|
||||||
import { createSimpleContext } from "./helper"
|
import { createSimpleContext } from "./helper"
|
||||||
import { useSDK } from "./sdk"
|
import { useClient } from "./client"
|
||||||
import { createSignal, onCleanup } from "solid-js"
|
import { createSignal, onCleanup } from "solid-js"
|
||||||
|
|
||||||
export type DataSessionStatus = "idle" | "running"
|
export type DataSessionStatus = "idle" | "running"
|
||||||
|
|
@ -110,7 +110,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
location: {},
|
location: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const [defaultLocation, setDefaultLocation] = createSignal<LocationRef>({
|
const [defaultLocation, setDefaultLocation] = createSignal<LocationRef>({
|
||||||
directory: process.cwd(),
|
directory: process.cwd(),
|
||||||
})
|
})
|
||||||
|
|
@ -328,7 +328,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
time: { created: event.created },
|
time: { created: event.created },
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.message({ sessionID: event.data.sessionID, messageID: messageIDFromEvent(event.id) })
|
.message({ sessionID: event.data.sessionID, messageID: messageIDFromEvent(event.id) })
|
||||||
.then((item) => {
|
.then((item) => {
|
||||||
message.update(event.data.sessionID, (draft, index) => {
|
message.update(event.data.sessionID, (draft, index) => {
|
||||||
|
|
@ -851,8 +851,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
on: sdk.event.on,
|
on: client.event.on,
|
||||||
listen: sdk.event.listen,
|
listen: client.event.listen,
|
||||||
session: {
|
session: {
|
||||||
list() {
|
list() {
|
||||||
return Object.values(store.session.info).toSorted((a, b) => b.time.updated - a.time.updated)
|
return Object.values(store.session.info).toSorted((a, b) => b.time.updated - a.time.updated)
|
||||||
|
|
@ -899,7 +899,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.session.pending[sessionID] ?? []
|
return store.session.pending[sessionID] ?? []
|
||||||
},
|
},
|
||||||
async refresh(sessionID: string) {
|
async refresh(sessionID: string) {
|
||||||
const pending = await sdk.api.session.pending.list({ sessionID })
|
const pending = await client.api.session.pending.list({ sessionID })
|
||||||
setStore("session", "pending", sessionID, reconcile(pending))
|
setStore("session", "pending", sessionID, reconcile(pending))
|
||||||
setStore(
|
setStore(
|
||||||
"session",
|
"session",
|
||||||
|
|
@ -916,7 +916,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async refresh(sessionID: string) {
|
async refresh(sessionID: string) {
|
||||||
setStore("session", "info", sessionID, await sdk.api.session.get({ sessionID }))
|
setStore("session", "info", sessionID, await client.api.session.get({ sessionID }))
|
||||||
registerSession(sessionID)
|
registerSession(sessionID)
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
|
|
@ -929,7 +929,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return position === undefined ? undefined : messages?.[position]
|
return position === undefined ? undefined : messages?.[position]
|
||||||
},
|
},
|
||||||
async refresh(sessionID: string) {
|
async refresh(sessionID: string) {
|
||||||
const messages = (await sdk.api.message.list({ sessionID, limit: 200, order: "desc" })).data.toReversed()
|
const messages = (await client.api.message.list({ sessionID, limit: 200, order: "desc" })).data.toReversed()
|
||||||
messageIndex.set(sessionID, new Map(messages.map((message, index) => [message.id, index])))
|
messageIndex.set(sessionID, new Map(messages.map((message, index) => [message.id, index])))
|
||||||
setStore("session", "message", sessionID, reconcile(messages))
|
setStore("session", "message", sessionID, reconcile(messages))
|
||||||
},
|
},
|
||||||
|
|
@ -939,7 +939,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.session.permission[sessionID]
|
return store.session.permission[sessionID]
|
||||||
},
|
},
|
||||||
async refresh(sessionID: string) {
|
async refresh(sessionID: string) {
|
||||||
setStore("session", "permission", sessionID, await sdk.api.permission.list({ sessionID }))
|
setStore("session", "permission", sessionID, await client.api.permission.list({ sessionID }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -952,7 +952,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
},
|
},
|
||||||
async refresh(sessionID: string, ref?: LocationRef) {
|
async refresh(sessionID: string, ref?: LocationRef) {
|
||||||
if (sessionID === "global") {
|
if (sessionID === "global") {
|
||||||
const response = await sdk.api.form.request.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const response = await client.api.form.request.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const location = {
|
const location = {
|
||||||
directory: response.location.directory,
|
directory: response.location.directory,
|
||||||
workspaceID: response.location.workspaceID,
|
workspaceID: response.location.workspaceID,
|
||||||
|
|
@ -966,7 +966,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
])
|
])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("session", "form", sessionID, await sdk.api.form.list({ sessionID }))
|
setStore("session", "form", sessionID, await client.api.form.list({ sessionID }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -976,7 +976,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.project.permission[projectID]
|
return store.project.permission[projectID]
|
||||||
},
|
},
|
||||||
async refresh(projectID: string) {
|
async refresh(projectID: string) {
|
||||||
setStore("project", "permission", projectID, await sdk.api.permission.saved.list({ projectID }))
|
setStore("project", "permission", projectID, await client.api.permission.saved.list({ projectID }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -990,7 +990,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
.find((shell) => shell !== undefined)
|
.find((shell) => shell !== undefined)
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.shell.list({ location: locationQuery(ref) })
|
const result = await client.api.shell.list({ location: locationQuery(ref) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, {
|
setStore("location", key, {
|
||||||
...store.location[key],
|
...store.location[key],
|
||||||
|
|
@ -1003,7 +1003,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return defaultLocation()
|
return defaultLocation()
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const location = await sdk.api.location.get({ location: locationQuery(ref ?? defaultLocation()) })
|
const location = await client.api.location.get({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(location)
|
const key = locationKey(location)
|
||||||
if (!store.location[key]) setStore("location", key, {})
|
if (!store.location[key]) setStore("location", key, {})
|
||||||
if (!ref) setDefaultLocation({ directory: location.directory, workspaceID: location.workspaceID })
|
if (!ref) setDefaultLocation({ directory: location.directory, workspaceID: location.workspaceID })
|
||||||
|
|
@ -1013,7 +1013,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.agent
|
return store.location[locationKey(location ?? defaultLocation())]?.agent
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.agent.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.agent.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], agent: result.data })
|
setStore("location", key, { ...store.location[key], agent: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1023,7 +1023,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.command
|
return store.location[locationKey(location ?? defaultLocation())]?.command
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.command.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.command.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], command: result.data })
|
setStore("location", key, { ...store.location[key], command: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1033,7 +1033,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.integration
|
return store.location[locationKey(location ?? defaultLocation())]?.integration
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.integration.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.integration.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], integration: result.data })
|
setStore("location", key, { ...store.location[key], integration: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1044,7 +1044,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.mcp?.server
|
return store.location[locationKey(location ?? defaultLocation())]?.mcp?.server
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.mcp.list({ location: locationQuery(ref) })
|
const result = await client.api.mcp.list({ location: locationQuery(ref) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, {
|
setStore("location", key, {
|
||||||
...store.location[key],
|
...store.location[key],
|
||||||
|
|
@ -1057,7 +1057,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.mcp?.resource
|
return store.location[locationKey(location ?? defaultLocation())]?.mcp?.resource
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.mcp.resource.catalog({ location: locationQuery(ref) })
|
const result = await client.api.mcp.resource.catalog({ location: locationQuery(ref) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, {
|
setStore("location", key, {
|
||||||
...store.location[key],
|
...store.location[key],
|
||||||
|
|
@ -1071,7 +1071,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.model
|
return store.location[locationKey(location ?? defaultLocation())]?.model
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.model.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.model.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], model: result.data })
|
setStore("location", key, { ...store.location[key], model: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1081,7 +1081,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.provider
|
return store.location[locationKey(location ?? defaultLocation())]?.provider
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.provider.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.provider.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], provider: result.data })
|
setStore("location", key, { ...store.location[key], provider: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1091,7 +1091,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.reference
|
return store.location[locationKey(location ?? defaultLocation())]?.reference
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.reference.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.reference.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], reference: result.data })
|
setStore("location", key, { ...store.location[key], reference: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1101,7 +1101,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
return store.location[locationKey(location ?? defaultLocation())]?.skill
|
return store.location[locationKey(location ?? defaultLocation())]?.skill
|
||||||
},
|
},
|
||||||
async refresh(ref?: LocationRef) {
|
async refresh(ref?: LocationRef) {
|
||||||
const result = await sdk.api.skill.list({ location: locationQuery(ref ?? defaultLocation()) })
|
const result = await client.api.skill.list({ location: locationQuery(ref ?? defaultLocation()) })
|
||||||
const key = locationKey(result.location)
|
const key = locationKey(result.location)
|
||||||
setStore("location", key, { ...store.location[key], skill: result.data })
|
setStore("location", key, { ...store.location[key], skill: result.data })
|
||||||
},
|
},
|
||||||
|
|
@ -1113,7 +1113,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
if (bootstrapping) return bootstrapping
|
if (bootstrapping) return bootstrapping
|
||||||
bootstrapping = Promise.allSettled([
|
bootstrapping = Promise.allSettled([
|
||||||
sdk.api.session
|
client.api.session
|
||||||
.list({
|
.list({
|
||||||
limit: 50,
|
limit: 50,
|
||||||
order: "desc",
|
order: "desc",
|
||||||
|
|
@ -1130,7 +1130,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
)
|
)
|
||||||
for (const session of response.data) registerSession(session.id)
|
for (const session of response.data) registerSession(session.id)
|
||||||
}),
|
}),
|
||||||
sdk.api.permission.request.list({ location: locationQuery(defaultLocation()) }).then((response) => {
|
client.api.permission.request.list({ location: locationQuery(defaultLocation()) }).then((response) => {
|
||||||
const permissions = response.data.reduce<Record<string, PermissionV2Request[]>>(
|
const permissions = response.data.reduce<Record<string, PermissionV2Request[]>>(
|
||||||
(result, request) => ({
|
(result, request) => ({
|
||||||
...result,
|
...result,
|
||||||
|
|
@ -1140,7 +1140,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
)
|
)
|
||||||
setStore("session", "permission", reconcile(permissions))
|
setStore("session", "permission", reconcile(permissions))
|
||||||
}),
|
}),
|
||||||
sdk.api.form.request.list({ location: locationQuery(defaultLocation()) }).then((response) => {
|
client.api.form.request.list({ location: locationQuery(defaultLocation()) }).then((response) => {
|
||||||
const location = {
|
const location = {
|
||||||
directory: response.location.directory,
|
directory: response.location.directory,
|
||||||
workspaceID: response.location.workspaceID,
|
workspaceID: response.location.workspaceID,
|
||||||
|
|
@ -1193,7 +1193,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshActive() {
|
function refreshActive() {
|
||||||
void sdk.api.session
|
void client.api.session
|
||||||
.active()
|
.active()
|
||||||
.then((active) => {
|
.then((active) => {
|
||||||
setStore(
|
setStore(
|
||||||
|
|
@ -1206,7 +1206,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||||
}
|
}
|
||||||
|
|
||||||
onCleanup(
|
onCleanup(
|
||||||
sdk.event.listen(({ details }) => {
|
client.event.listen(({ details }) => {
|
||||||
if (details.type === "server.connected") {
|
if (details.type === "server.connected") {
|
||||||
const messages = connected ? Object.keys(store.session.message) : []
|
const messages = connected ? Object.keys(store.session.message) : []
|
||||||
const compactions = connected ? Object.keys(store.session.compaction) : []
|
const compactions = connected ? Object.keys(store.session.compaction) : []
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { OpenCodeEvent } from "@opencode-ai/client"
|
import type { OpenCodeEvent } from "@opencode-ai/client"
|
||||||
import { useSDK } from "./sdk"
|
import { useClient } from "./client"
|
||||||
|
|
||||||
type EventMetadata = {
|
type EventMetadata = {
|
||||||
directory: string | undefined
|
directory: string | undefined
|
||||||
|
|
@ -7,10 +7,10 @@ type EventMetadata = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEvent() {
|
export function useEvent() {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
|
|
||||||
function subscribe(handler: (event: OpenCodeEvent, metadata: EventMetadata) => void) {
|
function subscribe(handler: (event: OpenCodeEvent, metadata: EventMetadata) => void) {
|
||||||
return sdk.event.listen(({ details }) => {
|
return client.event.listen(({ details }) => {
|
||||||
if (details.type === "server.connected") return
|
if (details.type === "server.connected") return
|
||||||
handler(details, { directory: details.location?.directory, workspace: details.location?.workspaceID })
|
handler(details, { directory: details.location?.directory, workspace: details.location?.workspaceID })
|
||||||
})
|
})
|
||||||
|
|
@ -20,7 +20,7 @@ export function useEvent() {
|
||||||
type: T,
|
type: T,
|
||||||
handler: (event: Extract<OpenCodeEvent, { type: T }>, metadata: EventMetadata) => void,
|
handler: (event: Extract<OpenCodeEvent, { type: T }>, metadata: EventMetadata) => void,
|
||||||
) {
|
) {
|
||||||
return sdk.event.on(type, (event) => {
|
return client.event.on(type, (event) => {
|
||||||
handler(event, { directory: event.location?.directory, workspace: event.location?.workspaceID })
|
handler(event, { directory: event.location?.directory, workspace: event.location?.workspaceID })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { useEvent } from "./event"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { useTuiPaths } from "./runtime"
|
import { useTuiPaths } from "./runtime"
|
||||||
import { useArgs } from "./args"
|
import { useArgs } from "./args"
|
||||||
import { useSDK } from "./sdk"
|
import { useClient } from "./client"
|
||||||
import { RGBA } from "@opentui/core"
|
import { RGBA } from "@opentui/core"
|
||||||
import { readJson, writeJsonAtomic } from "../util/persistence"
|
import { readJson, writeJsonAtomic } from "../util/persistence"
|
||||||
import { useTheme } from "./theme"
|
import { useTheme } from "./theme"
|
||||||
|
|
@ -52,7 +52,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||||
name: "Local",
|
name: "Local",
|
||||||
init: () => {
|
init: () => {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const theme = useTheme().theme
|
const theme = useTheme().theme
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
@ -493,22 +493,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||||
|
|
||||||
const session = createSession()
|
const session = createSession()
|
||||||
|
|
||||||
const mcp = {
|
|
||||||
isEnabled(name: string) {
|
|
||||||
return data.location.mcp.server.list()?.find((item) => item.name === name)?.status.status === "connected"
|
|
||||||
},
|
|
||||||
async toggle(name: string) {
|
|
||||||
const status = data.location.mcp.server.list()?.find((item) => item.name === name)?.status.status
|
|
||||||
if (status === "connected") {
|
|
||||||
// Disable: disconnect the MCP
|
|
||||||
await sdk.client.mcp.disconnect({ name })
|
|
||||||
} else {
|
|
||||||
// Enable/Retry: connect the MCP (handles disabled, failed, and other states)
|
|
||||||
await sdk.client.mcp.connect({ name })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const value = agent.current()
|
const value = agent.current()
|
||||||
if (!value?.model) return
|
if (!value?.model) return
|
||||||
|
|
@ -523,7 +507,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||||
const result = {
|
const result = {
|
||||||
model,
|
model,
|
||||||
agent,
|
agent,
|
||||||
mcp,
|
|
||||||
session,
|
session,
|
||||||
permission,
|
permission,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { batch } from "solid-js"
|
import { batch } from "solid-js"
|
||||||
import { createStore, reconcile } from "solid-js/store"
|
import { createStore, reconcile } from "solid-js/store"
|
||||||
import { createSimpleContext } from "./helper"
|
import { createSimpleContext } from "./helper"
|
||||||
import { useSDK } from "./sdk"
|
import { useClient } from "./client"
|
||||||
|
|
||||||
export const { use: useProject, provider: ProjectProvider } = createSimpleContext({
|
export const { use: useProject, provider: ProjectProvider } = createSimpleContext({
|
||||||
name: "Project",
|
name: "Project",
|
||||||
init: () => {
|
init: () => {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
|
|
||||||
const defaultPath = {
|
const defaultPath = {
|
||||||
home: "",
|
home: "",
|
||||||
|
|
@ -33,8 +33,8 @@ export const { use: useProject, provider: ProjectProvider } = createSimpleContex
|
||||||
async function sync() {
|
async function sync() {
|
||||||
const workspace = store.workspace.current
|
const workspace = store.workspace.current
|
||||||
const location = { workspace }
|
const location = { workspace }
|
||||||
const current = await sdk.api.location.get({ location })
|
const current = await client.api.location.get({ location })
|
||||||
const directories = await sdk.api.project.directories({ projectID: current.project.id, location })
|
const directories = await client.api.project.directories({ projectID: current.project.id, location })
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore(
|
setStore(
|
||||||
"instance",
|
"instance",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import type { PluginRuntime } from "../plugin/runtime"
|
||||||
import HomeFooter from "./home/footer"
|
import HomeFooter from "./home/footer"
|
||||||
import HomeTips from "./home/tips"
|
import HomeTips from "./home/tips"
|
||||||
import SidebarContext from "./sidebar/context"
|
import SidebarContext from "./sidebar/context"
|
||||||
import SidebarFiles from "./sidebar/files"
|
|
||||||
import SidebarFooter from "./sidebar/footer"
|
import SidebarFooter from "./sidebar/footer"
|
||||||
import SidebarLsp from "./sidebar/lsp"
|
import SidebarLsp from "./sidebar/lsp"
|
||||||
import SidebarMcp from "./sidebar/mcp"
|
import SidebarMcp from "./sidebar/mcp"
|
||||||
|
|
@ -26,7 +25,6 @@ export function createBuiltinPlugins(): BuiltinTuiPlugin[] {
|
||||||
SidebarContext,
|
SidebarContext,
|
||||||
SidebarMcp,
|
SidebarMcp,
|
||||||
SidebarLsp,
|
SidebarLsp,
|
||||||
SidebarFiles,
|
|
||||||
SidebarFooter,
|
SidebarFooter,
|
||||||
Notifications,
|
Notifications,
|
||||||
PluginManager,
|
PluginManager,
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
|
||||||
import type { BuiltinTuiPlugin } from "../builtins"
|
|
||||||
import { createMemo, For, Show, createSignal } from "solid-js"
|
|
||||||
import { FilePath } from "../../ui/file-path"
|
|
||||||
|
|
||||||
const id = "internal:sidebar-files"
|
|
||||||
|
|
||||||
function changeCountWidth(item: { additions: number; deletions: number }) {
|
|
||||||
return [item.additions ? `+${item.additions}` : "", item.deletions ? `-${item.deletions}` : ""]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ").length
|
|
||||||
}
|
|
||||||
|
|
||||||
function View(props: { api: TuiPluginApi; session_id: string }) {
|
|
||||||
const [open, setOpen] = createSignal(true)
|
|
||||||
const theme = () => props.api.theme.current
|
|
||||||
const list = createMemo(() => props.api.state.session.diff(props.session_id))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Show when={list().length > 0}>
|
|
||||||
<box>
|
|
||||||
<box flexDirection="row" gap={1} onMouseDown={() => list().length > 2 && setOpen((x) => !x)}>
|
|
||||||
<Show when={list().length > 2}>
|
|
||||||
<text fg={theme().text}>{open() ? "▼" : "▶"}</text>
|
|
||||||
</Show>
|
|
||||||
<text fg={theme().text}>
|
|
||||||
<b>Modified Files</b>
|
|
||||||
</text>
|
|
||||||
</box>
|
|
||||||
<Show when={list().length <= 2 || open()}>
|
|
||||||
<For each={list()}>
|
|
||||||
{(item) => (
|
|
||||||
<box flexDirection="row" gap={1} justifyContent="space-between">
|
|
||||||
<FilePath
|
|
||||||
value={item.file}
|
|
||||||
maxWidth={Math.max(2, 36 - changeCountWidth(item))}
|
|
||||||
fg={theme().textMuted}
|
|
||||||
/>
|
|
||||||
<box flexDirection="row" gap={1} flexShrink={0}>
|
|
||||||
<Show when={item.additions}>
|
|
||||||
<text fg={theme().diffAdded}>+{item.additions}</text>
|
|
||||||
</Show>
|
|
||||||
<Show when={item.deletions}>
|
|
||||||
<text fg={theme().diffRemoved}>-{item.deletions}</text>
|
|
||||||
</Show>
|
|
||||||
</box>
|
|
||||||
</box>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</box>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tui: TuiPlugin = async (api) => {
|
|
||||||
api.slots.register({
|
|
||||||
order: 500,
|
|
||||||
slots: {
|
|
||||||
sidebar_content(_ctx, props) {
|
|
||||||
return <View api={api} session_id={props.session_id} />
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const plugin: BuiltinTuiPlugin = {
|
|
||||||
id,
|
|
||||||
tui,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default plugin
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/** @jsxImportSource @opentui/solid */
|
/** @jsxImportSource @opentui/solid */
|
||||||
import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
|
import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
|
||||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client"
|
import type { FileDiffInfo } from "@opencode-ai/client"
|
||||||
import {
|
import {
|
||||||
TextAttributes,
|
TextAttributes,
|
||||||
type BorderSides,
|
type BorderSides,
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { LANGUAGE_EXTENSIONS } from "../../util/filetype"
|
import { LANGUAGE_EXTENSIONS } from "../../util/filetype"
|
||||||
import { useBindings, useCommandShortcut } from "../../keymap"
|
import { useBindings, useCommandShortcut } from "../../keymap"
|
||||||
import { useTheme } from "../../context/theme"
|
import { useTheme } from "../../context/theme"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useTerminalDimensions } from "@opentui/solid"
|
import { useTerminalDimensions } from "@opentui/solid"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||||
|
|
@ -42,7 +42,7 @@ const MIN_SPLIT_WIDTH = 100
|
||||||
const FILE_TREE_WIDTH = 32
|
const FILE_TREE_WIDTH = 32
|
||||||
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
const PLAIN_TEXT_FILETYPE = "opencode-plain-text"
|
||||||
const VCS_DIFF_CONTEXT_LINES = 12
|
const VCS_DIFF_CONTEXT_LINES = 12
|
||||||
type DiffMode = "working" | "branch" | "last-turn"
|
type DiffMode = "working" | "branch"
|
||||||
type DiffViewerFocus = "patches" | "files"
|
type DiffViewerFocus = "patches" | "files"
|
||||||
type DiffView = "split" | "unified"
|
type DiffView = "split" | "unified"
|
||||||
type SelectedHunk = { readonly fileIndex: number; readonly hunkIndex: number; readonly scrollTop: number }
|
type SelectedHunk = { readonly fileIndex: number; readonly hunkIndex: number; readonly scrollTop: number }
|
||||||
|
|
@ -55,20 +55,14 @@ type DiffFile = {
|
||||||
readonly status: "added" | "deleted" | "modified"
|
readonly status: "added" | "deleted" | "modified"
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeDiffs = (diffs: readonly (FileDiffInfo | FileDiffLegacyInfo)[]): DiffFile[] =>
|
const normalizeDiffs = (diffs: readonly FileDiffInfo[]): DiffFile[] =>
|
||||||
diffs.flatMap((item) =>
|
diffs.map((item) => ({
|
||||||
item.file
|
file: item.file,
|
||||||
? [
|
patch: item.patch,
|
||||||
{
|
additions: item.additions,
|
||||||
file: item.file,
|
deletions: item.deletions,
|
||||||
patch: item.patch,
|
status: item.status,
|
||||||
additions: item.additions,
|
}))
|
||||||
deletions: item.deletions,
|
|
||||||
status: item.status ?? "modified",
|
|
||||||
} satisfies DiffFile,
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
)
|
|
||||||
|
|
||||||
function filetype(input?: string) {
|
function filetype(input?: string) {
|
||||||
if (!input) return "none"
|
if (!input) return "none"
|
||||||
|
|
@ -82,14 +76,13 @@ function storedView(value: unknown): DiffView | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffSourceLabel(mode: DiffMode) {
|
function diffSourceLabel(mode: DiffMode) {
|
||||||
if (mode === "last-turn") return "last turn"
|
|
||||||
if (mode === "branch") return "main branch"
|
if (mode === "branch") return "main branch"
|
||||||
return "working tree"
|
return "working tree"
|
||||||
}
|
}
|
||||||
|
|
||||||
function DiffViewer(props: { api: TuiPluginApi }) {
|
function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const config = useConfig()
|
const config = useConfig()
|
||||||
const themeState = useTheme()
|
const themeState = useTheme()
|
||||||
const theme = () => props.api.theme.current
|
const theme = () => props.api.theme.current
|
||||||
|
|
@ -98,7 +91,6 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
| {
|
| {
|
||||||
mode?: DiffMode
|
mode?: DiffMode
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
messageID?: string
|
|
||||||
returnRoute?: TuiRouteCurrent
|
returnRoute?: TuiRouteCurrent
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
|
|
@ -108,22 +100,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
return {
|
return {
|
||||||
mode: mode(),
|
mode: mode(),
|
||||||
sessionID,
|
sessionID,
|
||||||
messageID: params()?.messageID,
|
|
||||||
directory: sessionID ? props.api.state.session.get(sessionID)?.directory : undefined,
|
directory: sessionID ? props.api.state.session.get(sessionID)?.directory : undefined,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const [diff] = createResource(diffInput, async (input) => {
|
const [diff] = createResource(diffInput, async (input) => {
|
||||||
if (input.mode === "last-turn") {
|
const result = await client.api.vcs.diff(
|
||||||
const sessionID = input.sessionID
|
|
||||||
if (!sessionID) return []
|
|
||||||
const result = await props.api.client.session.diff(
|
|
||||||
{ sessionID, messageID: input.messageID },
|
|
||||||
{ throwOnError: true },
|
|
||||||
)
|
|
||||||
return normalizeDiffs(result.data ?? [])
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await sdk.api.vcs.diff(
|
|
||||||
{
|
{
|
||||||
location: input.directory ? { directory: input.directory } : undefined,
|
location: input.directory ? { directory: input.directory } : undefined,
|
||||||
mode: input.mode,
|
mode: input.mode,
|
||||||
|
|
@ -704,26 +685,16 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
]
|
]
|
||||||
|
|
||||||
const switchDiffOptions = createMemo(() => {
|
const switchDiffOptions = createMemo(() => {
|
||||||
const vcs = props.api.state.vcs
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: "Working tree",
|
title: "Working tree",
|
||||||
value: "working" as const,
|
value: "working" as const,
|
||||||
description: "Show current git changes",
|
description: "Show current git changes",
|
||||||
},
|
},
|
||||||
...(vcs?.branch && vcs.default_branch && vcs.branch !== vcs.default_branch
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: "Main branch",
|
|
||||||
value: "branch" as const,
|
|
||||||
description: "Show changes compared to main branch",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
title: "Last turn",
|
title: "Main branch",
|
||||||
value: "last-turn" as const,
|
value: "branch" as const,
|
||||||
description: "Show changes from the last assistant turn",
|
description: "Show changes compared to main branch",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
@ -742,7 +713,6 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
props.api.route.navigate(ROUTE, {
|
props.api.route.navigate(ROUTE, {
|
||||||
mode: option.value,
|
mode: option.value,
|
||||||
sessionID: params()?.sessionID,
|
sessionID: params()?.sessionID,
|
||||||
messageID: params()?.messageID,
|
|
||||||
returnRoute: params()?.returnRoute,
|
returnRoute: params()?.returnRoute,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -1011,7 +981,7 @@ function DiffViewerHelpDialog() {
|
||||||
{
|
{
|
||||||
shortcut: useCommandShortcut("diff.switch_source"),
|
shortcut: useCommandShortcut("diff.switch_source"),
|
||||||
action: "Switch source",
|
action: "Switch source",
|
||||||
description: "Choose working tree, main branch, or last-turn changes",
|
description: "Choose working tree or main branch changes",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
shortcut: useCommandShortcut("diff.toggle_view"),
|
shortcut: useCommandShortcut("diff.toggle_view"),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@opencod
|
||||||
import type { Config } from "../config"
|
import type { Config } from "../config"
|
||||||
import type { useEvent } from "../context/event"
|
import type { useEvent } from "../context/event"
|
||||||
import type { useRoute } from "../context/route"
|
import type { useRoute } from "../context/route"
|
||||||
import type { useSDK } from "../context/sdk"
|
import type { useClient } from "../context/client"
|
||||||
import type { useData } from "../context/data"
|
import type { useData } from "../context/data"
|
||||||
import type { useProject } from "../context/project"
|
import type { useProject } from "../context/project"
|
||||||
import type { useTheme } from "../context/theme"
|
import type { useTheme } from "../context/theme"
|
||||||
|
|
@ -28,7 +28,7 @@ type Input = {
|
||||||
route: ReturnType<typeof useRoute>
|
route: ReturnType<typeof useRoute>
|
||||||
routes: PluginRoutes
|
routes: PluginRoutes
|
||||||
event: ReturnType<typeof useEvent>
|
event: ReturnType<typeof useEvent>
|
||||||
sdk: ReturnType<typeof useSDK>
|
client: ReturnType<typeof useClient>
|
||||||
project: ReturnType<typeof useProject>
|
project: ReturnType<typeof useProject>
|
||||||
data: ReturnType<typeof useData>
|
data: ReturnType<typeof useData>
|
||||||
theme: ReturnType<typeof useTheme>
|
theme: ReturnType<typeof useTheme>
|
||||||
|
|
@ -167,6 +167,15 @@ function appApi(version: string): TuiPluginApi["app"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unsupportedClient = new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get() {
|
||||||
|
throw new Error("The legacy plugin client is not supported in V2")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as TuiPluginApi["client"]
|
||||||
|
|
||||||
export function createTuiApiAdapters(input: Input): Omit<TuiPluginApi, "lifecycle"> {
|
export function createTuiApiAdapters(input: Input): Omit<TuiPluginApi, "lifecycle"> {
|
||||||
return {
|
return {
|
||||||
app: appApi(input.version),
|
app: appApi(input.version),
|
||||||
|
|
@ -292,9 +301,7 @@ export function createTuiApiAdapters(input: Input): Omit<TuiPluginApi, "lifecycl
|
||||||
ready: true,
|
ready: true,
|
||||||
},
|
},
|
||||||
state: stateApi(input.project, input.data),
|
state: stateApi(input.project, input.data),
|
||||||
get client() {
|
client: unsupportedClient,
|
||||||
return input.sdk.client
|
|
||||||
},
|
|
||||||
event: input.event,
|
event: input.event,
|
||||||
renderer: input.renderer,
|
renderer: input.renderer,
|
||||||
slots: {
|
slots: {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { createStore } from "solid-js/store"
|
||||||
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
||||||
import { useData } from "../../../context/data"
|
import { useData } from "../../../context/data"
|
||||||
import { useLocation } from "../../../context/location"
|
import { useLocation } from "../../../context/location"
|
||||||
import { useSDK } from "../../../context/sdk"
|
import { useClient } from "../../../context/client"
|
||||||
import { useTheme, selectedForeground } from "../../../context/theme"
|
import { useTheme, selectedForeground } from "../../../context/theme"
|
||||||
import { useBindings, useCommandShortcut } from "../../../keymap"
|
import { useBindings, useCommandShortcut } from "../../../keymap"
|
||||||
import { useComposerTab } from "./index"
|
import { useComposerTab } from "./index"
|
||||||
|
|
@ -11,7 +11,7 @@ import { useComposerTab } from "./index"
|
||||||
export function ShellTab(props: { sessionID: string }) {
|
export function ShellTab(props: { sessionID: string }) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const fg = selectedForeground(theme)
|
const fg = selectedForeground(theme)
|
||||||
const composer = useComposerTab()
|
const composer = useComposerTab()
|
||||||
|
|
@ -84,7 +84,7 @@ export function ShellTab(props: { sessionID: string }) {
|
||||||
const entry = selectedEntry()
|
const entry = selectedEntry()
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
const ref = location()
|
const ref = location()
|
||||||
void sdk.api.shell.remove({
|
void client.api.shell.remove({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
location: ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined,
|
location: ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { createStore } from "solid-js/store"
|
||||||
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
import { TextAttributes, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
||||||
import { useRoute, useRouteData } from "../../../context/route"
|
import { useRoute, useRouteData } from "../../../context/route"
|
||||||
import { useData } from "../../../context/data"
|
import { useData } from "../../../context/data"
|
||||||
import { useSDK } from "../../../context/sdk"
|
import { useClient } from "../../../context/client"
|
||||||
import { useTheme, selectedForeground } from "../../../context/theme"
|
import { useTheme, selectedForeground } from "../../../context/theme"
|
||||||
import { Locale } from "../../../util/locale"
|
import { Locale } from "../../../util/locale"
|
||||||
import { useBindings, useCommandShortcut } from "../../../keymap"
|
import { useBindings, useCommandShortcut } from "../../../keymap"
|
||||||
|
|
@ -20,7 +20,7 @@ interface SubagentEntry {
|
||||||
export function SubagentsTab(props: { sessionID: string }) {
|
export function SubagentsTab(props: { sessionID: string }) {
|
||||||
const route = useRouteData("session")
|
const route = useRouteData("session")
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const fg = selectedForeground(theme)
|
const fg = selectedForeground(theme)
|
||||||
const navigate = useRoute().navigate
|
const navigate = useRoute().navigate
|
||||||
|
|
@ -183,7 +183,7 @@ export function SubagentsTab(props: { sessionID: string }) {
|
||||||
run() {
|
run() {
|
||||||
const entry = selectedEntry()
|
const entry = selectedEntry()
|
||||||
if (!entry || entry.status !== "running") return
|
if (!entry || entry.status !== "running") return
|
||||||
void sdk.api.session.interrupt({ sessionID: entry.sessionID })
|
void client.api.session.interrupt({ sessionID: entry.sessionID })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { createMemo, createSignal, onMount, Show } from "solid-js"
|
||||||
import { unwrap } from "solid-js/store"
|
import { unwrap } from "solid-js/store"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { useRoute } from "../../context/route"
|
import { useRoute } from "../../context/route"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { Spinner } from "../../component/spinner"
|
import { Spinner } from "../../component/spinner"
|
||||||
import { DialogSelect, type DialogSelectOption } from "../../ui/dialog-select"
|
import { DialogSelect, type DialogSelectOption } from "../../ui/dialog-select"
|
||||||
import { useDialog } from "../../ui/dialog"
|
import { useDialog } from "../../ui/dialog"
|
||||||
|
|
@ -13,14 +13,14 @@ import { Locale } from "../../util/locale"
|
||||||
export function DialogFork(props: { sessionID: string; messageID?: string; onMove?: (messageID?: string) => void }) {
|
export function DialogFork(props: { sessionID: string; messageID?: string; onMove?: (messageID?: string) => void }) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const [pending, setPending] = createSignal(false)
|
const [pending, setPending] = createSignal(false)
|
||||||
|
|
||||||
const fork = async (messageID?: string) => {
|
const fork = async (messageID?: string) => {
|
||||||
setPending(true)
|
setPending(true)
|
||||||
const result = await sdk.api.session.fork({ sessionID: props.sessionID, messageID }).catch((error) => {
|
const result = await client.api.session.fork({ sessionID: props.sessionID, messageID }).catch((error) => {
|
||||||
toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
toast.show({ message: errorMessage(error), variant: "error", duration: 5000 })
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useData } from "../../context/data"
|
||||||
import { DialogSelect } from "../../ui/dialog-select"
|
import { DialogSelect } from "../../ui/dialog-select"
|
||||||
import { useClipboard } from "../../context/clipboard"
|
import { useClipboard } from "../../context/clipboard"
|
||||||
import { useToast } from "../../ui/toast"
|
import { useToast } from "../../ui/toast"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
import { DialogFork } from "./dialog-fork"
|
import { DialogFork } from "./dialog-fork"
|
||||||
import type { PromptInfo } from "../../prompt/history"
|
import type { PromptInfo } from "../../prompt/history"
|
||||||
|
|
@ -16,7 +16,7 @@ export function DialogMessage(props: {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const clipboard = useClipboard()
|
const clipboard = useClipboard()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const message = createMemo(() => data.session.message.get(props.sessionID, props.messageID))
|
const message = createMemo(() => data.session.message.get(props.sessionID, props.messageID))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -45,7 +45,7 @@ export function DialogMessage(props: {
|
||||||
pasted: [],
|
pasted: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
void sdk.api.session.revert
|
void client.api.session.revert
|
||||||
.stage({ sessionID: props.sessionID, messageID: props.messageID })
|
.stage({ sessionID: props.sessionID, messageID: props.messageID })
|
||||||
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { selectedForeground, useTheme } from "../../context/theme"
|
||||||
import { tint } from "../../theme/color"
|
import { tint } from "../../theme/color"
|
||||||
import type { FormField, FormValue } from "@opencode-ai/client"
|
import type { FormField, FormValue } from "@opencode-ai/client"
|
||||||
import type { FormWithLocation } from "../../context/data"
|
import type { FormWithLocation } from "../../context/data"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useClipboard } from "../../context/clipboard"
|
import { useClipboard } from "../../context/clipboard"
|
||||||
import { SplitBorder } from "../../ui/border"
|
import { SplitBorder } from "../../ui/border"
|
||||||
import { useToast } from "../../ui/toast"
|
import { useToast } from "../../ui/toast"
|
||||||
|
|
@ -146,7 +146,7 @@ function requestOptions(form: FormWithLocation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormPrompt(props: { form: FormWithLocation }) {
|
export function FormPrompt(props: { form: FormWithLocation }) {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
|
|
@ -297,7 +297,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function replySingle(field: Field, value: FormValue) {
|
function replySingle(field: Field, value: FormValue) {
|
||||||
sdk.api.form
|
client.api.form
|
||||||
.reply(
|
.reply(
|
||||||
{
|
{
|
||||||
sessionID: props.form.sessionID,
|
sessionID: props.form.sessionID,
|
||||||
|
|
@ -479,7 +479,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id }, requestOptions(props.form))
|
void client.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id }, requestOptions(props.form))
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExternal() {
|
function openExternal() {
|
||||||
|
|
@ -531,7 +531,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
||||||
setStore("error", validateValue(invalid, store.answers[invalid.key]) ?? "Invalid answer")
|
setStore("error", validateValue(invalid, store.answers[invalid.key]) ?? "Invalid answer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sdk.api.form
|
client.api.form
|
||||||
.reply(
|
.reply(
|
||||||
{
|
{
|
||||||
sessionID: props.form.sessionID,
|
sessionID: props.form.sessionID,
|
||||||
|
|
@ -582,7 +582,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
|
||||||
group: "Form",
|
group: "Form",
|
||||||
cmd: () => {
|
cmd: () => {
|
||||||
if (textual()) {
|
if (textual()) {
|
||||||
void sdk.api.form.cancel(
|
void client.api.form.cancel(
|
||||||
{ sessionID: props.form.sessionID, formID: props.form.id },
|
{ sessionID: props.form.sessionID, formID: props.form.id },
|
||||||
requestOptions(props.form),
|
requestOptions(props.form),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import { Locale } from "../../util/locale"
|
||||||
import { FilePath } from "../../ui/file-path"
|
import { FilePath } from "../../ui/file-path"
|
||||||
import { webSearchProviderLabel } from "../../util/tool-display"
|
import { webSearchProviderLabel } from "../../util/tool-display"
|
||||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { useEditorContext } from "../../context/editor"
|
import { useEditorContext } from "../../context/editor"
|
||||||
import { openEditor } from "../../editor"
|
import { openEditor } from "../../editor"
|
||||||
import { useDialog } from "../../ui/dialog"
|
import { useDialog } from "../../ui/dialog"
|
||||||
|
|
@ -215,7 +215,7 @@ export function Session() {
|
||||||
|
|
||||||
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const editor = useEditorContext()
|
const editor = useEditorContext()
|
||||||
const rows = createSessionRows(() => route.sessionID)
|
const rows = createSessionRows(() => route.sessionID)
|
||||||
|
|
||||||
|
|
@ -389,7 +389,7 @@ export function Session() {
|
||||||
aliases: ["summarize"],
|
aliases: ["summarize"],
|
||||||
},
|
},
|
||||||
run: () => {
|
run: () => {
|
||||||
void sdk.api.session.compact({ sessionID: route.sessionID })
|
void client.api.session.compact({ sessionID: route.sessionID })
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -417,7 +417,7 @@ export function Session() {
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void sdk.api.session.revert
|
void client.api.session.revert
|
||||||
.stage({ sessionID: route.sessionID, messageID: message.id })
|
.stage({ sessionID: route.sessionID, messageID: message.id })
|
||||||
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
|
||||||
prompt?.set({
|
prompt?.set({
|
||||||
|
|
@ -445,7 +445,7 @@ export function Session() {
|
||||||
slash: { name: "redo" },
|
slash: { name: "redo" },
|
||||||
run: () => {
|
run: () => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const error = await sdk.api.session.revert.clear({ sessionID: route.sessionID }).then(
|
const error = await client.api.session.revert.clear({ sessionID: route.sessionID }).then(
|
||||||
() => undefined,
|
() => undefined,
|
||||||
(error) => error,
|
(error) => error,
|
||||||
)
|
)
|
||||||
|
|
@ -722,11 +722,11 @@ export function Session() {
|
||||||
: await (async () => {
|
: await (async () => {
|
||||||
if (options.debug) {
|
if (options.debug) {
|
||||||
const events: { readonly created: number }[] = []
|
const events: { readonly created: number }[] = []
|
||||||
for await (const event of sdk.api.session.log({ sessionID: sessionData.id, follow: false })) {
|
for await (const event of client.api.session.log({ sessionID: sessionData.id, follow: false })) {
|
||||||
if (event.type !== "log.synced") events.push(event)
|
if (event.type !== "log.synced") events.push(event)
|
||||||
}
|
}
|
||||||
// Durable events stay in aggregate order even when their wall-clock timestamps differ.
|
// Durable events stay in aggregate order even when their wall-clock timestamps differ.
|
||||||
sdk.connection.internal.history().forEach((event) => {
|
client.connection.internal.history().forEach((event) => {
|
||||||
const index = events.findIndex((item) => item.created > event.created)
|
const index = events.findIndex((item) => item.created > event.created)
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
events.push(event)
|
events.push(event)
|
||||||
|
|
@ -740,7 +740,7 @@ export function Session() {
|
||||||
const messages: unknown[] = []
|
const messages: unknown[] = []
|
||||||
let cursor: string | undefined
|
let cursor: string | undefined
|
||||||
do {
|
do {
|
||||||
const page = await sdk.api.message.list(
|
const page = await client.api.message.list(
|
||||||
cursor
|
cursor
|
||||||
? { sessionID: sessionData.id, limit: 200, cursor }
|
? { sessionID: sessionData.id, limit: 200, cursor }
|
||||||
: { sessionID: sessionData.id, limit: 200, order: "asc" },
|
: { sessionID: sessionData.id, limit: 200, order: "asc" },
|
||||||
|
|
@ -776,7 +776,7 @@ export function Session() {
|
||||||
category: "Session",
|
category: "Session",
|
||||||
hidden: true,
|
hidden: true,
|
||||||
run: () => {
|
run: () => {
|
||||||
void sdk.api.session.background({ sessionID: route.sessionID })
|
void client.api.session.background({ sessionID: route.sessionID })
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1380,7 +1380,7 @@ function RevertMessage(props: {
|
||||||
const ctx = use()
|
const ctx = use()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const route = useRouteData("session")
|
const route = useRouteData("session")
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const [hover, setHover] = createSignal(false)
|
const [hover, setHover] = createSignal(false)
|
||||||
|
|
@ -1392,7 +1392,7 @@ function RevertMessage(props: {
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const error = await sdk.api.session.revert.clear({ sessionID: route.sessionID }).then(
|
const error = await client.api.session.revert.clear({ sessionID: route.sessionID }).then(
|
||||||
() => undefined,
|
() => undefined,
|
||||||
(error) => error,
|
(error) => error,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Portal, useRenderer, useTerminalDimensions, type JSX } from "@opentui/s
|
||||||
import type { TextareaRenderable } from "@opentui/core"
|
import type { TextareaRenderable } from "@opentui/core"
|
||||||
import { useTheme, selectedForeground } from "../../context/theme"
|
import { useTheme, selectedForeground } from "../../context/theme"
|
||||||
import type { PermissionV2Request } from "@opencode-ai/client"
|
import type { PermissionV2Request } from "@opencode-ai/client"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useClient } from "../../context/client"
|
||||||
import { SplitBorder } from "../../ui/border"
|
import { SplitBorder } from "../../ui/border"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { filetype } from "../../util/filetype"
|
import { filetype } from "../../util/filetype"
|
||||||
|
|
@ -135,7 +135,7 @@ function TextBody(props: { title: string; description?: string; icon?: string })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PermissionPrompt(props: { request: PermissionV2Request; directory?: string }) {
|
export function PermissionPrompt(props: { request: PermissionV2Request; directory?: string }) {
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
stage: "permission" as PermissionStage,
|
stage: "permission" as PermissionStage,
|
||||||
|
|
@ -187,7 +187,7 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
setStore("stage", "permission")
|
setStore("stage", "permission")
|
||||||
if (option === "cancel") return
|
if (option === "cancel") return
|
||||||
void sdk.api.permission.reply({
|
void client.api.permission.reply({
|
||||||
sessionID: props.request.sessionID,
|
sessionID: props.request.sessionID,
|
||||||
reply: "always",
|
reply: "always",
|
||||||
requestID: props.request.id,
|
requestID: props.request.id,
|
||||||
|
|
@ -198,7 +198,7 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
|
||||||
<Match when={store.stage === "reject"}>
|
<Match when={store.stage === "reject"}>
|
||||||
<RejectPrompt
|
<RejectPrompt
|
||||||
onConfirm={(message) => {
|
onConfirm={(message) => {
|
||||||
void sdk.api.permission.reply({
|
void client.api.permission.reply({
|
||||||
sessionID: props.request.sessionID,
|
sessionID: props.request.sessionID,
|
||||||
reply: "reject",
|
reply: "reject",
|
||||||
requestID: props.request.id,
|
requestID: props.request.id,
|
||||||
|
|
@ -444,14 +444,14 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
|
||||||
setStore("stage", "reject")
|
setStore("stage", "reject")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void sdk.api.permission.reply({
|
void client.api.permission.reply({
|
||||||
sessionID: props.request.sessionID,
|
sessionID: props.request.sessionID,
|
||||||
reply: "reject",
|
reply: "reject",
|
||||||
requestID: props.request.id,
|
requestID: props.request.id,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
void sdk.api.permission.reply({
|
void client.api.permission.reply({
|
||||||
sessionID: props.request.sessionID,
|
sessionID: props.request.sessionID,
|
||||||
reply: "once",
|
reply: "once",
|
||||||
requestID: props.request.id,
|
requestID: props.request.id,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { createTestRenderer } from "@opentui/core/testing"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Global } from "@opencode-ai/core/global"
|
||||||
import { createEventStream, createFetch, directory, json } from "./fixture/tui-sdk"
|
import { createEventStream, createFetch, directory, json } from "./fixture/tui-client"
|
||||||
|
|
||||||
test("SIGHUP clears title and disposes scoped resources once", async () => {
|
test("SIGHUP clears title and disposes scoped resources once", async () => {
|
||||||
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
|
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import Notifications from "../../../../src/feature-plugins/system/notifications"
|
import Notifications from "../../../../src/feature-plugins/system/notifications"
|
||||||
import type { OpenCodeEvent, PermissionAsked, QuestionAsked } from "@opencode-ai/client"
|
import type { OpenCodeEvent, PermissionAsked, QuestionAsked } from "@opencode-ai/client"
|
||||||
import type { Session } from "@opencode-ai/sdk/v2"
|
import type { TuiAttentionNotifyInput, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||||
import type { TuiAttentionNotifyInput } from "@opencode-ai/plugin/tui"
|
|
||||||
import { createTuiPluginApi } from "../../../fixture/tui-plugin"
|
import { createTuiPluginApi } from "../../../fixture/tui-plugin"
|
||||||
|
|
||||||
|
type Session = NonNullable<ReturnType<TuiPluginApi["state"]["session"]["get"]>>
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
const notifications: TuiAttentionNotifyInput[] = []
|
const notifications: TuiAttentionNotifyInput[] = []
|
||||||
const handlers = new Map<OpenCodeEvent["type"], ((event: OpenCodeEvent) => void)[]>()
|
const handlers = new Map<OpenCodeEvent["type"], ((event: OpenCodeEvent) => void)[]>()
|
||||||
const session = (id: string, title: string, parentID?: string): Session => ({
|
const session = (
|
||||||
|
id: string,
|
||||||
|
title: string,
|
||||||
|
parentID?: string,
|
||||||
|
): Session => ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
slug: id,
|
slug: id,
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { onMount } from "solid-js"
|
import { onMount } from "solid-js"
|
||||||
import { ProjectProvider } from "../../../src/context/project"
|
import { ProjectProvider } from "../../../src/context/project"
|
||||||
import { SDKProvider, useSDK } from "../../../src/context/sdk"
|
import { ClientProvider, useClient } from "../../../src/context/client"
|
||||||
import { DataProvider, useData } from "../../../src/context/data"
|
import { DataProvider, useData } from "../../../src/context/data"
|
||||||
import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows"
|
import { createSessionRows, type SessionRow } from "../../../src/routes/session/rows"
|
||||||
import { createApi, createClient, createEventStream, createFetch, directory, json } from "../../fixture/tui-sdk"
|
import { createApi, createEventStream, createFetch, directory, json } from "../../fixture/tui-client"
|
||||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||||
|
|
||||||
const formFields = [{ key: "authorization", type: "external", url: "https://example.com" }] satisfies [
|
const formFields = [{ key: "authorization", type: "external", url: "https://example.com" }] satisfies [
|
||||||
|
|
@ -90,13 +90,13 @@ test("refreshes resources into reactive getters", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -148,13 +148,13 @@ test("applies absolute usage events to session info", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -233,13 +233,13 @@ test("truncates committed revert messages without changing lifetime usage", asyn
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -371,13 +371,13 @@ test("updates session location when moved", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -430,13 +430,13 @@ test("restores running manual compaction before applying live deltas", async ()
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -514,23 +514,23 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
||||||
})
|
})
|
||||||
}, events)
|
}, events)
|
||||||
let data!: ReturnType<typeof useData>
|
let data!: ReturnType<typeof useData>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
data = useData()
|
data = useData()
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -539,15 +539,15 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
||||||
await wait(() => data.session.status("session-stale") === "running")
|
await wait(() => data.session.status("session-stale") === "running")
|
||||||
await data.session.message.refresh("session-stale")
|
await data.session.message.refresh("session-stale")
|
||||||
expect(data.session.message.get("session-stale", "message-stale")?.id).toBe("message-stale")
|
expect(data.session.message.get("session-stale", "message-stale")?.id).toBe("message-stale")
|
||||||
expect(sdk.connection.status()).toBe("connected")
|
expect(client.connection.status()).toBe("connected")
|
||||||
expect(sdk.connection.attempt()).toBe(0)
|
expect(client.connection.attempt()).toBe(0)
|
||||||
|
|
||||||
events.disconnect()
|
events.disconnect()
|
||||||
await wait(() => sdk.connection.status() === "reconnecting")
|
await wait(() => client.connection.status() === "reconnecting")
|
||||||
expect(sdk.connection.attempt()).toBe(1)
|
expect(client.connection.attempt()).toBe(1)
|
||||||
expect(sdk.connection.error()).toBe("Event stream disconnected")
|
expect(client.connection.error()).toBe("Event stream disconnected")
|
||||||
|
|
||||||
await wait(() => requests.active === 2 && sdk.connection.status() === "connected", 4000)
|
await wait(() => requests.active === 2 && client.connection.status() === "connected", 4000)
|
||||||
resolveActive(json({ data: { "session-new": { type: "running" } } }))
|
resolveActive(json({ data: { "session-new": { type: "running" } } }))
|
||||||
|
|
||||||
await wait(() => data.location.model.list()?.[0]?.id === "model-2", 4000)
|
await wait(() => data.location.model.list()?.[0]?.id === "model-2", 4000)
|
||||||
|
|
@ -565,9 +565,9 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
||||||
await wait(() => data.session.status("session-new") === "running")
|
await wait(() => data.session.status("session-new") === "running")
|
||||||
expect(requests.event).toBe(2)
|
expect(requests.event).toBe(2)
|
||||||
expect(requests.message).toBe(2)
|
expect(requests.message).toBe(2)
|
||||||
expect(sdk.connection.status()).toBe("connected")
|
expect(client.connection.status()).toBe("connected")
|
||||||
expect(sdk.connection.attempt()).toBe(0)
|
expect(client.connection.attempt()).toBe(0)
|
||||||
expect(sdk.connection.error()).toBeUndefined()
|
expect(client.connection.error()).toBeUndefined()
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
@ -588,13 +588,13 @@ test("completes exploration when a queued prompt is promoted", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -668,13 +668,13 @@ test("removes committed revert messages from local state", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -733,34 +733,34 @@ test("distinguishes initial connection from reconnection", async () => {
|
||||||
const calls = createFetch((url) => {
|
const calls = createFetch((url) => {
|
||||||
if (url.pathname === "/api/event") return eventResponse()
|
if (url.pathname === "/api/event") return eventResponse()
|
||||||
})
|
})
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => stream !== undefined)
|
await wait(() => stream !== undefined)
|
||||||
expect(sdk.connection.status()).toBe("connecting")
|
expect(client.connection.status()).toBe("connecting")
|
||||||
|
|
||||||
connect()
|
connect()
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
|
|
||||||
disconnect()
|
disconnect()
|
||||||
await wait(() => sdk.connection.status() === "reconnecting")
|
await wait(() => client.connection.status() === "reconnecting")
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
@ -811,13 +811,13 @@ test("tracks session status from active sessions and execution events", async ()
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1180,13 +1180,13 @@ test("restores queued compaction from durable pending input", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1295,13 +1295,13 @@ test("refreshes integrations after integration updates", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1351,13 +1351,13 @@ test("refreshes MCP resources after catalog updates", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1400,13 +1400,13 @@ test("refreshes effective catalog data after catalog updates", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<box />
|
<box />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1448,13 +1448,13 @@ test("refreshes agents after agent updates", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1492,13 +1492,13 @@ test("refreshes references after updates", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1547,13 +1547,13 @@ test("keeps shell state scoped to location", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1593,28 +1593,28 @@ test("adds and dismisses permission requests from live events", async () => {
|
||||||
const events = createEventStream()
|
const events = createEventStream()
|
||||||
const calls = createFetch(undefined, events)
|
const calls = createFetch(undefined, events)
|
||||||
let data!: ReturnType<typeof useData>
|
let data!: ReturnType<typeof useData>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
data = useData()
|
data = useData()
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
emitEvent(events, {
|
emitEvent(events, {
|
||||||
id: "evt_permission_asked_1",
|
id: "evt_permission_asked_1",
|
||||||
created: 0,
|
created: 0,
|
||||||
|
|
@ -1681,13 +1681,13 @@ test("reconciles all pending permission requests when the event stream reconnect
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(fetch.fetch)} api={createApi(fetch.fetch)}>
|
<ClientProvider api={createApi(fetch.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -1715,28 +1715,28 @@ test("adds, dismisses, and refreshes form requests", async () => {
|
||||||
})
|
})
|
||||||
}, events)
|
}, events)
|
||||||
let data!: ReturnType<typeof useData>
|
let data!: ReturnType<typeof useData>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
data = useData()
|
data = useData()
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
emitEvent(events, {
|
emitEvent(events, {
|
||||||
id: "evt_form_created_1",
|
id: "evt_form_created_1",
|
||||||
created: 0,
|
created: 0,
|
||||||
|
|
@ -1785,28 +1785,28 @@ test("tracks global forms by location", async () => {
|
||||||
const calls = createFetch(undefined, events)
|
const calls = createFetch(undefined, events)
|
||||||
const other = { directory: "/tmp/opencode-other", workspaceID: "wrk_other" }
|
const other = { directory: "/tmp/opencode-other", workspaceID: "wrk_other" }
|
||||||
let data!: ReturnType<typeof useData>
|
let data!: ReturnType<typeof useData>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
data = useData()
|
data = useData()
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
events.emit({
|
events.emit({
|
||||||
id: "evt_form_created_global_other",
|
id: "evt_form_created_global_other",
|
||||||
created: 0,
|
created: 0,
|
||||||
|
|
@ -1871,28 +1871,28 @@ test("refreshes global forms for the requested location", async () => {
|
||||||
})
|
})
|
||||||
}, events)
|
}, events)
|
||||||
let data!: ReturnType<typeof useData>
|
let data!: ReturnType<typeof useData>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
|
|
||||||
function Probe() {
|
function Probe() {
|
||||||
data = useData()
|
data = useData()
|
||||||
sdk = useSDK()
|
client = useClient()
|
||||||
return <box />
|
return <box />
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected" && requests.length > 0)
|
await wait(() => client.connection.status() === "connected" && requests.length > 0)
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
|
|
||||||
await data.session.form.refresh("global", { directory })
|
await data.session.form.refresh("global", { directory })
|
||||||
|
|
@ -1957,13 +1957,13 @@ test("refreshes global forms once per loaded location after reconnect", async ()
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2025,13 +2025,13 @@ test("reconciles all pending form requests when the event stream reconnects", as
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(fetch.fetch)} api={createApi(fetch.fetch)}>
|
<ClientProvider api={createApi(fetch.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2078,13 +2078,13 @@ test("settles pending tools when a live failure arrives", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2225,13 +2225,13 @@ test("renders admitted prompts immediately and tracks them until promoted", asyn
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2318,13 +2318,13 @@ test("projects live instruction updates with their message ID", async () => {
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2387,13 +2387,13 @@ async function mountData(parents: Record<string, string>, costs: Record<string,
|
||||||
}
|
}
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
<ClientProvider api={createApi(calls.fetch)}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<DataProvider>
|
<DataProvider>
|
||||||
<Probe />
|
<Probe />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
await mounted
|
await mounted
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,16 @@ import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
|
||||||
import { DiffRenderable, type Renderable, ScrollBoxRenderable } from "@opentui/core"
|
import { DiffRenderable, type Renderable, ScrollBoxRenderable } from "@opentui/core"
|
||||||
import { testRender, useRenderer } from "@opentui/solid"
|
import { testRender, useRenderer } from "@opentui/solid"
|
||||||
import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
import type { TuiPluginApi, TuiPluginMeta, TuiRouteCurrent, TuiRouteDefinition } from "@opencode-ai/plugin/tui"
|
||||||
import type { Session } from "@opencode-ai/sdk/v2"
|
|
||||||
import { ThemeProvider } from "../../../src/context/theme"
|
import { ThemeProvider } from "../../../src/context/theme"
|
||||||
import { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { SDKProvider } from "../../../src/context/sdk"
|
import { ClientProvider } from "../../../src/context/client"
|
||||||
import { TuiKeybind } from "../../../src/config/keybind"
|
import { TuiKeybind } from "../../../src/config/keybind"
|
||||||
import { OpencodeKeymapProvider } from "../../../src/keymap"
|
import { OpencodeKeymapProvider } from "../../../src/keymap"
|
||||||
import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer"
|
import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer"
|
||||||
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
import { createTuiPluginApi } from "../../fixture/tui-plugin"
|
||||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||||
import { createApi, createClient, createEventStream, createFetch, json } from "../../fixture/tui-sdk"
|
import { createApi, createEventStream, createFetch, json } from "../../fixture/tui-client"
|
||||||
|
|
||||||
test("closing the diff viewer returns to the route it opened from", async () => {
|
test("closing the diff viewer returns to the route it opened from", async () => {
|
||||||
const viewer = await renderDiffViewer([])
|
const viewer = await renderDiffViewer([])
|
||||||
|
|
@ -111,7 +110,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
||||||
let current = initialRoute ?? startRoute
|
let current = initialRoute ?? startRoute
|
||||||
let renderDiff: TuiRouteDefinition["render"] | undefined
|
let renderDiff: TuiRouteDefinition["render"] | undefined
|
||||||
let vcsDiffInput: unknown
|
let vcsDiffInput: unknown
|
||||||
let sessionDiffInput: unknown
|
|
||||||
const config = createTuiResolvedConfig()
|
const config = createTuiResolvedConfig()
|
||||||
const transport = createFetch((url) => {
|
const transport = createFetch((url) => {
|
||||||
if (url.pathname !== "/api/vcs/diff") return
|
if (url.pathname !== "/api/vcs/diff") return
|
||||||
|
|
@ -135,14 +133,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
||||||
}
|
}
|
||||||
const base = createTuiPluginApi({
|
const base = createTuiPluginApi({
|
||||||
keymap,
|
keymap,
|
||||||
client: {
|
|
||||||
session: {
|
|
||||||
diff: async (input: unknown) => {
|
|
||||||
sessionDiffInput = input
|
|
||||||
return { data: [] }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as unknown as TuiPluginApi["client"],
|
|
||||||
state: {
|
state: {
|
||||||
session: {
|
session: {
|
||||||
get: () => session,
|
get: () => session,
|
||||||
|
|
@ -170,7 +160,7 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TestTuiContexts>
|
<TestTuiContexts>
|
||||||
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
<ClientProvider api={createApi(transport.fetch)}>
|
||||||
<OpencodeKeymapProvider keymap={keymap}>
|
<OpencodeKeymapProvider keymap={keymap}>
|
||||||
<ConfigProvider config={config}>
|
<ConfigProvider config={config}>
|
||||||
<ThemeProvider mode="dark">
|
<ThemeProvider mode="dark">
|
||||||
|
|
@ -178,7 +168,7 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +180,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
|
||||||
commands,
|
commands,
|
||||||
current: () => current,
|
current: () => current,
|
||||||
vcsDiffInput: () => vcsDiffInput,
|
vcsDiffInput: () => vcsDiffInput,
|
||||||
sessionDiffInput: () => sessionDiffInput,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +206,7 @@ const session = {
|
||||||
created: 0,
|
created: 0,
|
||||||
updated: 0,
|
updated: 0,
|
||||||
},
|
},
|
||||||
} satisfies Session
|
} satisfies NonNullable<ReturnType<TuiPluginApi["state"]["session"]["get"]>>
|
||||||
|
|
||||||
test("branch diff source requests branch VCS diff", async () => {
|
test("branch diff source requests branch VCS diff", async () => {
|
||||||
const viewer = await renderDiffViewer([], 20, {
|
const viewer = await renderDiffViewer([], 20, {
|
||||||
|
|
@ -234,24 +223,6 @@ test("branch diff source requests branch VCS diff", async () => {
|
||||||
mode: "branch",
|
mode: "branch",
|
||||||
context: "12",
|
context: "12",
|
||||||
})
|
})
|
||||||
expect(viewer.sessionDiffInput()).toBeUndefined()
|
|
||||||
} finally {
|
|
||||||
viewer.app.renderer.destroy()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
test("last-turn diff source requests session diff", async () => {
|
|
||||||
const viewer = await renderDiffViewer([], 20, {
|
|
||||||
name: "diff",
|
|
||||||
params: { mode: "last-turn", sessionID: "session-1", messageID: "message-1", returnRoute: startRoute },
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
expect(viewer.current()).toEqual({
|
|
||||||
name: "diff",
|
|
||||||
params: { mode: "last-turn", sessionID: "session-1", messageID: "message-1", returnRoute: startRoute },
|
|
||||||
})
|
|
||||||
expect(viewer.sessionDiffInput()).toEqual({ sessionID: "session-1", messageID: "message-1" })
|
|
||||||
expect(viewer.vcsDiffInput()).toBeUndefined()
|
|
||||||
} finally {
|
} finally {
|
||||||
viewer.app.renderer.destroy()
|
viewer.app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import path from "node:path"
|
||||||
import { onCleanup } from "solid-js"
|
import { onCleanup } from "solid-js"
|
||||||
import { ClipboardProvider } from "../../../src/context/clipboard"
|
import { ClipboardProvider } from "../../../src/context/clipboard"
|
||||||
import type { FormWithLocation } from "../../../src/context/data"
|
import type { FormWithLocation } from "../../../src/context/data"
|
||||||
import { SDKProvider } from "../../../src/context/sdk"
|
import { ClientProvider } from "../../../src/context/client"
|
||||||
import { ThemeProvider } from "../../../src/context/theme"
|
import { ThemeProvider } from "../../../src/context/theme"
|
||||||
import { ConfigProvider } from "../../../src/config"
|
import { ConfigProvider } from "../../../src/config"
|
||||||
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap"
|
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap"
|
||||||
|
|
@ -15,7 +15,7 @@ import { ToastProvider } from "../../../src/ui/toast"
|
||||||
import { tmpdir } from "../../fixture/fixture"
|
import { tmpdir } from "../../fixture/fixture"
|
||||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||||
import { createApi, createClient, createEventStream, createFetch } from "../../fixture/tui-sdk"
|
import { createApi, createEventStream, createFetch } from "../../fixture/tui-client"
|
||||||
|
|
||||||
async function mountForm(root: string, width = 80) {
|
async function mountForm(root: string, width = 80) {
|
||||||
const state = path.join(root, "state")
|
const state = path.join(root, "state")
|
||||||
|
|
@ -75,13 +75,13 @@ async function mountForm(root: string, width = 80) {
|
||||||
>
|
>
|
||||||
<OpencodeKeymapProvider keymap={keymap}>
|
<OpencodeKeymapProvider keymap={keymap}>
|
||||||
<ConfigProvider config={config}>
|
<ConfigProvider config={config}>
|
||||||
<SDKProvider client={createClient(transport.fetch)} api={createApi(transport.fetch)}>
|
<ClientProvider api={createApi(transport.fetch)}>
|
||||||
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({}) }}>
|
<ThemeProvider mode="dark" source={{ discover: () => Promise.resolve({}) }}>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<FormPrompt form={form} />
|
<FormPrompt form={form} />
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</OpencodeKeymapProvider>
|
</OpencodeKeymapProvider>
|
||||||
</ClipboardProvider>
|
</ClipboardProvider>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test"
|
||||||
// Before the fix, two concurrent `submit()` calls (e.g. a double-pressed
|
// Before the fix, two concurrent `submit()` calls (e.g. a double-pressed
|
||||||
// Enter, or the input's native onSubmit racing another dispatch) each
|
// Enter, or the input's native onSubmit racing another dispatch) each
|
||||||
// passed the `if (!store.prompt.text) return false` guard, each
|
// passed the `if (!store.prompt.text) return false` guard, each
|
||||||
// `await sdk.client.session.create(...)`, and each only captured
|
// `await client.api.session.create(...)`, and each only captured
|
||||||
// `inputText = store.prompt.text` AFTER that await. The first invocation
|
// `inputText = store.prompt.text` AFTER that await. The first invocation
|
||||||
// finished, sent the prompt, and cleared the store; the second invocation,
|
// finished, sent the prompt, and cleared the store; the second invocation,
|
||||||
// now past its await, read the cleared store and sent an empty prompt to a
|
// now past its await, read the cleared store and sent an empty prompt to a
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
|
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
|
||||||
import { testRender } from "@opentui/solid"
|
import { testRender } from "@opentui/solid"
|
||||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2"
|
|
||||||
import { onMount } from "solid-js"
|
import { onMount } from "solid-js"
|
||||||
import { ProjectProvider, useProject } from "../../../src/context/project"
|
import { ProjectProvider, useProject } from "../../../src/context/project"
|
||||||
import { SDKProvider, useSDK } from "../../../src/context/sdk"
|
import { ClientProvider, useClient } from "../../../src/context/client"
|
||||||
import { useEvent } from "../../../src/context/event"
|
import { useEvent } from "../../../src/context/event"
|
||||||
import { createApi, createClient, createEventStream, createFetch } from "../../fixture/tui-sdk"
|
import { createApi, createEventStream, createFetch } from "../../fixture/tui-client"
|
||||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||||
import type { LogLevel, LogSink } from "../../../src/context/log"
|
import type { LogLevel, LogSink } from "../../../src/context/log"
|
||||||
|
|
||||||
|
|
@ -54,7 +53,7 @@ function update(version: string): OpenCodeEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mount(
|
async function mount(
|
||||||
reconnect?: (attempt: number) => Promise<{ client: OpencodeClient; api: OpenCodeClient }>,
|
reconnect?: (attempt: number) => Promise<{ api: OpenCodeClient }>,
|
||||||
log?: LogSink,
|
log?: LogSink,
|
||||||
) {
|
) {
|
||||||
const events = createEventStream()
|
const events = createEventStream()
|
||||||
|
|
@ -62,7 +61,7 @@ async function mount(
|
||||||
const seen: OpenCodeEvent[] = []
|
const seen: OpenCodeEvent[] = []
|
||||||
const workspaces: Array<string | undefined> = []
|
const workspaces: Array<string | undefined> = []
|
||||||
let project!: ReturnType<typeof useProject>
|
let project!: ReturnType<typeof useProject>
|
||||||
let sdk!: ReturnType<typeof useSDK>
|
let client!: ReturnType<typeof useClient>
|
||||||
let done!: () => void
|
let done!: () => void
|
||||||
const ready = new Promise<void>((resolve) => {
|
const ready = new Promise<void>((resolve) => {
|
||||||
done = resolve
|
done = resolve
|
||||||
|
|
@ -70,12 +69,12 @@ async function mount(
|
||||||
|
|
||||||
const app = await testRender(() => (
|
const app = await testRender(() => (
|
||||||
<TestTuiContexts log={log}>
|
<TestTuiContexts log={log}>
|
||||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)} reconnect={reconnect}>
|
<ClientProvider api={createApi(calls.fetch)} reconnect={reconnect}>
|
||||||
<ProjectProvider>
|
<ProjectProvider>
|
||||||
<Probe
|
<Probe
|
||||||
onReady={async (ctx) => {
|
onReady={async (ctx) => {
|
||||||
project = ctx.project
|
project = ctx.project
|
||||||
sdk = ctx.sdk
|
client = ctx.client
|
||||||
await project.sync()
|
await project.sync()
|
||||||
done()
|
done()
|
||||||
}}
|
}}
|
||||||
|
|
@ -83,21 +82,21 @@ async function mount(
|
||||||
workspaces={workspaces}
|
workspaces={workspaces}
|
||||||
/>
|
/>
|
||||||
</ProjectProvider>
|
</ProjectProvider>
|
||||||
</SDKProvider>
|
</ClientProvider>
|
||||||
</TestTuiContexts>
|
</TestTuiContexts>
|
||||||
))
|
))
|
||||||
|
|
||||||
await ready
|
await ready
|
||||||
return { app, events, emit: events.emit, project, sdk, seen, workspaces }
|
return { app, events, emit: events.emit, project, client, seen, workspaces }
|
||||||
}
|
}
|
||||||
|
|
||||||
function Probe(props: {
|
function Probe(props: {
|
||||||
seen: OpenCodeEvent[]
|
seen: OpenCodeEvent[]
|
||||||
workspaces: Array<string | undefined>
|
workspaces: Array<string | undefined>
|
||||||
onReady: (ctx: { project: ReturnType<typeof useProject>; sdk: ReturnType<typeof useSDK> }) => void
|
onReady: (ctx: { project: ReturnType<typeof useProject>; client: ReturnType<typeof useClient> }) => void
|
||||||
}) {
|
}) {
|
||||||
const project = useProject()
|
const project = useProject()
|
||||||
const sdk = useSDK()
|
const client = useClient()
|
||||||
const event = useEvent()
|
const event = useEvent()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
@ -105,7 +104,7 @@ function Probe(props: {
|
||||||
props.seen.push(evt)
|
props.seen.push(evt)
|
||||||
props.workspaces.push(workspace)
|
props.workspaces.push(workspace)
|
||||||
})
|
})
|
||||||
props.onReady({ project, sdk })
|
props.onReady({ project, client })
|
||||||
})
|
})
|
||||||
|
|
||||||
return <box />
|
return <box />
|
||||||
|
|
@ -137,7 +136,7 @@ describe("useEvent", () => {
|
||||||
{
|
{
|
||||||
level: "debug",
|
level: "debug",
|
||||||
message: "event",
|
message: "event",
|
||||||
tags: { component: "sdk", type: "session.renamed", aggregateID: "ses_test", seq: 1 },
|
tags: { component: "client", type: "session.renamed", aggregateID: "ses_test", seq: 1 },
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -194,25 +193,24 @@ describe("useEvent", () => {
|
||||||
const attempts: number[] = []
|
const attempts: number[] = []
|
||||||
const replacementEvents = createEventStream()
|
const replacementEvents = createEventStream()
|
||||||
const replacementCalls = createFetch(undefined, replacementEvents)
|
const replacementCalls = createFetch(undefined, replacementEvents)
|
||||||
const replacement = { client: createClient(replacementCalls.fetch), api: createApi(replacementCalls.fetch) }
|
const replacement = { api: createApi(replacementCalls.fetch) }
|
||||||
const { app, events, sdk, seen } = await mount(async (attempt) => {
|
const { app, events, client, seen } = await mount(async (attempt) => {
|
||||||
attempts.push(attempt)
|
attempts.push(attempt)
|
||||||
return replacement
|
return replacement
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
// Reconnection only runs when the stream is down, never while connected.
|
// Reconnection only runs when the stream is down, never while connected.
|
||||||
expect(attempts).toEqual([])
|
expect(attempts).toEqual([])
|
||||||
events.disconnect()
|
events.disconnect()
|
||||||
await wait(() => sdk.connection.status() === "connected" && attempts.length > 0)
|
await wait(() => client.connection.status() === "connected" && attempts.length > 0)
|
||||||
replacementEvents.emit(event(vcs("rediscovered"), { directory: "/tmp/rediscovered" }))
|
replacementEvents.emit(event(vcs("rediscovered"), { directory: "/tmp/rediscovered" }))
|
||||||
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "rediscovered"))
|
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "rediscovered"))
|
||||||
|
|
||||||
expect(sdk.client).toBe(replacement.client)
|
expect(client.api).toBe(replacement.api)
|
||||||
expect(sdk.api).toBe(replacement.api)
|
|
||||||
expect(attempts).toEqual([1])
|
expect(attempts).toEqual([1])
|
||||||
const history = sdk.connection.internal.history()
|
const history = client.connection.internal.history()
|
||||||
expect(history.map((event) => [event.data.status, event.data.attempt])).toEqual([
|
expect(history.map((event) => [event.data.status, event.data.attempt])).toEqual([
|
||||||
["connecting", 0],
|
["connecting", 0],
|
||||||
["connected", 0],
|
["connected", 0],
|
||||||
|
|
@ -228,22 +226,22 @@ describe("useEvent", () => {
|
||||||
|
|
||||||
test("keeps the current client when reconnection fails", async () => {
|
test("keeps the current client when reconnection fails", async () => {
|
||||||
let calls = 0
|
let calls = 0
|
||||||
const { app, events, sdk, seen } = await mount(async () => {
|
const { app, events, client, seen } = await mount(async () => {
|
||||||
calls += 1
|
calls += 1
|
||||||
throw new Error("no server")
|
throw new Error("no server")
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await wait(() => sdk.connection.status() === "connected")
|
await wait(() => client.connection.status() === "connected")
|
||||||
const original = sdk.client
|
const original = client.api
|
||||||
events.disconnect()
|
events.disconnect()
|
||||||
// Reconnection rejects; the loop retries against the last known transport,
|
// Reconnection rejects; the loop retries against the last known transport,
|
||||||
// which succeeds once the fixture accepts the reconnect.
|
// which succeeds once the fixture accepts the reconnect.
|
||||||
await wait(() => calls > 0 && sdk.connection.status() === "connected")
|
await wait(() => calls > 0 && client.connection.status() === "connected")
|
||||||
events.emit(event(vcs("recovered"), { directory: "/tmp/recovered" }))
|
events.emit(event(vcs("recovered"), { directory: "/tmp/recovered" }))
|
||||||
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "recovered"))
|
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "recovered"))
|
||||||
|
|
||||||
expect(sdk.client).toBe(original)
|
expect(client.api).toBe(original)
|
||||||
} finally {
|
} finally {
|
||||||
app.renderer.destroy()
|
app.renderer.destroy()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client"
|
import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
|
||||||
|
|
||||||
export const worktree = "/tmp/opencode"
|
export const worktree = "/tmp/opencode"
|
||||||
export const directory = `${worktree}/packages/tui`
|
export const directory = `${worktree}/packages/tui`
|
||||||
|
|
@ -135,10 +134,6 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
|
||||||
return { fetch, session }
|
return { fetch, session }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createClient(fetch: typeof globalThis.fetch) {
|
|
||||||
return createOpencodeClient({ baseUrl: "http://test", fetch })
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createApi(fetch: typeof globalThis.fetch) {
|
export function createApi(fetch: typeof globalThis.fetch) {
|
||||||
return OpenCode.make({ baseUrl: "http://test", fetch })
|
return OpenCode.make({ baseUrl: "http://test", fetch })
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue