## Summary Adds [OrcaRouter](https://www.orcarouter.ai) as a first-class named model provider, mirroring the existing OpenRouter integration. OrcaRouter is an OpenAI-compatible gateway — `https://api.orcarouter.ai/v1` — that routes to models from many providers (Anthropic, OpenAI, DeepSeek, Google, Grok, Qwen, Zhipu, and more) through a single API key. It also runs gateway-level, zero-trust security for AI agents on the same endpoint — screening every prompt/response and governing every tool call on a default-deny basis, with no application code changes. What this PR does: - **Catalog sync**: `syncStartupModels` now fetches the OrcaRouter `/v1/models` catalog at startup (gated by a new `orcarouterModelSync` global setting, default enabled) and registers an `openai-completions` provider at `https://api.orcarouter.ai/v1`. The key resolves from the `ORCAROUTER_API_KEY` environment variable. - **Auth + onboarding**: OrcaRouter appears in the API-key auth catalog, onboarding setup copy + key hints, and quick-start provider list (key format `sk-orca-...`, dashboard link to `https://www.orcarouter.ai`). - **Icons**: New OrcaRouter brand mark (`OrcaRouterIcon`) with a dedicated color token, wired into `ProviderIcon` and `inferProviderIconKey` so `orcarouter/...` model ids render the brand mark everywhere. - **Settings**: Startup-sync toggle under **Settings → Global Models**, settings search entry, and save-split allowlists. - **Docs + i18n**: `docs/settings-reference.md` / `docs/storage.md` rows and `en` + secondary-locale catalog keys. - **Changeset**: `.changeset/add-orcarouter-provider.md` (minor). Disclosure: I'm an engineer on the OrcaRouter team. ## Test plan - `pnpm --filter @runfusion/fusion exec vitest run src/commands/__tests__/startup-model-sync.test.ts` — 18 passed - `pnpm --filter @fusion/core exec vitest run src/__tests__/settings-parity.test.ts` — 80 passed - `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/ProviderIcon.test.tsx` — 115 passed - `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/AuthenticationSection.test.tsx app/components/__tests__/ModelOnboardingModal.test.tsx` — 222 passed - `tsc --noEmit` in `@fusion/cli`, `@fusion/core`, `@fusion/dashboard`, `@fusion/engine` — all clean - `pnpm lint` on changed files — clean - `pnpm check:changesets` — clean - **L3 live test**: exercised the production `syncStartupModels` path against the live OrcaRouter API — catalog sync registered 209 models and a real chat completion returned **HTTP 200** (`finish_reason: "stop"`). Note: `pnpm i18n:status` reports pre-existing Jira key-parity gaps in the `app.json` catalogs on a clean checkout (unrelated to this change); the `common.json` catalogs touched here are in parity. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added OrcaRouter as a built-in OpenAI-compatible provider. - Added API-key setup, validation, authentication, onboarding guidance, provider branding, and model catalog visibility. - Added automatic startup synchronization of OrcaRouter models, enabled by default. - Added a global setting to enable or disable model synchronization. - **Documentation** - Documented the new synchronization setting and its configuration. - **Localization** - Added OrcaRouter setup and usage text across supported languages. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: XiaoHuo888-hue <jinhao.song@myflashcloud.com> Co-authored-by: XiaoHuo888-hue <jinhao.song@myflashcloud.com> Co-authored-by: Claude <noreply@anthropic.com>
79 lines
3.5 KiB
TypeScript
79 lines
3.5 KiB
TypeScript
/*
|
|
FNXC:ProviderIcons 2026-06-25-00:00:
|
|
Command Center model analytics group rows by model id only, so dashboard model-name surfaces must infer the provider icon from the model/provider text before handing off to ProviderIcon's fallback-safe renderer.
|
|
|
|
FNXC:ProviderIcons 2026-06-28-20:46:
|
|
Standalone GLM model ids such as glm-5.1, glm-4.5-air, and glm-5v-turbo are Z.ai/Zhipu-family models even when the analytics label omits zai/zhipu. Match GLM only at model-id segment boundaries so unrelated words do not hijack the Z.ai provider icon.
|
|
*/
|
|
const GLM_MODEL_SEGMENT_PATTERN = /(?:^|[/:_-])glm(?:$|[\d._:-]|-[\da-z])/;
|
|
const OMP_PROVIDER_SEGMENT_PATTERN = /(?:^|[/:_\s-])omp(?:$|[/:_\s-])/;
|
|
const OMP_PROVIDER_NAME_PATTERN = /oh[-\s]my[-\s]pi/;
|
|
const XIAOMI_MIMO_MODEL_SEGMENT_PATTERN = /(?:^|[/:_-])mimo(?:$|[/:_.-])/;
|
|
|
|
export function inferProviderIconKey(modelOrProviderName: string): string {
|
|
const normalized = modelOrProviderName.toLowerCase();
|
|
|
|
// Map common provider/model names to their icon keys.
|
|
/*
|
|
FNXC:ProviderIcons 2026-07-11-00:00:
|
|
FN-7818: model-id-shaped Cursor provider strings (cursor, cursor-agent, cursor/<model>) must resolve to the cursor-cli brand icon on model-selection surfaces instead of the generic CPU fallback, mirroring UsageIndicator's local mapping.
|
|
Run before broad model-family checks so cursor/gpt-5 stays Cursor-branded instead of OpenAI-branded.
|
|
*/
|
|
if (normalized.includes("cursor")) {
|
|
return "cursor-cli";
|
|
}
|
|
/*
|
|
FNXC:ProviderIcons 2026-07-18-18:24:
|
|
FN-8354: omp ACP model ids can contain another vendor model name (for example omp/claude-sonnet-4), but must retain the OMP brand mark on selection and analytics surfaces. Match only an OMP segment or the Oh My Pi name so unrelated strings and untagged model ids keep existing inference/fallback behavior.
|
|
*/
|
|
if (OMP_PROVIDER_SEGMENT_PATTERN.test(normalized) || OMP_PROVIDER_NAME_PATTERN.test(normalized)) {
|
|
return "omp-cli";
|
|
}
|
|
/*
|
|
FNXC:ProviderIcons 2026-07-22-17:04:
|
|
FN-8500 requires the Hermes Xiaomi provider and Xiaomi MiMo model IDs to resolve through the shared icon path. Restrict MiMo matching to model-id segment boundaries so unrelated custom names such as "mimosa" retain the fallback.
|
|
*/
|
|
if (normalized === "xiaomi" || XIAOMI_MIMO_MODEL_SEGMENT_PATTERN.test(normalized)) {
|
|
return "xiaomi";
|
|
}
|
|
if (normalized.includes("claude") || normalized.includes("anthropic")) {
|
|
return "anthropic";
|
|
}
|
|
if (normalized.includes("codex") || normalized.includes("openai") || normalized.includes("gpt")) {
|
|
return "openai";
|
|
}
|
|
if (normalized.includes("gemini") || normalized.includes("google") || normalized.includes("antigravity")) {
|
|
return "google";
|
|
}
|
|
if (normalized.includes("ollama")) {
|
|
return "ollama";
|
|
}
|
|
if (normalized.includes("minimax")) {
|
|
return "minimax";
|
|
}
|
|
if (normalized.includes("zai") || normalized.includes("zhipu") || GLM_MODEL_SEGMENT_PATTERN.test(normalized)) {
|
|
return "zai";
|
|
}
|
|
if (normalized.includes("kimi") || normalized.includes("moonshot")) {
|
|
return "kimi";
|
|
}
|
|
if (normalized.includes("bedrock") || normalized.includes("amazon")) {
|
|
return "bedrock";
|
|
}
|
|
if (normalized.includes("xai") || normalized.includes("grok")) {
|
|
return "xai";
|
|
}
|
|
if (normalized.includes("opencode")) {
|
|
return "opencode";
|
|
}
|
|
if (normalized.includes("orcarouter")) {
|
|
return "orcarouter";
|
|
}
|
|
if (normalized.includes("copilot") || normalized === "github copilot") {
|
|
return "github-copilot";
|
|
}
|
|
|
|
// Return the original name as fallback (ProviderIcon will show a default icon).
|
|
return modelOrProviderName;
|
|
}
|