fix(app): own v2 rendering projections

This commit is contained in:
Brendan Allan 2026-07-29 18:41:23 +08:00
commit d4105e04e5
No known key found for this signature in database
GPG key ID: 41E835AEA046A32E
6 changed files with 209 additions and 45 deletions

View file

@ -1,5 +1,5 @@
import type { OpenCodeEvent } from "@opencode-ai/client/promise"
import type { Event } from "@/types"
import type { Event, PermissionRequest } from "@/types"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { makeEventListener } from "@solid-primitives/event-listener"
@ -39,9 +39,9 @@ export function adaptServerEvent(event: OpenCodeEvent): ServerEvent {
event.data.source?.type === "tool"
? { messageID: event.data.source.messageID, callID: event.data.source.callID }
: undefined,
},
} satisfies PermissionRequest,
current: event,
} as ServerEvent
}
}
return { id: event.id, type: event.type, properties: event.data, current: event } as ServerEvent
}

View file

@ -28,6 +28,7 @@ const session = (id: string, parentID?: string): Session => ({
type UserMessage = Extract<Message, { role: "user" }>
type AssistantMessage = Extract<Message, { role: "assistant" }>
type TextPart = Extract<Part, { type: "text" }>
type CurrentToolObject = Extract<SessionMessageAssistantTool["state"], { status: "running" }>["input"]
type MessageResponse = {
data: { info: Message; parts: Part[] }[]
response: { headers: Headers }
@ -97,21 +98,21 @@ function currentMessages(data: MessageResponse["data"]): SessionMessageInfo[] {
if (part.state.status === "running")
return {
status: "running" as const,
input: part.state.input,
metadata: part.state.metadata ?? {},
input: part.state.input as CurrentToolObject,
metadata: (part.state.metadata ?? {}) as CurrentToolObject,
}
if (part.state.status === "error")
return {
status: "error" as const,
input: part.state.input,
input: part.state.input as CurrentToolObject,
error: { type: "tool_error", message: part.state.error },
metadata: part.state.metadata,
metadata: part.state.metadata as CurrentToolObject | undefined,
}
return {
status: "completed" as const,
input: part.state.input,
input: part.state.input as CurrentToolObject,
content: [{ type: "text" as const, text: part.state.output }],
metadata: part.state.metadata,
metadata: part.state.metadata as CurrentToolObject,
}
})()
return [

View file

@ -1,59 +1,45 @@
import type {
AgentPart,
AssistantMessage,
EventSubscribeOutput,
FileDiffInfo,
FileDiffLegacyInfo,
FilePart,
FilePartSource,
Message,
Part,
Project,
QuestionAnswer,
QuestionInfo,
QuestionV2Request,
QuestionRequest,
ReferenceInfo,
SessionInfo,
SessionNotFoundError,
SessionStatus,
SessionV1Info,
SessionsResponse,
TextPart,
ToolPart,
UserMessage,
} from "@opencode-ai/client/promise"
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
export type {
AgentPart,
AssistantMessage,
FilePart,
FilePartSource,
Message,
Part,
Project,
QuestionAnswer,
QuestionRequest,
ReferenceInfo,
SessionNotFoundError,
SessionStatus,
TextPart,
ToolPart,
UserMessage,
}
export type Session = SessionV1Info
export type SessionV2Info = SessionInfo
export type V2SessionListResponse = SessionsResponse
export type QuestionRequest = QuestionV2Request
export type SnapshotFileDiff = FileDiffLegacyInfo
export type VcsFileDiff = FileDiffInfo
export type Event = EventSubscribeOutput extends infer Item
type CurrentEvent = EventSubscribeOutput extends infer Item
? Item extends { type: infer Type extends string; data: infer Data }
? { type: Type; properties: Data }
: never
: never
export type Event =
| Exclude<CurrentEvent, { type: "permission.asked" }>
| { type: "permission.asked"; properties: PermissionRequest }
export type EventSessionError = Extract<Event, { type: "session.error" }>
export type PermissionRequest = {
@ -66,6 +52,193 @@ export type PermissionRequest = {
tool?: { messageID: string; callID: string }
}
type MessageError =
| { name: "ProviderAuthError"; data: { providerID: string; message: string } }
| { name: "UnknownError"; data: { message: string; ref?: string } }
| { name: "MessageOutputLengthError"; data: Record<string, unknown> }
| { name: "MessageAbortedError"; data: { message: string } }
| { name: "StructuredOutputError"; data: { message: string; retries: number } }
| { name: "ContextOverflowError"; data: { message: string; responseBody?: string } }
| { name: "ContentFilterError"; data: { message: string } }
| {
name: "APIError"
data: {
message: string
statusCode?: number
isRetryable: boolean
responseHeaders?: Record<string, string>
responseBody?: string
metadata?: Record<string, string>
}
}
export type UserMessage = {
id: string
sessionID: string
role: "user"
time: { created: number }
format?: { type: "text" } | { type: "json_schema"; schema: Record<string, unknown>; retryCount?: number }
summary?: { title?: string; body?: string; diffs: SnapshotFileDiff[] }
agent: string
model: { providerID: string; modelID: string; variant?: string }
system?: string
tools?: Record<string, boolean>
}
export type AssistantMessage = {
id: string
sessionID: string
role: "assistant"
time: { created: number; completed?: number }
error?: MessageError
parentID: string
modelID: string
providerID: string
mode: string
agent: string
path: { cwd: string; root: string }
summary?: boolean
cost: number
tokens: {
total?: number
input: number
output: number
reasoning: number
cache: { read: number; write: number }
}
structured?: unknown
variant?: string
finish?: string
}
export type Message = UserMessage | AssistantMessage
type PartBase = { id: string; sessionID: string; messageID: string }
export type TextPart = PartBase & {
type: "text"
text: string
synthetic?: boolean
ignored?: boolean
time?: { start: number; end?: number }
metadata?: Record<string, unknown>
}
type FilePartSourceText = { value: string; start: number; end: number }
type FileSource = { text: FilePartSourceText; type: "file"; path: string }
type SymbolSource = {
text: FilePartSourceText
type: "symbol"
path: string
range: { start: { line: number; character: number }; end: { line: number; character: number } }
name: string
kind: number
}
type ResourceSource = { text: FilePartSourceText; type: "resource"; clientName: string; uri: string }
export type FilePartSource = FileSource | SymbolSource | ResourceSource
export type FilePart = PartBase & {
type: "file"
mime: string
filename?: string
url: string
source?: FilePartSource
}
export type ToolState =
| { status: "pending"; input: Record<string, unknown>; raw: string }
| {
status: "running"
input: Record<string, unknown>
title?: string
metadata?: Record<string, unknown>
time: { start: number }
}
| {
status: "completed"
input: Record<string, unknown>
output: string
title: string
metadata: Record<string, unknown>
time: { start: number; end: number; compacted?: number }
attachments?: FilePart[]
}
| {
status: "error"
input: Record<string, unknown>
error: string
metadata?: Record<string, unknown>
time: { start: number; end: number }
}
export type ToolPart = PartBase & {
type: "tool"
callID: string
tool: string
state: ToolState
metadata?: Record<string, unknown>
}
export type AgentPart = PartBase & {
type: "agent"
name: string
source?: { value: string; start: number; end: number }
}
type ReasoningPart = PartBase & {
type: "reasoning"
text: string
metadata?: Record<string, unknown>
time: { start: number; end?: number }
}
type SubtaskPart = PartBase & {
type: "subtask"
prompt: string
description: string
agent: string
model?: { providerID: string; modelID: string }
command?: string
}
type StepStartPart = PartBase & { type: "step-start"; snapshot?: string }
type StepFinishPart = PartBase & {
type: "step-finish"
reason: string
snapshot?: string
cost: number
tokens: AssistantMessage["tokens"]
}
type SnapshotPart = PartBase & { type: "snapshot"; snapshot: string }
type PatchPart = PartBase & { type: "patch"; hash: string; files: string[] }
type RetryPart = PartBase & {
type: "retry"
attempt: number
error: Extract<MessageError, { name: "APIError" }>
time: { created: number }
}
type CompactionPart = PartBase & {
type: "compaction"
auto: boolean
overflow?: boolean
tail_start_id?: string
}
export type Part =
| TextPart
| SubtaskPart
| ReasoningPart
| FilePart
| ToolPart
| StepStartPart
| StepFinishPart
| SnapshotPart
| PatchPart
| AgentPart
| RetryPart
| CompactionPart
export type Todo = {
content: string
status: string