Skip to content

Commit 07b81bf

Browse files
steipetehyspacex
andcommitted
fix(ui): show selected agent default model
Co-authored-by: Harry Xie <[email protected]>
1 parent d6c880a commit 07b81bf

5 files changed

Lines changed: 141 additions & 0 deletions

File tree

ui/src/e2e/chat-flow.e2e.test.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,104 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
14401440
}
14411441
});
14421442

1443+
it("restores the selected agent model after clearing a session override", async () => {
1444+
const context = await newBrowserContext({
1445+
locale: "en-US",
1446+
serviceWorkers: "block",
1447+
viewport: { height: 900, width: 1280 },
1448+
});
1449+
const page = await context.newPage();
1450+
const agentsList = {
1451+
agents: [
1452+
{
1453+
id: "ops",
1454+
model: { primary: "anthropic/claude-opus-4-5" },
1455+
name: "Operations",
1456+
},
1457+
],
1458+
defaultId: "ops",
1459+
mainKey: "main",
1460+
scope: "agent",
1461+
};
1462+
const sessionsList = {
1463+
count: 1,
1464+
defaults: {
1465+
contextTokens: null,
1466+
model: "gpt-5.5",
1467+
modelProvider: "openai",
1468+
},
1469+
path: "",
1470+
sessions: [
1471+
{
1472+
key: "agent:ops:session-a",
1473+
kind: "direct",
1474+
label: "Operations",
1475+
updatedAt: Date.now(),
1476+
},
1477+
],
1478+
ts: Date.now(),
1479+
};
1480+
const gateway = await installMockGateway(page, {
1481+
assistantAgentId: "ops",
1482+
defaultAgentId: "ops",
1483+
methodResponses: {
1484+
"agents.list": agentsList,
1485+
"chat.startup": {
1486+
agentsList,
1487+
messages: [],
1488+
metadata: {
1489+
models: [
1490+
{ id: "gpt-5.5", name: "GPT-5.5", provider: "openai" },
1491+
{
1492+
id: "claude-opus-4-5",
1493+
name: "Claude Opus 4.5",
1494+
provider: "anthropic",
1495+
},
1496+
],
1497+
},
1498+
sessionId: "control-ui-e2e-session",
1499+
thinkingLevel: null,
1500+
},
1501+
"sessions.list": sessionsList,
1502+
},
1503+
models: [
1504+
{ id: "gpt-5.5", name: "GPT-5.5", provider: "openai" },
1505+
{ id: "claude-opus-4-5", name: "Claude Opus 4.5", provider: "anthropic" },
1506+
],
1507+
sessionKey: "agent:ops:session-a",
1508+
});
1509+
1510+
try {
1511+
await page.goto(`${server.baseUrl}chat`);
1512+
const main = page.getByRole("main");
1513+
const modelSelect = main.locator('[data-chat-model-select="true"]').first();
1514+
await modelSelect.waitFor({ state: "visible", timeout: 10_000 });
1515+
expect(await modelSelect.textContent()).toContain("Claude Opus 4.5");
1516+
expect(await modelSelect.getAttribute("data-chat-select-value")).toBe("");
1517+
1518+
await modelSelect.click();
1519+
await main.locator('[data-chat-model-option="openai/gpt-5.5"]').click();
1520+
const firstPatch = await gateway.waitForRequest("sessions.patch");
1521+
expect(requireRecord(firstPatch.params)).toMatchObject({
1522+
key: "agent:ops:session-a",
1523+
model: "openai/gpt-5.5",
1524+
});
1525+
expect(await modelSelect.textContent()).toContain("GPT-5.5");
1526+
1527+
await modelSelect.click();
1528+
await main.locator('[data-chat-model-option=""]').click();
1529+
const patches = await waitForRequests(gateway, "sessions.patch", 2);
1530+
expect(requireRecord(patches[1]?.params)).toMatchObject({
1531+
key: "agent:ops:session-a",
1532+
model: null,
1533+
});
1534+
expect(await modelSelect.textContent()).toContain("Claude Opus 4.5");
1535+
expect(await modelSelect.getAttribute("data-chat-select-value")).toBe("");
1536+
} finally {
1537+
await closeBrowserContext(context);
1538+
}
1539+
});
1540+
14431541
it("clears hover marquee state when a session switch reshuffles recent rows", async () => {
14441542
const context = await newBrowserContext({
14451543
locale: "en-US",

ui/src/lib/chat/model-select-state.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,34 @@ describe("chat-model-select-state", () => {
197197
]);
198198
});
199199

200+
it("uses the active agent model for the default label", () => {
201+
const state = createChatModelState({
202+
agentDefaultModel: "anthropic/claude-opus-4-5",
203+
chatModelCatalog: createModelCatalog(
204+
{
205+
id: "gpt-5.5",
206+
name: "GPT-5.5",
207+
provider: "openai",
208+
},
209+
{
210+
id: "claude-opus-4-5",
211+
name: "Claude Opus 4.5",
212+
provider: "anthropic",
213+
},
214+
),
215+
sessionsResult: createSessionsListResult({
216+
defaultsModel: "gpt-5.5",
217+
defaultsProvider: "openai",
218+
model: "claude-opus-4-5",
219+
modelProvider: "anthropic",
220+
}),
221+
});
222+
223+
const resolved = resolveChatModelSelectState(state);
224+
expect(resolved.defaultModel).toBe("anthropic/claude-opus-4-5");
225+
expect(resolved.defaultLabel).toBe("Default (Claude Opus 4.5)");
226+
});
227+
200228
it("disambiguates duplicate friendly names in picker options and default labels", () => {
201229
const state = createChatModelState({
202230
chatModelCatalog: createModelCatalog(

ui/src/lib/chat/model-select-state.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "./model-ref.ts";
1818

1919
type ChatModelSelectStateInput = {
20+
agentDefaultModel?: string;
2021
chatModelCatalog: ModelCatalogEntry[];
2122
modelOverrides: Readonly<Record<string, string | null | undefined>>;
2223
sessionKey: string;
@@ -87,6 +88,14 @@ export function resolveChatModelOverrideValue(state: ChatModelSelectStateInput):
8788
}
8889

8990
function resolveDefaultModelValue(state: ChatModelSelectStateInput): string {
91+
const agentDefault = resolvePreferredServerChatModelValue(
92+
state.agentDefaultModel,
93+
undefined,
94+
state.chatModelCatalog ?? [],
95+
);
96+
if (agentDefault) {
97+
return agentDefault;
98+
}
9099
return resolvePreferredServerChatModelValue(
91100
state.sessionsResult?.defaults?.model,
92101
state.sessionsResult?.defaults?.modelProvider,

ui/src/pages/chat/chat-page.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,9 @@ export class ChatPage extends LitElement {
612612
return html`<main class="app-shell app-shell--booting" aria-busy="true"></main>`;
613613
}
614614
const currentAgentId = resolveChatAgentId(state);
615+
const agentDefaultModel = this.context.agents.state.agentsList?.agents.find(
616+
(agent) => agent.id === currentAgentId,
617+
)?.model?.primary;
615618
const selectedSessionArchived =
616619
state.selectedChatSessionArchived ||
617620
state.sessionsResult?.sessions.some(
@@ -672,6 +675,7 @@ export class ChatPage extends LitElement {
672675
manualRefreshInFlight: state.chatManualRefreshInFlight,
673676
model: {
674677
activeRunId: state.chatRunId,
678+
agentDefaultModel,
675679
connected: state.connected,
676680
gatewayAvailable: Boolean(state.client),
677681
loading: state.chatLoading,

ui/src/pages/chat/components/chat-model-controls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818

1919
export type ChatModelControlsProps = {
2020
activeRunId: string | null;
21+
agentDefaultModel?: string;
2122
connected: boolean;
2223
gatewayAvailable: boolean;
2324
loading: boolean;
@@ -40,6 +41,7 @@ export function renderChatModelControls(props: ChatModelControlsProps) {
4041
defaultLabel,
4142
options: selectOptions,
4243
} = resolveChatModelSelectState({
44+
agentDefaultModel: props.agentDefaultModel,
4345
chatModelCatalog: props.modelCatalog,
4446
modelOverrides: props.modelOverrides ?? {},
4547
sessionKey: props.sessionKey,

0 commit comments

Comments
 (0)