mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
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
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:
parent
e2a2a64d3c
commit
4f7bf8c1e2
3 changed files with 186 additions and 6 deletions
80
ui/desktop/src/acp/__tests__/providers.test.ts
Normal file
80
ui/desktop/src/acp/__tests__/providers.test.ts
Normal 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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue