diff --git a/apps/nestjs-backend/src/features/ai/ai.service.spec.ts b/apps/nestjs-backend/src/features/ai/ai.service.spec.ts index 0afd3e95c3..06439b95cf 100644 --- a/apps/nestjs-backend/src/features/ai/ai.service.spec.ts +++ b/apps/nestjs-backend/src/features/ai/ai.service.spec.ts @@ -6,8 +6,10 @@ import { AiService } from './ai.service'; const openAIProviderName = 'custom-openai'; const openRouterProviderName = 'custom-openrouter'; +const orcaRouterProviderName = 'custom-orcarouter'; const gptImage2Model = 'gpt-image-2'; const openRouterModel = `openai/${gptImage2Model}`; +const orcaRouterModel = `openai/${gptImage2Model}`; const imageGenerationTag = 'image-generation'; const setBaseConfig = (service: AiService, isCloud: boolean) => { @@ -66,6 +68,21 @@ describe('AiService.getModelTags', () => { expect(tags).toEqual([]); }); + + it('does not infer tags for OrcaRouter models without explicit config', async () => { + const tags = await service.getModelTags( + `${LLMProviderType.ORCAROUTER}@${orcaRouterModel}@${orcaRouterProviderName}`, + [ + { + type: LLMProviderType.ORCAROUTER, + name: orcaRouterProviderName, + models: orcaRouterModel, + }, + ] + ); + + expect(tags).toEqual([]); + }); }); describe('AiService model mappings', () => { diff --git a/apps/nestjs-backend/src/features/ai/util.ts b/apps/nestjs-backend/src/features/ai/util.ts index 17187eca81..ed72f5f12f 100644 --- a/apps/nestjs-backend/src/features/ai/util.ts +++ b/apps/nestjs-backend/src/features/ai/util.ts @@ -104,6 +104,9 @@ export const modelProviders = { [LLMProviderType.OLLAMA]: createOllama, [LLMProviderType.AMAZONBEDROCK]: createAmazonBedrock, [LLMProviderType.OPENROUTER]: createOpenRouter, + // OrcaRouter is an OpenAI-compatible meta-router; reuse the compatible wrapper + // (no dedicated SDK/dependency needed), same as the generic OpenAI-compatible type. + [LLMProviderType.ORCAROUTER]: createOpenAICompatibleWrapper, [LLMProviderType.OPENAI_COMPATIBLE]: createOpenAICompatibleWrapper, // AI_GATEWAY is handled separately in ai.service.ts using createGateway from 'ai' } as const; @@ -130,6 +133,7 @@ export const getAdaptedProviderOptions = ( } case LLMProviderType.OLLAMA: return { name, baseURL: originalBaseURL }; + case LLMProviderType.ORCAROUTER: case LLMProviderType.OPENAI_COMPATIBLE: return { ...originalOptions, includeUsage: true }; case LLMProviderType.AI_GATEWAY: diff --git a/apps/nextjs-app/src/features/app/blocks/admin/setting/components/ai-config/constant.ts b/apps/nextjs-app/src/features/app/blocks/admin/setting/components/ai-config/constant.ts index 3c3380c6a1..c7de4eeef7 100644 --- a/apps/nextjs-app/src/features/app/blocks/admin/setting/components/ai-config/constant.ts +++ b/apps/nextjs-app/src/features/app/blocks/admin/setting/components/ai-config/constant.ts @@ -13,6 +13,7 @@ import { Ollama, AmazonBedrock, OpenRouter, + OrcaRouter, Zap, // Gateway provider icons Meta, @@ -54,6 +55,7 @@ export const LLM_PROVIDER_ICONS = { [LLMProviderType.OLLAMA]: Ollama, [LLMProviderType.AMAZONBEDROCK]: AmazonBedrock, [LLMProviderType.OPENROUTER]: OpenRouter, + [LLMProviderType.ORCAROUTER]: OrcaRouter, [LLMProviderType.OPENAI_COMPATIBLE]: Openai, [LLMProviderType.AI_GATEWAY]: Zap, // AI Gateway uses Zap icon }; @@ -165,6 +167,13 @@ export const LLM_PROVIDERS = [ modelsPlaceholder: 'anthropic/claude-sonnet-4-6,google/gemini-2.5-flash', Icon: LLM_PROVIDER_ICONS[LLMProviderType.OPENROUTER], }, + { + value: LLMProviderType.ORCAROUTER, + label: 'OrcaRouter', + baseUrlPlaceholder: 'https://api.orcarouter.ai/v1', + modelsPlaceholder: 'orcarouter/auto,openai/gpt-5.2,anthropic/claude-opus-4.8', + Icon: LLM_PROVIDER_ICONS[LLMProviderType.ORCAROUTER], + }, { value: LLMProviderType.OPENAI_COMPATIBLE, label: 'OpenAI Compatible', diff --git a/packages/icons/src/components/OrcaRouter.tsx b/packages/icons/src/components/OrcaRouter.tsx new file mode 100644 index 0000000000..3f8fb49d84 --- /dev/null +++ b/packages/icons/src/components/OrcaRouter.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import type { SVGProps } from 'react'; +const OrcaRouter = (props: SVGProps) => ( + + + + + + + +); +export default OrcaRouter; diff --git a/packages/icons/src/index.ts b/packages/icons/src/index.ts index baefa48018..c5a72f362b 100644 --- a/packages/icons/src/index.ts +++ b/packages/icons/src/index.ts @@ -158,6 +158,7 @@ export { default as Object } from './components/Object'; export { default as Ollama } from './components/Ollama'; export { default as Openai } from './components/Openai'; export { default as OpenRouter } from './components/OpenRouter'; +export { default as OrcaRouter } from './components/OrcaRouter'; export { default as PackageCheck } from './components/PackageCheck'; export { default as PaintBucket } from './components/PaintBucket'; export { default as Pencil } from './components/Pencil'; diff --git a/packages/openapi/src/admin/setting/update.ts b/packages/openapi/src/admin/setting/update.ts index 10789fff72..285716c4d6 100644 --- a/packages/openapi/src/admin/setting/update.ts +++ b/packages/openapi/src/admin/setting/update.ts @@ -37,6 +37,7 @@ export enum LLMProviderType { OLLAMA = 'ollama', AMAZONBEDROCK = 'amazonBedrock', OPENROUTER = 'openRouter', + ORCAROUTER = 'orcarouter', OPENAI_COMPATIBLE = 'openaiCompatible', // Vercel AI Gateway - unified model access via modelId AI_GATEWAY = 'aiGateway',