fix(run/cli): wait for selected model catalog (#35130)

This commit is contained in:
Simon Klee 2026-07-03 13:21:06 +02:00 committed by GitHub
parent a1b6c84135
commit b62d24e40b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 23 deletions

View file

@ -26,7 +26,7 @@ import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@openc
import { FormatError, FormatUnknownError } from "../error"
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./run/runtime.stdin"
import { isImageAttachment, isPdfAttachment } from "@/util/media"
import { loadRunAgents } from "./run/catalog.shared"
import { loadRunAgents, waitForCatalogReady } from "./run/catalog.shared"
type ModelInput = Parameters<OpencodeClient["session"]["prompt"]>[0]["model"]
@ -400,22 +400,22 @@ export const RunCommand = effectCmd({
const rules: PermissionV1.Ruleset = interactive
? []
: [
{
permission: "question",
action: "deny",
pattern: "*",
},
{
permission: "plan_enter",
action: "deny",
pattern: "*",
},
{
permission: "plan_exit",
action: "deny",
pattern: "*",
},
]
{
permission: "question",
action: "deny",
pattern: "*",
},
{
permission: "plan_enter",
action: "deny",
pattern: "*",
},
{
permission: "plan_exit",
action: "deny",
pattern: "*",
},
]
const currentPrompt = !interactive && !args.command && fileInputs.every((file) => !file.isDirectory)
const inlineFiles = interactive || currentPrompt
@ -669,10 +669,10 @@ export const RunCommand = effectCmd({
agent: input.agent,
model: input.model
? {
providerID: input.model.providerID,
id: input.model.modelID,
variant: input.variant,
}
providerID: input.model.providerID,
id: input.model.modelID,
variant: input.variant,
}
: undefined,
location: { directory: await current(sdk) },
})
@ -917,7 +917,7 @@ export const RunCommand = effectCmd({
UI.println(
UI.Style.TEXT_WARNING_BOLD + "!",
UI.Style.TEXT_NORMAL +
`permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
`permission requested: ${permission.permission} (${permission.patterns.join(", ")}); auto-rejecting`,
)
await client.permission.reply({
requestID: permission.id,
@ -931,12 +931,20 @@ export const RunCommand = effectCmd({
const cwd = args.attach ? (directory ?? sess.directory ?? (await current(sdk))) : (directory ?? root)
const client = args.attach ? attachSDK(cwd) : sdk
// Current-session flows resolve explicit --model refs from a location
// catalog that populates asynchronously after boot. Wait only when the
// user supplied a model; default-model races.
const selectedModel = pick(args.model)
if (selectedModel && (interactive || (currentPrompt && sess.current !== false))) {
await waitForCatalogReady({ sdk: client, directory: cwd, model: selectedModel })
}
// Validate agent if specified
const agent = await pickAgent(client)
if (!interactive) {
if (currentPrompt && sess.current !== false) {
const model = pick(args.model)
const model = selectedModel
const { runNonInteractivePrompt } = await import("./run/noninteractive")
try {
await runNonInteractivePrompt({

View file

@ -84,6 +84,27 @@ export function runProviders(providers: CurrentProvider[], models: CurrentModel[
return [...grouped.values()]
}
// A location boots its plugins in a deferred background batch after the layer
// is built, so first-turn model resolution can observe empty catalog state.
// For explicit --model flows, wait for that exact ref to appear before prompt
// admission. On timeout, return and let the real execution error surface.
export async function waitForCatalogReady(input: {
sdk: OpencodeClient
directory: string
model: { providerID: string; modelID: string }
timeoutMs?: number
}) {
const deadline = Date.now() + (input.timeoutMs ?? 5_000)
while (Date.now() < deadline) {
const models = await input.sdk.v2.model
.list(location(input.directory), { throwOnError: true })
.then((result) => result.data?.data ?? [])
.catch(() => undefined)
if (models?.some((model) => model.providerID === input.model.providerID && model.id === input.model.modelID)) return
await new Promise((resolve) => setTimeout(resolve, 25))
}
}
export async function loadRunAgents(sdk: OpencodeClient, directory: string): Promise<RunAgent[]> {
const result = await sdk.v2.agent.list(location(directory), { throwOnError: true })
return (result.data?.data ?? []).map(runAgent)