Commit - use types from SDK

This commit is contained in:
Shantur Rathore
2025-11-11 21:06:37 +00:00
parent 063a11db76
commit 89dbe43d87
17 changed files with 691 additions and 244 deletions

View File

@@ -7,20 +7,40 @@ import { getLanguageFromPath } from "../lib/markdown"
import { isRenderableDiffText } from "../lib/diff-utils"
import { getToolRenderCache, setToolRenderCache } from "../lib/tool-render-cache"
import { preferences, setDiffViewMode, type DiffViewMode } from "../stores/preferences"
import type { TextPart } from "../types/message"
import type { TextPart, SDKPart, ClientPart } from "../types/message"
type ToolCallPart = Extract<ClientPart, { type: "tool" }>
// Import ToolState types from SDK
type ToolState = import("@opencode-ai/sdk").ToolState
type ToolStateRunning = import("@opencode-ai/sdk").ToolStateRunning
type ToolStateCompleted = import("@opencode-ai/sdk").ToolStateCompleted
type ToolStateError = import("@opencode-ai/sdk").ToolStateError
// Type guards
function isToolStateRunning(state: ToolState): state is ToolStateRunning {
return state.status === "running"
}
function isToolStateCompleted(state: ToolState): state is ToolStateCompleted {
return state.status === "completed"
}
function isToolStateError(state: ToolState): state is ToolStateError {
return state.status === "error"
}
const toolScrollState = new Map<string, { scrollTop: number; atBottom: boolean }>()
function makeRenderCacheKey(
baseId?: string | null,
toolCallId?: string | null,
messageId?: string,
messageVersion?: number,
partVersion?: number,
) {
if (!baseId && !messageId) return undefined
const suffix = `${messageVersion ?? 0}:${partVersion ?? 0}`
const keyBase = baseId || messageId || "tool"
const keyBase = `${messageId}:${toolCallId}`
return `${keyBase}::${suffix}`
}
@@ -55,7 +75,7 @@ function restoreScrollState(id: string, element: HTMLElement) {
interface ToolCallProps {
toolCall: any
toolCall: Extract<ClientPart, { type: "tool" }>
toolCallId?: string
messageId?: string
messageVersion?: number
@@ -122,12 +142,17 @@ interface DiffPayload {
filePath?: string
}
function extractDiffPayload(toolName: string, state: any): DiffPayload | null {
function extractDiffPayload(toolName: string, state: ToolState): DiffPayload | null {
if (!diffCapableTools.has(toolName)) return null
if (!state) return null
const metadata = state.metadata || {}
const candidates = [metadata.diff, state.output, metadata.output]
const metadata = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.metadata || {}
: {}
const output = isToolStateCompleted(state) ? state.output : undefined
const candidates = [metadata.diff, output, metadata.output]
let diffText: string | null = null
for (const candidate of candidates) {
@@ -141,8 +166,12 @@ function extractDiffPayload(toolName: string, state: any): DiffPayload | null {
return null
}
const input = state.input || {}
const filePath = input.filePath || metadata.filePath || input.path
const input = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.input as Record<string, unknown>
: {}
const filePath = (typeof input.filePath === "string" ? input.filePath : undefined) ||
(typeof metadata.filePath === "string" ? metadata.filePath : undefined) ||
(typeof input.path === "string" ? input.path : undefined)
return { diffText, filePath }
}
@@ -202,7 +231,12 @@ export default function ToolCall(props: ToolCallProps) {
createEffect(() => {
if (props.toolCall?.tool !== "task") return
const summarySignature = JSON.stringify(props.toolCall?.state?.metadata?.summary ?? [])
const state = props.toolCall?.state
const summarySignature = JSON.stringify(
state && (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.metadata?.summary ?? []
: []
)
requestAnimationFrame(() => {
void summarySignature
handleScrollRendered()
@@ -288,14 +322,20 @@ export default function ToolCall(props: ToolCallProps) {
const renderToolTitle = () => {
const toolName = props.toolCall?.tool || ""
const state = props.toolCall?.state || {}
const input = state.input || {}
const state = props.toolCall?.state
if (state.status === "pending") {
return renderToolAction()
if (!state) return renderToolAction()
if (state.status === "pending") return renderToolAction()
const input = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? (state.input as Record<string, unknown>)
: {} as Record<string, unknown>
if (isToolStateRunning(state) && state.title) {
return state.title
}
if (state.title) {
if (isToolStateCompleted(state)) {
return state.title
}
@@ -303,20 +343,20 @@ export default function ToolCall(props: ToolCallProps) {
switch (toolName) {
case "read":
if (input.filePath) {
if (typeof input.filePath === "string") {
return `${name} ${getRelativePath(input.filePath)}`
}
return name
case "edit":
case "write":
if (input.filePath) {
if (typeof input.filePath === "string") {
return `${name} ${getRelativePath(input.filePath)}`
}
return name
case "bash":
if (input.description) {
if (typeof input.description === "string") {
return `${name} ${input.description}`
}
return name
@@ -344,7 +384,7 @@ export default function ToolCall(props: ToolCallProps) {
return "Plan"
case "invalid":
if (input.tool) {
if (typeof input.tool === "string") {
return getToolName(input.tool)
}
return name
@@ -454,7 +494,7 @@ export default function ToolCall(props: ToolCallProps) {
)
}
function renderMarkdownTool(toolName: string, state: any) {
function renderMarkdownTool(toolName: string, state: ToolState) {
const content = getMarkdownContent(toolName, state)
if (!content) {
return null
@@ -496,53 +536,69 @@ export default function ToolCall(props: ToolCallProps) {
)
}
function getMarkdownContent(toolName: string, state: any): string | null {
const input = state?.input || {}
const metadata = state?.metadata || {}
function getMarkdownContent(toolName: string, state: ToolState): string | null {
if (!state) return null
const input = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.input as Record<string, unknown>
: {}
const metadata = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.metadata || {}
: {}
switch (toolName) {
case "read": {
const preview = typeof metadata.preview === "string" ? metadata.preview : null
const language = getLanguageFromPath(input.filePath || "")
const language = getLanguageFromPath(typeof input.filePath === "string" ? input.filePath : "")
return ensureMarkdownContent(preview, language, true)
}
case "edit": {
const diffText = typeof metadata.diff === "string" ? metadata.diff : null
const fallback = typeof state.output === "string" ? state.output : null
const fallback = isToolStateCompleted(state) && typeof state.output === "string" ? state.output : null
return ensureMarkdownContent(diffText || fallback, "diff", true)
}
case "write": {
const content = typeof input.content === "string" ? input.content : null
const metadataContent = typeof metadata.content === "string" ? metadata.content : null
const language = getLanguageFromPath(input.filePath || "")
const language = getLanguageFromPath(typeof input.filePath === "string" ? input.filePath : "")
return ensureMarkdownContent(content || metadataContent, language, true)
}
case "patch": {
const patchContent = typeof metadata.diff === "string" ? metadata.diff : null
const fallback = typeof state.output === "string" ? state.output : null
const fallback = isToolStateCompleted(state) && typeof state.output === "string" ? state.output : null
return ensureMarkdownContent(patchContent || fallback, "diff", true)
}
case "bash": {
const command = typeof input.command === "string" && input.command.length > 0 ? `$ ${input.command}` : ""
const outputResult = formatUnknown(metadata.output ?? state.output)
const outputResult = formatUnknown(
isToolStateCompleted(state) ? state.output :
(isToolStateRunning(state) || isToolStateError(state)) && metadata.output ? metadata.output :
undefined
)
const parts = [command, outputResult?.text].filter(Boolean)
const combined = parts.join("\n")
return ensureMarkdownContent(combined, "bash", true)
}
case "webfetch": {
const result = formatUnknown(state.output ?? metadata.output)
const result = formatUnknown(
isToolStateCompleted(state) ? state.output :
(isToolStateRunning(state) || isToolStateError(state)) && metadata.output ? metadata.output :
undefined
)
if (!result) return null
return ensureMarkdownContent(result.text, result.language, true)
}
default: {
const result = formatUnknown(
state.output ?? metadata.output ?? metadata.diff ?? metadata.preview ?? input.content,
isToolStateCompleted(state) ? state.output :
(isToolStateRunning(state) || isToolStateError(state)) && metadata.output ? metadata.output :
metadata.diff ?? metadata.preview ?? input.content,
)
if (!result) return null
return ensureMarkdownContent(result.text, result.language, true)
@@ -618,8 +674,12 @@ export default function ToolCall(props: ToolCallProps) {
}
const renderTodowriteTool = () => {
const state = props.toolCall?.state || {}
const metadata = state.metadata || {}
const state = props.toolCall?.state
if (!state) return null
const metadata = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.metadata || {}
: {}
const todos = metadata.todos || []
if (!Array.isArray(todos) || todos.length === 0) {
@@ -677,8 +737,12 @@ export default function ToolCall(props: ToolCallProps) {
}
const renderTaskTool = () => {
const state = props.toolCall?.state || {}
const metadata = state.metadata || {}
const state = props.toolCall?.state
if (!state) return null
const metadata = (isToolStateRunning(state) || isToolStateCompleted(state) || isToolStateError(state))
? state.metadata || {}
: {}
const summary = metadata.summary || []
if (!Array.isArray(summary) || summary.length === 0) {