Skip to content

Commit 41dc4fc

Browse files
Share provider-catalog refresh between TUI connect and Go prefetch
1 parent a06720d commit 41dc4fc

3 files changed

Lines changed: 87 additions & 75 deletions

File tree

src/tui/runner/provider-refresh.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Shared "reload settings, re-resolve the live provider catalog, repaint the
2+
// model surfaces" step for the TUI runner. The Alt+A provider connect handler
3+
// and the post-startup / post-connect OpenCode Go model prefetch each carried
4+
// their own copy of this block; both now resolve through this module so a
5+
// live connect and a startup prefetch cannot drift apart.
6+
7+
import { getLogger } from "@intx/log";
8+
9+
import { LOG_NAMESPACE_ROOT } from "../../branding.js";
10+
import {
11+
listFavoriteModels,
12+
listRecentModels,
13+
loadSettings,
14+
type ResolvedProvider,
15+
} from "../../config/settings.js";
16+
import { refreshLiveProviderCatalog } from "../../config/index.js";
17+
import { prefetchGoModels } from "../../provider/opencode-go-models.js";
18+
import type { ModelCatalogProvidersInput, ModelCatalogRef } from "../model-catalog.js";
19+
import type { RunnerState } from "./state.js";
20+
21+
const tuiLogger = getLogger([LOG_NAMESPACE_ROOT, "tui"]);
22+
23+
/** Model-surface repaint target for a refreshed catalog (host or holder). */
24+
export type RefreshModels = (
25+
recentModels: readonly ModelCatalogRef[],
26+
favoriteModels: readonly ModelCatalogRef[],
27+
providers?: ModelCatalogProvidersInput,
28+
) => void;
29+
30+
// Reload the on-disk settings and re-resolve the live provider catalog
31+
// (including OAuth profiles) against the current config, then repaint the
32+
// model surfaces. Shared by the provider connect handler and the Go-model
33+
// prefetch so a newly authorized provider's models appear the same way in
34+
// every path.
35+
export async function refreshProviderCatalogAndSurfaces(
36+
state: RunnerState,
37+
refreshModels: RefreshModels,
38+
): Promise<void> {
39+
const onDisk = await loadSettings(state.trueGlobalSettingsPath);
40+
const resolvedForCatalog: ResolvedProvider = {
41+
apiKey: state.config.apiKey,
42+
baseURL: state.config.baseURL,
43+
model: state.config.model,
44+
providerName: state.config.providerName,
45+
...(state.config.keyless !== undefined ? { keyless: state.config.keyless } : {}),
46+
};
47+
const providers = await refreshLiveProviderCatalog(onDisk, resolvedForCatalog);
48+
state.config = {
49+
...state.config,
50+
providers,
51+
...(onDisk !== null ? { settings: onDisk } : {}),
52+
};
53+
refreshModels(
54+
listRecentModels(state.config.settings ?? { providers: {} }),
55+
listFavoriteModels(state.config.settings ?? { providers: {} }),
56+
providers,
57+
);
58+
}
59+
60+
// Prefetch the OpenCode Go model catalog, then re-resolve settings and
61+
// repaint. Shared by wirePostStartup and the connect handler's post-connect
62+
// refresh. `getRefreshModels` is a callback so the host-availability guard is
63+
// re-checked when the prefetch settles, not when it was started.
64+
export function prefetchGoModelsAndRefresh(
65+
state: RunnerState,
66+
getRefreshModels: () => RefreshModels | undefined,
67+
): void {
68+
void prefetchGoModels()
69+
.then(async () => {
70+
const refreshModels = getRefreshModels();
71+
if (refreshModels === undefined) return;
72+
await refreshProviderCatalogAndSurfaces(state, refreshModels);
73+
})
74+
.catch((err: unknown) => {
75+
tuiLogger.debug("go model prefetch failed: {error}", {
76+
error: err instanceof Error ? err.message : String(err),
77+
});
78+
});
79+
}

src/tui/runner/settings.ts

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@ import {
2121
toggleFavoriteModel,
2222
type LocalSettings,
2323
type ModelRef,
24-
type ResolvedProvider,
2524
type Settings,
2625
} from "../../config/settings.js";
2726
import { getTelemetry } from "../../telemetry/singleton.js";
28-
import { refreshLiveProviderCatalog } from "../../config/index.js";
2927
import { createTelemetryToggleHandler } from "../../telemetry/toggle.js";
3028
import { telemetryFirstRunPending } from "../../telemetry/first-run.js";
3129
import { TELEMETRY_NOTICE } from "../../telemetry/index.js";
@@ -35,14 +33,17 @@ import type { GrantScope } from "../../permission/types.js";
3533
import { connectProviderInline } from "../provider/connect.js";
3634
import { persistConnectedSelection } from "../provider/submit.js";
3735
import { modelOptionId } from "../model-catalog.js";
38-
import { prefetchGoModels } from "../../provider/opencode-go-models.js";
3936
import { isOpenCodeGoProvider } from "../../../packages/opencode-go/src/index.js";
4037
import { applyLiveModelSwitch } from "../../session/live-model-switch.js";
4138
import { applyFocus } from "../shell/chrome.js";
4239
import { setShellInputSuspended } from "../shell/prompt.js";
4340
import { warningsForPluginEntry } from "../../plugins/diagnostics.js";
4441
import { isPluginEnabledForSurface } from "../plugin-surface.js";
4542
import { resolveWaitForApproval } from "../tool-execution-watchdog.js";
43+
import {
44+
prefetchGoModelsAndRefresh,
45+
refreshProviderCatalogAndSurfaces,
46+
} from "./provider-refresh.js";
4647
import { hostOf, type RunnerServices, type RunnerState } from "./state.js";
4748
import { LOG_NAMESPACE_ROOT } from "../../branding.js";
4849

