diff --git a/bun.lock b/bun.lock
index edb60172f5..7589f01e7f 100644
--- a/bun.lock
+++ b/bun.lock
@@ -1006,7 +1006,6 @@
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
- "@opencode-ai/sdk": "workspace:*",
"@opencode-ai/simulation": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@opentui/core": "catalog:",
diff --git a/packages/tui/package.json b/packages/tui/package.json
index ee70ca50b0..fe3cdeb69c 100644
--- a/packages/tui/package.json
+++ b/packages/tui/package.json
@@ -22,7 +22,7 @@
"./context/log": "./src/context/log.tsx",
"./context/project": "./src/context/project.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/editor": "./src/context/editor.ts",
"./context/clipboard": "./src/context/clipboard.tsx",
@@ -55,7 +55,6 @@
"@opencode-ai/client": "workspace:*",
"@opencode-ai/core": "workspace:*",
"@opencode-ai/plugin": "workspace:*",
- "@opencode-ai/sdk": "workspace:*",
"@opencode-ai/simulation": "workspace:*",
"@opencode-ai/ui": "workspace:*",
"@opentui/core": "catalog:",
diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx
index 574686ced1..99a58b2e47 100644
--- a/packages/tui/src/app.tsx
+++ b/packages/tui/src/app.tsx
@@ -41,7 +41,7 @@ import { PluginRouteMissing } from "./component/plugin-route-missing"
import { ProjectProvider, useProject } from "./context/project"
import { EditorContextProvider } from "./context/editor"
import { useEvent } from "./context/event"
-import { SDKProvider, useSDK } from "./context/sdk"
+import { ClientProvider, useClient } from "./context/client"
import { StartupLoading } from "./component/startup-loading"
import { Reconnecting } from "./component/reconnecting"
import { DataProvider, useData } from "./context/data"
@@ -55,7 +55,6 @@ import { DialogStatus } from "./component/dialog-status"
import { DialogConfig } from "./component/dialog-config"
import { DialogDebug } from "./component/dialog-debug"
import { DialogPair, type DialogPairCredentials } from "./component/dialog-pair"
-import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { DialogThemeList } from "./component/dialog-theme-list"
import { DialogHelp } from "./ui/dialog-help"
import { DialogAgent } from "./component/dialog-agent"
@@ -66,8 +65,6 @@ import { Session } from "./routes/session"
import { PromptHistoryProvider } from "./component/prompt/history"
import { FrecencyProvider } from "./component/prompt/frecency"
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 { isDefaultTitle } from "./util/session"
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 next = { baseUrl: endpoint.url, headers: Service.headers(endpoint) }
return {
- client: createOpencodeClient({ ...next, directory }),
api: OpenCode.make(next),
}
}
@@ -336,8 +332,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
}
>
-
-
+
@@ -426,7 +421,7 @@ function App(props: {
const local = useLocal()
const keymap = useOpencodeKeymap()
const event = useEvent()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const themeState = useTheme()
const { theme, mode, setMode, locked, lock, unlock } = themeState
@@ -475,7 +470,7 @@ function App(props: {
route,
routes: pluginRuntime.routes,
event,
- sdk,
+ client,
project,
data,
theme: themeState,
@@ -585,7 +580,7 @@ function App(props: {
if (continued || !args.continue) return
continued = true
const location = data.location.default()
- void sdk.api.session
+ void client.api.session
.list({
limit: 1,
order: "desc",
@@ -600,7 +595,7 @@ function App(props: {
route.navigate({ type: "session", sessionID: match })
return
}
- void sdk.api.session
+ void client.api.session
.fork({ sessionID: match })
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
.catch(toast.error)
@@ -613,7 +608,7 @@ function App(props: {
createEffect(() => {
if (forked || !args.sessionID || !args.fork) return
forked = true
- void sdk.api.session
+ void client.api.session
.fork({ sessionID: args.sessionID })
.then((result) => route.navigate({ type: "session", sessionID: result.id }))
.catch(toast.error)
@@ -815,7 +810,7 @@ function App(props: {
},
category: "System",
},
- ...(sdk.reload
+ ...(client.reload
? [
{
name: "server.reload",
@@ -826,7 +821,7 @@ function App(props: {
toast.show({ variant: "info", message: "Reloading server...", duration: 30000 })
// reload resolves once the replacement service is healthy; the
// event stream reattaches through the reconnect loop.
- await sdk.reload!()
+ await client.reload!()
.then(() => toast.show({ variant: "success", message: "Server reloaded" }))
.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(() => {
if (!ready()) return
if (route.data.type !== "plugin") return
@@ -1148,7 +1104,7 @@ function App(props: {
clearTimeout(reconnectTimer)
reconnectTimer = undefined
}
- const status = sdk.connection.status()
+ const status = client.connection.status()
if (status === "connected") {
setShowReconnecting(false)
return
@@ -1211,7 +1167,7 @@ function App(props: {
-
+
)
diff --git a/packages/tui/src/component/dialog-integration.tsx b/packages/tui/src/component/dialog-integration.tsx
index 1ae8bbec77..74aa76040b 100644
--- a/packages/tui/src/component/dialog-integration.tsx
+++ b/packages/tui/src/component/dialog-integration.tsx
@@ -8,7 +8,7 @@ import type {
import { createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
import { useClipboard } from "../context/clipboard"
import { useData } from "../context/data"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { useTheme } from "../context/theme"
import { useBindings } from "../keymap"
import { useDialog } from "../ui/dialog"
@@ -102,7 +102,7 @@ function manageConnections(
) {
dialog.replace(() => {
const data = useData()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
return (
{
- void sdk.api.credential
+ void client.api.credential
.remove({ credentialID: connection.id, location: location(data) })
.then(() => disconnected(integration.name, data, dialog, toast))
.catch(toast.error)
@@ -172,7 +172,7 @@ function KeyMethod(props: {
}) {
const data = useData()
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const { theme } = useTheme()
const [error, setError] = createSignal()
@@ -183,7 +183,7 @@ function KeyMethod(props: {
placeholder="API key"
onConfirm={(key) => {
if (!key) return
- void sdk.api.integration
+ void client.api.integration
.connect.key({
integrationID: props.integration.id,
location: location(data),
@@ -218,11 +218,11 @@ function OAuthStarting(props: {
}) {
const data = useData()
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
onMount(() => {
- void sdk.api.integration
+ void client.api.integration
.connect.oauth({
integrationID: props.integration.id,
location: location(data),
@@ -267,7 +267,7 @@ function OAuthAuto(props: {
}) {
const data = useData()
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const clipboard = useClipboard()
let timer: ReturnType | undefined
@@ -291,7 +291,7 @@ function OAuthAuto(props: {
}))
const poll = () => {
- void sdk.api.integration
+ void client.api.integration
.attempt.status({ attemptID: props.attempt.attemptID, location: location(data) })
.then((result) => {
const status = result.data
@@ -318,7 +318,7 @@ function OAuthAuto(props: {
onCleanup(() => {
if (timer) clearTimeout(timer)
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 (
@@ -340,7 +340,7 @@ function OAuthCode(props: {
}) {
const data = useData()
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const { theme } = useTheme()
const [error, setError] = createSignal()
@@ -348,7 +348,7 @@ function OAuthCode(props: {
onCleanup(() => {
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 (
@@ -357,7 +357,7 @@ function OAuthCode(props: {
placeholder="Authorization code"
onConfirm={(code) => {
if (!code) return
- void sdk.api.integration
+ void client.api.integration
.attempt.complete({ attemptID: props.attempt.attemptID, location: location(data), code })
.then(() => {
settled = true
diff --git a/packages/tui/src/component/dialog-move-session.tsx b/packages/tui/src/component/dialog-move-session.tsx
index 3ea26a50d5..bc30045b40 100644
--- a/packages/tui/src/component/dialog-move-session.tsx
+++ b/packages/tui/src/component/dialog-move-session.tsx
@@ -4,7 +4,7 @@ import { createMemo, createResource, createSignal, onMount, Show } from "solid-j
import path from "path"
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
import { useDialog } from "../ui/dialog"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { useTheme } from "../context/theme"
import { useData } from "../context/data"
import { abbreviateHome } from "../runtime"
@@ -35,7 +35,7 @@ type DialogMoveSessionProps = {
export function DialogMoveSession(props: DialogMoveSessionProps) {
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const dimensions = useTerminalDimensions()
const { theme } = useTheme()
const sessionData = useData()
@@ -63,7 +63,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
const [loadedProject] = createResource(
() => (projectContext.project() === undefined ? props.projectID : undefined),
(projectID) =>
- sdk.api.project
+ client.api.project
.current({ location: { directory: projectContext.instance.directory() || paths.cwd } })
.then((project) => (project.id === projectID ? project.directory : undefined))
.catch(() => undefined),
@@ -78,11 +78,11 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
async (projectID, info): Promise | undefined> => {
try {
const location = { directory: projectContext.instance.directory() || paths.cwd }
- await sdk.api.projectCopy.refresh({
+ await client.api.projectCopy.refresh({
projectID,
location,
})
- const directories = await sdk.api.project.directories({
+ const directories = await client.api.project.directories({
projectID,
location,
})
@@ -232,7 +232,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
setToDelete(undefined)
setRemoving(selected.directory)
setWorking(true)
- const error = await sdk.api.projectCopy
+ const error = await client.api.projectCopy
.remove({
projectID: props.projectID,
location: { directory: projectContext.instance.directory() || paths.cwd },
@@ -247,7 +247,9 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
setRemoving(undefined)
setWorking(false)
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 ?? [], {
title: "Delete working copy?",
message: "This working copy has file changes. Do you want to delete it anyway?",
@@ -257,7 +259,7 @@ export function DialogMoveSession(props: DialogMoveSessionProps) {
return
}
reopen(selected.directory)
- const forcedError = await sdk.api.projectCopy
+ const forcedError = await client.api.projectCopy
.remove({
projectID: props.projectID,
location: { directory: projectContext.instance.directory() || paths.cwd },
diff --git a/packages/tui/src/component/dialog-pair.tsx b/packages/tui/src/component/dialog-pair.tsx
index 807cac0abb..d1b5b10787 100644
--- a/packages/tui/src/component/dialog-pair.tsx
+++ b/packages/tui/src/component/dialog-pair.tsx
@@ -2,7 +2,7 @@ import { TextAttributes } from "@opentui/core"
import { useTerminalDimensions } from "@opentui/solid"
import { createMemo, createResource, createSignal, For, Show } from "solid-js"
import { renderUnicodeCompact } from "uqr"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { useTheme } from "../context/theme"
import { useDialog } from "../ui/dialog"
import { errorMessage } from "../util/error"
@@ -13,7 +13,7 @@ export type DialogPairCredentials = {
}
export function DialogPair(props: { credentials?: DialogPairCredentials }) {
- const sdk = useSDK()
+ const client = useClient()
const dialog = useDialog()
const dimensions = useTerminalDimensions()
const { theme } = useTheme()
@@ -25,7 +25,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
dialog.setCentered(true)
const [server] = createResource(() =>
- sdk.api.server
+ client.api.server
.get()
.catch((error) => {
setLoadError(error)
diff --git a/packages/tui/src/component/dialog-session-list.tsx b/packages/tui/src/component/dialog-session-list.tsx
index 2d305f54e6..07830e4202 100644
--- a/packages/tui/src/component/dialog-session-list.tsx
+++ b/packages/tui/src/component/dialog-session-list.tsx
@@ -8,7 +8,7 @@ import { useData } from "../context/data"
import { Locale } from "../util/locale"
import { useProject } from "../context/project"
import { useTheme } from "../context/theme"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { useLocal } from "../context/local"
import { createDebouncedSignal } from "../util/signal"
import { useToast } from "../ui/toast"
@@ -23,7 +23,7 @@ export function DialogSessionList() {
const data = useData()
const project = useProject()
const { theme } = useTheme()
- const sdk = useSDK()
+ const client = useClient()
const local = useLocal()
const toast = useToast()
const [search, setSearch] = createDebouncedSignal("", 150)
@@ -36,7 +36,7 @@ export function DialogSessionList() {
if (!query) return
const location = data.location.default()
try {
- const response = await sdk.api.session.list({
+ const response = await client.api.session.list({
search: query,
limit: 50,
order: "desc",
@@ -140,7 +140,7 @@ export function DialogSessionList() {
setToDelete(option.value)
return
}
- void sdk.api.session.remove({ sessionID: option.value }).catch((error) => {
+ void client.api.session.remove({ sessionID: option.value }).catch((error) => {
setToDelete(undefined)
toast.show({
message: `Failed to delete session: ${errorMessage(error)}`,
diff --git a/packages/tui/src/component/dialog-session-rename.tsx b/packages/tui/src/component/dialog-session-rename.tsx
index f2b1d07832..034bc4bcbb 100644
--- a/packages/tui/src/component/dialog-session-rename.tsx
+++ b/packages/tui/src/component/dialog-session-rename.tsx
@@ -1,12 +1,12 @@
import { DialogPrompt } from "../ui/dialog-prompt"
import { type DialogContext, useDialog } from "../ui/dialog"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { useToast } from "../ui/toast"
import { errorMessage } from "../util/error"
export function DialogSessionRename(props: { sessionID: string; currentTitle?: string }) {
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
return (
@@ -17,7 +17,7 @@ export function DialogSessionRename(props: { sessionID: string; currentTitle?: s
onConfirm={(value) => {
const title = value.trim()
if (!title) return
- void sdk.api.session
+ void client.api.session
.rename({ sessionID: props.sessionID, title })
.then(() => dialog.clear())
.catch((error) =>
diff --git a/packages/tui/src/component/dialog-tag.tsx b/packages/tui/src/component/dialog-tag.tsx
index aa32c98ab7..69d0c51de1 100644
--- a/packages/tui/src/component/dialog-tag.tsx
+++ b/packages/tui/src/component/dialog-tag.tsx
@@ -2,11 +2,11 @@ import { createMemo, createResource } from "solid-js"
import { DialogSelect } from "../ui/dialog-select"
import { useDialog } from "../ui/dialog"
import { useProject } from "../context/project"
-import { useSDK } from "../context/sdk"
+import { useClient } from "../context/client"
import { createStore } from "solid-js/store"
export function DialogTag(props: { onSelect?: (value: string) => void }) {
- const sdk = useSDK()
+ const client = useClient()
const dialog = useDialog()
const project = useProject()
@@ -17,7 +17,7 @@ export function DialogTag(props: { onSelect?: (value: string) => void }) {
const [files] = createResource(
() => [store.filter],
async () => {
- const result = await sdk.api.file
+ const result = await client.api.file
.find({
query: store.filter,
type: "file",
diff --git a/packages/tui/src/component/prompt/autocomplete.tsx b/packages/tui/src/component/prompt/autocomplete.tsx
index 3f01827880..03e1b5fcdb 100644
--- a/packages/tui/src/component/prompt/autocomplete.tsx
+++ b/packages/tui/src/component/prompt/autocomplete.tsx
@@ -7,7 +7,7 @@ import { createMemo, createResource, createEffect, onMount, onCleanup, Index, Sh
import { createStore } from "solid-js/store"
import { useEditorContext } from "../../context/editor"
import { useProject } from "../../context/project"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useData } from "../../context/data"
import { getScrollAcceleration } from "../../util/scroll"
import { useTuiPaths } from "../../context/runtime"
@@ -84,7 +84,7 @@ export function Autocomplete(props: {
promptPartTypeId: () => number
}) {
const editor = useEditorContext()
- const sdk = useSDK()
+ const client = useClient()
const data = useData()
const project = useProject()
const slashes = useCommandSlashes()
@@ -315,7 +315,7 @@ export function Autocomplete(props: {
if (referenceMatch()) return []
const { lineRange, baseQuery } = extractLineRange(input.query ?? "")
- const result = await sdk.api.file
+ const result = await client.api.file
.find({
query: baseQuery,
limit: 20,
diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx
index 55a7f2ee7d..94ecbb4654 100644
--- a/packages/tui/src/component/prompt/index.tsx
+++ b/packages/tui/src/component/prompt/index.tsx
@@ -21,7 +21,7 @@ import { EmptyBorder, SplitBorder } from "../../ui/border"
import { useTuiPaths, useTuiTerminalEnvironment } from "../../context/runtime"
import { useClipboard } from "../../context/clipboard"
import { Spinner } from "../spinner"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useRoute } from "../../context/route"
import { useProject } from "../../context/project"
import { useEvent } from "../../context/event"
@@ -148,7 +148,7 @@ export function Prompt(props: PromptProps) {
const paths = useTuiPaths()
const terminalEnvironment = useTuiTerminalEnvironment()
const clipboard = useClipboard()
- const sdk = useSDK()
+ const client = useClient()
const editor = useEditorContext()
const route = useRoute()
const project = useProject()
@@ -421,7 +421,7 @@ export function Prompt(props: PromptProps) {
}, 5000)
if (store.interrupt >= 2) {
- void sdk.api.session.interrupt({
+ void client.api.session.interrupt({
sessionID: props.sessionID,
})
setStore("interrupt", 0)
@@ -440,7 +440,7 @@ export function Prompt(props: PromptProps) {
if (!input.focused) return
if (!props.sessionID) return
- void sdk.api.session.background({
+ void client.api.session.background({
sessionID: props.sessionID,
})
dialog.clear()
@@ -965,7 +965,7 @@ export function Prompt(props: PromptProps) {
finishMoveProgress = Boolean(move.progress())
const location = data.location.default()
- const created = await sdk.api.session
+ const created = await client.api.session
.create({
location: directory ? { directory } : location,
agent: agent.id,
@@ -1009,7 +1009,7 @@ export function Prompt(props: PromptProps) {
if (store.mode === "shell") {
move.startSubmit()
- void sdk.api.session.shell({
+ void client.api.session.shell({
sessionID,
command: inputText,
})
@@ -1028,7 +1028,7 @@ export function Prompt(props: PromptProps) {
const restOfInput = firstLineEnd === -1 ? "" : inputText.slice(firstLineEnd + 1)
const args = firstLineArgs.join(" ") + (restOfInput ? "\n" + restOfInput : "")
- void sdk.api.session
+ void client.api.session
.command({
sessionID,
command: command.slice(1),
@@ -1048,7 +1048,7 @@ export function Prompt(props: PromptProps) {
)
) {
move.startSubmit()
- void sdk.api.session.skill({
+ void client.api.session.skill({
sessionID,
skill: inputText.split("\n")[0].split(" ")[0].slice(1),
})
@@ -1059,20 +1059,20 @@ export function Prompt(props: PromptProps) {
session = data.session.get(sessionID)
}
if (session?.agent !== agent.id) {
- await sdk.api.session.switchAgent({ sessionID, agent: agent.id })
+ await client.api.session.switchAgent({ sessionID, agent: agent.id })
}
if (
session?.model?.providerID !== selectedModel.providerID ||
session.model.id !== selectedModel.modelID ||
session.model.variant !== variant
) {
- await sdk.api.session.switchModel({
+ await client.api.session.switchModel({
sessionID,
model: { providerID: selectedModel.providerID, id: selectedModel.modelID, variant },
})
}
if (session?.revert) {
- const error = await sdk.api.session.revert.commit({ sessionID }).then(
+ const error = await client.api.session.revert.commit({ sessionID }).then(
() => undefined,
(error) => error,
)
@@ -1083,7 +1083,7 @@ export function Prompt(props: PromptProps) {
}
if (pendingEditorSelection) {
// 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({
sessionID,
text: formatEditorContext(pendingEditorSelection),
@@ -1098,7 +1098,7 @@ export function Prompt(props: PromptProps) {
return false
}
}
- const error = await sdk.api.session
+ const error = await client.api.session
.prompt({
sessionID,
text: inputText,
diff --git a/packages/tui/src/component/prompt/move.tsx b/packages/tui/src/component/prompt/move.tsx
index 1139385034..9421916374 100644
--- a/packages/tui/src/component/prompt/move.tsx
+++ b/packages/tui/src/component/prompt/move.tsx
@@ -3,7 +3,7 @@ import path from "path"
import { useTuiPaths } from "../../context/runtime"
import { errorMessage } from "../../util/error"
import { useDialog } from "../../ui/dialog"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useToast } from "../../ui/toast"
import { DialogMoveSession, type MoveSessionSelection } from "../dialog-move-session"
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 }) {
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const homeDestination = useHomeSessionDestination()
const project = useProject()
@@ -33,7 +33,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
setCreating(true)
setProgress("Creating copy")
try {
- const result = await sdk.api.projectCopy.create({
+ const result = await client.api.projectCopy.create({
projectID,
location: { directory: project.instance.directory() || paths.cwd },
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")
// 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")
return directory
@@ -98,7 +98,9 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
async function moveExistingSession(sessionID: string, selection: MoveSessionSelection) {
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"
if (!choice) return
dialog.clear()
@@ -110,8 +112,8 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
}
setProgress("Moving session")
try {
- await sdk.api.session.move({ sessionID, destination: { directory }, moveChanges: choice === "yes" })
- await sdk.api.session
+ await client.api.session.move({ sessionID, destination: { directory }, moveChanges: choice === "yes" })
+ await client.api.session
.synthetic({ sessionID, text: moveReminderText(directory), resume: false })
.catch(() => undefined)
dialog.clear()
@@ -129,7 +131,7 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
if (projectID) return projectID
const sessionID = input.sessionID()
if (sessionID) return (await resolveSession(sessionID))?.projectID
- return sdk.api.project
+ return client.api.project
.current({ location: { directory: project.instance.directory() || paths.cwd } })
.then((project) => project.id)
.catch(() => undefined)
diff --git a/packages/tui/src/context/sdk.tsx b/packages/tui/src/context/client.tsx
similarity index 86%
rename from packages/tui/src/context/sdk.tsx
rename to packages/tui/src/context/client.tsx
index d1364378ef..04e347900a 100644
--- a/packages/tui/src/context/sdk.tsx
+++ b/packages/tui/src/context/client.tsx
@@ -1,13 +1,12 @@
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
-import type { OpencodeClient } from "@opencode-ai/sdk/v2"
import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { onCleanup, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "./helper"
import { useLog } from "./log"
-export type SDKConnectionStatus = "connected" | "connecting" | "reconnecting"
-export type SDKConnectionEvent = {
+export type ClientConnectionStatus = "connected" | "connecting" | "reconnecting"
+export type ClientConnectionEvent = {
readonly type: "client.connection"
readonly created: number
readonly data: {
@@ -17,27 +16,25 @@ export type SDKConnectionEvent = {
}
}
-type SDKEventMap = { [Type in OpenCodeEvent["type"]]: Extract }
+type ClientEventMap = { [Type in OpenCodeEvent["type"]]: Extract }
const connectTimeout = 2_000
const connectionHistoryLimit = 50
-export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
- name: "SDK",
+export const { use: useClient, provider: ClientProvider } = createSimpleContext({
+ name: "Client",
init: (props: {
- client: 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.
reload?: () => Promise
}) => {
- const log = useLog({ component: "sdk" })
+ const log = useLog({ component: "client" })
const abort = new AbortController()
- const history: SDKConnectionEvent[] = []
- let client = props.client
+ const history: ClientConnectionEvent[] = []
let api = props.api
- const events = createGlobalEmitter()
+ const events = createGlobalEmitter()
const [connection, setConnection] = createStore<{
- status: SDKConnectionStatus
+ status: ClientConnectionStatus
attempt: number
error?: string
}>({
@@ -46,7 +43,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
})
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 } })
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)
if (abort.signal.aborted || controller.signal.aborted) return
if (next) {
- client = next.client
api = next.api
}
}
@@ -144,9 +140,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
})
return {
- get client() {
- return client
- },
get api() {
return api
},
diff --git a/packages/tui/src/context/data.tsx b/packages/tui/src/context/data.tsx
index c5f4fddd85..66ca56fcc9 100644
--- a/packages/tui/src/context/data.tsx
+++ b/packages/tui/src/context/data.tsx
@@ -30,7 +30,7 @@ import type {
import type { Data } from "@opencode-ai/plugin/v2/tui/context"
import { createStore, produce, reconcile } from "solid-js/store"
import { createSimpleContext } from "./helper"
-import { useSDK } from "./sdk"
+import { useClient } from "./client"
import { createSignal, onCleanup } from "solid-js"
export type DataSessionStatus = "idle" | "running"
@@ -110,7 +110,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
location: {},
})
- const sdk = useSDK()
+ const client = useClient()
const [defaultLocation, setDefaultLocation] = createSignal({
directory: process.cwd(),
})
@@ -328,7 +328,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
time: { created: event.created },
})
})
- void sdk.api.session
+ void client.api.session
.message({ sessionID: event.data.sessionID, messageID: messageIDFromEvent(event.id) })
.then((item) => {
message.update(event.data.sessionID, (draft, index) => {
@@ -851,8 +851,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
}
const result = {
- on: sdk.event.on,
- listen: sdk.event.listen,
+ on: client.event.on,
+ listen: client.event.listen,
session: {
list() {
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] ?? []
},
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",
@@ -916,7 +916,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
},
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)
},
message: {
@@ -929,7 +929,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return position === undefined ? undefined : messages?.[position]
},
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])))
setStore("session", "message", sessionID, reconcile(messages))
},
@@ -939,7 +939,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return store.session.permission[sessionID]
},
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: {
@@ -952,7 +952,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
async refresh(sessionID: string, ref?: LocationRef) {
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 = {
directory: response.location.directory,
workspaceID: response.location.workspaceID,
@@ -966,7 +966,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
])
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]
},
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)
},
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)
setStore("location", key, {
...store.location[key],
@@ -1003,7 +1003,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return defaultLocation()
},
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)
if (!store.location[key]) setStore("location", key, {})
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
},
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)
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
},
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)
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
},
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)
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
},
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)
setStore("location", key, {
...store.location[key],
@@ -1057,7 +1057,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return store.location[locationKey(location ?? defaultLocation())]?.mcp?.resource
},
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)
setStore("location", key, {
...store.location[key],
@@ -1071,7 +1071,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
return store.location[locationKey(location ?? defaultLocation())]?.model
},
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)
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
},
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)
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
},
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)
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
},
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)
setStore("location", key, { ...store.location[key], skill: result.data })
},
@@ -1113,7 +1113,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
async function bootstrap() {
if (bootstrapping) return bootstrapping
bootstrapping = Promise.allSettled([
- sdk.api.session
+ client.api.session
.list({
limit: 50,
order: "desc",
@@ -1130,7 +1130,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
)
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>(
(result, request) => ({
...result,
@@ -1140,7 +1140,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
)
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 = {
directory: response.location.directory,
workspaceID: response.location.workspaceID,
@@ -1193,7 +1193,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
}
function refreshActive() {
- void sdk.api.session
+ void client.api.session
.active()
.then((active) => {
setStore(
@@ -1206,7 +1206,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
}
onCleanup(
- sdk.event.listen(({ details }) => {
+ client.event.listen(({ details }) => {
if (details.type === "server.connected") {
const messages = connected ? Object.keys(store.session.message) : []
const compactions = connected ? Object.keys(store.session.compaction) : []
diff --git a/packages/tui/src/context/event.ts b/packages/tui/src/context/event.ts
index 9accb5ec5e..eb8c5b0315 100644
--- a/packages/tui/src/context/event.ts
+++ b/packages/tui/src/context/event.ts
@@ -1,5 +1,5 @@
import type { OpenCodeEvent } from "@opencode-ai/client"
-import { useSDK } from "./sdk"
+import { useClient } from "./client"
type EventMetadata = {
directory: string | undefined
@@ -7,10 +7,10 @@ type EventMetadata = {
}
export function useEvent() {
- const sdk = useSDK()
+ const client = useClient()
function subscribe(handler: (event: OpenCodeEvent, metadata: EventMetadata) => void) {
- return sdk.event.listen(({ details }) => {
+ return client.event.listen(({ details }) => {
if (details.type === "server.connected") return
handler(details, { directory: details.location?.directory, workspace: details.location?.workspaceID })
})
@@ -20,7 +20,7 @@ export function useEvent() {
type: T,
handler: (event: Extract, 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 })
})
}
diff --git a/packages/tui/src/context/local.tsx b/packages/tui/src/context/local.tsx
index e8be304866..56655646ec 100644
--- a/packages/tui/src/context/local.tsx
+++ b/packages/tui/src/context/local.tsx
@@ -5,7 +5,7 @@ import { useEvent } from "./event"
import path from "path"
import { useTuiPaths } from "./runtime"
import { useArgs } from "./args"
-import { useSDK } from "./sdk"
+import { useClient } from "./client"
import { RGBA } from "@opentui/core"
import { readJson, writeJsonAtomic } from "../util/persistence"
import { useTheme } from "./theme"
@@ -52,7 +52,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
name: "Local",
init: () => {
const data = useData()
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const theme = useTheme().theme
const route = useRoute()
@@ -493,22 +493,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
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(() => {
const value = agent.current()
if (!value?.model) return
@@ -523,7 +507,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const result = {
model,
agent,
- mcp,
session,
permission,
}
diff --git a/packages/tui/src/context/project.tsx b/packages/tui/src/context/project.tsx
index 73584ceda1..466e663ab1 100644
--- a/packages/tui/src/context/project.tsx
+++ b/packages/tui/src/context/project.tsx
@@ -1,12 +1,12 @@
import { batch } from "solid-js"
import { createStore, reconcile } from "solid-js/store"
import { createSimpleContext } from "./helper"
-import { useSDK } from "./sdk"
+import { useClient } from "./client"
export const { use: useProject, provider: ProjectProvider } = createSimpleContext({
name: "Project",
init: () => {
- const sdk = useSDK()
+ const client = useClient()
const defaultPath = {
home: "",
@@ -33,8 +33,8 @@ export const { use: useProject, provider: ProjectProvider } = createSimpleContex
async function sync() {
const workspace = store.workspace.current
const location = { workspace }
- const current = await sdk.api.location.get({ location })
- const directories = await sdk.api.project.directories({ projectID: current.project.id, location })
+ const current = await client.api.location.get({ location })
+ const directories = await client.api.project.directories({ projectID: current.project.id, location })
batch(() => {
setStore(
"instance",
diff --git a/packages/tui/src/feature-plugins/builtins.ts b/packages/tui/src/feature-plugins/builtins.ts
index 0069d56937..9684b71334 100644
--- a/packages/tui/src/feature-plugins/builtins.ts
+++ b/packages/tui/src/feature-plugins/builtins.ts
@@ -3,7 +3,6 @@ import type { PluginRuntime } from "../plugin/runtime"
import HomeFooter from "./home/footer"
import HomeTips from "./home/tips"
import SidebarContext from "./sidebar/context"
-import SidebarFiles from "./sidebar/files"
import SidebarFooter from "./sidebar/footer"
import SidebarLsp from "./sidebar/lsp"
import SidebarMcp from "./sidebar/mcp"
@@ -26,7 +25,6 @@ export function createBuiltinPlugins(): BuiltinTuiPlugin[] {
SidebarContext,
SidebarMcp,
SidebarLsp,
- SidebarFiles,
SidebarFooter,
Notifications,
PluginManager,
diff --git a/packages/tui/src/feature-plugins/sidebar/files.tsx b/packages/tui/src/feature-plugins/sidebar/files.tsx
deleted file mode 100644
index e76db91444..0000000000
--- a/packages/tui/src/feature-plugins/sidebar/files.tsx
+++ /dev/null
@@ -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 (
- 0}>
-
- list().length > 2 && setOpen((x) => !x)}>
- 2}>
- {open() ? "▼" : "▶"}
-
-
- Modified Files
-
-
-
-
- {(item) => (
-
-
-
-
- +{item.additions}
-
-
- -{item.deletions}
-
-
-
- )}
-
-
-
-
- )
-}
-
-const tui: TuiPlugin = async (api) => {
- api.slots.register({
- order: 500,
- slots: {
- sidebar_content(_ctx, props) {
- return
- },
- },
- })
-}
-
-const plugin: BuiltinTuiPlugin = {
- id,
- tui,
-}
-
-export default plugin
diff --git a/packages/tui/src/feature-plugins/system/diff-viewer.tsx b/packages/tui/src/feature-plugins/system/diff-viewer.tsx
index 6444a86950..650bd29474 100644
--- a/packages/tui/src/feature-plugins/system/diff-viewer.tsx
+++ b/packages/tui/src/feature-plugins/system/diff-viewer.tsx
@@ -1,6 +1,6 @@
/** @jsxImportSource @opentui/solid */
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 {
TextAttributes,
type BorderSides,
@@ -11,7 +11,7 @@ import {
import { LANGUAGE_EXTENSIONS } from "../../util/filetype"
import { useBindings, useCommandShortcut } from "../../keymap"
import { useTheme } from "../../context/theme"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useTerminalDimensions } from "@opentui/solid"
import path from "path"
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 PLAIN_TEXT_FILETYPE = "opencode-plain-text"
const VCS_DIFF_CONTEXT_LINES = 12
-type DiffMode = "working" | "branch" | "last-turn"
+type DiffMode = "working" | "branch"
type DiffViewerFocus = "patches" | "files"
type DiffView = "split" | "unified"
type SelectedHunk = { readonly fileIndex: number; readonly hunkIndex: number; readonly scrollTop: number }
@@ -55,20 +55,14 @@ type DiffFile = {
readonly status: "added" | "deleted" | "modified"
}
-const normalizeDiffs = (diffs: readonly (FileDiffInfo | FileDiffLegacyInfo)[]): DiffFile[] =>
- diffs.flatMap((item) =>
- item.file
- ? [
- {
- file: item.file,
- patch: item.patch,
- additions: item.additions,
- deletions: item.deletions,
- status: item.status ?? "modified",
- } satisfies DiffFile,
- ]
- : [],
- )
+const normalizeDiffs = (diffs: readonly FileDiffInfo[]): DiffFile[] =>
+ diffs.map((item) => ({
+ file: item.file,
+ patch: item.patch,
+ additions: item.additions,
+ deletions: item.deletions,
+ status: item.status,
+ }))
function filetype(input?: string) {
if (!input) return "none"
@@ -82,14 +76,13 @@ function storedView(value: unknown): DiffView | undefined {
}
function diffSourceLabel(mode: DiffMode) {
- if (mode === "last-turn") return "last turn"
if (mode === "branch") return "main branch"
return "working tree"
}
function DiffViewer(props: { api: TuiPluginApi }) {
const dimensions = useTerminalDimensions()
- const sdk = useSDK()
+ const client = useClient()
const config = useConfig()
const themeState = useTheme()
const theme = () => props.api.theme.current
@@ -98,7 +91,6 @@ function DiffViewer(props: { api: TuiPluginApi }) {
| {
mode?: DiffMode
sessionID?: string
- messageID?: string
returnRoute?: TuiRouteCurrent
}
| undefined
@@ -108,22 +100,11 @@ function DiffViewer(props: { api: TuiPluginApi }) {
return {
mode: mode(),
sessionID,
- messageID: params()?.messageID,
directory: sessionID ? props.api.state.session.get(sessionID)?.directory : undefined,
}
})
const [diff] = createResource(diffInput, async (input) => {
- if (input.mode === "last-turn") {
- 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(
+ const result = await client.api.vcs.diff(
{
location: input.directory ? { directory: input.directory } : undefined,
mode: input.mode,
@@ -704,26 +685,16 @@ function DiffViewer(props: { api: TuiPluginApi }) {
]
const switchDiffOptions = createMemo(() => {
- const vcs = props.api.state.vcs
return [
{
title: "Working tree",
value: "working" as const,
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",
- value: "last-turn" as const,
- description: "Show changes from the last assistant turn",
+ title: "Main branch",
+ value: "branch" as const,
+ description: "Show changes compared to main branch",
},
]
})
@@ -742,7 +713,6 @@ function DiffViewer(props: { api: TuiPluginApi }) {
props.api.route.navigate(ROUTE, {
mode: option.value,
sessionID: params()?.sessionID,
- messageID: params()?.messageID,
returnRoute: params()?.returnRoute,
})
},
@@ -1011,7 +981,7 @@ function DiffViewerHelpDialog() {
{
shortcut: useCommandShortcut("diff.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"),
diff --git a/packages/tui/src/plugin/adapters.tsx b/packages/tui/src/plugin/adapters.tsx
index 4c27c7f9b4..b3c9d81ab7 100644
--- a/packages/tui/src/plugin/adapters.tsx
+++ b/packages/tui/src/plugin/adapters.tsx
@@ -2,7 +2,7 @@ import type { TuiDialogSelectOption, TuiPluginApi, TuiSlotProps } from "@opencod
import type { Config } from "../config"
import type { useEvent } from "../context/event"
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 { useProject } from "../context/project"
import type { useTheme } from "../context/theme"
@@ -28,7 +28,7 @@ type Input = {
route: ReturnType
routes: PluginRoutes
event: ReturnType
- sdk: ReturnType
+ client: ReturnType
project: ReturnType
data: ReturnType
theme: ReturnType
@@ -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 {
return {
app: appApi(input.version),
@@ -292,9 +301,7 @@ export function createTuiApiAdapters(input: Input): Omit void }) {
const data = useData()
const dialog = useDialog()
- const sdk = useSDK()
+ const client = useClient()
const route = useRoute()
const toast = useToast()
const [pending, setPending] = createSignal(false)
const fork = async (messageID?: string) => {
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 })
return undefined
})
diff --git a/packages/tui/src/routes/session/dialog-message.tsx b/packages/tui/src/routes/session/dialog-message.tsx
index 889d19f716..8935524634 100644
--- a/packages/tui/src/routes/session/dialog-message.tsx
+++ b/packages/tui/src/routes/session/dialog-message.tsx
@@ -3,7 +3,7 @@ import { useData } from "../../context/data"
import { DialogSelect } from "../../ui/dialog-select"
import { useClipboard } from "../../context/clipboard"
import { useToast } from "../../ui/toast"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { errorMessage } from "../../util/error"
import { DialogFork } from "./dialog-fork"
import type { PromptInfo } from "../../prompt/history"
@@ -16,7 +16,7 @@ export function DialogMessage(props: {
const data = useData()
const clipboard = useClipboard()
const toast = useToast()
- const sdk = useSDK()
+ const client = useClient()
const message = createMemo(() => data.session.message.get(props.sessionID, props.messageID))
return (
@@ -45,7 +45,7 @@ export function DialogMessage(props: {
pasted: [],
})
}
- void sdk.api.session.revert
+ void client.api.session.revert
.stage({ sessionID: props.sessionID, messageID: props.messageID })
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
dialog.clear()
diff --git a/packages/tui/src/routes/session/form.tsx b/packages/tui/src/routes/session/form.tsx
index 922c8b1afd..9f41e708a9 100644
--- a/packages/tui/src/routes/session/form.tsx
+++ b/packages/tui/src/routes/session/form.tsx
@@ -7,7 +7,7 @@ import { selectedForeground, useTheme } from "../../context/theme"
import { tint } from "../../theme/color"
import type { FormField, FormValue } from "@opencode-ai/client"
import type { FormWithLocation } from "../../context/data"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useClipboard } from "../../context/clipboard"
import { SplitBorder } from "../../ui/border"
import { useToast } from "../../ui/toast"
@@ -146,7 +146,7 @@ function requestOptions(form: FormWithLocation) {
}
export function FormPrompt(props: { form: FormWithLocation }) {
- const sdk = useSDK()
+ const client = useClient()
const { theme } = useTheme()
const renderer = useRenderer()
const dimensions = useTerminalDimensions()
@@ -297,7 +297,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
}
function replySingle(field: Field, value: FormValue) {
- sdk.api.form
+ client.api.form
.reply(
{
sessionID: props.form.sessionID,
@@ -479,7 +479,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
}
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() {
@@ -531,7 +531,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
setStore("error", validateValue(invalid, store.answers[invalid.key]) ?? "Invalid answer")
return
}
- sdk.api.form
+ client.api.form
.reply(
{
sessionID: props.form.sessionID,
@@ -582,7 +582,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
group: "Form",
cmd: () => {
if (textual()) {
- void sdk.api.form.cancel(
+ void client.api.form.cancel(
{ sessionID: props.form.sessionID, formID: props.form.id },
requestOptions(props.form),
)
diff --git a/packages/tui/src/routes/session/index.tsx b/packages/tui/src/routes/session/index.tsx
index 788bbbe8e1..0da67d4a08 100644
--- a/packages/tui/src/routes/session/index.tsx
+++ b/packages/tui/src/routes/session/index.tsx
@@ -41,7 +41,7 @@ import { Locale } from "../../util/locale"
import { FilePath } from "../../ui/file-path"
import { webSearchProviderLabel } from "../../util/tool-display"
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { useEditorContext } from "../../context/editor"
import { openEditor } from "../../editor"
import { useDialog } from "../../ui/dialog"
@@ -215,7 +215,7 @@ export function Session() {
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
const toast = useToast()
- const sdk = useSDK()
+ const client = useClient()
const editor = useEditorContext()
const rows = createSessionRows(() => route.sessionID)
@@ -389,7 +389,7 @@ export function Session() {
aliases: ["summarize"],
},
run: () => {
- void sdk.api.session.compact({ sessionID: route.sessionID })
+ void client.api.session.compact({ sessionID: route.sessionID })
dialog.clear()
},
},
@@ -417,7 +417,7 @@ export function Session() {
dialog.clear()
return
}
- void sdk.api.session.revert
+ void client.api.session.revert
.stage({ sessionID: route.sessionID, messageID: message.id })
.catch((error) => toast.show({ message: errorMessage(error), variant: "error", duration: 5000 }))
prompt?.set({
@@ -445,7 +445,7 @@ export function Session() {
slash: { name: "redo" },
run: () => {
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,
(error) => error,
)
@@ -722,11 +722,11 @@ export function Session() {
: await (async () => {
if (options.debug) {
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)
}
// 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)
if (index === -1) {
events.push(event)
@@ -740,7 +740,7 @@ export function Session() {
const messages: unknown[] = []
let cursor: string | undefined
do {
- const page = await sdk.api.message.list(
+ const page = await client.api.message.list(
cursor
? { sessionID: sessionData.id, limit: 200, cursor }
: { sessionID: sessionData.id, limit: 200, order: "asc" },
@@ -776,7 +776,7 @@ export function Session() {
category: "Session",
hidden: true,
run: () => {
- void sdk.api.session.background({ sessionID: route.sessionID })
+ void client.api.session.background({ sessionID: route.sessionID })
dialog.clear()
},
},
@@ -1380,7 +1380,7 @@ function RevertMessage(props: {
const ctx = use()
const { theme } = useTheme()
const route = useRouteData("session")
- const sdk = useSDK()
+ const client = useClient()
const toast = useToast()
const renderer = useRenderer()
const [hover, setHover] = createSignal(false)
@@ -1392,7 +1392,7 @@ function RevertMessage(props: {
onMouseUp={() => {
if (renderer.getSelection()?.getSelectedText()) return
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,
(error) => error,
)
diff --git a/packages/tui/src/routes/session/permission.tsx b/packages/tui/src/routes/session/permission.tsx
index a9382c85d3..d20cf3d58c 100644
--- a/packages/tui/src/routes/session/permission.tsx
+++ b/packages/tui/src/routes/session/permission.tsx
@@ -5,7 +5,7 @@ import { Portal, useRenderer, useTerminalDimensions, type JSX } from "@opentui/s
import type { TextareaRenderable } from "@opentui/core"
import { useTheme, selectedForeground } from "../../context/theme"
import type { PermissionV2Request } from "@opencode-ai/client"
-import { useSDK } from "../../context/sdk"
+import { useClient } from "../../context/client"
import { SplitBorder } from "../../ui/border"
import { useData } from "../../context/data"
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 }) {
- const sdk = useSDK()
+ const client = useClient()
const data = useData()
const [store, setStore] = createStore({
stage: "permission" as PermissionStage,
@@ -187,7 +187,7 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
onSelect={(option) => {
setStore("stage", "permission")
if (option === "cancel") return
- void sdk.api.permission.reply({
+ void client.api.permission.reply({
sessionID: props.request.sessionID,
reply: "always",
requestID: props.request.id,
@@ -198,7 +198,7 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
{
- void sdk.api.permission.reply({
+ void client.api.permission.reply({
sessionID: props.request.sessionID,
reply: "reject",
requestID: props.request.id,
@@ -444,14 +444,14 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
setStore("stage", "reject")
return
}
- void sdk.api.permission.reply({
+ void client.api.permission.reply({
sessionID: props.request.sessionID,
reply: "reject",
requestID: props.request.id,
})
return
}
- void sdk.api.permission.reply({
+ void client.api.permission.reply({
sessionID: props.request.sessionID,
reply: "once",
requestID: props.request.id,
diff --git a/packages/tui/test/app-lifecycle.test.tsx b/packages/tui/test/app-lifecycle.test.tsx
index 3827333856..d5b1736a5a 100644
--- a/packages/tui/test/app-lifecycle.test.tsx
+++ b/packages/tui/test/app-lifecycle.test.tsx
@@ -4,7 +4,7 @@ import { createTestRenderer } from "@opentui/core/testing"
import { Effect } from "effect"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
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 () => {
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
diff --git a/packages/tui/test/cli/cmd/tui/notifications.test.ts b/packages/tui/test/cli/cmd/tui/notifications.test.ts
index 6f8399fbdd..1fa253b0fd 100644
--- a/packages/tui/test/cli/cmd/tui/notifications.test.ts
+++ b/packages/tui/test/cli/cmd/tui/notifications.test.ts
@@ -1,14 +1,19 @@
import { describe, expect, test } from "bun:test"
import Notifications from "../../../../src/feature-plugins/system/notifications"
import type { OpenCodeEvent, PermissionAsked, QuestionAsked } from "@opencode-ai/client"
-import type { Session } from "@opencode-ai/sdk/v2"
-import type { TuiAttentionNotifyInput } from "@opencode-ai/plugin/tui"
+import type { TuiAttentionNotifyInput, TuiPluginApi } from "@opencode-ai/plugin/tui"
import { createTuiPluginApi } from "../../../fixture/tui-plugin"
+type Session = NonNullable>
+
async function setup() {
const notifications: TuiAttentionNotifyInput[] = []
const handlers = new Map void)[]>()
- const session = (id: string, title: string, parentID?: string): Session => ({
+ const session = (
+ id: string,
+ title: string,
+ parentID?: string,
+ ): Session => ({
id,
title,
slug: id,
diff --git a/packages/tui/test/cli/tui/data.test.tsx b/packages/tui/test/cli/tui/data.test.tsx
index 0e4102d30c..2671ba1e22 100644
--- a/packages/tui/test/cli/tui/data.test.tsx
+++ b/packages/tui/test/cli/tui/data.test.tsx
@@ -6,10 +6,10 @@ import { SessionMessage } from "@opencode-ai/core/session/message"
import { EventV2 } from "@opencode-ai/core/event"
import { onMount } from "solid-js"
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 { 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"
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(() => (
-
+
-
+
))
@@ -148,13 +148,13 @@ test("applies absolute usage events to session info", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -233,13 +233,13 @@ test("truncates committed revert messages without changing lifetime usage", asyn
const app = await testRender(() => (
-
+
-
+
))
@@ -371,13 +371,13 @@ test("updates session location when moved", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -430,13 +430,13 @@ test("restores running manual compaction before applying live deltas", async ()
const app = await testRender(() => (
-
+
-
+
))
@@ -514,23 +514,23 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
})
}, events)
let data!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
data = useData()
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
@@ -539,15 +539,15 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
await wait(() => data.session.status("session-stale") === "running")
await data.session.message.refresh("session-stale")
expect(data.session.message.get("session-stale", "message-stale")?.id).toBe("message-stale")
- expect(sdk.connection.status()).toBe("connected")
- expect(sdk.connection.attempt()).toBe(0)
+ expect(client.connection.status()).toBe("connected")
+ expect(client.connection.attempt()).toBe(0)
events.disconnect()
- await wait(() => sdk.connection.status() === "reconnecting")
- expect(sdk.connection.attempt()).toBe(1)
- expect(sdk.connection.error()).toBe("Event stream disconnected")
+ await wait(() => client.connection.status() === "reconnecting")
+ expect(client.connection.attempt()).toBe(1)
+ 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" } } }))
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")
expect(requests.event).toBe(2)
expect(requests.message).toBe(2)
- expect(sdk.connection.status()).toBe("connected")
- expect(sdk.connection.attempt()).toBe(0)
- expect(sdk.connection.error()).toBeUndefined()
+ expect(client.connection.status()).toBe("connected")
+ expect(client.connection.attempt()).toBe(0)
+ expect(client.connection.error()).toBeUndefined()
} finally {
app.renderer.destroy()
}
@@ -588,13 +588,13 @@ test("completes exploration when a queued prompt is promoted", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -668,13 +668,13 @@ test("removes committed revert messages from local state", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -733,34 +733,34 @@ test("distinguishes initial connection from reconnection", async () => {
const calls = createFetch((url) => {
if (url.pathname === "/api/event") return eventResponse()
})
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
try {
await wait(() => stream !== undefined)
- expect(sdk.connection.status()).toBe("connecting")
+ expect(client.connection.status()).toBe("connecting")
connect()
- await wait(() => sdk.connection.status() === "connected")
+ await wait(() => client.connection.status() === "connected")
disconnect()
- await wait(() => sdk.connection.status() === "reconnecting")
+ await wait(() => client.connection.status() === "reconnecting")
} finally {
app.renderer.destroy()
}
@@ -811,13 +811,13 @@ test("tracks session status from active sessions and execution events", async ()
const app = await testRender(() => (
-
+
-
+
))
@@ -1180,13 +1180,13 @@ test("restores queued compaction from durable pending input", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1295,13 +1295,13 @@ test("refreshes integrations after integration updates", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1351,13 +1351,13 @@ test("refreshes MCP resources after catalog updates", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1400,13 +1400,13 @@ test("refreshes effective catalog data after catalog updates", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1448,13 +1448,13 @@ test("refreshes agents after agent updates", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1492,13 +1492,13 @@ test("refreshes references after updates", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1547,13 +1547,13 @@ test("keeps shell state scoped to location", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -1593,28 +1593,28 @@ test("adds and dismisses permission requests from live events", async () => {
const events = createEventStream()
const calls = createFetch(undefined, events)
let data!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
data = useData()
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
try {
- await wait(() => sdk.connection.status() === "connected")
+ await wait(() => client.connection.status() === "connected")
emitEvent(events, {
id: "evt_permission_asked_1",
created: 0,
@@ -1681,13 +1681,13 @@ test("reconciles all pending permission requests when the event stream reconnect
const app = await testRender(() => (
-
+
-
+
))
@@ -1715,28 +1715,28 @@ test("adds, dismisses, and refreshes form requests", async () => {
})
}, events)
let data!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
data = useData()
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
try {
- await wait(() => sdk.connection.status() === "connected")
+ await wait(() => client.connection.status() === "connected")
emitEvent(events, {
id: "evt_form_created_1",
created: 0,
@@ -1785,28 +1785,28 @@ test("tracks global forms by location", async () => {
const calls = createFetch(undefined, events)
const other = { directory: "/tmp/opencode-other", workspaceID: "wrk_other" }
let data!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
data = useData()
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
try {
- await wait(() => sdk.connection.status() === "connected")
+ await wait(() => client.connection.status() === "connected")
events.emit({
id: "evt_form_created_global_other",
created: 0,
@@ -1871,28 +1871,28 @@ test("refreshes global forms for the requested location", async () => {
})
}, events)
let data!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
function Probe() {
data = useData()
- sdk = useSDK()
+ client = useClient()
return
}
const app = await testRender(() => (
-
+
-
+
))
try {
- await wait(() => sdk.connection.status() === "connected" && requests.length > 0)
+ await wait(() => client.connection.status() === "connected" && requests.length > 0)
requests.length = 0
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(() => (
-
+
-
+
))
@@ -2025,13 +2025,13 @@ test("reconciles all pending form requests when the event stream reconnects", as
const app = await testRender(() => (
-
+
-
+
))
@@ -2078,13 +2078,13 @@ test("settles pending tools when a live failure arrives", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -2225,13 +2225,13 @@ test("renders admitted prompts immediately and tracks them until promoted", asyn
const app = await testRender(() => (
-
+
-
+
))
@@ -2318,13 +2318,13 @@ test("projects live instruction updates with their message ID", async () => {
const app = await testRender(() => (
-
+
-
+
))
@@ -2387,13 +2387,13 @@ async function mountData(parents: Record, costs: Record (
-
+
-
+
))
await mounted
diff --git a/packages/tui/test/cli/tui/diff-viewer.test.tsx b/packages/tui/test/cli/tui/diff-viewer.test.tsx
index 494837d9a0..a4f2ec0ef6 100644
--- a/packages/tui/test/cli/tui/diff-viewer.test.tsx
+++ b/packages/tui/test/cli/tui/diff-viewer.test.tsx
@@ -4,17 +4,16 @@ import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
import { DiffRenderable, type Renderable, ScrollBoxRenderable } from "@opentui/core"
import { testRender, useRenderer } from "@opentui/solid"
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 { ConfigProvider } from "../../../src/config"
-import { SDKProvider } from "../../../src/context/sdk"
+import { ClientProvider } from "../../../src/context/client"
import { TuiKeybind } from "../../../src/config/keybind"
import { OpencodeKeymapProvider } from "../../../src/keymap"
import diffViewerPlugin from "../../../src/feature-plugins/system/diff-viewer"
import { createTuiPluginApi } from "../../fixture/tui-plugin"
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
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 () => {
const viewer = await renderDiffViewer([])
@@ -111,7 +110,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
let current = initialRoute ?? startRoute
let renderDiff: TuiRouteDefinition["render"] | undefined
let vcsDiffInput: unknown
- let sessionDiffInput: unknown
const config = createTuiResolvedConfig()
const transport = createFetch((url) => {
if (url.pathname !== "/api/vcs/diff") return
@@ -135,14 +133,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
}
const base = createTuiPluginApi({
keymap,
- client: {
- session: {
- diff: async (input: unknown) => {
- sessionDiffInput = input
- return { data: [] }
- },
- },
- } as unknown as TuiPluginApi["client"],
state: {
session: {
get: () => session,
@@ -170,7 +160,7 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
return (
-
+
@@ -178,7 +168,7 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
-
+
)
}
@@ -190,7 +180,6 @@ async function renderDiffViewer(vcsDiff: unknown[], height = 20, initialRoute?:
commands,
current: () => current,
vcsDiffInput: () => vcsDiffInput,
- sessionDiffInput: () => sessionDiffInput,
}
}
@@ -217,7 +206,7 @@ const session = {
created: 0,
updated: 0,
},
-} satisfies Session
+} satisfies NonNullable>
test("branch diff source requests branch VCS diff", async () => {
const viewer = await renderDiffViewer([], 20, {
@@ -234,24 +223,6 @@ test("branch diff source requests branch VCS diff", async () => {
mode: "branch",
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 {
viewer.app.renderer.destroy()
}
diff --git a/packages/tui/test/cli/tui/form.test.tsx b/packages/tui/test/cli/tui/form.test.tsx
index b4ab1f57f3..c1c83e8723 100644
--- a/packages/tui/test/cli/tui/form.test.tsx
+++ b/packages/tui/test/cli/tui/form.test.tsx
@@ -7,7 +7,7 @@ import path from "node:path"
import { onCleanup } from "solid-js"
import { ClipboardProvider } from "../../../src/context/clipboard"
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 { ConfigProvider } from "../../../src/config"
import { OpencodeKeymapProvider, registerOpencodeKeymap } from "../../../src/keymap"
@@ -15,7 +15,7 @@ import { ToastProvider } from "../../../src/ui/toast"
import { tmpdir } from "../../fixture/fixture"
import { TestTuiContexts } from "../../fixture/tui-environment"
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) {
const state = path.join(root, "state")
@@ -75,13 +75,13 @@ async function mountForm(root: string, width = 80) {
>
-
+
Promise.resolve({}) }}>
-
+
diff --git a/packages/tui/test/cli/tui/prompt-submit-race.test.ts b/packages/tui/test/cli/tui/prompt-submit-race.test.ts
index c758762bfb..640561185a 100644
--- a/packages/tui/test/cli/tui/prompt-submit-race.test.ts
+++ b/packages/tui/test/cli/tui/prompt-submit-race.test.ts
@@ -6,7 +6,7 @@ import { describe, expect, test } from "bun:test"
// Before the fix, two concurrent `submit()` calls (e.g. a double-pressed
// Enter, or the input's native onSubmit racing another dispatch) 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
// 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
diff --git a/packages/tui/test/cli/tui/use-event.test.tsx b/packages/tui/test/cli/tui/use-event.test.tsx
index 94194b122a..68b5f275c4 100644
--- a/packages/tui/test/cli/tui/use-event.test.tsx
+++ b/packages/tui/test/cli/tui/use-event.test.tsx
@@ -2,12 +2,11 @@
import { describe, expect, test } from "bun:test"
import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client"
import { testRender } from "@opentui/solid"
-import type { OpencodeClient } from "@opencode-ai/sdk/v2"
import { onMount } from "solid-js"
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 { createApi, createClient, createEventStream, createFetch } from "../../fixture/tui-sdk"
+import { createApi, createEventStream, createFetch } from "../../fixture/tui-client"
import { TestTuiContexts } from "../../fixture/tui-environment"
import type { LogLevel, LogSink } from "../../../src/context/log"
@@ -54,7 +53,7 @@ function update(version: string): OpenCodeEvent {
}
async function mount(
- reconnect?: (attempt: number) => Promise<{ client: OpencodeClient; api: OpenCodeClient }>,
+ reconnect?: (attempt: number) => Promise<{ api: OpenCodeClient }>,
log?: LogSink,
) {
const events = createEventStream()
@@ -62,7 +61,7 @@ async function mount(
const seen: OpenCodeEvent[] = []
const workspaces: Array = []
let project!: ReturnType
- let sdk!: ReturnType
+ let client!: ReturnType
let done!: () => void
const ready = new Promise((resolve) => {
done = resolve
@@ -70,12 +69,12 @@ async function mount(
const app = await testRender(() => (
-
+
{
project = ctx.project
- sdk = ctx.sdk
+ client = ctx.client
await project.sync()
done()
}}
@@ -83,21 +82,21 @@ async function mount(
workspaces={workspaces}
/>
-
+
))
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: {
seen: OpenCodeEvent[]
workspaces: Array
- onReady: (ctx: { project: ReturnType; sdk: ReturnType }) => void
+ onReady: (ctx: { project: ReturnType; client: ReturnType }) => void
}) {
const project = useProject()
- const sdk = useSDK()
+ const client = useClient()
const event = useEvent()
onMount(() => {
@@ -105,7 +104,7 @@ function Probe(props: {
props.seen.push(evt)
props.workspaces.push(workspace)
})
- props.onReady({ project, sdk })
+ props.onReady({ project, client })
})
return
@@ -137,7 +136,7 @@ describe("useEvent", () => {
{
level: "debug",
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 {
@@ -194,25 +193,24 @@ describe("useEvent", () => {
const attempts: number[] = []
const replacementEvents = createEventStream()
const replacementCalls = createFetch(undefined, replacementEvents)
- const replacement = { client: createClient(replacementCalls.fetch), api: createApi(replacementCalls.fetch) }
- const { app, events, sdk, seen } = await mount(async (attempt) => {
+ const replacement = { api: createApi(replacementCalls.fetch) }
+ const { app, events, client, seen } = await mount(async (attempt) => {
attempts.push(attempt)
return replacement
})
try {
- await wait(() => sdk.connection.status() === "connected")
+ await wait(() => client.connection.status() === "connected")
// Reconnection only runs when the stream is down, never while connected.
expect(attempts).toEqual([])
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" }))
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "rediscovered"))
- expect(sdk.client).toBe(replacement.client)
- expect(sdk.api).toBe(replacement.api)
+ expect(client.api).toBe(replacement.api)
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([
["connecting", 0],
["connected", 0],
@@ -228,22 +226,22 @@ describe("useEvent", () => {
test("keeps the current client when reconnection fails", async () => {
let calls = 0
- const { app, events, sdk, seen } = await mount(async () => {
+ const { app, events, client, seen } = await mount(async () => {
calls += 1
throw new Error("no server")
})
try {
- await wait(() => sdk.connection.status() === "connected")
- const original = sdk.client
+ await wait(() => client.connection.status() === "connected")
+ const original = client.api
events.disconnect()
// Reconnection rejects; the loop retries against the last known transport,
// 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" }))
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 {
app.renderer.destroy()
}
diff --git a/packages/tui/test/fixture/tui-sdk.ts b/packages/tui/test/fixture/tui-client.ts
similarity index 96%
rename from packages/tui/test/fixture/tui-sdk.ts
rename to packages/tui/test/fixture/tui-client.ts
index 8d0f72206f..e60f79e964 100644
--- a/packages/tui/test/fixture/tui-sdk.ts
+++ b/packages/tui/test/fixture/tui-client.ts
@@ -1,5 +1,4 @@
import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client"
-import { createOpencodeClient } from "@opencode-ai/sdk/v2"
export const worktree = "/tmp/opencode"
export const directory = `${worktree}/packages/tui`
@@ -135,10 +134,6 @@ export function createFetch(override?: FetchHandler, events?: ReturnType