diff --git a/changelog.d/fixes/7151-hermes-agent-model-aliases.md b/changelog.d/fixes/7151-hermes-agent-model-aliases.md new file mode 100644 index 0000000000..b408b03462 --- /dev/null +++ b/changelog.d/fixes/7151-hermes-agent-model-aliases.md @@ -0,0 +1 @@ +- fix(dashboard): wire modelAliases fetch into HermesAgentToolCard so OpenRouter and other passthrough providers appear in the Hermes Agent role picker (#7151) diff --git a/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx b/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx index c7d356ee36..a16d5f9998 100644 --- a/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx +++ b/src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx @@ -47,6 +47,10 @@ export default function HermesAgentToolCard({ const [previewYaml, setPreviewYaml] = useState(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); const [firstSetupAt, setFirstSetupAt] = useState(null); + // Model aliases drive the passthrough provider groups (OpenRouter, Requesty, + // DGrid, AgentRouter, Charm Hyper, ...) in ModelSelectModal — without them, + // those providers never surface in the Hermes Agent role picker (#7151). + const [modelAliases, setModelAliases] = useState({}); // Track whether we have already seeded from batchStatus on this expand const seededFromBatchRef = useRef(false); @@ -109,8 +113,19 @@ export default function HermesAgentToolCard({ }); } loadCurrentConfig(); + fetchModelAliases(); }, [isExpanded, batchStatus, loadCurrentConfig]); + const fetchModelAliases = async () => { + try { + const res = await fetch("/api/models/alias"); + const data = await res.json(); + if (res.ok) setModelAliases(data.aliases || {}); + } catch (error) { + console.warn("Error fetching model aliases:", error); + } + }; + const setRoleSelection = (roleId: string, model: string, provider = "OmniRoute") => { setSelections((prev) => ({ ...prev, [roleId]: { model, provider } })); }; @@ -522,6 +537,7 @@ export default function HermesAgentToolCard({ showCombos={true} activeProviders={activeProviders} alwaysIncludeProviders={HERMES_AGENT_ZERO_CONFIG_PROVIDERS} + modelAliases={modelAliases} /> ); diff --git a/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx b/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx new file mode 100644 index 0000000000..5d58e13640 --- /dev/null +++ b/tests/unit/ui/HermesAgentToolCard-model-aliases.test.tsx @@ -0,0 +1,46 @@ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +// Regression probe for issue #7151: OpenRouter (and every other +// `passthroughModels` provider — requesty, dgrid, agentrouter, charm-hyper, +// etc.) never appears in the Hermes Agent role model picker. +// +// Root cause: derives a passthrough provider's model list +// from the `modelAliases` prop (ModelSelectModal.tsx groupedModels → +// buildPassthroughAliasModels(modelAliases, providerId)). When `modelAliases` +// is `{}` (the component default), that helper returns `[]` and the provider +// group is skipped entirely — see modelSelectModalHelpers.ts. Every sibling +// CLI tool card (Codex, Claude, Cline, Kilo, Droid, OpenClaw, Antigravity) +// fetches `/api/models/alias` and passes the result through, but +// HermesAgentToolCard never does, so OpenRouter's managed-available-model +// aliases (synced automatically after the connection is tested — see +// syncManagedAvailableModelAliases in src/lib/providerModels/managedAvailableModels.ts) +// are invisible to it. + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const CARD_PATH = resolve( + __dirname, + "../../../src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx" +); + +describe("HermesAgentToolCard model alias wiring (#7151)", () => { + const source = readFileSync(CARD_PATH, "utf8"); + + it("declares modelAliases state", () => { + expect(source).toMatch(/const \[modelAliases, setModelAliases\] = useState\(\{\}\)/); + }); + + it("fetches /api/models/alias when expanded", () => { + expect(source).toContain('fetch("/api/models/alias")'); + }); + + it("passes modelAliases prop to ModelSelectModal", () => { + // Regression guard: this prop is what unlocks passthrough provider groups + // (OpenRouter, Requesty, DGrid, AgentRouter, Charm Hyper, ...) in the + // Hermes Agent role picker. Without it, OpenRouter is silently absent + // from the "Select" modal for every role (Default, Delegation, ...). + expect(source).toMatch(/modelAliases=\{modelAliases\}/); + }); +});