diff --git a/package.json b/package.json index fd7eab6..dcc1b01 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@troed/oc-ls-stats", - "version": "1.4.0", + "version": "1.4.1", "type": "module", "files": [ "tui.tsx", diff --git a/src/stats.ts b/src/stats.ts index 22ee523..443cf53 100644 --- a/src/stats.ts +++ b/src/stats.ts @@ -220,22 +220,34 @@ export function stripBaseUrlPath(baseURL: string): string { } } +// V1 stores a provider endpoint under options; V2 under settings +// (migrate-v1: "api becomes settings.baseURL", "provider options are separated +// into settings, headers, and body"). Read whichever is present. +function extractBaseURL(entry: unknown): string | undefined { + if (!entry || typeof entry !== "object") return undefined + const record = entry as Record + for (const key of ["settings", "options"]) { + const section = record[key] + if (!section || typeof section !== "object") continue + const baseURL = + (section as Record).baseURL || (section as Record).base_url + if (typeof baseURL === "string") return baseURL + } + return undefined +} + export function extractProviderUrls(config: unknown): string[] { if (!config || typeof config !== "object") return [] const obj = config as Record - const provider = obj.provider + const provider = obj.providers ?? obj.provider if (!provider || typeof provider !== "object") return [] const urls: string[] = [] for (const [key, val] of Object.entries(provider as Record)) { - if (val && typeof val === "object") { - const opts = (val as Record).options - if (opts && typeof opts === "object") { - const baseURL = (opts as Record).baseURL || (opts as Record).base_url - if (typeof baseURL === "string" && key.toLowerCase().includes("llama") && !key.toLowerCase().includes("ollama")) { - urls.push(stripBaseUrlPath(baseURL)) - } - } + const baseURL = extractBaseURL(val) + const hay = key.toLowerCase() + if (typeof baseURL === "string" && hay.includes("llama") && !hay.includes("ollama")) { + urls.push(stripBaseUrlPath(baseURL)) } } return urls @@ -251,9 +263,7 @@ export function extractProviderUrlsFromList(providers: unknown): string[] { const name = typeof provider.name === "string" ? provider.name.toLowerCase() : "" const hay = `${id} ${name}` if (!hay.includes("llama") || hay.includes("ollama")) continue - const opts = provider.options - if (!opts || typeof opts !== "object") continue - const baseURL = (opts as Record).baseURL || (opts as Record).base_url + const baseURL = extractBaseURL(provider) if (typeof baseURL === "string") { urls.push(stripBaseUrlPath(baseURL)) } @@ -289,14 +299,10 @@ export function selectSessionUrls( if (!entry || typeof entry !== "object") continue const provider = entry as Record if (provider.id !== providerID) continue - const opts = provider.options - if (opts && typeof opts === "object") { - const baseURL = - (opts as Record).baseURL || (opts as Record).base_url - if (typeof baseURL === "string") { - const normalized = stripBaseUrlPath(baseURL) - if (candidates.includes(normalized)) return [normalized] - } + const baseURL = extractBaseURL(provider) + if (typeof baseURL === "string") { + const normalized = stripBaseUrlPath(baseURL) + if (candidates.includes(normalized)) return [normalized] } return [] } diff --git a/test-backoff.ts b/test-backoff.ts index dbbf58f..752a8bd 100644 --- a/test-backoff.ts +++ b/test-backoff.ts @@ -239,3 +239,53 @@ test("duplicate provider URLs are deduped", () => { ] assert.deepEqual(resolveServerUrls(undefined, providers, {}), ["http://same:7"]) }) + +// --- V2 provider shape ------------------------------------------------------- +// V2 renames provider config "options" to "settings" and the "provider" map to +// "providers" (see migrate-v1: "api becomes settings.baseURL"). Provider records +// returned by the TUI data layer therefore carry settings, not options. These +// tests pin the shape the plugin actually receives at runtime. + +const v2LlamaProviders = [ + { + id: "llama-local", + name: "llama.cpp (conduct)", + package: "aisdk:@ai-sdk/openai-compatible", + settings: { baseURL: "http://:8100/v1" }, + models: {}, + }, +] + +test("V2 provider settings.baseURL is detected", () => { + assert.deepEqual(resolveServerUrls(undefined, v2LlamaProviders, undefined), [ + "http://:8100", + ]) +}) + +test("V2 provider settings.base_url variant is detected", () => { + const snake = [ + { id: "llama-local", name: "llama.cpp", settings: { base_url: "http://localhost:9090/v1" } }, + ] + assert.deepEqual(resolveServerUrls(undefined, snake, undefined), ["http://localhost:9090"]) +}) + +test("selectSessionUrls uses V2 provider settings.baseURL", () => { + assert.deepEqual( + selectSessionUrls(["http://:8100"], v2LlamaProviders, "llama-local"), + ["http://:8100"], + ) +}) + +test("V2 config providers/settings fallback is parsed", () => { + const v2Config = { + providers: { + "llama-local": { settings: { baseURL: "http://localhost:9090/v1" } }, + }, + } + assert.deepEqual(resolveServerUrls(undefined, undefined, v2Config), ["http://localhost:9090"]) +}) + +test("V2 provider settings still excludes non-candidate providers", () => { + const providers = [{ id: "llama-local", name: "llama.cpp", settings: { baseURL: "http://other:1" } }] + assert.deepEqual(selectSessionUrls(["http://localhost:8080"], providers, "llama-local"), []) +})