fix(opencode): keep model switching runtime-hot

This commit is contained in:
2026-08-21 01:26:20 +08:00
parent 605fef417f
commit cce772299a
10 changed files with 256 additions and 88 deletions

View File

@@ -511,6 +511,7 @@ function expectedUserModelProxyConfig(
async function rebindLocalProxyHostApiTokenIfNeeded(
expectedBaseUrl: string,
markPending: () => void,
runtimeAlreadyUsesCurrentHostApiToken: boolean,
signal?: AbortSignal,
): Promise<boolean> {
const providerService = getProviderService();
@@ -532,14 +533,31 @@ async function rebindLocalProxyHostApiTokenIfNeeded(
return false;
}
markPending();
await providerService.updateAccount(
NIANCODE_USER_MODEL_ACCOUNT_ID,
account,
currentHostApiToken,
);
signal?.throwIfAborted();
return true;
if (!runtimeAlreadyUsesCurrentHostApiToken) {
markPending();
}
const markPendingOnAbort = runtimeAlreadyUsesCurrentHostApiToken
? () => markPending()
: undefined;
if (markPendingOnAbort) {
signal?.addEventListener('abort', markPendingOnAbort, { once: true });
}
try {
await providerService.updateAccount(
NIANCODE_USER_MODEL_ACCOUNT_ID,
account,
currentHostApiToken,
);
signal?.throwIfAborted();
} catch (error) {
markPending();
throw error;
} finally {
if (markPendingOnAbort) {
signal?.removeEventListener('abort', markPendingOnAbort);
}
}
return !runtimeAlreadyUsesCurrentHostApiToken;
}
async function runtimeUsesExpectedUserModelProxy(
@@ -607,7 +625,12 @@ async function ensureRuntimeUserModelProxyConfig(
try {
if (
expectedConfig.usesLocalProxy
&& await rebindLocalProxyHostApiTokenIfNeeded(expectedConfig.baseUrl, markPending, signal)
&& await rebindLocalProxyHostApiTokenIfNeeded(
expectedConfig.baseUrl,
markPending,
ctx.opencodeManager.getRuntimeGenerationProvenance() === 'fresh',
signal,
)
) {
if (!allowRestart) {
markPending();

View File

@@ -361,10 +361,13 @@ export async function importCurrentUserModelConfig(
createdAt: existing?.createdAt ?? now,
updatedAt: now,
};
const shouldRestartRuntime = importedProviderRuntimeShapeChanged(existing, account)
|| await importedProviderApiKeyChanged(providerService, existing, accountApiKey);
signal.throwIfAborted();
const runtimeIsActive = ctx.opencodeManager.getStatus().state !== 'stopped';
const runtimeAlreadyUsesLocalProxyApiKey = useLocalAiProxy
&& ctx.opencodeManager.getRuntimeGenerationProvenance?.() === 'fresh';
const shouldRestartRuntime = importedProviderRuntimeShapeChanged(existing, account)
|| (!runtimeAlreadyUsesLocalProxyApiKey
&& await importedProviderApiKeyChanged(providerService, existing, accountApiKey));
signal.throwIfAborted();
const refreshAlreadyPending = lease.isRefreshPending();
const runtimeRefreshRequired = runtimeIsActive
&& (shouldRestartRuntime || refreshAlreadyPending);

View File

@@ -108,6 +108,7 @@ const useSecureWorksSquareSessionPersistence = shouldUseSecureWorksSquareSession
async function buildMakeloreOpencodeRuntimeConfig() {
return await buildOpencodeRuntimeConfigFromNianCodeProviders({
localProxyApiKey: getHostApiToken() || undefined,
mcpServers: {
[PLAYWRIGHT_MCP_SERVER_ID]: resolvePlaywrightMcpServer(),
},

View File

@@ -278,7 +278,12 @@ export function createOpencodeClient(options: OpencodeClientOptions) {
): Promise<void> =>
request<void>(`/api/session/${encodeURIComponent(sessionID)}/model`, {
method: 'POST',
body: JSON.stringify({ model }),
body: JSON.stringify({
model: {
providerID: model.providerID,
id: model.modelID,
},
}),
signal: options?.signal,
}),
executeSessionCommand: (

View File

@@ -23,6 +23,7 @@ const OPENAI_COMPATIBLE_PACKAGE = '@ai-sdk/openai-compatible';
const OPENAI_PACKAGE = '@ai-sdk/openai';
const ANTHROPIC_PACKAGE = '@ai-sdk/anthropic';
const WORKS_SQUARE_AI_GATEWAY_CREDENTIAL_MODE = 'works_square_ai_gateway';
const WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE = 'works_square_ai_gateway_proxy';
const OPENCODE_BUILTIN_PROVIDER_IDS = new Set([
'anthropic',
@@ -120,6 +121,7 @@ export interface BuildOpencodeRuntimeConfigOptions {
accounts: ProviderAccount[];
defaultAccountId?: string | null;
resolveApiKey: ResolveOpencodeProviderApiKey;
localProxyApiKey?: string;
mcpServers?: Record<string, OpencodeMcpServerEntry>;
}
@@ -303,7 +305,11 @@ export async function buildOpencodeRuntimeConfig(
continue;
}
const apiKey = await options.resolveApiKey(account, opencodeProviderId);
const localProxyApiKey = account.id === NIANCODE_USER_MODEL_ACCOUNT_ID
&& account.metadata?.worksSquareCredentialMode === WORKS_SQUARE_AI_GATEWAY_PROXY_CREDENTIAL_MODE
? options.localProxyApiKey
: undefined;
const apiKey = localProxyApiKey ?? await options.resolveApiKey(account, opencodeProviderId);
if (!apiKey && accountNeedsApiKey(account)) {
continue;
}
@@ -367,6 +373,7 @@ async function resolveNianCodeApiKey(
}
export interface BuildOpencodeRuntimeConfigFromNianCodeProvidersOptions {
localProxyApiKey?: string;
mcpServers?: Record<string, OpencodeMcpServerEntry>;
}
@@ -383,6 +390,7 @@ export async function buildOpencodeRuntimeConfigFromNianCodeProviders(
accounts,
defaultAccountId,
resolveApiKey: resolveNianCodeApiKey,
localProxyApiKey: options.localProxyApiKey,
mcpServers: options.mcpServers,
});
}