@@ -252,25 +253,7 @@ export async function wireSettings(
252253
}
253254
if (!result.connected) return;
254255

255-
const onDisk = await loadSettings(trueGlobalSettingsPath);
256-
const resolvedForCatalog: ResolvedProvider = {
257-
apiKey: state.config.apiKey,
258-
baseURL: state.config.baseURL,
259-
model: state.config.model,
260-
providerName: state.config.providerName,
261-
...(state.config.keyless !== undefined ? { keyless: state.config.keyless } : {}),
262-
};
263-
const providers = await refreshLiveProviderCatalog(onDisk, resolvedForCatalog);
264-
state.config = {
265-
...state.config,
266-
providers,
267-
...(onDisk !== null ? { settings: onDisk } : {}),
268-
};
269-
hostOf(state).refreshModels(
270-
listRecentModels(state.config.settings ?? { providers: {} }),
271-
listFavoriteModels(state.config.settings ?? { providers: {} }),
272-
providers,
273-
);
256+
await refreshProviderCatalogAndSurfaces(state, hostOf(state).refreshModels);
274257
// Reopen positioned at the account just connected — the picker's
275258
// default open (top of list) would otherwise leave the operator to
276259
// hunt for the row they just authorized.
@@ -280,27 +263,7 @@ export async function wireSettings(
280263
);
281264
state.systemNotice?.(`Connected ${connectedName}. Open /model to pick a model.`);
282265
if (isOpenCodeGoProvider({ name: providerName })) {
283-
void prefetchGoModels()
284-
.then(async () => {
285-
if (services.hostHolder.instance === undefined) return;
286-
const nextDisk = await loadSettings(trueGlobalSettingsPath);
287-
const nextProviders = await refreshLiveProviderCatalog(nextDisk, resolvedForCatalog);
288-
state.config = {
289-
...state.config,
290-
providers: nextProviders,
291-
...(nextDisk !== null ? { settings: nextDisk } : {}),
292-
};
293-
services.hostHolder.instance.refreshModels(
294-
listRecentModels(state.config.settings ?? { providers: {} }),
295-
listFavoriteModels(state.config.settings ?? { providers: {} }),
296-
nextProviders,
297-
);
298-
})
299-
.catch((err: unknown) => {
300-
tuiLogger.debug("go model prefetch failed: {error}", {
301-
error: err instanceof Error ? err.message : String(err),
302-
});
303-
});
266+
prefetchGoModelsAndRefresh(state, () => services.hostHolder.instance?.refreshModels);
304267
}
305268
})().catch((err: unknown) => {
306269
tuiLogger.debug("provider connect failed: {error}", {

src/tui/runner/wiring.ts

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
*/
88

99
import { getLogger } from "@intx/log";
10-
import { loadSettings, listFavoriteModels, listRecentModels } from "../../config/settings.js";
11-
import { refreshLiveProviderCatalog } from "../../config/index.js";
12-
import type { ResolvedProvider } from "../../config/settings.js";
13-
import { prefetchGoModels } from "../../provider/opencode-go-models.js";
1410
import { isOpenCodeGoProvider } from "../../../packages/opencode-go/src/index.js";
1511
import { loadRecentTurns } from "../../session/optimized-context-store.js";
1612
import { loadSentMessages } from "../../session/sent-messages.js";
@@ -43,6 +39,7 @@ import {
4339
import { listPathSuggestions } from "../components/at-mention/list.js";
4440
import { listCommands } from "../commands/registry.js";
4541
import type { MCPConnectCallbacks } from "../../agent/tools.js";
42+
import { prefetchGoModelsAndRefresh } from "./provider-refresh.js";
4643
import { createRuntimeShutdown } from "./shutdown.js";
4744
import { resumeTranscriptLoadErrorBlock } from "./exit.js";
4845
import { userInboundMessage } from "./submit.js";
@@ -57,34 +54,7 @@ export function wirePostStartup(
5754
mcpConnectCallbacks: MCPConnectCallbacks,
5855
): void {
5956
if (state.config.providers.some((p) => isOpenCodeGoProvider(p))) {
60-
void prefetchGoModels()
61-
.then(async () => {
62-
if (services.hostHolder.instance === undefined) return;
63-
const onDisk = await loadSettings(state.trueGlobalSettingsPath);
64-
const resolvedForCatalog: ResolvedProvider = {
65-
apiKey: state.config.apiKey,
66-
baseURL: state.config.baseURL,
67-
model: state.config.model,
68-
providerName: state.config.providerName,
69-
...(state.config.keyless !== undefined ? { keyless: state.config.keyless } : {}),
70-
};
71-
const providers = await refreshLiveProviderCatalog(onDisk, resolvedForCatalog);
72-
state.config = {
73-
...state.config,
74-
providers,
75-
...(onDisk !== null ? { settings: onDisk } : {}),
76-
};
77-
services.hostHolder.instance.refreshModels(
78-
listRecentModels(state.config.settings ?? { providers: {} }),
79-
listFavoriteModels(state.config.settings ?? { providers: {} }),
80-
providers,
81-
);
82-
})
83-
.catch((err: unknown) => {
84-
tuiLogger.debug("go model prefetch failed: {error}", {
85-
error: err instanceof Error ? err.message : String(err),
86-
});
87-
});
57+
prefetchGoModelsAndRefresh(state, () => services.hostHolder.instance?.refreshModels);
8858
}
8959

9060
const shutdownRuntime = createRuntimeShutdown({

0 commit comments

Comments
 (0)