mini: add reconnect, forms, and shared targets (#37811)

Centralize session target resolution for mini and noninteractive
run paths. Recover from transport drops, replace questions with
forms, and keep tool/catalog state location-scoped with live
progress and theme discovery.
This commit is contained in:
Simon Klee 2026-07-19 22:45:10 +02:00 committed by GitHub
commit 925c2423de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 5631 additions and 4292 deletions

View file

@ -1,8 +1,10 @@
import { describe, expect, test } from "bun:test"
import { ClientError, OpenCode } from "@opencode-ai/client/promise"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
import path from "node:path"
import { mergeInput as mergeInteractiveInput } from "../src/mini"
import { mergeInput as mergeNonInteractiveInput, parseRunModel, pickRunModel } from "../src/run/run"
import { createMiniConnection, mergeInput as mergeInteractiveInput, resolveMiniTarget } from "../src/mini"
import { mergeInput as mergeNonInteractiveInput, parseRunModel } from "../src/run/run"
import { parseSessionTargetModel } from "../src/session-target"
async function cli(args: string[]) {
const child = Bun.spawn([process.execPath, "run", "src/index.ts", ...args], {
@ -24,26 +26,91 @@ describe("mini command", () => {
expect(mergeInteractiveInput("from stdin", "from flag")).toBe("from stdin\nfrom flag")
})
test("constructs a fresh authenticated client for a replacement endpoint", async () => {
const authorization: Array<string | null> = []
const initial = Bun.serve({
port: 0,
fetch() {
return Response.json({ healthy: true, version: InstallationVersion, pid: process.pid })
},
})
const replacement = Bun.serve({
port: 0,
fetch(request) {
authorization.push(request.headers.get("authorization"))
return Response.json({ healthy: true, version: InstallationVersion, pid: process.pid })
},
})
const controller = new AbortController()
let signal: AbortSignal | undefined
try {
const connection = createMiniConnection({
endpoint: { url: initial.url.toString() },
reconnect: async (next) => {
signal = next
return {
url: replacement.url.toString(),
auth: { type: "basic", username: "replacement", password: "secret" },
}
},
})
const client = await connection.reconnect?.(controller.signal)
if (!client) throw new Error("Expected a replacement client")
await client.health.get()
expect(client).not.toBe(connection.sdk)
expect(signal).toBe(controller.signal)
expect(authorization).toEqual([`Basic ${btoa("replacement:secret")}`])
expect(createMiniConnection({ endpoint: { url: initial.url.toString() } }).reconnect).toBeUndefined()
} finally {
initial.stop(true)
replacement.stop(true)
}
})
test("re-resolves a managed target when the endpoint moves before transport construction", async () => {
const initial = OpenCode.make({ baseUrl: "https://initial.opencode.test" })
const replacement = OpenCode.make({ baseUrl: "https://replacement.opencode.test" })
const controller = new AbortController()
const seen: (typeof initial)[] = []
let reconnects = 0
const result = await resolveMiniTarget({
sdk: initial,
reconnect: async (signal) => {
expect(signal).toBe(controller.signal)
reconnects++
if (reconnects === 1) throw new Error("service still moving")
return replacement
},
signal: controller.signal,
resolve: async (sdk) => {
seen.push(sdk)
if (sdk === initial) throw new ClientError("Transport")
return "ses-replacement"
},
})
expect(seen).toEqual([initial, replacement])
expect(reconnects).toBe(2)
expect(result).toEqual({ sdk: replacement, value: "ses-replacement" })
})
test("merges non-interactive argument and stdin input", () => {
expect(mergeNonInteractiveInput("from args", "from stdin")).toBe("from args\nfrom stdin")
expect(mergeNonInteractiveInput(undefined, "from stdin")).toBe("from stdin")
})
test("applies a variant to a resumed session's model", () => {
expect(
pickRunModel(
undefined,
"high",
{ providerID: "session-provider", modelID: "session-model" },
{ providerID: "default-provider", modelID: "default-model" },
),
).toEqual({ providerID: "session-provider", modelID: "session-model" })
})
test("parses model variants from the model reference", () => {
expect(JSON.stringify(parseRunModel("openrouter/openai/gpt-5#high"))).toBe(
JSON.stringify({ model: { providerID: "openrouter", modelID: "openai/gpt-5" }, variant: "high" }),
)
expect(parseSessionTargetModel("openrouter/openai/gpt-5#high")).toEqual({
providerID: "openrouter",
id: "openai/gpt-5",
variant: "high",
})
})
test("is registered in the preview CLI", async () => {