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:
parent
71cb419570
commit
925c2423de
65 changed files with 5631 additions and 4292 deletions
|
|
@ -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 () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue