diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0f34140e..08207a7b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### ✨ New Features +- **feat(dashboard):** the web-session credential guide now shows an **"Open {host}" link** to the provider's sign-in site (derived from the provider `website` via `getProviderWebsiteHost`), so you can jump straight to the page where the cookie/session must be captured. Regression guard: `tests/unit/web-session-provider-link-6316.test.ts`. (thanks @jordansilly77-stack) - **feat(cerebras):** add the **Gemma 4 31B** model (`gemma-4-31b`) to the Cerebras registry + pricing table. Regression guard extends `tests/unit/t28-model-catalog-updates.test.ts`. (thanks @backryun) - **feat(providers):** add **Yuanbao (web)** as a cookie-session provider ([#6196](https://github.com/diegosouzapw/OmniRoute/issues/6196)) — `yuanbao-web` (Tencent Yuanbao, `yuanbao.tencent.com`) with cookie-only auth (`hy_user`/`hy_token` + public agent id), SSE→OpenAI translation incl. `reasoning_content`, exposing DeepSeek V3/R1 + Hunyuan / Hunyuan-T1. Regression guard: `tests/unit/providers-yuanbao-web.test.ts`. `together-web` was **deferred** (no verifiable web-session endpoint — needs a captured request) and `huggingchat-web` **dropped** (the existing `huggingchat` already is a web-cookie provider). (thanks @chirag127) - **feat(providers):** route the built-in **agentrouter** through the dynamic Claude-Code wire image ([#6056](https://github.com/diegosouzapw/OmniRoute/issues/6056)) — a small static allow-set (`CC_WIRE_IMAGE_BUILTINS` in `open-sse/services/ccWireImageBuiltins.ts`), consulted by `isClaudeCodeCompatible` / `isClaudeCodeCompatibleProvider` / `applyFingerprint`, makes agentrouter adopt the CC wire-image headers + fingerprint **while guarding the CC baseUrl/auth branches** so it keeps its own registry `baseUrl` and `x-api-key` auth. Regression guard: `tests/unit/agentrouter-cc-wire-image.test.ts` (asserts the wire image is applied AND agentrouter's baseUrl/auth are preserved). Live WAF-acceptance against agentrouter.org is a VPS validation follow-up (Hard Rule #18). diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx index b0af90baa4..8c7df58b9c 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/ProviderModalsPanel.tsx @@ -31,6 +31,7 @@ import type { ProviderMessageTranslator } from "../providerPageHelpers"; interface ProviderInfo { name: string; riskNoticeVariant?: string; + website?: string; [key: string]: unknown; } @@ -277,6 +278,7 @@ export default function ProviderModalsPanel({ isOpen={showAddApiKeyModal} provider={providerId} providerName={providerInfo.name} + providerWebsite={providerInfo.website} initialBaseUrl={siliconFlowInitialBaseUrl} isCompatible={isCompatible} isAnthropic={isAnthropicProtocolCompatible} @@ -312,6 +314,7 @@ export default function ProviderModalsPanel({ isOpen={showEditModal} connection={selectedConnection} providerId={providerId} + providerWebsite={providerInfo.website} onSave={handleUpdateConnection} onResyncModels={(id) => handleCompatibleImportWithProgress(id, "sync")} onClose={() => setShowEditModal(false)} diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx index f4f30ddce9..e3743acdfb 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx @@ -10,14 +10,30 @@ import { providerText, type ProviderMessageTranslator } from "../providerPageHel export interface WebSessionCredentialGuideProps { requirement: WebSessionCredentialRequirement; providerName: string; + providerWebsite?: string; t: ProviderMessageTranslator; } +export function getProviderWebsiteHost(providerWebsite?: string): string | null { + if (!providerWebsite) { + return null; + } + + try { + return new URL(providerWebsite).host; + } catch { + return providerWebsite; + } +} + export default function WebSessionCredentialGuide({ requirement, providerName, + providerWebsite, t, }: WebSessionCredentialGuideProps) { + const providerWebsiteHost = getProviderWebsiteHost(providerWebsite); + if (requirement.kind === "none") { return (
@@ -76,6 +92,21 @@ export default function WebSessionCredentialGuide({ {providerText(t, "webSessionGuideStep1", "Sign in to {provider} in your browser.", { provider: providerName, })} + {providerWebsite && providerWebsiteHost && ( + + {providerText(t, "webSessionGuideOpenProvider", "Open {host}", { + host: providerWebsiteHost, + })} + + + )}
  • {providerText( diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx index ef0c741b89..3163deee46 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx @@ -34,6 +34,7 @@ export interface AddApiKeyModalProps { isOpen: boolean; provider?: string; providerName?: string; + providerWebsite?: string; initialBaseUrl?: string; isCompatible?: boolean; isAnthropic?: boolean; @@ -56,6 +57,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, + providerWebsite, initialBaseUrl, isCompatible, isAnthropic, @@ -657,6 +659,7 @@ export default function AddApiKeyModal({ )} diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index ff373cf5ca..94bc105b05 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -72,6 +72,7 @@ export interface EditConnectionModalProps { isOpen: boolean; connection: EditConnectionModalConnection | null; providerId: string; + providerWebsite?: string; onSave: (data: unknown) => Promise; /** Triggered after a successful save when the "import only free models" flag changed. */ onResyncModels?: (connectionId: string) => void | Promise; @@ -84,6 +85,7 @@ export default function EditConnectionModal({ isOpen, connection, providerId, + providerWebsite, onSave, onResyncModels, onClose, @@ -795,6 +797,7 @@ export default function EditConnectionModal({ )} diff --git a/tests/unit/web-session-provider-link-6316.test.ts b/tests/unit/web-session-provider-link-6316.test.ts new file mode 100644 index 0000000000..d8d39e849b --- /dev/null +++ b/tests/unit/web-session-provider-link-6316.test.ts @@ -0,0 +1,25 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; + +// #6316: the web-session credential guide shows an "Open {host}" link to the +// provider site. The host is derived from the provider's website URL via +// getProviderWebsiteHost — a full URL collapses to its host, a bare/invalid +// string falls back to itself, and an empty value yields null (no link). +const { getProviderWebsiteHost } = await import( + "../../src/app/(dashboard)/dashboard/providers/[id]/components/WebSessionCredentialGuide.tsx" +); + +test("#6316: full URL collapses to host", () => { + assert.equal(getProviderWebsiteHost("https://chat.qwen.ai/path?x=1"), "chat.qwen.ai"); + assert.equal(getProviderWebsiteHost("https://www.kimi.com"), "www.kimi.com"); +}); + +test("#6316: bare/invalid string falls back to itself", () => { + assert.equal(getProviderWebsiteHost("kimi.com"), "kimi.com"); + assert.equal(getProviderWebsiteHost("not a url"), "not a url"); +}); + +test("#6316: empty/undefined yields null (no link rendered)", () => { + assert.equal(getProviderWebsiteHost(undefined), null); + assert.equal(getProviderWebsiteHost(""), null); +});