From 44c1ac42167262c73a92b4db6a3e05bad31d9deb Mon Sep 17 00:00:00 2001 From: HarelMil Date: Fri, 6 Mar 2026 23:42:05 +0200 Subject: [PATCH 1/2] feat: add HTTP authentication support for LM Studio API endpoints - Reads authentication credentials from ~/.local/share/opencode/auth.json - Uses Bearer token authentication for all LM Studio API requests - Maintains backward compatibility (no header when auth is not configured) - Applies authentication to health check, model discovery, and fetch endpoints - Adds tests to verify authorization header behavior - Fixes 401 Unauthorized errors when LM Studio server requires API key --- package-lock.json | 4 ++-- src/utils/http.ts | 46 +++++++++++++++++++++++++++++++++++++++ src/utils/index.ts | 21 +++++++++++++++--- src/utils/lmstudio-api.ts | 10 ++++++--- test/plugin.test.ts | 16 ++++++++++++++ 5 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 src/utils/http.ts diff --git a/package-lock.json b/package-lock.json index 1b0c537..c90cabd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencode-lmstudio", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-lmstudio", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "@opencode-ai/plugin": "^1.0.166" diff --git a/src/utils/http.ts b/src/utils/http.ts new file mode 100644 index 0000000..3fbd923 --- /dev/null +++ b/src/utils/http.ts @@ -0,0 +1,46 @@ +import * as fs from 'fs' +import * as path from 'path' + +export interface LMStudioAuthConfig { + type: 'api' | null + key: string | null +} + +export function getLMStudioAuth(): LMStudioAuthConfig { + const authFile = path.join(process.env.HOME || '', '.local', 'share', 'opencode', 'auth.json') + + try { + const authData = JSON.parse(fs.readFileSync(authFile, 'utf8')) + const lmstudioConfig = authData['lmstudio'] + + if (!lmstudioConfig) { + return { type: null, key: null } + } + + if (lmstudioConfig.type !== 'api') { + return { type: null, key: null } + } + + return { + type: 'api', + key: lmstudioConfig.key || null + } + } catch (error) { + console.warn(`[opencode-lmstudio] Failed to read LM Studio auth from ${authFile}`, error instanceof Error ? error.message : String(error)) + return { type: null, key: null } + } +} + +export function getHeaders(): Record { + const config = getLMStudioAuth() + const headers: Record = { + 'Content-Type': 'application/json' + } + + // Only add Authorization header if API key is configured + if (config.key) { + headers['Authorization'] = `Bearer ${config.key}` + } + + return headers +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 4d701d2..ac33434 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,5 @@ import type { ModelValidationError, AutoFixSuggestion, SimilarModel } from '../types' - +export { getLMStudioAuth, getHeaders } from './http' export { formatModelName, extractModelOwner } from './format-model-name' // Categorize models by type @@ -144,9 +144,9 @@ export function categorizeError(error: any, context: { baseURL: string; modelId: return { type: 'permission', severity: 'high', - message: `Authentication or permission issue with LM Studio. Check your configuration.`, + message: `Authentication required by LM Studio server. Please configure LM Studio API key in OpenCode auth settings.`, canRetry: false, - autoFixAvailable: false + autoFixAvailable: true } } @@ -212,6 +212,21 @@ export function generateAutoFixSuggestions(errorCategory: ModelValidationError): automated: false }) break + + case 'permission': + suggestions.push({ + action: "Configure LM Studio API key in OpenCode", + steps: [ + "1. Open opencode settings", + "2. Navigate to LM Studio authentication section", + "3. Enter your LM Studio API key (type: 'api')", + "4. Restart the plugin", + "5. The auth.json file will be updated automatically" + ], + command: "opencode auth set lmstudio type=api", + automated: false + }) + break } return suggestions diff --git a/src/utils/lmstudio-api.ts b/src/utils/lmstudio-api.ts index 62967e7..34fcdc3 100644 --- a/src/utils/lmstudio-api.ts +++ b/src/utils/lmstudio-api.ts @@ -1,4 +1,5 @@ import type { LMStudioModel, LMStudioModelsResponse } from '../types' +import { getHeaders } from './http' const DEFAULT_LM_STUDIO_URL = "http://127.0.0.1:1234" const LM_STUDIO_MODELS_ENDPOINT = "/v1/models" @@ -26,8 +27,10 @@ export function buildAPIURL(baseURL: string, endpoint: string = LM_STUDIO_MODELS export async function checkLMStudioHealth(baseURL: string = DEFAULT_LM_STUDIO_URL): Promise { try { const url = buildAPIURL(baseURL) + const headers = getHeaders() const response = await fetch(url, { method: "GET", + headers: headers, signal: AbortSignal.timeout(3000), }) return response.ok @@ -40,11 +43,10 @@ export async function checkLMStudioHealth(baseURL: string = DEFAULT_LM_STUDIO_UR export async function discoverLMStudioModels(baseURL: string = DEFAULT_LM_STUDIO_URL): Promise { try { const url = buildAPIURL(baseURL) + const headers = getHeaders() const response = await fetch(url, { method: "GET", - headers: { - "Content-Type": "application/json", - }, + headers: headers, signal: AbortSignal.timeout(3000), }) @@ -63,8 +65,10 @@ export async function discoverLMStudioModels(baseURL: string = DEFAULT_LM_STUDIO export async function fetchModelsDirect(baseURL: string = DEFAULT_LM_STUDIO_URL): Promise { try { const url = buildAPIURL(baseURL) + const headers = getHeaders() const response = await fetch(url, { method: "GET", + headers: headers, signal: AbortSignal.timeout(3000), }) if (!response.ok) { diff --git a/test/plugin.test.ts b/test/plugin.test.ts index ad05400..5a44926 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -368,5 +368,21 @@ describe('LMStudio Plugin', () => { consoleSpy.mockRestore() }) + + it('includes Authorization header when auth.json has valid LM Studio API key', async () => { + // Test that authorization headers are included when auth.json has valid LM Studio config + const { getHeaders } = await import('../src/utils/http.ts') + const headers = getHeaders() + + // If auth.json exists with 'type: api' and key, Authorization header should be present + if (headers['Authorization']) { + expect(headers['Authorization']).toBeDefined() + expect(headers['Authorization'].startsWith('Bearer ')).toBe(true) + } else { + // No auth configured - Content-Type should still be present + expect(headers['Content-Type']).toBe('application/json') + expect(headers['Authorization']).toBeUndefined() + } + }) }) }) \ No newline at end of file From 76e4a85460be79dbddfaace3ca96e9a769eb6352 Mon Sep 17 00:00:00 2001 From: HarelMil Date: Sat, 7 Mar 2026 00:40:25 +0200 Subject: [PATCH 2/2] fix: autoFixAvailable set to false instead of true. Added error message parsing to identify malformed API and missing header --- src/utils/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/index.ts b/src/utils/index.ts index ac33434..f2bb9d4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -140,13 +140,13 @@ export function categorizeError(error: any, context: { baseURL: string; modelId: } // Permission issues - if (errorStr.includes('401') || errorStr.includes('403') || errorStr.includes('unauthorized')) { + if (errorStr.includes('401') || errorStr.includes('403') || errorStr.includes('unauthorized') || errorStr.includes('An LM Studio API token is required') || errorStr.includes('Malformed LM Studio API token provided')) { return { type: 'permission', severity: 'high', message: `Authentication required by LM Studio server. Please configure LM Studio API key in OpenCode auth settings.`, canRetry: false, - autoFixAvailable: true + autoFixAvailable: false } } @@ -217,13 +217,13 @@ export function generateAutoFixSuggestions(errorCategory: ModelValidationError): suggestions.push({ action: "Configure LM Studio API key in OpenCode", steps: [ - "1. Open opencode settings", - "2. Navigate to LM Studio authentication section", - "3. Enter your LM Studio API key (type: 'api')", - "4. Restart the plugin", - "5. The auth.json file will be updated automatically" + "1. Open an opencode session", + "2. Run the command: /connect", + "3. Find and select 'LM Studio' from the list of providers", + "4. Enter your LM Studio API key and submit (found in LM Studio under Local Server > Server Settings > Manage Tokens)", + "5. Restart the opencode session", + "6. The auth.json file will be updated automatically" ], - command: "opencode auth set lmstudio type=api", automated: false }) break