Jelajahi Sumber

refactor(tui): migrate v2 calls to new client

Dax Raad 1 bulan lalu
induk
melakukan
e2eed76101

+ 1 - 6
packages/client/script/build.ts

@@ -17,12 +17,7 @@ await Effect.runPromise(
     [
       write(
         emitPromise(promiseContract, {
-          outputTypes: {
-            "events.subscribe": {
-              name: "OpenCodeEventEncoded",
-              import: 'import type { OpenCodeEventEncoded } from "@opencode-ai/protocol/groups/event"',
-            },
-          },
+          mutableOutputs: true,
         }),
         fileURLToPath(new URL("../src/promise/generated", import.meta.url)),
       ),

File diff ditekan karena terlalu besar
+ 676 - 836
packages/client/src/promise/generated/types.ts


+ 10 - 3
packages/httpapi-codegen/src/index.ts

@@ -296,6 +296,7 @@ export function emitPromise(
   contract: Contract,
   options?: {
     readonly outputTypes?: Readonly<Record<string, { readonly name: string; readonly import: string }>>
+    readonly mutableOutputs?: boolean
   },
 ): Output {
   const groups = contract.groups
@@ -305,7 +306,7 @@ export function emitPromise(
   return {
     operations: promiseOperations(groups),
     files: [
-      { path: "types.ts", content: renderPromiseTypes(groups, options?.outputTypes) },
+      { path: "types.ts", content: renderPromiseTypes(groups, options?.outputTypes, options?.mutableOutputs ?? false) },
       {
         path: "client-error.ts",
         content: `export type ClientErrorReason = "Transport" | "UnexpectedStatus" | "UnsupportedContentType" | "MalformedResponse"\n\nexport class ClientError extends Error {\n  override readonly name = "ClientError"\n  constructor(readonly reason: ClientErrorReason, options?: ErrorOptions) {\n    super(reason, options)\n  }\n}\n`,
@@ -568,6 +569,7 @@ function renderImportedProjection(groups: ReadonlyArray<Group>, endpoints: Reado
 function renderPromiseTypes(
   groups: ReadonlyArray<Group>,
   outputTypes?: Readonly<Record<string, { readonly name: string; readonly import: string }>>,
+  mutableOutputs = false,
 ) {
   const types = new Map<SchemaAST.AST, string>()
   const typeOf = (schema: Schema.Top, decoded = false) => {
@@ -623,20 +625,25 @@ function renderPromiseTypes(
                 : successSchema.events
               : successSchema,
           )
+        const output = mutableOutputs ? mutableType(success) : success
         return [
           ...(promiseInputMode(endpoint) === "none" ? [] : [`export type ${prefix}Input = { ${input} }`]),
-          `export type ${prefix}Output = ${endpoint.unwrapData ? `(${success})["data"]` : success}`,
+          `export type ${prefix}Output = ${endpoint.unwrapData ? `(${output})["data"]` : output}`,
         ]
       }),
     )
     .join("\n\n")
   const json = operations.includes("JsonValue")
-    ? "export type JsonValue = null | boolean | number | string | ReadonlyArray<JsonValue> | { readonly [key: string]: JsonValue }"
+    ? `export type JsonValue = null | boolean | number | string | ${mutableOutputs ? "Array<JsonValue> | { [key: string]: JsonValue }" : "ReadonlyArray<JsonValue> | { readonly [key: string]: JsonValue }"}`
     : ""
   const imports = [...new Set(Object.values(outputTypes ?? {}).map((override) => override.import))]
   return [...imports, json, ...errorTypes, operations].filter(Boolean).join("\n\n")
 }
 
+function mutableType(type: string) {
+  return type.replaceAll("ReadonlyArray<", "Array<").replaceAll(/\breadonly\s+/g, "")
+}
+
 function renderPromiseClient(groups: ReadonlyArray<Group>) {
   const imports = groups.flatMap((group) =>
     group.endpoints.flatMap((endpoint) => {

+ 18 - 0
packages/httpapi-codegen/test/generate.test.ts

@@ -513,6 +513,24 @@ describe("HttpApiCodegen.generate", () => {
     )
   })
 
+  test("emits mutable Promise outputs without restricting inputs", () => {
+    const output = emitPromise(
+      compileContract(
+        api(
+          HttpApiEndpoint.post("create", "/session", {
+            payload: Schema.Struct({ values: Schema.Array(Schema.String) }),
+            success: Schema.Struct({ data: Schema.Array(Schema.Struct({ values: Schema.Array(Schema.String) })) }),
+          }),
+        ),
+      ),
+      { mutableOutputs: true },
+    )
+    const types = output.files.find((file) => file.path === "types.ts")?.content
+
+    expect(types).toContain('readonly "values": ReadonlyArray<string>')
+    expect(types).toContain('export type SessionCreateOutput = ({ "data": Array<{ "values": Array<string> }> })["data"]')
+  })
+
   test("expands Promise references only at identifier boundaries", () => {
     const Session = Schema.Struct({ name: Schema.Literal("Session"), id: Schema.String }).annotate({
       identifier: "Session",

+ 3 - 3
packages/plugin/src/tui.ts

@@ -1,6 +1,5 @@
 import type {
   OpencodeClient,
-  V2Event,
   LspStatus,
   McpStatus,
   Message,
@@ -12,6 +11,7 @@ import type {
   SessionStatus,
   Config as SdkConfig,
 } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
 import type { PromptInput } from "@opencode-ai/schema"
 import type { Types } from "effect"
 import type { CliRenderer, KeyEvent, RGBA, Renderable, SlotMode } from "@opentui/core"
@@ -507,9 +507,9 @@ export type TuiSlots = {
 }
 
 export type TuiEventBus = {
-  on: <Type extends V2Event["type"]>(
+  on: <Type extends OpenCodeEvent["type"]>(
     type: Type,
-    handler: (event: Extract<V2Event, { type: Type }>) => void,
+    handler: (event: Extract<OpenCodeEvent, { type: Type }>) => void,
   ) => () => void
 }
 

+ 2 - 3
packages/tui/src/component/dialog-pair.tsx

@@ -25,9 +25,8 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
   dialog.setCentered(true)
 
   const [server] = createResource(() =>
-    sdk.client.v2.server
-      .get({ throwOnError: true })
-      .then((result) => result.data)
+    sdk.api.server
+      .get()
       .catch((error) => {
         setLoadError(error)
         return undefined

+ 2 - 3
packages/tui/src/component/dialog-session-list.tsx

@@ -44,8 +44,7 @@ export function DialogSessionList() {
         directory: location.directory,
         workspace: location.workspaceID,
       })
-      // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generated client output is readonly; session list UI reuses legacy mutable session types.
-      return { query, sessions: structuredClone(response.data) as SessionInfo[] }
+      return { query, sessions: response.data }
     } catch (error) {
       // A transient transport failure must degrade search, not crash the TUI
       // through the root ErrorBoundary when the errored resource is read.
@@ -141,7 +140,7 @@ export function DialogSessionList() {
               setToDelete(option.value)
               return
             }
-            void sdk.client.v2.session.remove({ sessionID: option.value }, { throwOnError: true }).catch((error) => {
+            void sdk.api.session.remove({ sessionID: option.value }).catch((error) => {
               setToDelete(undefined)
               toast.show({
                 message: `Failed to delete session: ${errorMessage(error)}`,

+ 3 - 4
packages/tui/src/component/prompt/index.tsx

@@ -37,7 +37,7 @@ import { usePromptStash } from "../../prompt/stash"
 import { DialogStash } from "../dialog-stash"
 import { type AutocompleteRef, Autocomplete } from "./autocomplete"
 import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
-import type { SessionInfo, UserMessage } from "@opencode-ai/sdk/v2"
+import type { UserMessage } from "@opencode-ai/sdk/v2"
 import { Locale } from "../../util/locale"
 import { errorMessage } from "../../util/error"
 import { createColors, createFrames } from "../../ui/spinner"
@@ -1027,8 +1027,7 @@ export function Prompt(props: PromptProps) {
       }
 
       sessionID = created.id
-      // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generated client output is readonly; prompt state still uses legacy mutable session types.
-      session = structuredClone(created) as SessionInfo
+      session = created
     }
 
     const inputText = expandTrackedPastedText(
@@ -1064,7 +1063,7 @@ export function Prompt(props: PromptProps) {
 
     if (store.mode === "shell") {
       move.startSubmit()
-      void sdk.client.v2.session.shell({
+      void sdk.api.session.shell({
         sessionID,
         command: inputText,
       })

+ 28 - 42
packages/tui/src/context/data.tsx

@@ -24,8 +24,8 @@ import type {
   SessionInfo,
   Shell,
   SkillInfo,
-  V2Event,
 } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
 import { createStore, produce, reconcile } from "solid-js/store"
 import { createSimpleContext } from "./helper"
 import { useSDK } from "./sdk"
@@ -82,14 +82,6 @@ function locationQuery(ref?: LocationRef) {
   return ref ? { directory: ref.directory, workspace: ref.workspaceID } : undefined
 }
 
-type Mutable<T> =
-  T extends ReadonlyArray<infer U> ? Mutable<U>[] : T extends object ? { -readonly [K in keyof T]: Mutable<T[K]> } : T
-
-function mutable<T>(value: T): Mutable<T> {
-  // oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generated client data is readonly; the TUI store mutates cloned state.
-  return structuredClone(value) as Mutable<T>
-}
-
 export const { use: useData, provider: DataProvider } = createSimpleContext({
   name: "Data",
   init: () => {
@@ -241,7 +233,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
       )
     }
 
-    function handleEvent(event: V2Event) {
+    function handleEvent(event: OpenCodeEvent) {
       switch (event.type) {
         case "session.created":
           void result.session.refresh(event.data.sessionID)
@@ -300,8 +292,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             .then((item) => {
               message.update(event.data.sessionID, (draft, index) => {
                 const position = index.get(item.id)
-                if (position === undefined) return message.append(draft, index, mutable(item))
-                draft[position] = mutable(item)
+                if (position === undefined) return message.append(draft, index, item)
+                draft[position] = item
               })
             })
             .catch((error) => console.error("Failed to load projected model switch message", error))
@@ -312,7 +304,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           break
         case "session.moved":
           if (store.session.info[event.data.sessionID]) {
-            setStore("session", "info", event.data.sessionID, "location", mutable(event.data.location))
+            setStore("session", "info", event.data.sessionID, "location", event.data.location)
             setStore("session", "info", event.data.sessionID, "subpath", event.data.subpath)
           }
           break
@@ -745,11 +737,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           if (store.session.form[event.data.form.sessionID]?.some((form) => form.id === event.data.form.id)) break
           setStore("session", "form", event.data.form.sessionID, [
             ...(store.session.form[event.data.form.sessionID] ?? []),
-            mutable(
-              event.data.form.sessionID === "global"
-                ? { ...event.data.form, location: event.location }
-                : event.data.form,
-            ),
+            event.data.form.sessionID === "global"
+              ? { ...event.data.form, location: event.location }
+              : event.data.form,
           ])
           break
         case "form.replied":
@@ -830,7 +820,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           },
         },
         async refresh(sessionID: string) {
-          setStore("session", "info", sessionID, mutable(await sdk.api.session.get({ sessionID })))
+          setStore("session", "info", sessionID, await sdk.api.session.get({ sessionID }))
           registerSession(sessionID)
         },
         message: {
@@ -846,9 +836,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             return position === undefined ? undefined : messages?.[position]
           },
           async refresh(sessionID: string) {
-            const messages = mutable(
-              (await sdk.api.message.list({ sessionID, limit: 200, order: "desc" })).data,
-            ).toReversed()
+            const messages = (await sdk.api.message.list({ sessionID, limit: 200, order: "desc" })).data.toReversed()
             messageIndex.set(sessionID, new Map(messages.map((message, index) => [message.id, index])))
             setStore("session", "message", sessionID, reconcile(messages))
           },
@@ -858,7 +846,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             return store.session.permission[sessionID]
           },
           async refresh(sessionID: string) {
-            setStore("session", "permission", sessionID, mutable(await sdk.api.permission.list({ sessionID })))
+            setStore("session", "permission", sessionID, await sdk.api.permission.list({ sessionID }))
           },
         },
         form: {
@@ -881,13 +869,11 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
                 ...(store.session.form[sessionID] ?? []).filter(
                   (form) => form.location && locationKey(form.location) !== key,
                 ),
-                ...mutable(
-                  response.data.filter((form) => form.sessionID === "global").map((form) => ({ ...form, location })),
-                ),
+                ...response.data.filter((form) => form.sessionID === "global").map((form) => ({ ...form, location })),
               ])
               return
             }
-            setStore("session", "form", sessionID, mutable(await sdk.api.form.list({ sessionID })))
+            setStore("session", "form", sessionID, await sdk.api.form.list({ sessionID }))
           },
         },
       },
@@ -897,7 +883,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             return store.project.permission[projectID]
           },
           async refresh(projectID: string) {
-            setStore("project", "permission", projectID, mutable(await sdk.api.permission.saved.list({ projectID })))
+            setStore("project", "permission", projectID, await sdk.api.permission.saved.list({ projectID }))
           },
         },
       },
@@ -915,7 +901,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           const key = locationKey(result.location)
           setStore("location", key, {
             ...store.location[key],
-            shell: Object.fromEntries(mutable(result.data).map((info) => [info.id, info])),
+            shell: Object.fromEntries(result.data.map((info) => [info.id, info])),
           })
         },
       },
@@ -936,7 +922,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.agent.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], agent: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], agent: result.data })
           },
         },
         command: {
@@ -946,7 +932,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.command.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], command: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], command: result.data })
           },
         },
         integration: {
@@ -956,7 +942,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.integration.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], integration: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], integration: result.data })
           },
         },
         mcp: {
@@ -964,9 +950,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             return store.location[locationKey(location ?? defaultLocation())]?.mcp
           },
           async refresh(ref?: LocationRef) {
-            const result = await sdk.client.v2.mcp.list({ location: locationQuery(ref) }, { throwOnError: true })
-            const key = locationKey(result.data.location)
-            setStore("location", key, { ...store.location[key], mcp: result.data.data })
+            const result = await sdk.api["server.mcp"].list({ location: locationQuery(ref) })
+            const key = locationKey(result.location)
+            setStore("location", key, { ...store.location[key], mcp: result.data })
           },
         },
         model: {
@@ -976,7 +962,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.model.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], model: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], model: result.data })
           },
         },
         provider: {
@@ -986,7 +972,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.provider.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], provider: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], provider: result.data })
           },
         },
         reference: {
@@ -996,7 +982,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.reference.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], reference: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], reference: result.data })
           },
         },
         skill: {
@@ -1006,7 +992,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
           async refresh(ref?: LocationRef) {
             const result = await sdk.api.skill.list({ location: locationQuery(ref ?? defaultLocation()) })
             const key = locationKey(result.location)
-            setStore("location", key, { ...store.location[key], skill: mutable(result.data) })
+            setStore("location", key, { ...store.location[key], skill: result.data })
           },
         },
       },
