feat: support session-scoped model overrides

Model selection was acting like a provider-level preference.

That made resumed sessions drift back to a default or request-time model.

Users expect /models changes made inside a conversation to affect that session.

Store explicit session choices in app-owned ~/.cloudcli state.

This avoids editing provider transcripts or native provider config.

Resolve the effective model before launching each provider runtime.

Claude, Cursor, Codex, Gemini, and OpenCode now honor stored resume choices.

Expose a backend active-model change endpoint for existing sessions.

The models modal can now distinguish default changes from session overrides.

It also shows when a selected model will apply on the next response.

For Claude, stop probing active model state by resuming with a dummy prompt.

Read the indexed JSONL transcript from the end instead.

This preserves provider history while honoring /model stdout or model fields.

Add service tests for adapter delegation and resume-model precedence.

The tests keep cache state, override state, and requested fallback separate.
This commit is contained in:
Haileyesus
2026-05-18 16:57:29 +03:00
parent bc5e768579
commit 9aa927002e
19 changed files with 872 additions and 191 deletions

View File

@@ -3,6 +3,7 @@ import crossSpawn from 'cross-spawn';
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
import { sessionsService } from './modules/providers/services/sessions.service.js';
import { providerAuthService } from './modules/providers/services/provider-auth.service.js';
import { providerModelsService } from './modules/providers/services/provider-models.service.js';
import { createNormalizedMessage } from './shared/utils.js';
// Use cross-spawn on Windows for better command execution
@@ -28,6 +29,7 @@ function isWorkspaceTrustPrompt(text = '') {
async function spawnCursor(command, options = {}, ws) {
return new Promise(async (resolve, reject) => {
const { sessionId, projectPath, cwd, resume, toolsSettings, skipPermissions, model, sessionSummary } = options;
const resolvedModel = await providerModelsService.resolveResumeModel('cursor', sessionId, model);
let capturedSessionId = sessionId; // Track session ID throughout the process
let sessionCreatedSent = false; // Track if we've already sent session-created event
let hasRetriedWithTrust = false;
@@ -52,9 +54,10 @@ async function spawnCursor(command, options = {}, ws) {
// Provide a prompt (works for both new and resumed sessions)
baseArgs.push('-p', command);
// Add model flag if specified (only meaningful for new sessions; harmless on resume)
if (!sessionId && model) {
baseArgs.push('--model', model);
// Model overrides are applied to both new and resumed sessions so a
// session-scoped change request can take effect on the next turn.
if (resolvedModel) {
baseArgs.push('--model', resolvedModel);
}
// Request streaming JSON when we are providing a prompt