fix: sync the sesion store after session provider and model update and also update thinking effort (#10060)
Some checks are pending
Canary / bundle-desktop-linux (push) Blocked by required conditions
Canary / bundle-desktop-windows (push) Blocked by required conditions
Canary / bundle-desktop-windows-cuda (push) Blocked by required conditions
Canary / bundle-desktop (push) Blocked by required conditions
Canary / bundle-desktop-intel (push) Blocked by required conditions
Canary / Prepare Version (push) Waiting to run
Canary / build-cli (push) Blocked by required conditions
Canary / Upload Install Script (push) Blocked by required conditions
Canary / Release (push) Blocked by required conditions
Unused Dependencies / machete (push) Waiting to run
CI / changes (push) Waiting to run
CI / Check Rust Code Format (push) Blocked by required conditions
CI / Build and Test Rust Project (push) Blocked by required conditions
CI / Build Rust Project on Windows (push) Waiting to run
CI / Check MSRV (push) Blocked by required conditions
CI / Lint Rust Code (push) Blocked by required conditions
CI / Check Generated Schemas are Up-to-Date (push) Blocked by required conditions
CI / Test and Lint Electron Desktop App (push) Blocked by required conditions
Create Minor Release PR / check-version-bump-pr (push) Waiting to run
Create Minor Release PR / release (push) Blocked by required conditions
Live Provider Tests / check-fork (push) Waiting to run
Live Provider Tests / changes (push) Blocked by required conditions
Live Provider Tests / Build Binary (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (push) Blocked by required conditions
Live Provider Tests / Smoke Tests (Code Execution) (push) Blocked by required conditions
Live Provider Tests / Compaction Tests (push) Blocked by required conditions
Live Provider Tests / goose server HTTP integration tests (push) Blocked by required conditions
Publish Docker Image / docker (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run

This commit is contained in:
Lifei Zhou 2026-06-27 17:45:19 +10:00 committed by GitHub
parent e2a2a64d3c
commit 4f7bf8c1e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 186 additions and 6 deletions

View file

@ -0,0 +1,80 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getAcpClient } from '../acpConnection';
import { acpSetSessionProviderModel } from '../providers';
vi.mock('../acpConnection', () => ({
getAcpClient: vi.fn(),
}));
function selectConfigOption(id: string, currentValue: string) {
return {
id,
name: id,
type: 'select',
currentValue,
options: [],
};
}
describe('ACP providers', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('sets thinking effort after provider and model, then returns the final config response', async () => {
const client = {
setSessionConfigOption: vi
.fn()
.mockResolvedValueOnce({
configOptions: [
selectConfigOption('provider', 'anthropic'),
selectConfigOption('model', 'provider-default-model'),
],
})
.mockResolvedValueOnce({
configOptions: [
selectConfigOption('provider', 'anthropic'),
selectConfigOption('model', 'claude-sonnet-4-5'),
],
})
.mockResolvedValueOnce({
configOptions: [
selectConfigOption('provider', 'anthropic'),
selectConfigOption('model', 'claude-sonnet-4-5'),
selectConfigOption('thinking_effort', 'high'),
],
}),
};
vi.mocked(getAcpClient).mockResolvedValue(
client as unknown as Awaited<ReturnType<typeof getAcpClient>>
);
const applied = await acpSetSessionProviderModel(
'session-1',
'anthropic',
'claude-sonnet-4-5',
'high'
);
expect(client.setSessionConfigOption).toHaveBeenCalledTimes(3);
expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(1, {
sessionId: 'session-1',
configId: 'provider',
value: 'anthropic',
});
expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(2, {
sessionId: 'session-1',
configId: 'model',
value: 'claude-sonnet-4-5',
});
expect(client.setSessionConfigOption).toHaveBeenNthCalledWith(3, {
sessionId: 'session-1',
configId: 'thinking_effort',
value: 'high',
});
expect(applied).toEqual({
providerId: 'anthropic',
modelId: 'claude-sonnet-4-5',
});
});
});

View file

@ -198,6 +198,55 @@ export async function acpSaveThinkingEffort(effort: ThinkingEffort): Promise<voi
});
}
export type AppliedSessionProviderModel = {
providerId?: string;
modelId?: string;
};
function extractAppliedSessionProviderModel(configOptions: unknown): AppliedSessionProviderModel {
if (!Array.isArray(configOptions)) {
return {};
}
const applied: AppliedSessionProviderModel = {};
for (const option of configOptions) {
if (!option || typeof option !== 'object') {
continue;
}
const id = 'id' in option ? option.id : undefined;
if (id !== 'provider' && id !== 'model') {
continue;
}
const currentValue = selectCurrentValue(option);
if (typeof currentValue !== 'string') {
continue;
}
if (id === 'provider') {
applied.providerId = currentValue;
} else {
applied.modelId = currentValue;
}
}
return applied;
}
function selectCurrentValue(kind: unknown): unknown {
if (!kind || typeof kind !== 'object') {
return undefined;
}
if ('type' in kind && kind.type === 'select' && 'currentValue' in kind) {
return kind.currentValue;
}
return undefined;
}
/**
* Switch the provider (and model) for an active session via ACP config options.
*
@ -207,11 +256,29 @@ export async function acpSaveThinkingEffort(effort: ThinkingEffort): Promise<voi
export async function acpSetSessionProviderModel(
sessionId: string,
providerId: string,
modelId?: string | null
): Promise<void> {
modelId?: string | null,
thinkingEffort?: ThinkingEffort | null
): Promise<AppliedSessionProviderModel> {
const client = await getAcpClient();
await client.setSessionConfigOption({ sessionId, configId: 'provider', value: providerId });
let response = await client.setSessionConfigOption({
sessionId,
configId: 'provider',
value: providerId,
});
if (modelId) {
await client.setSessionConfigOption({ sessionId, configId: 'model', value: modelId });
response = await client.setSessionConfigOption({
sessionId,
configId: 'model',
value: modelId,
});
}
if (thinkingEffort != null) {
response = await client.setSessionConfigOption({
sessionId,
configId: 'thinking_effort',
value: thinkingEffort,
});
}
return extractAppliedSessionProviderModel(response.configOptions);
}

View file

@ -2,7 +2,13 @@ import React, { createContext, useContext, useState, useEffect, useMemo, useCall
import { toastError, toastSuccess } from '../toasts';
import Model, { getProviderMetadata } from './settings/models/modelInterface';
import { ProviderMetadata } from '../api';
import { acpReadDefaults, acpSaveDefaults, acpSetSessionProviderModel } from '../acp/providers';
import { acpChatSessionActions, acpChatSessionStore } from '../acp/chatSessionStore';
import {
acpReadDefaults,
acpSaveDefaults,
acpSetSessionProviderModel,
type AppliedSessionProviderModel,
} from '../acp/providers';
import { errorMessage } from '../utils/conversionUtils';
import {
getModelDisplayName,
@ -57,6 +63,27 @@ const ModelAndProviderContext = createContext<ModelAndProviderContextType | unde
export { i18n as modelAndProviderMessages };
function patchAcpSessionProviderModel(
sessionId: string,
{ providerId, modelId }: AppliedSessionProviderModel
) {
if (!providerId && !modelId) return;
const currentSession = acpChatSessionStore.getSnapshot(sessionId)?.session;
if (!currentSession) return;
acpChatSessionActions.setSessionMetadata(sessionId, {
...currentSession,
provider_name: providerId ?? currentSession.provider_name,
model_config: modelId
? {
...(currentSession.model_config ?? { toolshim: false }),
model_name: modelId,
}
: currentSession.model_config,
});
}
export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> = ({ children }) => {
const [currentModel, setCurrentModel] = useState<string | null>(null);
const [currentProvider, setCurrentProvider] = useState<string | null>(null);
@ -70,7 +97,13 @@ export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> =
try {
if (sessionId) {
await acpSetSessionProviderModel(sessionId, providerName, modelName);
const applied = await acpSetSessionProviderModel(
sessionId,
providerName,
modelName,
model.request_params?.thinking_effort ?? null
);
patchAcpSessionProviderModel(sessionId, applied);
}
// Only update the global config default when there's no session