refactor(server): canonicalize service API (#31049)
This commit is contained in:
parent
53ff1b57c9
commit
fe0c4f8c74
388 changed files with 7075 additions and 4064 deletions
65
packages/tui/src/feature-plugins/sidebar/context.tsx
Normal file
65
packages/tui/src/feature-plugins/sidebar/context.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
|
||||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo } from "solid-js"
|
||||
|
||||
const id = "internal:sidebar-context"
|
||||
|
||||
const money = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
})
|
||||
|
||||
function View(props: { api: TuiPluginApi; session_id: string }) {
|
||||
const theme = () => props.api.theme.current
|
||||
const msg = createMemo(() => props.api.state.session.messages(props.session_id))
|
||||
const session = createMemo(() => props.api.state.session.get(props.session_id))
|
||||
const cost = createMemo(() => session()?.cost ?? 0)
|
||||
|
||||
const state = createMemo(() => {
|
||||
const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
|
||||
if (!last) {
|
||||
return {
|
||||
tokens: 0,
|
||||
percent: null,
|
||||
}
|
||||
}
|
||||
|
||||
const tokens =
|
||||
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
|
||||
const model = props.api.state.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
|
||||
return {
|
||||
tokens,
|
||||
percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : null,
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box>
|
||||
<text fg={theme().text}>
|
||||
<b>Context</b>
|
||||
</text>
|
||||
<text fg={theme().textMuted}>{state().tokens.toLocaleString()} tokens</text>
|
||||
<text fg={theme().textMuted}>{state().percent ?? 0}% used</text>
|
||||
<text fg={theme().textMuted}>{money.format(cost())} spent</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.slots.register({
|
||||
order: 100,
|
||||
slots: {
|
||||
sidebar_content(_ctx, props) {
|
||||
return <View api={api} session_id={props.session_id} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: BuiltinTuiPlugin = {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
70
packages/tui/src/feature-plugins/sidebar/files.tsx
Normal file
70
packages/tui/src/feature-plugins/sidebar/files.tsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo, For, Show, createSignal } from "solid-js"
|
||||
import { Locale } from "../../util/locale"
|
||||
|
||||
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">
|
||||
<text fg={theme().textMuted} wrapMode="none">
|
||||
{Locale.truncateLeft(item.file, Math.max(2, 36 - changeCountWidth(item)))}
|
||||
</text>
|
||||
<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
|
||||
97
packages/tui/src/feature-plugins/sidebar/footer.tsx
Normal file
97
packages/tui/src/feature-plugins/sidebar/footer.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo, Show } from "solid-js"
|
||||
import { abbreviateHome, useTuiEnvironment } from "../../runtime"
|
||||
|
||||
const id = "internal:sidebar-footer"
|
||||
|
||||
function View(props: { api: TuiPluginApi; sessionID: string }) {
|
||||
const environment = useTuiEnvironment()
|
||||
const theme = () => props.api.theme.current
|
||||
const has = createMemo(() =>
|
||||
props.api.state.provider.some(
|
||||
(item) => item.id !== "opencode" || Object.values(item.models).some((model) => model.cost?.input !== 0),
|
||||
),
|
||||
)
|
||||
const done = createMemo(() => props.api.kv.get("dismissed_getting_started", false))
|
||||
const show = createMemo(() => !has() && !done())
|
||||
const path = createMemo(() => {
|
||||
const session = props.api.state.session.get(props.sessionID)
|
||||
const dir = session?.directory || props.api.state.path.directory || environment.cwd
|
||||
const out = abbreviateHome(dir, environment.paths.home)
|
||||
const branch = session?.directory === props.api.state.path.directory ? props.api.state.vcs?.branch : undefined
|
||||
const text = branch ? out + ":" + branch : out
|
||||
const list = text.split("/")
|
||||
return {
|
||||
parent: list.slice(0, -1).join("/"),
|
||||
name: list.at(-1) ?? "",
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box gap={1}>
|
||||
<Show when={show()}>
|
||||
<box
|
||||
backgroundColor={theme().backgroundElement}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
>
|
||||
<text flexShrink={0} fg={theme().text}>
|
||||
⬖
|
||||
</text>
|
||||
<box flexGrow={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme().text}>
|
||||
<b>Getting started</b>
|
||||
</text>
|
||||
<text fg={theme().textMuted} onMouseDown={() => props.api.kv.set("dismissed_getting_started", true)}>
|
||||
✕
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme().textMuted}>OpenCode includes free models so you can start immediately.</text>
|
||||
<text fg={theme().textMuted}>
|
||||
Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc
|
||||
</text>
|
||||
<box flexDirection="row" gap={1} justifyContent="space-between">
|
||||
<text fg={theme().text}>Connect provider</text>
|
||||
<text fg={theme().textMuted}>/connect</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
<text>
|
||||
<span style={{ fg: theme().textMuted }}>{path().parent}/</span>
|
||||
<span style={{ fg: theme().text }}>{path().name}</span>
|
||||
</text>
|
||||
<text fg={theme().textMuted}>
|
||||
<span style={{ fg: theme().success }}>•</span> <b>Open</b>
|
||||
<span style={{ fg: theme().text }}>
|
||||
<b>Code</b>
|
||||
</span>{" "}
|
||||
<span>{props.api.app.version}</span>
|
||||
</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.slots.register({
|
||||
order: 100,
|
||||
slots: {
|
||||
sidebar_footer(_ctx, props) {
|
||||
return <View api={api} sessionID={props.session_id} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: BuiltinTuiPlugin = {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
65
packages/tui/src/feature-plugins/sidebar/lsp.tsx
Normal file
65
packages/tui/src/feature-plugins/sidebar/lsp.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo, For, Show, createSignal } from "solid-js"
|
||||
|
||||
const id = "internal:sidebar-lsp"
|
||||
|
||||
function View(props: { api: TuiPluginApi }) {
|
||||
const [open, setOpen] = createSignal(true)
|
||||
const theme = () => props.api.theme.current
|
||||
const list = createMemo(() => props.api.state.lsp())
|
||||
const off = createMemo(() => !props.api.state.config.lsp)
|
||||
|
||||
return (
|
||||
<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>LSP</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={list().length <= 2 || open()}>
|
||||
<Show when={list().length === 0}>
|
||||
<text fg={theme().textMuted}>{off() ? "LSPs are disabled" : "LSPs will activate as files are read"}</text>
|
||||
</Show>
|
||||
<For each={list()}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: item.status === "connected" ? theme().success : theme().error,
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme().textMuted}>
|
||||
{item.id} {item.root}
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.slots.register({
|
||||
order: 300,
|
||||
slots: {
|
||||
sidebar_content() {
|
||||
return <View api={api} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: BuiltinTuiPlugin = {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
97
packages/tui/src/feature-plugins/sidebar/mcp.tsx
Normal file
97
packages/tui/src/feature-plugins/sidebar/mcp.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo, For, Match, Show, Switch, createSignal } from "solid-js"
|
||||
|
||||
const id = "internal:sidebar-mcp"
|
||||
|
||||
function View(props: { api: TuiPluginApi }) {
|
||||
const [open, setOpen] = createSignal(true)
|
||||
const theme = () => props.api.theme.current
|
||||
const list = createMemo(() => props.api.state.mcp())
|
||||
const on = createMemo(() => list().filter((item) => item.status === "connected").length)
|
||||
const bad = createMemo(
|
||||
() =>
|
||||
list().filter(
|
||||
(item) =>
|
||||
item.status === "failed" || item.status === "needs_auth" || item.status === "needs_client_registration",
|
||||
).length,
|
||||
)
|
||||
|
||||
const dot = (status: string) => {
|
||||
if (status === "connected") return theme().success
|
||||
if (status === "failed") return theme().error
|
||||
if (status === "disabled") return theme().textMuted
|
||||
if (status === "needs_auth") return theme().warning
|
||||
if (status === "needs_client_registration") return theme().error
|
||||
return theme().textMuted
|
||||
}
|
||||
|
||||
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>MCP</b>
|
||||
<Show when={!open()}>
|
||||
<span style={{ fg: theme().textMuted }}>
|
||||
{" "}
|
||||
({on()} active{bad() > 0 ? `, ${bad()} error${bad() > 1 ? "s" : ""}` : ""})
|
||||
</span>
|
||||
</Show>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={list().length <= 2 || open()}>
|
||||
<For each={list()}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: dot(item.status),
|
||||
}}
|
||||
>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme().text} wrapMode="word">
|
||||
{item.name}{" "}
|
||||
<span style={{ fg: theme().textMuted }}>
|
||||
<Switch fallback={item.status}>
|
||||
<Match when={item.status === "connected"}>Connected</Match>
|
||||
<Match when={item.status === "failed"}>
|
||||
<i>{item.error}</i>
|
||||
</Match>
|
||||
<Match when={item.status === "disabled"}>Disabled</Match>
|
||||
<Match when={item.status === "needs_auth"}>Needs auth</Match>
|
||||
<Match when={item.status === "needs_client_registration"}>Needs client ID</Match>
|
||||
</Switch>
|
||||
</span>
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.slots.register({
|
||||
order: 200,
|
||||
slots: {
|
||||
sidebar_content() {
|
||||
return <View api={api} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: BuiltinTuiPlugin = {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
49
packages/tui/src/feature-plugins/sidebar/todo.tsx
Normal file
49
packages/tui/src/feature-plugins/sidebar/todo.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { createMemo, For, Show, createSignal } from "solid-js"
|
||||
import { TodoItem } from "../../component/todo-item"
|
||||
|
||||
const id = "internal:sidebar-todo"
|
||||
|
||||
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.todo(props.session_id))
|
||||
const show = createMemo(() => list().length > 0 && list().some((item) => item.status !== "completed"))
|
||||
|
||||
return (
|
||||
<Show when={show()}>
|
||||
<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>Todo</b>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={list().length <= 2 || open()}>
|
||||
<For each={list()}>{(item) => <TodoItem status={item.status} content={item.content} />}</For>
|
||||
</Show>
|
||||
</box>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
const tui: TuiPlugin = async (api) => {
|
||||
api.slots.register({
|
||||
order: 400,
|
||||
slots: {
|
||||
sidebar_content(_ctx, props) {
|
||||
return <View api={api} session_id={props.session_id} />
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const plugin: BuiltinTuiPlugin = {
|
||||
id,
|
||||
tui,
|
||||
}
|
||||
|
||||
export default plugin
|
||||
Loading…
Add table
Add a link
Reference in a new issue