Files
claudecodeui/server/shared/interfaces.ts
Haileyesus bc5e768579 feat(models): resolve active session models through provider adapters
The model inventory command was showing a mix of catalog defaults and
composer-local state instead of the model that is actually active for a
real provider session. That made /models, /cost, and /status
misleading once a session had already started, especially for providers
whose effective runtime model can differ from the optimistic model value
held in the UI.

Introduce an explicit getCurrentActiveModel() contract on
IProviderModels so model resolution lives next to each provider's
catalog logic and uses the provider-native source of truth:

- Claude reads the init event from a resumed stream-json run
- Codex reads model from ~/.codex/config.toml
- Cursor reads lastUsedModel from the chat store.db
- OpenCode reads the persisted session model from opencode.db
- Gemini intentionally returns its default because the CLI does not
  provide a reliable active-session lookup

Keep the returned shape intentionally minimal ({ model }). The goal is
to expose only what downstream command consumers need and avoid leaking
provider-specific metadata into a shared transport shape that would
create extra UI coupling and future cleanup cost.

Also make command behavior session-aware: when there is no concrete
session id, do not spawn provider processes or inspect provider session
storage just to answer /models, /cost, or /status. In a new-session
view the correct answer is simply the provider default, and doing more
work there adds latency and unnecessary side effects for no user value.

As part of this, centralize two supporting concerns:

- add a shared helper for building the default current-model result from
  a provider catalog so fallbacks stay aligned with DEFAULT
- move leaf-directory validation into shared utils so Cursor session
  readers and model lookup code enforce the same path-safety rule

Tests were expanded to cover both the new service delegation path and
the sessionless command behavior, while keeping cache-sensitive tests
isolated from persisted host cache state.

Why this change:
- command output should reflect the model actually driving a session
- new-session views should stay fast and side-effect free
- provider-specific active-model lookup should not be scattered across
  routes or UI code
- fallback behavior should be explicit, consistent, and limited to the
  provider default when no true active model can be resolved
2026-05-18 14:54:32 +03:00

142 lines
4.9 KiB
TypeScript

import type {
FetchHistoryOptions,
FetchHistoryResult,
LLMProvider,
McpScope,
NormalizedMessage,
ProviderSkill,
ProviderSkillListOptions,
ProviderAuthStatus,
ProviderCurrentActiveModel,
ProviderModelsDefinition,
ProviderMcpServer,
UpsertProviderMcpServerInput,
} from '@/shared/types.js';
//----------------- PROVIDER CONTRACT INTERFACES ------------
/**
* Main provider contract for CLI and SDK integrations.
*
* Each concrete provider owns its MCP/auth handlers plus the provider-specific
* logic for converting native events/history into the app's normalized shape.
*/
export interface IProvider {
readonly id: LLMProvider;
readonly models: IProviderModels;
readonly mcp: IProviderMcp;
readonly auth: IProviderAuth;
readonly skills: IProviderSkills;
readonly sessions: IProviderSessions;
readonly sessionSynchronizer: IProviderSessionSynchronizer;
}
// ---------------------------
//----------------- PROVIDER MODEL INTERFACE ------------
/**
* Model catalog contract for one provider.
*
* Implementations are responsible for resolving the provider's currently
* supported models and converting them into the shared
* `ProviderModelsDefinition` shape used by backend routes and frontend model
* pickers. The `DEFAULT` field should be the most appropriate default selection
* for that provider at the time the catalog is read.
*/
export interface IProviderModels {
/**
* Returns the provider's currently supported model catalog.
*/
getSupportedModels(): Promise<ProviderModelsDefinition>;
/**
* Returns the currently active model for one session or provider runtime.
*
* Implementations must use the provider-specific lookup mechanism approved
* for that provider and fall back only to the provider catalog default when
* no active model can be resolved.
*/
getCurrentActiveModel(sessionId?: string): Promise<ProviderCurrentActiveModel>;
}
// ---------------------------
//----------------- PROVIDER AUTH INTERFACE ------------
/**
* Auth contract for one provider.
*
* Implementations should return a complete installation/authentication status
* without throwing for normal "not installed" or "not authenticated" states.
*/
export interface IProviderAuth {
/**
* Checks whether the provider is installed and has usable credentials.
*/
getStatus(): Promise<ProviderAuthStatus>;
}
// ---------------------------
//----------------- PROVIDER SKILLS INTERFACE ------------
/**
* Skills contract for one provider.
*
* Implementations discover provider-native skill markdown locations and return
* normalized skill records with the exact command syntax expected by that
* provider. Each skill is read from a `SKILL.md` file under its skill directory.
*/
export interface IProviderSkills {
/**
* Lists all skills visible to this provider for the optional workspace.
*/
listSkills(options?: ProviderSkillListOptions): Promise<ProviderSkill[]>;
}
// ---------------------------
//----------------- PROVIDER MCP INTERFACE ------------
/**
* MCP contract for one provider.
*
* Implementations must map provider-native MCP config formats to shared
* `ProviderMcpServer` records used by routes and frontend state.
*/
export interface IProviderMcp {
listServers(options?: { workspacePath?: string }): Promise<Record<McpScope, ProviderMcpServer[]>>;
listServersForScope(scope: McpScope, options?: { workspacePath?: string }): Promise<ProviderMcpServer[]>;
upsertServer(input: UpsertProviderMcpServerInput): Promise<ProviderMcpServer>;
removeServer(
input: { name: string; scope?: McpScope; workspacePath?: string },
): Promise<{ removed: boolean; provider: LLMProvider; name: string; scope: McpScope }>;
}
// ---------------------------
//----------------- PROVIDER SESSION INTERFACE ------------
/**
* Session/history contract for one provider.
*
* Implementations normalize provider-specific events and message history into
* shared transport shapes consumed by API routes and realtime streams.
*/
export interface IProviderSessions {
normalizeMessage(raw: unknown, sessionId: string | null): NormalizedMessage[];
fetchHistory(sessionId: string, options?: FetchHistoryOptions): Promise<FetchHistoryResult>;
}
// ---------------------------
//----------------- PROVIDER SESSION SYNCHRONIZER INTERFACE ------------
/**
* Session indexing contract for one provider.
*
* Implementations scan provider-specific session artifacts on disk and upsert
* normalized session metadata into the database. The service layer uses this
* interface for both full rescans and single-file incremental sync triggered
* by filesystem watcher events.
*/
export interface IProviderSessionSynchronizer {
/**
* Scans provider session artifacts and upserts discovered sessions into DB.
*/
synchronize(since?: Date): Promise<number>;
/**
* Parses and upserts one provider artifact file without running a full scan.
*/
synchronizeFile(filePath: string): Promise<string | null>;
}