fix(settings): 모델 dropdown 에 보유 모델 전부 표시 (v2.2.209)

설정 패널 dropdown 이 LM Studio 에서 모델 1개만 보이고, 변경하면 원복되던
회귀 수정. 원인: settings 패널의 discoverModels 가 REST /v1/models 만 사용 →
JIT 로딩 환경에서 '현재 로드된' 모델만 반환. (사이드바는 SDK 로 전체를 가져옴)

- discoverModels: LM Studio SDK listDownloadedModels(전체 다운로드) 우선,
  실패/0개면 REST 폴백. 사이드바 ModelDiscovery 와 동일 정책으로 통일 →
  두 경로가 갈라져 다시 회귀하지 않도록 가이드라인 주석 명시.
- SettingsPanelDeps/SettingsSetupDeps 에 lmStudioDownloaded 콜백 추가,
  extension.ts 에서 lmStudioClient.listDownloadedCached 연결.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 10:35:23 +09:00
parent b733864375
commit b4ddd4f79a
6 changed files with 46 additions and 8 deletions
+31 -4
View File
@@ -3,17 +3,44 @@ import { resolveEngine, buildApiUrl, logError, logInfo } from '../utils';
/**
* Discover the model list exposed by the local AI engine at `baseUrl`.
*
* Same wire format as the sidebar's `_sendModels` (which still owns the
* sidebar-specific caching/UI logic) — extracted here so the settings panel
* can fetch the same list without depending on the sidebar provider.
* [가이드라인] 보유한 모델이 *전부* 나와야 한다. LM Studio 의 REST `/v1/models`
* 는 JIT(Just-In-Time) 로딩 설정에서 *현재 로드된* 모델만 반환하므로, 그것만
* 쓰면 dropdown 에 1개만 뜨는 회귀가 생긴다. 따라서 LM Studio 에서는 SDK
* `system.listDownloadedModels('llm')`(다운로드된 모든 LLM)을 **우선** 시도하고,
* 실패/0개일 때만 REST 로 폴백한다. 사이드바 `ModelDiscovery` 와 동일한 정책 —
* 두 경로가 갈라지면 또 회귀하므로 반드시 같은 우선순위를 유지할 것.
*
* `opts.lmStudioDownloaded` 는 LM Studio SDK 의 다운로드 모델 목록 콜백
* (보통 `lmStudioClient.listDownloadedCached`). 제공되지 않으면 REST 만 사용.
*
* Returns an empty array on any failure (offline engine, parse error, etc.).
* Callers should treat the result as a hint, not a hard list.
*/
export async function discoverModels(baseUrl: string, timeoutMs: number = 5000): Promise<string[]> {
export async function discoverModels(
baseUrl: string,
opts: { timeoutMs?: number; lmStudioDownloaded?: () => Promise<string[]> } = {},
): Promise<string[]> {
const { timeoutMs = 5000, lmStudioDownloaded } = opts;
const url = (baseUrl || '').trim();
if (!url) return [];
const engine = resolveEngine(url);
// 1) LM Studio + SDK 우선 — 다운로드된 모든 모델(로드 여부 무관).
if (engine === 'lmstudio' && lmStudioDownloaded) {
try {
const sdk = await lmStudioDownloaded();
const filtered = sdk.filter((m): m is string => typeof m === 'string' && m.length > 0);
if (filtered.length > 0) {
logInfo('discoverModels: SDK 다운로드 모델 사용', { count: filtered.length });
return filtered;
}
logInfo('discoverModels: SDK 0개 — REST 폴백', { engine });
} catch (e: any) {
logInfo('discoverModels: SDK 실패 — REST 폴백', { error: e?.message ?? String(e) });
}
}
// 2) REST 폴백 (`/v1/models` lmstudio · `/api/tags` ollama)
const modelsUrl = buildApiUrl(url, engine, 'models');
try {
const res = await fetch(modelsUrl, { signal: AbortSignal.timeout(timeoutMs) });