diff --git a/README.md b/README.md index 5e04057..5493446 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Instead of creating one provider per server, this plugin keeps one `local` provi - Includes supported default `127.0.0.1` targets automatically - Detects loaded models at runtime - Routes each model to the correct target URL -- Supports optional shared API key auth +- API key auth currently unsupported - Uses OpenCode global config, not project-local config ## Example @@ -45,27 +45,21 @@ Default targets are enabled automatically for these backends and ports: If your local providers do not need auth, you can start using the `local` provider immediately. -If your local providers share an API key, run: - -```bash -opencode auth login -``` - -Choose `local`, then choose `Set Shared API Key` and enter the shared API key. +**Note: API key authentication is currently unsupported.** ## Custom Targets If you need non-default hosts or ports, use the CLI auth flow to add an explicit target: ```bash -opencode auth login --provider local --method "Add Custom Target (CLI only)" +opencode auth login --provider local --method "Add Custom Target" ``` This will prompt for: - a target ID, like `studio` or `remote-ollama` - the local provider URL -- the shared API key for that provider again, or `none` if unused +- the API key (enter `none` since API keys are currently unsupported) The target is then stored in OpenCode global config. @@ -118,7 +112,7 @@ The plugin stores explicit targets in OpenCode global config under the `local` p With `includeDefaults: true`, the built-in default `127.0.0.1` targets are also checked at runtime even though they are not written into config. -If you set a shared API key, it is stored through OpenCode auth for the `local` provider. +**Note: API key authentication is currently unsupported.** ## How Models Appear @@ -136,7 +130,7 @@ Each generated model keeps its own target URL internally, so requests go to the - Model detection is runtime-based, not static - If loaded models change in your local server, OpenCode will see the updated list on the next provider refresh - Built-in default `127.0.0.1` targets are enabled unless you set `includeDefaults` to `false` -- Targets use one shared API key for the `local` provider +- **API key authentication is currently unsupported** ## Development diff --git a/src/plugin.ts b/src/plugin.ts index a7bc3ac..5276c99 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -10,7 +10,6 @@ import { } from "./constants" import { getConfiguredTargets, - getProviderApiKey, getProviderTargets, saveProviderTarget, } from "./config" @@ -27,12 +26,10 @@ async function probeModels(provider: Provider, ctx: ProviderHookContext) { const list = getProviderTargets(provider) if (!Object.keys(list).length) return {} - const auth = getProviderApiKey(provider, ctx.auth) - const all = await Promise.all( Object.entries(list).map(async ([id, item]) => { try { - const found = await probe(item.url, auth, item.kind) + const found = await probe(item.url, item.kind) return build(provider.id, id, item.url, found.models, provider.models) } catch { return {} @@ -75,11 +72,7 @@ export const LocalProviderPlugin: Plugin = async (ctx) => { methods: [ { type: "api", - label: "Set Shared API Key", - }, - { - type: "api", - label: "Add Custom Target (CLI only)", + label: "Add Custom Target", prompts: [ { type: "text", @@ -100,37 +93,34 @@ export const LocalProviderPlugin: Plugin = async (ctx) => { if (!trimURL(value ?? "")) return "URL is required" }, }, - { - type: "text", - key: "apiKey", - message: "Re-enter the shared API key for this provider (enter none if unused)", - placeholder: "none", - validate(value) { - if (!value?.trim()) return "API key is required; enter none if unused" - }, - }, ], async authorize(input = {}) { const id = input.target?.trim() ?? "" const raw = trimURL(input.baseURL ?? "") - const next = input.apiKey?.trim() ?? "" - const key = next === "none" ? "" : next - if (!id || !validID(id) || !raw || !next) return { type: "failed" as const } - - const kind = await detect(raw, key).catch(() => undefined) - if (!kind) return { type: "failed" as const } - try { - await probe(raw, key, kind) + if (!id || !validID(id) || !raw) { + throw new Error("Invalid target ID or URL") + } + + const result = await probe(raw) + const kind = result.kind await saveProviderTarget(ctx.serverUrl, ctx.client, id, raw, kind) - } catch { - return { type: "failed" as const } - } - return { - type: "success" as const, - provider: LOCAL_PROVIDER_ID, - key, + return { + type: "success" as const, + provider: LOCAL_PROVIDER_ID, + key: "", + } + } catch (e) { + const errorMessage = e instanceof Error ? e.message : String(e) + await ctx.client.app.log({ + body: { + service: LOCAL_PLUGIN_SERVICE, + level: "error", + message: `Authorization failed: ${errorMessage}`, + }, + }) + return { type: "failed" as const } } }, }, diff --git a/src/probe.ts b/src/probe.ts index 91f935d..55c72c5 100644 --- a/src/probe.ts +++ b/src/probe.ts @@ -3,31 +3,31 @@ import type { LocalProviderKind } from "./types" import { supportedProviders, supportedProviderKinds } from "./providers" import { rootURL } from "./url" -export async function detect(url: string, key?: string): Promise { +export async function detect(url: string): Promise { const root = rootURL(url) for (const kind of supportedProviderKinds) { - if (await supportedProviders[kind].detect(root, key)) return kind + if (await supportedProviders[kind].detect(root)) return kind } return null } -export async function probe(url: string, key?: string, kind?: LocalProviderKind) { +export async function probe(url: string, kind?: LocalProviderKind) { const root = rootURL(url) if (kind) { return { kind, - models: await supportedProviders[kind].probe(root, key), + models: await supportedProviders[kind].probe(root), } } - const detected = await detect(url, key) + const detected = await detect(url) if (!detected) throw new Error(`No supported local provider detected at: ${url}`) return { kind: detected, - models: await supportedProviders[detected].probe(root, key), + models: await supportedProviders[detected].probe(root), } } diff --git a/src/providers/exo.ts b/src/providers/exo.ts index 03ef104..8c43f11 100644 --- a/src/providers/exo.ts +++ b/src/providers/exo.ts @@ -1,5 +1,4 @@ import type { LocalModel } from "../types" -import { authHeaders } from "../url" import type { ProviderImpl } from "./shared" type ExoState = { @@ -29,10 +28,9 @@ type ExoState = { runners?: Record } -async function detect(url: string, key?: string) { +async function detect(url: string) { try { const res = await fetch(url + "/v1/models", { - headers: authHeaders(key), signal: AbortSignal.timeout(2000), }) if (!res.ok) return false @@ -44,9 +42,8 @@ async function detect(url: string, key?: string) { } } -async function probe(url: string, key?: string): Promise { +async function probe(url: string): Promise { const res = await fetch(url + "/state", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`Exo probe failed: ${res.status}`) diff --git a/src/providers/llamacpp.ts b/src/providers/llamacpp.ts index d2cbe17..84f9855 100644 --- a/src/providers/llamacpp.ts +++ b/src/providers/llamacpp.ts @@ -1,11 +1,9 @@ import type { LocalModel } from "../types" -import { authHeaders } from "../url" import type { ProviderImpl } from "./shared" -async function runtimeContext(url: string, key?: string) { +async function runtimeContext(url: string) { try { const propsRes = await fetch(url + "/props", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (propsRes.ok) { @@ -20,7 +18,6 @@ async function runtimeContext(url: string, key?: string) { try { const slotsRes = await fetch(url + "/slots", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (slotsRes.ok) { @@ -33,10 +30,9 @@ async function runtimeContext(url: string, key?: string) { return null } -async function detect(url: string, key?: string) { +async function detect(url: string) { try { const res = await fetch(url, { - headers: authHeaders(key), signal: AbortSignal.timeout(2000), }) if (!res.ok) return false @@ -46,10 +42,9 @@ async function detect(url: string, key?: string) { } } -async function probe(url: string, key?: string): Promise { - const loadedContext = await runtimeContext(url, key) +async function probe(url: string): Promise { + const loadedContext = await runtimeContext(url) const res = await fetch(url + "/v1/models", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`llama.cpp probe failed: ${res.status}`) diff --git a/src/providers/lmstudio.ts b/src/providers/lmstudio.ts index 42e107f..011c604 100644 --- a/src/providers/lmstudio.ts +++ b/src/providers/lmstudio.ts @@ -1,11 +1,9 @@ import type { LocalModel } from "../types" -import { authHeaders } from "../url" import type { ProviderImpl } from "./shared" -async function detect(url: string, key?: string) { +async function detect(url: string) { try { const res = await fetch(url + "/lmstudio-greeting", { - headers: authHeaders(key), signal: AbortSignal.timeout(2000), }) if (!res.ok) return false @@ -16,9 +14,8 @@ async function detect(url: string, key?: string) { } } -async function probe(url: string, key?: string): Promise { +async function probe(url: string): Promise { const res = await fetch(url + "/api/v0/models", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`LM Studio probe failed: ${res.status}`) diff --git a/src/providers/ollama.ts b/src/providers/ollama.ts index be11c1b..76830af 100644 --- a/src/providers/ollama.ts +++ b/src/providers/ollama.ts @@ -1,11 +1,9 @@ import type { LocalModel } from "../types" -import { authHeaders } from "../url" import type { ProviderImpl } from "./shared" -async function detect(url: string, key?: string) { +async function detect(url: string) { try { const res = await fetch(url, { - headers: authHeaders(key), signal: AbortSignal.timeout(2000), }) if (!res.ok) return false @@ -15,13 +13,12 @@ async function detect(url: string, key?: string) { } } -async function show(url: string, model: string, key?: string) { +async function show(url: string, model: string) { try { const res = await fetch(url + "/api/show", { method: "POST", headers: { "Content-Type": "application/json", - ...authHeaders(key), }, body: JSON.stringify({ model }), signal: AbortSignal.timeout(3000), @@ -33,9 +30,8 @@ async function show(url: string, model: string, key?: string) { } } -async function probe(url: string, key?: string): Promise { +async function probe(url: string): Promise { const res = await fetch(url + "/api/ps", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`Ollama probe failed: ${res.status}`) @@ -50,7 +46,7 @@ async function probe(url: string, key?: string): Promise { return Promise.all( body.models.map(async (item) => { - const extra = await show(url, item.model, key) + const extra = await show(url, item.model) return { id: item.name, context: item.context_length, diff --git a/src/providers/shared.ts b/src/providers/shared.ts index e3cdcb8..d95551b 100644 --- a/src/providers/shared.ts +++ b/src/providers/shared.ts @@ -1,8 +1,8 @@ import type { LocalModel, LocalProviderKind } from "../types" export type ProviderImpl = { - detect(url: string, key?: string): Promise - probe(url: string, key?: string): Promise + detect(url: string): Promise + probe(url: string): Promise } export type ProviderMap = Record diff --git a/src/providers/vllm.ts b/src/providers/vllm.ts index 897349c..8db1676 100644 --- a/src/providers/vllm.ts +++ b/src/providers/vllm.ts @@ -1,11 +1,9 @@ import type { LocalModel } from "../types" -import { authHeaders } from "../url" import type { ProviderImpl } from "./shared" -async function detect(url: string, key?: string) { +async function detect(url: string) { try { const res = await fetch(url + "/v1/models", { - headers: authHeaders(key), signal: AbortSignal.timeout(2000), }) if (!res.ok) return false @@ -18,9 +16,8 @@ async function detect(url: string, key?: string) { } } -async function probe(url: string, key?: string): Promise { +async function probe(url: string): Promise { const res = await fetch(url + "/v1/models", { - headers: authHeaders(key), signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`vLLM probe failed: ${res.status}`) diff --git a/src/url.ts b/src/url.ts index 532bbbc..423335f 100644 --- a/src/url.ts +++ b/src/url.ts @@ -13,8 +13,3 @@ export function rootURL(url: string) { if (!next) return "" return next.endsWith("/v1") ? next.slice(0, -3) : next } - -export function authHeaders(key?: string) { - if (!key) return {} as Record - return { Authorization: `Bearer ${key}` } -}