refactor(schema): apply session review decisions (#35793)
This commit is contained in:
parent
d27746e5b3
commit
ed6ad272ec
142 changed files with 4239 additions and 3174 deletions
|
|
@ -26,14 +26,14 @@ import { createSyntaxStyleMemo, generateSubtleSyntax, useTheme } from "../../con
|
|||
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
|
||||
import { Prompt, type PromptRef } from "../../component/prompt"
|
||||
import type {
|
||||
ModelV2Info,
|
||||
SessionMessage,
|
||||
ModelInfo,
|
||||
SessionMessageInfo,
|
||||
SessionMessageAssistant,
|
||||
SessionMessageAssistantReasoning,
|
||||
SessionMessageAssistantText,
|
||||
SessionMessageAssistantTool,
|
||||
SessionMessageUser,
|
||||
SessionV2Info,
|
||||
SessionInfo,
|
||||
} from "@opencode-ai/sdk/v2"
|
||||
import { useLocal } from "../../context/local"
|
||||
import { Locale } from "../../util/locale"
|
||||
|
|
@ -130,7 +130,7 @@ const context = createContext<{
|
|||
showGenericToolOutput: () => boolean
|
||||
groupExploration: () => boolean
|
||||
diffWrapMode: () => "word" | "none"
|
||||
models: () => ModelV2Info[]
|
||||
models: () => ModelInfo[]
|
||||
tui: ReturnType<typeof useTuiConfig>
|
||||
}>()
|
||||
|
||||
|
|
@ -172,16 +172,6 @@ export function Session() {
|
|||
})
|
||||
onCleanup(() => setEpilogue())
|
||||
const messages = sessionMessages
|
||||
const transientCompaction = createMemo(() => {
|
||||
if (
|
||||
messages().some(
|
||||
(message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"),
|
||||
)
|
||||
)
|
||||
return
|
||||
const text = data.session.compaction(route.sessionID)
|
||||
return text === undefined ? undefined : { text }
|
||||
})
|
||||
const descendantSessionIDs = createMemo(() => {
|
||||
if (session()?.parentID) return []
|
||||
return data.session.family(route.sessionID).filter((id) => id !== route.sessionID)
|
||||
|
|
@ -936,9 +926,6 @@ export function Session() {
|
|||
/>
|
||||
)}
|
||||
</For>
|
||||
<Show when={transientCompaction()}>
|
||||
{(compaction) => <CompactionMessage status="running" text={compaction().text} />}
|
||||
</Show>
|
||||
<BackgroundToolHint messages={messages()} />
|
||||
<Show when={session()?.revert?.messageID}>
|
||||
<RevertMessage
|
||||
|
|
@ -1024,7 +1011,7 @@ export function Session() {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionRowView(props: { row: SessionRow; message: (messageID: string) => SessionMessage | undefined }) {
|
||||
function SessionRowView(props: { row: SessionRow; message: (messageID: string) => SessionMessageInfo | undefined }) {
|
||||
return (
|
||||
<box marginTop={1} flexShrink={0}>
|
||||
<Switch>
|
||||
|
|
@ -1062,7 +1049,7 @@ function SessionRowView(props: { row: SessionRow; message: (messageID: string) =
|
|||
)
|
||||
}
|
||||
|
||||
function BackgroundToolHint(props: { messages: SessionMessage[] }) {
|
||||
function BackgroundToolHint(props: { messages: SessionMessageInfo[] }) {
|
||||
const { theme } = useTheme()
|
||||
const shortcut = useCommandShortcut("session.background")
|
||||
const visible = createMemo(() => {
|
||||
|
|
@ -1090,14 +1077,14 @@ function BackgroundToolHint(props: { messages: SessionMessage[] }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionMessageView(props: { message: SessionMessage }) {
|
||||
function SessionMessageView(props: { message: SessionMessageInfo }) {
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.message.type === "user"}>
|
||||
<UserMessage message={props.message as SessionMessageUser} />
|
||||
</Match>
|
||||
<Match when={props.message.type === "shell"}>
|
||||
<ShellMessage message={props.message as Extract<SessionMessage, { type: "shell" }>} />
|
||||
<ShellMessage message={props.message as Extract<SessionMessageInfo, { type: "shell" }>} />
|
||||
</Match>
|
||||
<Match when={props.message.type === "agent-switched" || props.message.type === "model-switched"}>
|
||||
<SessionSwitchMessageV2 message={props.message} />
|
||||
|
|
@ -1106,17 +1093,17 @@ function SessionMessageView(props: { message: SessionMessage }) {
|
|||
when={props.message.type === "system" || props.message.type === "synthetic" || props.message.type === "skill"}
|
||||
>
|
||||
<Show when={props.message.type === "skill"} fallback={<SessionNoticeMessageV2 message={props.message} />}>
|
||||
<SessionSkillMessage message={props.message as Extract<SessionMessage, { type: "skill" }>} />
|
||||
<SessionSkillMessage message={props.message as Extract<SessionMessageInfo, { type: "skill" }>} />
|
||||
</Show>
|
||||
</Match>
|
||||
<Match when={props.message.type === "compaction"}>
|
||||
<CompactionMessage message={props.message as Extract<SessionMessage, { type: "compaction" }>} />
|
||||
<CompactionMessage message={props.message as Extract<SessionMessageInfo, { type: "compaction" }>} />
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionPartView(props: { partRef: PartRef; message: (messageID: string) => SessionMessage | undefined }) {
|
||||
function SessionPartView(props: { partRef: PartRef; message: (messageID: string) => SessionMessageInfo | undefined }) {
|
||||
const message = createMemo(() => props.message(props.partRef.messageID))
|
||||
const part = createMemo(() => {
|
||||
const item = message()
|
||||
|
|
@ -1150,7 +1137,7 @@ function SessionGroupView(props: {
|
|||
refs: PartRef[]
|
||||
pending: PartRef[]
|
||||
completed: boolean
|
||||
message: (messageID: string) => SessionMessage | undefined
|
||||
message: (messageID: string) => SessionMessageInfo | undefined
|
||||
}) {
|
||||
const { theme } = useTheme()
|
||||
const ctx = use()
|
||||
|
|
@ -1260,7 +1247,7 @@ function AssistantFooter(props: { message: SessionMessageAssistant }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionSwitchMessageV2(props: { message: SessionMessage }) {
|
||||
function SessionSwitchMessageV2(props: { message: SessionMessageInfo }) {
|
||||
const ctx = use()
|
||||
const { theme } = useTheme()
|
||||
const text = () => {
|
||||
|
|
@ -1276,7 +1263,7 @@ function SessionSwitchMessageV2(props: { message: SessionMessage }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionNoticeMessageV2(props: { message: SessionMessage }) {
|
||||
function SessionNoticeMessageV2(props: { message: SessionMessageInfo }) {
|
||||
const { theme } = useTheme()
|
||||
const text = () => {
|
||||
if (props.message.type === "system") return "Instructions updated"
|
||||
|
|
@ -1290,7 +1277,7 @@ function SessionNoticeMessageV2(props: { message: SessionMessage }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SessionSkillMessage(props: { message: Extract<SessionMessage, { type: "skill" }> }) {
|
||||
function SessionSkillMessage(props: { message: Extract<SessionMessageInfo, { type: "skill" }> }) {
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
<InlineToolRow icon="→" color={theme.textMuted} pending="Skill" complete={true}>
|
||||
|
|
@ -1300,7 +1287,7 @@ function SessionSkillMessage(props: { message: Extract<SessionMessage, { type: "
|
|||
}
|
||||
|
||||
function CompactionMessage(props: {
|
||||
message?: Extract<SessionMessage, { type: "compaction" }>
|
||||
message?: Extract<SessionMessageInfo, { type: "compaction" }>
|
||||
status?: "running"
|
||||
text?: string
|
||||
}) {
|
||||
|
|
@ -1308,9 +1295,10 @@ function CompactionMessage(props: {
|
|||
const kv = useKV()
|
||||
const { theme, syntax } = useTheme()
|
||||
const status = () => props.message?.status ?? props.status
|
||||
const text = () => props.message?.summary ?? props.text ?? ""
|
||||
const text = () =>
|
||||
props.message?.status === "failed" ? props.message.error.message : (props.message?.summary ?? props.text ?? "")
|
||||
const color = () => (status() === "failed" ? theme.error : status() === "completed" ? theme.success : theme.textMuted)
|
||||
const border = () => (status() === "queued" ? theme.border : color())
|
||||
const border = color
|
||||
return (
|
||||
<box>
|
||||
<box flexDirection="row" alignItems="center">
|
||||
|
|
@ -1328,11 +1316,8 @@ function CompactionMessage(props: {
|
|||
<Match when={status() === "failed"}>
|
||||
<text fg={color()}>✗</text>
|
||||
</Match>
|
||||
<Match when={status() === "queued"}>
|
||||
<text fg={color()}>◇</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
<text fg={color()}>{status() === "queued" ? "Compaction queued" : "Compaction"}</text>
|
||||
<text fg={color()}>Compaction</text>
|
||||
</box>
|
||||
<box border={["top"]} borderColor={border()} flexGrow={1} />
|
||||
</box>
|
||||
|
|
@ -1363,7 +1348,7 @@ function statusLabel(status: "added" | "modified" | "deleted") {
|
|||
function RevertMessage(props: {
|
||||
count: number
|
||||
files: ReadonlyArray<{
|
||||
readonly path: string
|
||||
readonly file: string
|
||||
readonly status: "added" | "modified" | "deleted"
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
|
|
@ -1412,7 +1397,7 @@ function RevertMessage(props: {
|
|||
<box flexDirection="row" gap={1} flexShrink={0}>
|
||||
<text fg={theme.textMuted}>{statusLabel(file.status)}</text>
|
||||
<text fg={theme.text} wrapMode="none">
|
||||
{Locale.truncateLeft(file.path, 60)}
|
||||
{Locale.truncateLeft(file.file, 60)}
|
||||
</text>
|
||||
<Show when={file.additions > 0}>
|
||||
<text fg={theme.diffAdded}>+{file.additions}</text>
|
||||
|
|
@ -1433,7 +1418,7 @@ function RevertMessage(props: {
|
|||
)
|
||||
}
|
||||
|
||||
function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell" }> }) {
|
||||
function ShellMessage(props: { message: Extract<SessionMessageInfo, { type: "shell" }> }) {
|
||||
const { theme } = useTheme()
|
||||
const output = createMemo(() => stripAnsi(props.message.output?.output.trim() ?? ""))
|
||||
|
||||
|
|
@ -1448,7 +1433,7 @@ function ShellMessage(props: { message: Extract<SessionMessage, { type: "shell"
|
|||
customBorderChars={SplitBorder.customBorderChars}
|
||||
borderColor={theme.background}
|
||||
>
|
||||
<text fg={theme.text}>$ {props.message.shell.command}</text>
|
||||
<text fg={theme.text}>$ {props.message.command}</text>
|
||||
<Show when={output()}>
|
||||
<text fg={theme.textMuted}>{output()}</text>
|
||||
</Show>
|
||||
|
|
@ -1560,7 +1545,7 @@ function AssistantMessage(props: { message: SessionMessageAssistant; last: boole
|
|||
.map((part) =>
|
||||
part.type === "tool" &&
|
||||
["read", "glob", "grep"].includes(toolDisplay(part.name)) &&
|
||||
part.state.status !== "pending"
|
||||
part.state.status !== "streaming"
|
||||
? part
|
||||
: undefined,
|
||||
)
|
||||
|
|
@ -1832,7 +1817,7 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
|
|||
const data = useData()
|
||||
const display = createMemo(() => toolDisplay(props.part.name))
|
||||
const activeBackgroundWork = createMemo(() => {
|
||||
if (props.part.state.status === "pending") return false
|
||||
if (props.part.state.status === "streaming") return false
|
||||
if (display() === "shell") {
|
||||
const shellID = stringValue(props.part.state.structured.shellID)
|
||||
return Boolean(shellID && data.shell.get(shellID))
|
||||
|
|
@ -1856,13 +1841,13 @@ function ToolPart(props: { part: SessionMessageAssistantTool }) {
|
|||
|
||||
const toolprops = {
|
||||
get metadata() {
|
||||
return props.part.state.status === "pending" ? {} : props.part.state.structured
|
||||
return props.part.state.status === "streaming" ? {} : props.part.state.structured
|
||||
},
|
||||
get input() {
|
||||
return typeof props.part.state.input === "string" ? {} : props.part.state.input
|
||||
},
|
||||
get output() {
|
||||
if (props.part.state.status === "pending") return undefined
|
||||
if (props.part.state.status === "streaming") return undefined
|
||||
return props.part.state.content
|
||||
.flatMap((content) => (content.type === "text" ? [content.text] : [content.name ?? content.uri]))
|
||||
.join("\n")
|
||||
|
|
@ -2187,7 +2172,7 @@ function Shell(props: ToolProps) {
|
|||
const isRunning = createMemo(() => props.part.state.status === "running" || backgroundRunning())
|
||||
const command = createMemo(() => stringValue(props.input.command))
|
||||
const output = createMemo(() => {
|
||||
if (props.part.state.status === "pending") return ""
|
||||
if (props.part.state.status === "streaming") return ""
|
||||
if (shellID()) return ""
|
||||
const content = props.part.state.content[0]
|
||||
return stripAnsi(content?.type === "text" ? content.text.trim() : "")
|
||||
|
|
@ -2209,7 +2194,7 @@ function Shell(props: ToolProps) {
|
|||
<Show
|
||||
when={command()}
|
||||
fallback={
|
||||
isRunning() || props.part.state.status === "pending" ? (
|
||||
isRunning() || props.part.state.status === "streaming" ? (
|
||||
<Spinner color={color()}>Writing command...</Spinner>
|
||||
) : (
|
||||
<text fg={theme.textMuted}>Writing command...</text>
|
||||
|
|
@ -2418,7 +2403,7 @@ function executeCalls(value: unknown): ExecuteCall[] {
|
|||
function Execute(props: ToolProps) {
|
||||
const ctx = use()
|
||||
const { theme } = useTheme()
|
||||
const isLoading = createMemo(() => props.part.state.status === "pending" || props.part.state.status === "running")
|
||||
const isLoading = createMemo(() => props.part.state.status === "streaming" || props.part.state.status === "running")
|
||||
const calls = createMemo(() => executeCalls(props.metadata.toolCalls))
|
||||
const output = createMemo(() => stripAnsi(props.output?.trim() ?? ""))
|
||||
const hasRuntimeError = createMemo(() => props.metadata.error === true)
|
||||
|
|
@ -2514,7 +2499,7 @@ function Edit(props: ToolProps) {
|
|||
: "# Preparing edit..."
|
||||
}
|
||||
part={props.part}
|
||||
spinner={props.part.state.status === "pending"}
|
||||
spinner={props.part.state.status === "streaming"}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
|
@ -2606,7 +2591,7 @@ function ApplyPatch(props: ToolProps) {
|
|||
: "# Preparing patch..."
|
||||
}
|
||||
part={props.part}
|
||||
spinner={props.part.state.status === "pending"}
|
||||
spinner={props.part.state.status === "streaming"}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
|
@ -2756,15 +2741,15 @@ function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|||
}
|
||||
|
||||
function formatSessionTranscript(
|
||||
session: SessionV2Info,
|
||||
messages: SessionMessage[],
|
||||
session: SessionInfo,
|
||||
messages: SessionMessageInfo[],
|
||||
thinking: boolean,
|
||||
toolDetails: boolean,
|
||||
) {
|
||||
const body = messages.flatMap((message) => {
|
||||
if (message.type === "user") return [`## User\n\n${message.text}`]
|
||||
if (message.type === "shell")
|
||||
return [`## Shell\n\n\`\`\`\n$ ${message.shell.command}\n${message.output?.output ?? ""}\n\`\`\``]
|
||||
return [`## Shell\n\n\`\`\`\n$ ${message.command}\n${message.output?.output ?? ""}\n\`\`\``]
|
||||
if (message.type !== "assistant") return []
|
||||
const content = message.content.flatMap((item) => {
|
||||
if (item.type === "text") return [item.text]
|
||||
|
|
@ -2774,7 +2759,7 @@ function formatSessionTranscript(
|
|||
const output =
|
||||
item.state.status === "error"
|
||||
? item.state.error.message
|
||||
: item.state.status === "pending"
|
||||
: item.state.status === "streaming"
|
||||
? ""
|
||||
: item.state.content
|
||||
.flatMap((entry) => (entry.type === "text" ? [entry.text] : [entry.name ?? entry.uri]))
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export function PermissionPrompt(props: { request: PermissionV2Request; director
|
|||
const message = data.session.message.get(props.request.sessionID, tool.messageID)
|
||||
if (message?.type !== "assistant") return {}
|
||||
const part = message.content.find((part) => part.type === "tool" && part.id === tool.callID)
|
||||
if (part?.type === "tool" && part.state.status !== "pending") return part.state.input
|
||||
if (part?.type === "tool" && part.state.status !== "streaming") return part.state.input
|
||||
return {}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { SessionMessage, SessionMessageAssistant } from "@opencode-ai/sdk/v2"
|
||||
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/sdk/v2"
|
||||
import { createEffect, on, onCleanup, type Accessor } from "solid-js"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useData } from "../../context/data"
|
||||
|
|
@ -88,7 +88,7 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||
{
|
||||
id: message.id,
|
||||
created: message.time.created,
|
||||
input: message.status === "queued" || message.status === "running",
|
||||
input: message.status === "running",
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
|
@ -157,7 +157,7 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||
const isPending = (messageID: string) => {
|
||||
const message = data.session.message.get(sessionID(), messageID)
|
||||
if (message?.type === "user") return data.session.input.has(sessionID(), messageID)
|
||||
return message?.type === "compaction" && (message.status === "queued" || message.status === "running")
|
||||
return message?.type === "compaction" && message.status === "running"
|
||||
}
|
||||
|
||||
const queuedStart = (rows: SessionRow[]) => {
|
||||
|
|
@ -173,7 +173,7 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||
}
|
||||
const subscriptions = [
|
||||
data.on("session.prompt.admitted", input),
|
||||
data.on("session.compaction.admitted", input),
|
||||
data.on("session.compaction.started", message),
|
||||
data.on("session.instructions.updated", message),
|
||||
data.on("session.synthetic", (event) => {
|
||||
if (event.data.sessionID === sessionID() && event.data.description?.trim())
|
||||
|
|
@ -224,11 +224,9 @@ export function createSessionRows(sessionID: Accessor<string>) {
|
|||
return rows
|
||||
}
|
||||
|
||||
export function reduceSessionRows(messages: SessionMessage[], inputs = new Set<string>()) {
|
||||
const isInput = (message: SessionMessage) => inputs.has(message.id)
|
||||
const pendingCompactions = messages.filter(
|
||||
(message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"),
|
||||
)
|
||||
export function reduceSessionRows(messages: SessionMessageInfo[], inputs = new Set<string>()) {
|
||||
const isInput = (message: SessionMessageInfo) => inputs.has(message.id)
|
||||
const pendingCompactions = messages.filter((message) => message.type === "compaction" && message.status === "running")
|
||||
const pending = new Set([...pendingCompactions.map((message) => message.id), ...inputs])
|
||||
return [
|
||||
...messages.filter((message) => !pending.has(message.id)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue