diff --git a/core/index.d.ts b/core/index.d.ts index de08c1ea1f8..f31b62ed7d6 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -272,6 +272,10 @@ export interface Session { title: string; workspaceDirectory: string; history: ChatHistoryItem[]; + /** Optional: per-session UI mode (chat/agent/plan/background) */ + mode?: MessageModes; + /** Optional: title of the selected chat model for this session */ + chatModelTitle?: string | null; } export interface BaseSessionMetadata { diff --git a/core/util/history.ts b/core/util/history.ts index 02e3f8feeee..65d7d53f0bf 100644 --- a/core/util/history.ts +++ b/core/util/history.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import { Session, BaseSessionMetadata } from "../index.js"; +import { BaseSessionMetadata, Session } from "../index.js"; import { ListHistoryOptions } from "../protocol/core.js"; import { NEW_SESSION_TITLE } from "./constants.js"; @@ -108,6 +108,13 @@ export class HistoryManager { workspaceDirectory: session.workspaceDirectory, history: session.history, }; + if (session.mode) { + orderedSession.mode = session.mode; + } + if (session.chatModelTitle !== undefined) { + orderedSession.chatModelTitle = session.chatModelTitle; + } + fs.writeFileSync( getSessionFilePath(session.sessionId), JSON.stringify(orderedSession, undefined, 2), diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts index 0c20cdf8924..3656af0c185 100644 --- a/gui/src/redux/slices/sessionSlice.ts +++ b/gui/src/redux/slices/sessionSlice.ts @@ -693,6 +693,9 @@ export const sessionSlice = createSlice({ state.history = payload.history as any; state.title = payload.title; state.id = payload.sessionId; + if (payload.mode) { + state.mode = payload.mode; + } } else { state.history = []; state.title = NEW_SESSION_TITLE; diff --git a/gui/src/redux/thunks/session.ts b/gui/src/redux/thunks/session.ts index e89acb78f6b..03a94fa7aa2 100644 --- a/gui/src/redux/thunks/session.ts +++ b/gui/src/redux/thunks/session.ts @@ -5,6 +5,7 @@ import { NEW_SESSION_TITLE } from "core/util/constants"; import { renderChatMessage } from "core/util/messageContent"; import { IIdeMessenger } from "../../context/IdeMessenger"; import { selectSelectedChatModel } from "../slices/configSlice"; +import { selectSelectedProfile } from "../slices/profilesSlice"; import { deleteSessionMetadata, newSession, @@ -13,6 +14,7 @@ import { updateSessionMetadata, } from "../slices/sessionSlice"; import { ThunkApiType } from "../store"; +import { updateSelectedModelByRole } from "../thunks/updateSelectedModelByRole"; const MAX_TITLE_LENGTH = 100; @@ -103,7 +105,10 @@ export const loadSession = createAsyncThunk< ThunkApiType >( "session/load", - async ({ sessionId, saveCurrentSession: save }, { extra, dispatch }) => { + async ( + { sessionId, saveCurrentSession: save }, + { extra, dispatch, getState }, + ) => { if (save) { const result = await dispatch( saveCurrentSession({ @@ -115,6 +120,11 @@ export const loadSession = createAsyncThunk< } const session = await getSession(extra.ideMessenger, sessionId); dispatch(newSession(session)); + + // Restore selected chat model from session, if present + if (session.chatModelTitle) { + dispatch(selectChatModelForProfile(session.chatModelTitle)); + } }, ); @@ -127,7 +137,10 @@ export const loadRemoteSession = createAsyncThunk< ThunkApiType >( "session/loadRemote", - async ({ remoteId, saveCurrentSession: save }, { extra, dispatch }) => { + async ( + { remoteId, saveCurrentSession: save }, + { extra, dispatch, getState }, + ) => { if (save) { const result = await dispatch( saveCurrentSession({ @@ -139,6 +152,35 @@ export const loadRemoteSession = createAsyncThunk< } const session = await getRemoteSession(extra.ideMessenger, remoteId); dispatch(newSession(session)); + + // Restore selected chat model from session, if present + if (session.chatModelTitle) { + dispatch(selectChatModelForProfile(session.chatModelTitle)); + } + }, +); + +export const selectChatModelForProfile = createAsyncThunk< + void, + string, + ThunkApiType +>( + "session/selectModelForCurrentProfile", + async (modelTitle, { extra, dispatch, getState }) => { + const state = getState(); + const modelMatch = state.config.config?.modelsByRole?.chat?.find( + (m) => m.title === modelTitle, + ); + const selectedProfile = selectSelectedProfile(state); + if (selectedProfile && modelMatch) { + await dispatch( + updateSelectedModelByRole({ + role: "chat", + modelTitle: modelTitle, + selectedProfile, + }), + ); + } }, ); @@ -168,6 +210,9 @@ export const loadLastSession = createAsyncThunk( session = await getSession(extra.ideMessenger, lastSessionId); } dispatch(newSession(session)); + if (session.chatModelTitle) { + dispatch(selectChatModelForProfile(session.chatModelTitle)); + } }, ); @@ -246,11 +291,15 @@ export const saveCurrentSession = createAsyncThunk< title = NEW_SESSION_TITLE; } + const selectedChatModel = selectSelectedChatModel(state); + const session: Session = { sessionId: state.session.id, title, workspaceDirectory: window.workspacePaths?.[0] || "", history: state.session.history, + mode: state.session.mode, + chatModelTitle: selectedChatModel?.title ?? null, }; const result = await dispatch(updateSession(session));