fix: defer catalog validation to session execution (#38258)

This commit is contained in:
Simon Klee 2026-07-22 10:32:52 +02:00 committed by GitHub
commit 8bb1cfaa3b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 229 additions and 304 deletions

View file

@ -85,67 +85,6 @@ export function runProviders(providers: CurrentProvider[], models: CurrentModel[
return [...grouped.values()]
}
export async function waitForDefaultModel(input: {
sdk: OpenCodeClient
location: LocationRef
timeoutMs?: number
requestTimeoutMs?: number
active?: () => boolean
signal?: AbortSignal
}): Promise<{ providerID: string; modelID: string } | undefined> {
const deadline = Date.now() + (input.timeoutMs ?? 5_000)
while (Date.now() < deadline && !input.signal?.aborted && (input.active?.() ?? true)) {
const controller = new AbortController()
const timeout = setTimeout(
() => controller.abort(),
Math.min(input.requestTimeoutMs ?? 1_000, Math.max(1, deadline - Date.now())),
)
const abort = () => controller.abort()
input.signal?.addEventListener("abort", abort, { once: true })
const model = await abortable(
input.sdk.model
.default(location(input.location), { signal: controller.signal })
.then((result) => result.data)
.catch(() => undefined),
controller.signal,
).finally(() => {
clearTimeout(timeout)
input.signal?.removeEventListener("abort", abort)
})
if (model) return { providerID: model.providerID, modelID: model.id }
await wait(25, input.signal)
}
}
function abortable<A>(task: Promise<A>, signal: AbortSignal): Promise<A | undefined> {
if (signal.aborted) return Promise.resolve(undefined)
return new Promise((resolve) => {
const abort = () => {
signal.removeEventListener("abort", abort)
resolve(undefined)
}
signal.addEventListener("abort", abort, { once: true })
void task.then((value) => {
signal.removeEventListener("abort", abort)
resolve(value)
})
})
}
function wait(delay: number, signal?: AbortSignal) {
if (!signal) return new Promise<void>((resolve) => setTimeout(resolve, delay))
if (signal.aborted) return Promise.resolve()
return new Promise<void>((resolve) => {
const timer = setTimeout(done, delay)
signal.addEventListener("abort", done, { once: true })
function done() {
clearTimeout(timer)
signal?.removeEventListener("abort", done)
resolve()
}
})
}
export async function loadRunAgents(sdk: OpenCodeClient, ref: LocationRef, signal?: AbortSignal): Promise<RunAgent[]> {
const result = await sdk.agent.list(location(ref), ...requestOptions(signal))
return result.data.map(runAgent)

View file

@ -425,7 +425,7 @@ export function RunFooterView(props: RunFooterViewProps) {
return props.mono ? usage().replaceAll(" · ", " - ") : usage()
})
const modelStatus = createMemo(() => {
const current = model()
const current = model() ?? props.state().model.trim()
if (!footerDetails() || !prompt() || !responsive().statusline.showModel || !current) return
return {
model: current,

View file

@ -126,7 +126,7 @@ function footerLabels(input: Pick<RunInput, "agent" | "model" | "variant">): Foo
const agentLabel = Locale.titlecase(input.agent ?? "build")
return {
agentLabel,
modelLabel: input.model ? formatModelLabel(input.model, input.variant) : "",
modelLabel: input.model ? formatModelLabel(input.model, input.variant) : "Default model",
}
}

View file

@ -11,7 +11,7 @@
import { SessionMessage } from "@opencode-ai/schema/session-message"
import type { LocationRef } from "@opencode-ai/client/promise"
import type { Config } from "../config"
import { loadRunAgents, loadRunCommands, loadRunReferences, waitForDefaultModel } from "./catalog.shared"
import { loadRunAgents, loadRunCommands, loadRunReferences } from "./catalog.shared"
import {
resolveMiniSettings,
resolveModelInfo,
@ -492,39 +492,20 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
const signal = AbortSignal.any([runtimeController.signal, controller.signal])
modelAttempt = controller
try {
if (selected) {
const info = await abortable(resolveModelInfo(sdk, state.location, signal), signal)
if (
!info ||
!currentModelLoad(generation, sdk) ||
state.model?.providerID !== selected.providerID ||
state.model.modelID !== selected.modelID
)
return
applyModelInfo(info, session.variant, { sdk, generation, signal }, true, savedVariant)
const info = await abortable(resolveModelInfo(sdk, state.location, signal), signal)
if (
!info ||
!currentModelLoad(generation, sdk) ||
(selected &&
(state.model?.providerID !== selected.providerID || state.model.modelID !== selected.modelID))
)
return
}
const model = await waitForDefaultModel({
sdk,
location: state.location,
active: () => currentModelLoad(generation, sdk),
signal,
})
if (!currentModelLoad(generation, sdk)) return
const [fallbackSavedVariant, info] = await Promise.all([
input.host.preferences.resolveVariant(model),
abortable(resolveModelInfo(sdk, state.location, signal), signal),
])
if (!info || !currentModelLoad(generation, sdk)) return
if (model && !state.model) state.model = model
const boot = !!model && state.model?.providerID === model.providerID && state.model.modelID === model.modelID
applyModelInfo(
info,
boot ? session.variant : state.activeVariant,
selected ? session.variant : state.activeVariant,
{ sdk, generation, signal },
boot,
fallbackSavedVariant,
!!selected,
savedVariant,
)
} finally {
if (modelAttempt === controller) modelAttempt = undefined