fix(usage): stop leaking hidden noAuth providers (devin-cli, mimo-free)
The Usage page auto-adds every noAuth free provider so connectionless providers (opencode) still appear. It did not filter the registry's hidden flag, so devin-cli and mimo-free — both category:"free" with noAuth:true and hidden:true — showed up in Usage despite having no connection and being absent from the Providers page (which does filter hidden). Extract the list assembly into buildUsageProviderList (shared/utils/ usageProviders.js) and skip hidden free providers there. Behavior for visible noAuth providers (opencode) and dedup of active connections is unchanged; covered by tests/unit/usage-provider-list.test.js.
This commit is contained in:
1 parent
604b4d85d5
commit
9d7821bdcc
3 files changed
+121
-14
No files matched your search
@@ -3,6 +3,7 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||||
import { useSearchParams, useRouter } from "next/navigation";
|
import { useSearchParams, useRouter } from "next/navigation";
|
||||||
import { FREE_PROVIDERS, AI_PROVIDERS } from "@/shared/constants/providers";
|
import { FREE_PROVIDERS, AI_PROVIDERS } from "@/shared/constants/providers";
|
||||||
|
import { buildUsageProviderList } from "@/shared/utils/usageProviders";
|
||||||
|
|
||||||
// Keep providers without serviceKinds (default LLM) or with "llm" in serviceKinds
|
// Keep providers without serviceKinds (default LLM) or with "llm" in serviceKinds
|
||||||
function isLLMProvider(id) {
|
function isLLMProvider(id) {
|
||||||
@@ -232,21 +233,12 @@ export default function UsageStats({ period: periodProp, setPeriod: setPeriodPro
|
|||||||
for (const node of (nodesData?.nodes || [])) {
|
for (const node of (nodesData?.nodes || [])) {
|
||||||
nodeNameMap[node.id] = node.name;
|
nodeNameMap[node.id] = node.name;
|
||||||
}
|
}
|
||||||
const seen = new Set();
|
setProviders(buildUsageProviderList({
|
||||||
const unique = (d?.connections || []).filter((c) => {
|
connections: d?.connections || [],
|
||||||
if (c.isActive === false) return false;
|
freeProviders: FREE_PROVIDERS,
|
||||||
if (!isLLMProvider(c.provider)) return false;
|
nodeNameMap,
|
||||||
if (seen.has(c.provider)) return false;
|
isLLMProvider,
|
||||||
seen.add(c.provider);
|
|
||||||
return true;
|
|
||||||
}).map((c) => ({
|
|
||||||
...c,
|
|
||||||
nodeName: nodeNameMap[c.provider] || null,
|
|
||||||
}));
|
}));
|
||||||
const noAuthProviders = Object.values(FREE_PROVIDERS)
|
|
||||||
.filter((p) => p.noAuth && !seen.has(p.id) && isLLMProvider(p.id))
|
|
||||||
.map((p) => ({ provider: p.id, name: p.name }));
|
|
||||||
setProviders([...unique, ...noAuthProviders]);
|
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Provider list for the Usage page.
|
||||||
|
//
|
||||||
|
// Two sources, deduped by provider id:
|
||||||
|
// 1. Active LLM connections (one entry per provider).
|
||||||
|
// 2. noAuth free providers that need no connection (e.g. opencode).
|
||||||
|
//
|
||||||
|
// Hidden providers are excluded — the Providers page filters `hidden`, so a
|
||||||
|
// hidden noAuth provider (devin-cli, mimo-free) must not leak into Usage with
|
||||||
|
// zero connections and zero traffic.
|
||||||
|
export function buildUsageProviderList({
|
||||||
|
connections = [],
|
||||||
|
freeProviders = {},
|
||||||
|
nodeNameMap = {},
|
||||||
|
isLLMProvider = () => true,
|
||||||
|
} = {}) {
|
||||||
|
const seen = new Set();
|
||||||
|
const unique = connections
|
||||||
|
.filter((c) => {
|
||||||
|
if (c.isActive === false) return false;
|
||||||
|
if (!isLLMProvider(c.provider)) return false;
|
||||||
|
if (seen.has(c.provider)) return false;
|
||||||
|
seen.add(c.provider);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((c) => ({
|
||||||
|
...c,
|
||||||
|
nodeName: nodeNameMap[c.provider] || null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const noAuthProviders = Object.values(freeProviders)
|
||||||
|
.filter((p) => p.noAuth && !p.hidden && !seen.has(p.id) && isLLMProvider(p.id))
|
||||||
|
.map((p) => ({ provider: p.id, name: p.name }));
|
||||||
|
|
||||||
|
return [...unique, ...noAuthProviders];
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// The Usage page lists providers from two sources:
|
||||||
|
// 1. active LLM connections (deduped by provider id), and
|
||||||
|
// 2. noAuth free providers that need no connection (e.g. opencode).
|
||||||
|
//
|
||||||
|
// Hidden providers (devin-cli, mimo-free) must NOT be auto-added — they are
|
||||||
|
// excluded from the Providers page, so surfacing them in Usage (with zero
|
||||||
|
// connections and zero traffic) is a leak. Regression: devin-cli showed up in
|
||||||
|
// Usage but nowhere in Providers.
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { buildUsageProviderList } from "../../src/shared/utils/usageProviders.js";
|
||||||
|
|
||||||
|
const isLLM = () => true;
|
||||||
|
|
||||||
|
describe("buildUsageProviderList", () => {
|
||||||
|
it("does not auto-add hidden noAuth free providers", () => {
|
||||||
|
const freeProviders = {
|
||||||
|
opencode: { id: "opencode", name: "OpenCode", noAuth: true },
|
||||||
|
"devin-cli": { id: "devin-cli", name: "Devin CLI", noAuth: true, hidden: true },
|
||||||
|
"mimo-free": { id: "mimo-free", name: "MiMo Free", noAuth: true, hidden: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const list = buildUsageProviderList({
|
||||||
|
connections: [],
|
||||||
|
freeProviders,
|
||||||
|
isLLMProvider: isLLM,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ids = list.map((p) => p.provider);
|
||||||
|
expect(ids).toContain("opencode");
|
||||||
|
expect(ids).not.toContain("devin-cli");
|
||||||
|
expect(ids).not.toContain("mimo-free");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("includes active LLM connections, deduped by provider", () => {
|
||||||
|
const list = buildUsageProviderList({
|
||||||
|
connections: [
|
||||||
|
{ provider: "codex", isActive: true },
|
||||||
|
{ provider: "codex", isActive: true },
|
||||||
|
{ provider: "zed", isActive: true },
|
||||||
|
],
|
||||||
|
freeProviders: {},
|
||||||
|
isLLMProvider: isLLM,
|
||||||
|
});
|
||||||
|
expect(list.map((p) => p.provider)).toEqual(["codex", "zed"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips inactive connections and non-LLM providers", () => {
|
||||||
|
const list = buildUsageProviderList({
|
||||||
|
connections: [
|
||||||
|
{ provider: "codex", isActive: false },
|
||||||
|
{ provider: "whisper", isActive: true },
|
||||||
|
],
|
||||||
|
freeProviders: {},
|
||||||
|
isLLMProvider: (id) => id !== "whisper",
|
||||||
|
});
|
||||||
|
expect(list).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not duplicate a free provider that already has a connection", () => {
|
||||||
|
const freeProviders = {
|
||||||
|
opencode: { id: "opencode", name: "OpenCode", noAuth: true },
|
||||||
|
};
|
||||||
|
const list = buildUsageProviderList({
|
||||||
|
connections: [{ provider: "opencode", isActive: true }],
|
||||||
|
freeProviders,
|
||||||
|
isLLMProvider: isLLM,
|
||||||
|
});
|
||||||
|
expect(list.map((p) => p.provider)).toEqual(["opencode"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("attaches nodeName from the lookup when present", () => {
|
||||||
|
const list = buildUsageProviderList({
|
||||||
|
connections: [{ provider: "node-1", isActive: true }],
|
||||||
|
freeProviders: {},
|
||||||
|
nodeNameMap: { "node-1": "My Node" },
|
||||||
|
isLLMProvider: isLLM,
|
||||||
|
});
|
||||||
|
expect(list[0].nodeName).toBe("My Node");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user