From b4ddd4f79af0144945a8865c0a21769f851d40b1 Mon Sep 17 00:00:00 2001 From: g1nation Date: Tue, 9 Jun 2026 10:35:23 +0900 Subject: [PATCH] =?UTF-8?q?fix(settings):=20=EB=AA=A8=EB=8D=B8=20dropdown?= =?UTF-8?q?=20=EC=97=90=20=EB=B3=B4=EC=9C=A0=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=A0=84=EB=B6=80=20=ED=91=9C=EC=8B=9C=20(v2.2.209)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 설정 패널 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 --- package-lock.json | 4 +-- package.json | 2 +- src/extension.ts | 2 ++ src/extension/settingsSetup.ts | 3 ++ .../settings/settingsPanelProvider.ts | 8 ++++- src/lib/discoverModels.ts | 35 ++++++++++++++++--- 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 244ee11..57e8b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "astra", - "version": "2.2.208", + "version": "2.2.209", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "astra", - "version": "2.2.208", + "version": "2.2.209", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", diff --git a/package.json b/package.json index 95dbc88..cc125ec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.2.208", + "version": "2.2.209", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/extension.ts b/src/extension.ts index adb3c32..2a1635e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -281,6 +281,8 @@ export async function activate(context: vscode.ExtensionContext) { const { settingsPanel, disposables: settingsDisposables } = setupSettingsPanel(context, { telegramClient, telegramBot, + // 모델 dropdown 이 보유 모델 전부를 보이도록 SDK 다운로드 목록을 전달. + lmStudioDownloaded: () => lmStudioClient.listDownloadedCached(), }); context.subscriptions.push(...settingsDisposables); diff --git a/src/extension/settingsSetup.ts b/src/extension/settingsSetup.ts index 6d57ac9..4c6d1f6 100644 --- a/src/extension/settingsSetup.ts +++ b/src/extension/settingsSetup.ts @@ -8,6 +8,8 @@ import type { TelegramHttpClient } from '../integrations/telegram/telegramClient export interface SettingsSetupDeps { telegramClient: TelegramHttpClient; telegramBot: TelegramBot; + /** LM Studio SDK 다운로드 모델 목록 콜백 — 모델 dropdown 이 보유 모델 전부를 보이도록 전달. */ + lmStudioDownloaded?: () => Promise; } /** @@ -34,6 +36,7 @@ export function setupSettingsPanel( secrets: context.secrets, telegramClient: deps.telegramClient, telegramBot: deps.telegramBot, + lmStudioDownloaded: deps.lmStudioDownloaded, }); const disposables: vscode.Disposable[] = [ diff --git a/src/features/settings/settingsPanelProvider.ts b/src/features/settings/settingsPanelProvider.ts index 559b39a..e15bd5d 100644 --- a/src/features/settings/settingsPanelProvider.ts +++ b/src/features/settings/settingsPanelProvider.ts @@ -44,6 +44,12 @@ export interface SettingsPanelDeps { telegramClient: ITelegramClient; /** Returns the live bot instance for enrollNextChat. */ telegramBot: TelegramBot; + /** + * LM Studio SDK 의 '다운로드된 모든 LLM' 목록 콜백 (보통 lmStudioClient.listDownloadedCached). + * 모델 dropdown 이 보유 모델 전부를 보여주도록 discoverModels 에 전달한다. + * 없으면 REST `/v1/models` 만 사용 → JIT 환경에서 로드된 1개만 나오는 회귀 발생. + */ + lmStudioDownloaded?: () => Promise; } interface SettingsState { @@ -434,7 +440,7 @@ export class SettingsPanelProvider implements vscode.WebviewViewProvider { this._modelsLoading = true; await this._refreshState(); try { - const models = await discoverModels(url); + const models = await discoverModels(url, { lmStudioDownloaded: this._deps.lmStudioDownloaded }); this._modelsCache = { url, models, diff --git a/src/lib/discoverModels.ts b/src/lib/discoverModels.ts index 98fb83e..ada0582 100644 --- a/src/lib/discoverModels.ts +++ b/src/lib/discoverModels.ts @@ -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 { +export async function discoverModels( + baseUrl: string, + opts: { timeoutMs?: number; lmStudioDownloaded?: () => Promise } = {}, +): Promise { + 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) });