Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Part, AssistantMessage, ReasoningPart, TextPart, ToolPart, Message } from "@opencode-ai/sdk"
import type { Part, ReasoningPart, TextPart, ToolPart, Message, AssistantMessage, UserMessage } 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"
Expand All @@ -17,7 +17,20 @@ 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[] }) {
export function Message(props: { message: Message; parts: Part[] }) {
return (
<Switch>
<Match when={props.message.role === "user" && props.message}>
{(userMessage) => <UserMessage message={userMessage()} parts={props.parts} />}
</Match>
<Match when={props.message.role === "assistant" && props.message}>
{(assistantMessage) => <AssistantMessage message={assistantMessage()} parts={props.parts} />}
</Match>
</Switch>
)
}

function AssistantMessage(props: { message: AssistantMessage; parts: Part[] }) {
const filteredParts = createMemo(() => {
return props.parts?.filter((x) => {
if (x.type === "reasoning") return false
Expand All @@ -31,11 +44,26 @@ export function AssistantMessage(props: { message: AssistantMessage; parts: Part
)
}

export function Part(props: { part: Part; message: Message; readonly?: boolean }) {
function UserMessage(props: { message: UserMessage; parts: Part[] }) {
const text = createMemo(() =>
props.parts
?.filter((p) => p.type === "text" && !p.synthetic)
?.map((p) => (p as TextPart).text)
?.join(""),
)
return <div class="text-12-regular text-text-base line-clamp-3">{text()}</div>
}

export function Part(props: { part: Part; message: Message; hideDetails?: boolean }) {
const component = createMemo(() => PART_MAPPING[props.part.type as keyof typeof PART_MAPPING])
return (
<Show when={component()}>
<Dynamic component={component()} part={props.part as any} message={props.message} readonly={props.readonly} />
<Dynamic
component={component()}
part={props.part as any}
message={props.message}
hideDetails={props.hideDetails}
/>
</Show>
)
}
Expand All @@ -62,7 +90,7 @@ function TextPart(props: { part: TextPart; message: Message }) {
)
}

function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean }) {
function ToolPart(props: { part: ToolPart; message: Message; hideDetails?: boolean }) {
const component = createMemo(() => {
const render = ToolRegistry.render(props.part.tool) ?? GenericTool
const metadata = props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
Expand All @@ -75,7 +103,7 @@ function ToolPart(props: { part: ToolPart; message: Message; readonly?: boolean
tool={props.part.tool}
metadata={metadata}
output={props.part.state.status === "completed" ? props.part.state.output : undefined}
readonly={props.readonly}
hideDetails={props.hideDetails}
/>
)
})
Expand All @@ -101,7 +129,7 @@ function BasicTool(props: {
icon: IconProps["name"]
trigger: TriggerTitle | JSX.Element
children?: JSX.Element
readonly?: boolean
hideDetails?: boolean
}) {
const resolved = children(() => props.children)
return (
Expand Down Expand Up @@ -157,12 +185,12 @@ function BasicTool(props: {
</Switch>
</div>
</div>
<Show when={resolved() && !props.readonly}>
<Show when={resolved() && !props.hideDetails}>
<Collapsible.Arrow />
</Show>
</div>
</Collapsible.Trigger>
<Show when={resolved() && !props.readonly}>
<Show when={resolved() && !props.hideDetails}>
<Collapsible.Content>{resolved()}</Collapsible.Content>
</Show>
</Collapsible>
Expand All @@ -173,15 +201,15 @@ function BasicTool(props: {
}

function GenericTool(props: ToolProps<any>) {
return <BasicTool icon="mcp" trigger={{ title: props.tool }} readonly={props.readonly} />
return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
}

type ToolProps<T extends Tool.Info> = {
input: Partial<Tool.InferParameters<T>>
metadata: Partial<Tool.InferMetadata<T>>
tool: string
output?: string
readonly?: boolean
hideDetails?: boolean
}

const ToolRegistry = (() => {
Expand Down
25 changes: 17 additions & 8 deletions packages/desktop/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { Code } from "@/components/code"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { ProgressCircle } from "@/components/progress-circle"
import { AssistantMessage, Part } from "@/components/assistant-message"
import { Message, Part } from "@/components/message"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { DiffChanges } from "@/components/diff-changes"

Expand Down Expand Up @@ -198,6 +198,7 @@ export default function Page() {
}
if (!session) return

local.session.setActive(session.id)
const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))

const text = parts.map((part) => part.content).join("")
Expand Down Expand Up @@ -259,7 +260,6 @@ export default function Page() {
],
},
})
local.session.setActive(session.id)
}

const handleNewSession = () => {
Expand Down Expand Up @@ -639,8 +639,9 @@ export default function Page() {
<For each={local.session.userMessages()}>
{(message) => {
const [expanded, setExpanded] = createSignal(false)
const title = createMemo(() => message.summary?.title)
const parts = createMemo(() => sync.data.part[message.id])
const prompt = createMemo(() => local.session.getMessageText(message))
const title = createMemo(() => message.summary?.title)
const summary = createMemo(() => message.summary?.body)
const assistantMessages = createMemo(() => {
return sync.data.message[activeSession().id]?.filter(
Expand All @@ -665,7 +666,9 @@ export default function Page() {
</h1>
</div>
<Show when={title}>
<div class="-mt-8 text-12-regular text-text-base line-clamp-3">{prompt()}</div>
<div class="-mt-8">
<Message message={message} parts={parts()} />
</div>
</Show>
{/* Response */}
<div class="w-full flex flex-col gap-2">
Expand All @@ -686,7 +689,7 @@ export default function Page() {
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(() => sync.data.part[assistantMessage.id])
return <AssistantMessage message={assistantMessage} parts={parts()} />
return <Message message={assistantMessage} parts={parts()} />
}}
</For>
</div>
Expand Down Expand Up @@ -722,7 +725,9 @@ export default function Page() {
const lastTextPart = createMemo(() =>
sync.data.part[last().id].findLast((p) => p.type === "text"),
)
return <Part message={last()} part={lastTextPart()!} readonly />
return (
<Part message={last()} part={lastTextPart()!} hideDetails />
)
}}
</Match>
<Match when={lastMessageWithReasoning()}>
Expand All @@ -733,7 +738,11 @@ export default function Page() {
),
)
return (
<Part message={last()} part={lastReasoningPart()!} readonly />
<Part
message={last()}
part={lastReasoningPart()!}
hideDetails
/>
)
}}
</Match>
Expand All @@ -745,7 +754,7 @@ export default function Page() {
(p) => p.type === "tool" && p.state.status === "completed",
),
)
return <Part message={last()} part={lastToolPart()!} readonly />
return <Part message={last()} part={lastToolPart()!} hideDetails />
}}
</Show>
</div>
Expand Down
4 changes: 3 additions & 1 deletion packages/opencode/src/session/prompt/summarize.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
You are a helpful AI assistant tasked with summarizing conversations.

When asked to summarize, provide a detailed but concise summary of the conversation.
When asked to summarize, provide a detailed but concise summary of the conversation.
Focus on information that would be helpful for continuing the conversation, including:
- What was done
- What is currently being worked on
- Which files are being modified
- What needs to be done next
- Preserve custom rules from AGENTS.md
- Maintain agent-specific constraints

Your summary should be comprehensive enough to provide context but concise enough to be quickly understood.