refactor(tui): wire generated client reads (#34381)
This commit is contained in:
parent
e8ac44430b
commit
381d67572e
28 changed files with 509 additions and 427 deletions
|
|
@ -4,7 +4,7 @@ import { createTestRenderer } from "@opentui/core/testing"
|
|||
import { Effect } from "effect"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { createTuiResolvedConfig } from "./fixture/tui-runtime"
|
||||
import { createClient, createEventStream, createFetch, directory, json } from "./fixture/tui-sdk"
|
||||
import { createApi, createClient, createEventStream, createFetch, directory, json } from "./fixture/tui-sdk"
|
||||
|
||||
test("SIGHUP clears title and disposes scoped resources once", async () => {
|
||||
const setup = await createTestRenderer({ width: 80, height: 24, useThread: false })
|
||||
|
|
@ -30,6 +30,7 @@ test("SIGHUP clears title and disposes scoped resources once", async () => {
|
|||
const task = Effect.runPromise(
|
||||
run({
|
||||
client: createClient(calls.fetch),
|
||||
api: createApi(calls.fetch),
|
||||
config: createTuiResolvedConfig({ plugin_enabled: {} }),
|
||||
args: {},
|
||||
pluginHost: {
|
||||
|
|
@ -61,26 +62,23 @@ test("app.exit prints the session epilogue after scoped cleanup", async () => {
|
|||
const core = await import("@opentui/core")
|
||||
mock.module("@opentui/core", () => ({ ...core, createCliRenderer: async () => setup.renderer }))
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(
|
||||
(url) => {
|
||||
if (url.pathname === "/api/session")
|
||||
return json({
|
||||
data: [
|
||||
{
|
||||
id: "dummy",
|
||||
title: "Demo session",
|
||||
projectID: "project",
|
||||
location: { directory },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 0, updated: 0 },
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
})
|
||||
},
|
||||
events,
|
||||
)
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === "/api/session")
|
||||
return json({
|
||||
data: [
|
||||
{
|
||||
id: "dummy",
|
||||
title: "Demo session",
|
||||
projectID: "project",
|
||||
location: { directory },
|
||||
cost: 0,
|
||||
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
time: { created: 0, updated: 0 },
|
||||
},
|
||||
],
|
||||
cursor: {},
|
||||
})
|
||||
}, events)
|
||||
const originalWrite = process.stdout.write.bind(process.stdout)
|
||||
let stdout = ""
|
||||
let api: TuiPluginApi | undefined
|
||||
|
|
@ -99,6 +97,7 @@ test("app.exit prints the session epilogue after scoped cleanup", async () => {
|
|||
const task = Effect.runPromise(
|
||||
run({
|
||||
client: createClient(calls.fetch),
|
||||
api: createApi(calls.fetch),
|
||||
config: createTuiResolvedConfig({ plugin_enabled: {} }),
|
||||
args: { continue: true },
|
||||
pluginHost: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { ProjectProvider, useProject } from "../../../../src/context/project"
|
|||
import { SDKProvider } from "../../../../src/context/sdk"
|
||||
import { SyncProvider, useSync } from "../../../../src/context/sync"
|
||||
import { ExitProvider } from "../../../../src/context/exit"
|
||||
import { createClient, createEventStream, createFetch, type FetchHandler } from "../../../fixture/tui-sdk"
|
||||
import { createApi, createClient, createEventStream, createFetch, type FetchHandler } from "../../../fixture/tui-sdk"
|
||||
import { TestTuiContexts } from "../../../fixture/tui-environment"
|
||||
export { createEventStream, createFetch, directory, json, worktree } from "../../../fixture/tui-sdk"
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export async function mount(override?: FetchHandler, state?: string) {
|
|||
<TestTuiContexts paths={state ? { state } : undefined}>
|
||||
<ArgsProvider>
|
||||
<KVProvider>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<ExitProvider exit={() => {}}>
|
||||
<SyncProvider>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { onMount } from "solid-js"
|
|||
import { ProjectProvider } from "../../../src/context/project"
|
||||
import { SDKProvider } from "../../../src/context/sdk"
|
||||
import { DataProvider, useData } from "../../../src/context/data"
|
||||
import { createClient, createEventStream, createFetch, directory, json } from "../../fixture/tui-sdk"
|
||||
import { createApi, createClient, createEventStream, createFetch, directory, json } from "../../fixture/tui-sdk"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
|
||||
async function wait(fn: () => boolean, timeout = 2000) {
|
||||
|
|
@ -69,7 +69,7 @@ test("refreshes resources into reactive getters", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -139,7 +139,7 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -172,8 +172,7 @@ test("reconnects the event stream and bootstraps fresh data", async () => {
|
|||
test("tracks session status from active sessions and execution events", async () => {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch((url) => {
|
||||
if (url.pathname === "/api/session/active")
|
||||
return json({ data: { "session-active": { type: "running" } } })
|
||||
if (url.pathname === "/api/session/active") return json({ data: { "session-active": { type: "running" } } })
|
||||
}, events)
|
||||
let data!: ReturnType<typeof useData>
|
||||
|
||||
|
|
@ -184,7 +183,7 @@ test("tracks session status from active sessions and execution events", async ()
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -296,7 +295,7 @@ test("refreshes integrations after integration updates", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -337,7 +336,7 @@ test("refreshes effective catalog data after catalog updates", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<box />
|
||||
|
|
@ -382,7 +381,7 @@ test("refreshes references after updates", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -415,7 +414,7 @@ test("adds and dismisses permission requests from live events", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -480,7 +479,7 @@ test("adds and dismisses question requests from live events", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -548,7 +547,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -677,7 +676,7 @@ test("renders admitted prompts only after they become model-visible", async () =
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
@ -750,7 +749,7 @@ test("projects live context updates with their message ID", async () => {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)}>
|
||||
<ProjectProvider>
|
||||
<DataProvider>
|
||||
<Probe />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
/** @jsxImportSource @opentui/solid */
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { OpenCodeClient } from "@opencode-ai/client"
|
||||
import { testRender } from "@opentui/solid"
|
||||
import type { OpencodeClient, V2Event } from "@opencode-ai/sdk/v2"
|
||||
import { onMount } from "solid-js"
|
||||
import { ProjectProvider, useProject } from "../../../src/context/project"
|
||||
import { SDKProvider, useSDK } from "../../../src/context/sdk"
|
||||
import { useEvent } from "../../../src/context/event"
|
||||
import { createClient, createEventStream, createFetch, directory } from "../../fixture/tui-sdk"
|
||||
import { createApi, createClient, createEventStream, createFetch } from "../../fixture/tui-sdk"
|
||||
import { TestTuiContexts } from "../../fixture/tui-environment"
|
||||
|
||||
const projectID = "proj_test"
|
||||
|
|
@ -46,7 +47,7 @@ function update(version: string): V2Event {
|
|||
}
|
||||
}
|
||||
|
||||
async function mount(reload?: () => Promise<OpencodeClient>) {
|
||||
async function mount(reload?: () => Promise<{ client: OpencodeClient; api: OpenCodeClient }>) {
|
||||
const events = createEventStream()
|
||||
const calls = createFetch(undefined, events)
|
||||
const seen: V2Event[] = []
|
||||
|
|
@ -60,7 +61,7 @@ async function mount(reload?: () => Promise<OpencodeClient>) {
|
|||
|
||||
const app = await testRender(() => (
|
||||
<TestTuiContexts>
|
||||
<SDKProvider client={createClient(calls.fetch)} reload={reload}>
|
||||
<SDKProvider client={createClient(calls.fetch)} api={createApi(calls.fetch)} reload={reload}>
|
||||
<ProjectProvider>
|
||||
<Probe
|
||||
onReady={async (ctx) => {
|
||||
|
|
@ -150,7 +151,8 @@ describe("useEvent", () => {
|
|||
test("reloads the host and reconnects the event stream", async () => {
|
||||
let calls = 0
|
||||
const events = createEventStream()
|
||||
const replacement = createClient(createFetch(undefined, events).fetch)
|
||||
const replacementCalls = createFetch(undefined, events)
|
||||
const replacement = { client: createClient(replacementCalls.fetch), api: createApi(replacementCalls.fetch) }
|
||||
const { app, sdk, seen } = await mount(async () => {
|
||||
calls += 1
|
||||
return replacement
|
||||
|
|
@ -164,19 +166,21 @@ describe("useEvent", () => {
|
|||
await wait(() => seen.some((item) => item.type === "vcs.branch.updated" && item.data.branch === "reloaded"))
|
||||
|
||||
expect(calls).toBe(1)
|
||||
expect(sdk.client).toBe(replacement)
|
||||
expect(sdk.client).toBe(replacement.client)
|
||||
expect(sdk.api).toBe(replacement.api)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
test("keeps the current event stream alive while the host reload is pending", async () => {
|
||||
let complete!: (client: OpencodeClient) => void
|
||||
const pending = new Promise<OpencodeClient>((resolve) => {
|
||||
let complete!: (client: { client: OpencodeClient; api: OpenCodeClient }) => void
|
||||
const pending = new Promise<{ client: OpencodeClient; api: OpenCodeClient }>((resolve) => {
|
||||
complete = resolve
|
||||
})
|
||||
const replacementEvents = createEventStream()
|
||||
const replacement = createClient(createFetch(undefined, replacementEvents).fetch)
|
||||
const replacementCalls = createFetch(undefined, replacementEvents)
|
||||
const replacement = { client: createClient(replacementCalls.fetch), api: createApi(replacementCalls.fetch) }
|
||||
const { app, emit, sdk, seen } = await mount(() => pending)
|
||||
|
||||
try {
|
||||
|
|
@ -188,7 +192,8 @@ describe("useEvent", () => {
|
|||
expect(sdk.connection.status()).toBe("connected")
|
||||
complete(replacement)
|
||||
await reload
|
||||
expect(sdk.client).toBe(replacement)
|
||||
expect(sdk.client).toBe(replacement.client)
|
||||
expect(sdk.api).toBe(replacement.api)
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { OpenCode } from "@opencode-ai/client"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import type { V2Event } from "@opencode-ai/sdk/v2"
|
||||
|
||||
|
|
@ -117,3 +118,7 @@ export function createFetch(override?: FetchHandler, events?: ReturnType<typeof
|
|||
export function createClient(fetch: typeof globalThis.fetch) {
|
||||
return createOpencodeClient({ baseUrl: "http://test", fetch })
|
||||
}
|
||||
|
||||
export function createApi(fetch: typeof globalThis.fetch) {
|
||||
return OpenCode.make({ baseUrl: "http://test", fetch })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue