Parcourir la source

fix(app): own v2 rendering projections

Brendan Allan il y a 2 semaines
Parent
commit
d4105e04e5

+ 2 - 5
bun.lock

@@ -64,7 +64,6 @@
         "@opencode-ai/client": "workspace:*",
         "@opencode-ai/core": "workspace:*",
         "@opencode-ai/schema": "workspace:*",
-        "@opencode-ai/sdk": "file:vendor/opencode-ai-sdk-1.18.8-dev.tgz",
         "@opencode-ai/session-ui": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
         "@pierre/trees": "1.0.0-beta.4",
@@ -4069,10 +4068,10 @@
 
     "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
 
-    "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
-
     "ffi-rs": ["ffi-rs@1.3.2", "", { "optionalDependencies": { "@yuuang/ffi-rs-android-arm64": "1.3.2", "@yuuang/ffi-rs-darwin-arm64": "1.3.2", "@yuuang/ffi-rs-darwin-x64": "1.3.2", "@yuuang/ffi-rs-linux-arm-gnueabihf": "1.3.2", "@yuuang/ffi-rs-linux-arm64-gnu": "1.3.2", "@yuuang/ffi-rs-linux-arm64-musl": "1.3.2", "@yuuang/ffi-rs-linux-x64-gnu": "1.3.2", "@yuuang/ffi-rs-linux-x64-musl": "1.3.2", "@yuuang/ffi-rs-win32-arm64-msvc": "1.3.2", "@yuuang/ffi-rs-win32-ia32-msvc": "1.3.2", "@yuuang/ffi-rs-win32-x64-msvc": "1.3.2" } }, "sha512-4s8dX9VbBw/jd5NOuE3EJRqXaIVdjMyiumeeDzrOhtjQRwp6Bz2za7iksWXTnvTQKV/tTdm1s1w7mObe92zPjQ=="],
 
+    "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
+
     "filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
 
     "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -6327,8 +6326,6 @@
 
     "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
 
-    "@opencode-ai/app/@opencode-ai/sdk": ["@opencode-ai/sdk@vendor/opencode-ai-sdk-1.18.8-dev.tgz", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-C2nfk4x0sPINwE5V6DPkFSuH3PkUmKPWHPzxpXC1j+3Ui5hslLCWJbkk8WcOG1Lyt3C0+yp4ea64v/kmtYCO4w=="],
-
     "@opencode-ai/cli/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
 
     "@opencode-ai/console-app/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],

+ 2 - 10
packages/app/e2e/performance/timeline-stability/fixture.ts

@@ -2,16 +2,8 @@ import { base64Encode } from "@opencode-ai/core/util/encode"
 import { Event } from "@opencode-ai/schema/event"
 import { SessionStatusEvent } from "@opencode-ai/schema/session-status-event"
 import { SessionV1 } from "@opencode-ai/schema/session-v1"
-import type {
-  AssistantMessage,
-  Message,
-  Part,
-  SessionV1Info,
-  SessionStatus,
-  ToolPart,
-  ToolState,
-  UserMessage,
-} from "@opencode-ai/client/promise"
+import type { SessionV1Info, SessionStatus } from "@opencode-ai/client/promise"
+import type { AssistantMessage, Message, Part, ToolPart, ToolState, UserMessage } from "../../../src/types"
 import { expect, type Page } from "@playwright/test"
 import { Schema } from "effect"
 import { mockOpenCodeServer } from "../../utils/mock-server"

+ 1 - 0
packages/app/e2e/tsconfig.json

@@ -12,6 +12,7 @@
     "./performance/unit/visual-stability.test.ts",
     "./reproduction/timeline-suspense/**/*.ts",
     "./reproduction/timeline-suspense/**/*.tsx",
+    "../src/types.ts",
     "../src/pages/session/timeline/observe-element-offset.ts",
     "./regression/new-session-panel-corner.spec.ts",
     "./regression/session-timeline-context-resize.spec.ts",

+ 3 - 3
packages/app/src/context/server-sdk.tsx

@@ -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
 }

+ 7 - 6
packages/app/src/context/server-session.test.ts

@@ -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 [

+ 194 - 21
packages/app/src/types.ts

@@ -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