@@ -1027,13 +1013,13 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
               "session",
               "info",
               produce((draft) => {
-                for (const session of response.data) draft[session.id] = mutable(session)
+                for (const session of response.data) draft[session.id] = session
               }),
             )
             for (const session of response.data) registerSession(session.id)
           }),
         sdk.api.permission.request.list({ location: locationQuery(defaultLocation()) }).then((response) => {
-          const permissions = mutable(response.data).reduce<Record<string, PermissionV2Request[]>>(
+          const permissions = response.data.reduce<Record<string, PermissionV2Request[]>>(
             (result, request) => ({
               ...result,
               [request.sessionID]: [...(result[request.sessionID] ?? []), request],
@@ -1047,7 +1033,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
             directory: response.location.directory,
             workspaceID: response.location.workspaceID,
           }
-          const forms = mutable(response.data).reduce<Record<string, FormInfo[]>>(
+          const forms = response.data.reduce<Record<string, FormInfo[]>>(
             (result, form) => ({
               ...result,
               [form.sessionID]: [

+ 4 - 4
packages/tui/src/context/event.ts

@@ -1,4 +1,4 @@
-import type { V2Event } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
 import { useSDK } from "./sdk"
 
 type EventMetadata = {
@@ -9,16 +9,16 @@ type EventMetadata = {
 export function useEvent() {
   const sdk = useSDK()
 
-  function subscribe(handler: (event: V2Event, metadata: EventMetadata) => void) {
+  function subscribe(handler: (event: OpenCodeEvent, metadata: EventMetadata) => void) {
     return sdk.event.listen(({ details }) => {
       if (details.type === "server.connected") return
       handler(details, { directory: details.location?.directory, workspace: details.location?.workspaceID })
     })
   }
 
-  function on<T extends V2Event["type"]>(
+  function on<T extends OpenCodeEvent["type"]>(
     type: T,
-    handler: (event: Extract<V2Event, { type: T }>, metadata: EventMetadata) => void,
+    handler: (event: Extract<OpenCodeEvent, { type: T }>, metadata: EventMetadata) => void,
   ) {
     return sdk.event.on(type, (event) => {
       handler(event, { directory: event.location?.directory, workspace: event.location?.workspaceID })

+ 1 - 7
packages/tui/src/context/project.tsx

@@ -1,4 +1,4 @@
-import { batch, onCleanup } from "solid-js"
+import { batch } from "solid-js"
 import type { Path, Workspace } from "@opencode-ai/sdk/v2"
 import { createStore, reconcile } from "solid-js/store"
 import { createSimpleContext } from "./helper"
@@ -66,12 +66,6 @@ export const { use: useProject, provider: ProjectProvider } = createSimpleContex
       })
     }
 
-    onCleanup(
-      sdk.event.on("workspace.status", (event) => {
-        setStore("workspace", "status", event.data.workspaceID, event.data.status)
-      }),
-    )
-
     return {
       data: store,
       project() {

+ 4 - 9
packages/tui/src/context/sdk.tsx

@@ -1,5 +1,5 @@
-import type { OpenCodeClient } from "@opencode-ai/client/promise"
-import type { OpencodeClient, V2Event } from "@opencode-ai/sdk/v2"
+import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client/promise"
+import type { OpencodeClient } from "@opencode-ai/sdk/v2"
 import { createGlobalEmitter } from "@solid-primitives/event-bus"
 import { onCleanup, onMount } from "solid-js"
 import { createStore } from "solid-js/store"
@@ -17,7 +17,7 @@ export type SDKConnectionEvent = {
   }
 }
 
-type SDKEventMap = { [Type in V2Event["type"]]: Extract<V2Event, { type: Type }> }
+type SDKEventMap = { [Type in OpenCodeEvent["type"]]: Extract<OpenCodeEvent, { type: Type }> }
 const connectTimeout = 2_000
 const connectionHistoryLimit = 50
 
@@ -72,12 +72,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
           const error = await (async () => {
             record(attempt === 0 ? "connecting" : "reconnecting", attempt)
             log.info("event stream connecting", { attempt })
-            const response = await client.v2.event.subscribe({
-              signal: connection.signal,
-              sseMaxRetryAttempts: 0,
-              throwOnError: true,
-            })
-            const iterator = response.stream[Symbol.asyncIterator]()
+            const iterator = api.event.subscribe({ signal: connection.signal })[Symbol.asyncIterator]()
             const first = await iterator.next()
             if (abort.signal.aborted || controller.signal.aborted) return
             if (first.done)

+ 2 - 2
packages/tui/src/feature-plugins/system/notifications.ts

@@ -1,10 +1,10 @@
-import type { V2Event } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
 import type { TuiAttentionSoundName, TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
 import type { BuiltinTuiPlugin } from "../builtins"
 
 const id = "internal:notifications"
 
-type SessionError = Extract<V2Event, { type: "session.error" }>["data"]["error"]
+type SessionError = Extract<OpenCodeEvent, { type: "session.error" }>["data"]["error"]
 
 function notify(
   api: TuiPluginApi,

+ 16 - 9
packages/tui/test/cli/cmd/tui/notifications.test.ts

@@ -1,12 +1,13 @@
 import { describe, expect, test } from "bun:test"
 import Notifications from "../../../../src/feature-plugins/system/notifications"
-import type { PermissionRequest, QuestionRequest, Session, V2Event } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
+import type { PermissionRequest, QuestionRequest, Session } from "@opencode-ai/sdk/v2"
 import type { TuiAttentionNotifyInput } from "@opencode-ai/plugin/tui"
 import { createTuiPluginApi } from "../../../fixture/tui-plugin"
 
 async function setup() {
   const notifications: TuiAttentionNotifyInput[] = []
-  const handlers = new Map<V2Event["type"], ((event: V2Event) => void)[]>()
+  const handlers = new Map<OpenCodeEvent["type"], ((event: OpenCodeEvent) => void)[]>()
   const session = (id: string, title: string, parentID?: string): Session => ({
     id,
     title,
@@ -33,9 +34,12 @@ async function setup() {
         },
       },
       event: {
-        on: <Type extends V2Event["type"]>(type: Type, handler: (event: Extract<V2Event, { type: Type }>) => void) => {
+        on: <Type extends OpenCodeEvent["type"]>(
+          type: Type,
+          handler: (event: Extract<OpenCodeEvent, { type: Type }>) => void,
+        ) => {
           const list = handlers.get(type) ?? []
-          const wrapped = handler as (event: V2Event) => void
+          const wrapped = handler as (event: OpenCodeEvent) => void
           list.push(wrapped)
           handlers.set(type, list)
           return () => {
@@ -59,7 +63,7 @@ async function setup() {
 
   return {
     notifications,
-    emit(event: V2Event) {
+    emit(event: OpenCodeEvent) {
       for (const handler of handlers.get(event.type) ?? []) handler(event)
     },
   }
@@ -73,7 +77,10 @@ function question(id: string, sessionID = "session"): QuestionRequest {
   }
 }
 
-function form(id: string, sessionID = "session"): Extract<V2Event, { type: "form.created" }>["data"]["form"] {
+function form(
+  id: string,
+  sessionID = "session",
+): Extract<OpenCodeEvent, { type: "form.created" }>["data"]["form"] {
   return {
     id,
     sessionID,
@@ -98,7 +105,7 @@ function durable(sessionID: string): { aggregateID: string; seq: number; version
   return { aggregateID: sessionID, seq: 0, version: 1 }
 }
 
-function executionStarted(id: string, sessionID = "session"): V2Event {
+function executionStarted(id: string, sessionID = "session"): OpenCodeEvent {
   return {
     id,
     created: 0,
@@ -108,7 +115,7 @@ function executionStarted(id: string, sessionID = "session"): V2Event {
   }
 }
 
-function executionSucceeded(id: string, sessionID = "session"): V2Event {
+function executionSucceeded(id: string, sessionID = "session"): OpenCodeEvent {
   return {
     id,
     created: 0,
@@ -118,7 +125,7 @@ function executionSucceeded(id: string, sessionID = "session"): V2Event {
   }
 }
 
-function executionFailed(id: string, sessionID = "session"): V2Event {
+function executionFailed(id: string, sessionID = "session"): OpenCodeEvent {
   return {
     id,
     created: 0,

+ 2 - 2
packages/tui/test/cli/tui/data.test.tsx

@@ -1,7 +1,7 @@
 /** @jsxImportSource @opentui/solid */
 import { expect, test } from "bun:test"
 import { testRender } from "@opentui/solid"
-import type { V2Event } from "@opencode-ai/sdk/v2"
+import type { OpenCodeEvent } from "@opencode-ai/client/promise"
 import { SessionMessage } from "@opencode-ai/core/session/message"
 import { EventV2 } from "@opencode-ai/core/event"
 import { onMount } from "solid-js"
@@ -20,7 +20,7 @@ async function wait(fn: () => boolean, timeout = 2000) {
   }
 }
 
-function emitEvent(events: ReturnType<typeof createEventStream>, event: V2Event) {
+function emitEvent(events: ReturnType<typeof createEventStream>, event: OpenCodeEvent) {
   events.emit({ ...event, location: { directory } })
 }
 

+ 10 - 7
packages/tui/test/cli/tui/use-event.test.tsx

@@ -1,8 +1,8 @@
 /** @jsxImportSource @opentui/solid */
 import { describe, expect, test } from "bun:test"
-import type { OpenCodeClient } from "@opencode-ai/client/promise"
+import type { OpenCodeClient, OpenCodeEvent } from "@opencode-ai/client/promise"
 import { testRender } from "@opentui/solid"
-import type { OpencodeClient, V2Event } from "@opencode-ai/sdk/v2"
+import type { OpencodeClient } from "@opencode-ai/sdk/v2"
 import { onMount } from "solid-js"
 import { ProjectProvider, useProject } from "../../../src/context/project"
 import { SDKProvider, useSDK } from "../../../src/context/sdk"
@@ -21,14 +21,17 @@ async function wait(fn: () => boolean, timeout = 2000) {
   }
 }
 
-function event(payload: V2Event, input: { directory: string; project?: string; workspace?: string }): V2Event {
+function event(
+  payload: OpenCodeEvent,
+  input: { directory: string; project?: string; workspace?: string },
+): OpenCodeEvent {
   return {
     ...payload,
     location: { directory: input.directory, workspaceID: input.workspace },
   }
 }
 
-function vcs(branch: string): V2Event {
+function vcs(branch: string): OpenCodeEvent {
   return {
     id: `evt_vcs_${branch}`,
     created: 0,
@@ -39,7 +42,7 @@ function vcs(branch: string): V2Event {
   }
 }
 
-function update(version: string): V2Event {
+function update(version: string): OpenCodeEvent {
   return {
     id: `evt_update_${version}`,
     created: 0,
@@ -56,7 +59,7 @@ async function mount(
 ) {
   const events = createEventStream()
   const calls = createFetch(undefined, events)
-  const seen: V2Event[] = []
+  const seen: OpenCodeEvent[] = []
   const workspaces: Array<string | undefined> = []
   let project!: ReturnType<typeof useProject>
   let sdk!: ReturnType<typeof useSDK>
@@ -89,7 +92,7 @@ async function mount(
 }
 
 function Probe(props: {
-  seen: V2Event[]
+  seen: OpenCodeEvent[]
   workspaces: Array<string | undefined>
   onReady: (ctx: { project: ReturnType<typeof useProject>; sdk: ReturnType<typeof useSDK> }) => void
 }) {

+ 2 - 3
packages/tui/test/fixture/tui-sdk.ts

@@ -1,6 +1,5 @@
-import { OpenCode } from "@opencode-ai/client/promise"
+import { OpenCode, type OpenCodeEvent } from "@opencode-ai/client/promise"
 import { createOpencodeClient } from "@opencode-ai/sdk/v2"
-import type { V2Event } from "@opencode-ai/sdk/v2"
 
 export const worktree = "/tmp/opencode"
 export const directory = `${worktree}/packages/tui`
@@ -51,7 +50,7 @@ export function createEventStream() {
   }
 
   return {
-    emit(event: V2Event) {
+    emit(event: OpenCodeEvent) {
       send(v2, pending, event)
     },
     v2() {

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini