import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart, Message } from "@opencode-ai/sdk"
import { children, Component, createMemo, For, Match, Show, Switch, type JSX } from "solid-js"
import { Dynamic } from "solid-js/web"
import { Markdown } from "./markdown"
import { Checkbox, Collapsible, Diff, Icon, IconProps } from "@opencode-ai/ui"
import { getDirectory, getFilename } from "@/utils"
import type { Tool } from "opencode/tool/tool"
import type { ReadTool } from "opencode/tool/read"
import type { ListTool } from "opencode/tool/ls"
import type { GlobTool } from "opencode/tool/glob"
import type { GrepTool } from "opencode/tool/grep"
import type { WebFetchTool } from "opencode/tool/webfetch"
import type { TaskTool } from "opencode/tool/task"
import type { BashTool } from "opencode/tool/bash"
import type { EditTool } from "opencode/tool/edit"
import type { WriteTool } from "opencode/tool/write"
import type { TodoWriteTool } from "opencode/tool/todo"
import { DiffChanges } from "./diff-changes"
export function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
const filteredParts = createMemo(() => {
return props.parts?.filter((x) => {
if (x.type === "reasoning") return false
return x.type !== "tool" || x.tool !== "todoread"
})
})
return (
)
}
export function Part(props: { part: Part; message: Message; readonly?: boolean }) {
const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
return (
)
}
const PART_MAPPING = {
text: TextPart,
tool: ToolPart,
reasoning: ReasoningPart,
}
function ReasoningPart(props: { part: ReasoningPart; message: Message }) {
return (
)
}
function TextPart(props: { part: TextPart; message: Message }) {
return (
)
}
function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean }) {
const component = createMemo(() => {
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
const input = props.part.state.status === "completed" ? props.part.state.input : {}
return (
)
})
return {component()}
}
type TriggerTitle = {
title: string
titleClass?: string
subtitle?: string
subtitleClass?: string
args?: string[]
argsClass?: string
action?: JSX.Element
}
const isTriggerTitle = (val: any): val is TriggerTitle => {
return typeof val === "object" && val !== null && "title" in val && !(val instanceof Node)
}
function BasicTool(props: {
icon: IconProps["name"]
trigger: TriggerTitle | JSX.Element
children?: JSX.Element
readonly?: boolean
}) {
const resolved = children(() => props.children)
return (
{(trigger) => (
{trigger().title}
{trigger().subtitle}
{(arg) => (
{arg}
)}
{trigger().action}
)}
{props.trigger as JSX.Element}
{resolved()}
// <>
// {props.part.state.error.replace("Error: ", "")}
// >
)
}
function GenericTool(props: ToolProps) {
return
}
type ToolProps = {
input: Partial>
metadata: Partial>
tool: string
output?: string
readonly?: boolean
}
const ToolRegistry = (() => {
const state: Record<
string,
{
name: string
render?: Component>
}
> = {}
function register(input: { name: string; render?: Component> }) {
state[input.name] = input
return input
}
return {
register,
render(name: string) {
return state[name]?.render
},
}
})()
ToolRegistry.register({
name: "read",
render(props) {
return (
)
},
})
ToolRegistry.register({
name: "list",
render(props) {
return (
{props.output}
)
},
})
ToolRegistry.register({
name: "glob",
render(props) {
return (
{props.output}
)
},
})
ToolRegistry.register({
name: "grep",
render(props) {
const args = []
if (props.input.pattern) args.push("pattern=" + props.input.pattern)
if (props.input.include) args.push("include=" + props.input.include)
return (
{props.output}
)
},
})
ToolRegistry.register({
name: "webfetch",
render(props) {
return (
),
}}
>
{props.output}
)
},
})
ToolRegistry.register({
name: "task",
render(props) {
return (
{props.output}
)
},
})
ToolRegistry.register({
name: "bash",
render(props) {
return (
{props.output}
)
},
})
ToolRegistry.register({
name: "edit",
render(props) {
return (
Edit
{getDirectory(props.input.filePath!)}
{getFilename(props.input.filePath ?? "")}
}
>
)
},
})
ToolRegistry.register({
name: "write",
render(props) {
return (
Write
{getDirectory(props.input.filePath!)}
{getFilename(props.input.filePath ?? "")}
{/* */}
}
>
{props.output}
)
},
})
ToolRegistry.register({
name: "todowrite",
render(props) {
return (
t.status === "completed").length}/${props.input.todos?.length}`,
}}
>
{(todo) => (
{todo.content}
)}
)
},
})