Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Each agent has a `soul.md` (personality), `memory.md` (long-term memory), and a
- Network access to LLM API endpoints

> **Note:** Clawith does not run any AI models locally — all LLM inference is handled by external API providers (OpenAI, Anthropic, etc.). The local deployment is a standard web application with Docker orchestration.
>
> **OrcaRouter** is available as a first-class provider: in the enterprise LLM settings, add a model with provider **OrcaRouter** (base URL `https://api.orcarouter.ai/v1`) and any `sk-orca-...` key. See [orcarouter.ai](https://www.orcarouter.ai). 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.

#### Recommended Configurations

Expand Down
7 changes: 7 additions & 0 deletions backend/app/services/llm/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2376,6 +2376,13 @@ class ProviderSpec:
default_base_url="https://openrouter.ai/api/v1",
default_max_tokens=4096,
),
"orcarouter": ProviderSpec(
provider="orcarouter",
display_name="OrcaRouter",
protocol="openai_compatible",
default_base_url="https://api.orcarouter.ai/v1",
default_max_tokens=16384,
),
"zhipu": ProviderSpec(
provider="zhipu",
display_name="Zhipu",
Expand Down
55 changes: 55 additions & 0 deletions backend/tests/test_orcarouter_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Provider registry tests for the OrcaRouter integration."""

from app.services.llm.client import (
PROVIDER_REGISTRY,
create_llm_client,
get_provider_base_url,
get_provider_manifest,
normalize_provider,
)


def test_orcarouter_registered_in_provider_registry() -> None:
spec = PROVIDER_REGISTRY.get("orcarouter")
assert spec is not None
assert spec.provider == "orcarouter"
assert spec.display_name == "OrcaRouter"
assert spec.protocol == "openai_compatible"
assert spec.default_base_url == "https://api.orcarouter.ai/v1"
assert spec.supports_tool_choice is True
assert spec.default_max_tokens > 0


def test_orcarouter_manifest_round_trip() -> None:
manifest = {entry["provider"]: entry for entry in get_provider_manifest()}
entry = manifest.get("orcarouter")
assert entry is not None
assert entry["display_name"] == "OrcaRouter"
assert entry["default_base_url"] == "https://api.orcarouter.ai/v1"


def test_orcarouter_base_url_resolution() -> None:
assert get_provider_base_url("orcarouter") == "https://api.orcarouter.ai/v1"
# explicit custom base_url takes precedence
assert get_provider_base_url("orcarouter", "https://example.com/v1") == "https://example.com/v1"
# aliases normalize to the canonical provider id
assert normalize_provider("OrcaRouter") == "orcarouter"


def test_create_orcarouter_client_uses_openai_compatible_protocol() -> None:
client = create_llm_client(
provider="orcarouter",
api_key="sk-orca-test",
model="orcarouter/auto",
)
try:
assert client.base_url == "https://api.orcarouter.ai/v1"
assert client.model == "orcarouter/auto"
assert client.supports_tool_choice is True
# Bearer auth headers for the OpenAI-compatible gateway
headers = client._get_headers()
assert headers["Authorization"] == "Bearer sk-orca-test"
finally:
import asyncio

asyncio.run(client.close())
1 change: 1 addition & 0 deletions frontend/src/pages/enterprise-settings/tabs/LlmTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const FALLBACK_LLM_PROVIDERS: LLMProviderSpec[] = [
{ provider: 'baidu', display_name: 'Baidu (Qianfan)', protocol: 'openai_compatible', default_base_url: 'https://qianfan.baidubce.com/v2', supports_tool_choice: false, default_max_tokens: 4096 },
{ provider: 'gemini', display_name: 'Gemini', protocol: 'gemini', default_base_url: 'https://generativelanguage.googleapis.com/v1beta', supports_tool_choice: true, default_max_tokens: 8192 },
{ provider: 'openrouter', display_name: 'OpenRouter', protocol: 'openai_compatible', default_base_url: 'https://openrouter.ai/api/v1', supports_tool_choice: true, default_max_tokens: 4096 },
{ provider: 'orcarouter', display_name: 'OrcaRouter', protocol: 'openai_compatible', default_base_url: 'https://api.orcarouter.ai/v1', supports_tool_choice: true, default_max_tokens: 16384 },
{ provider: 'kimi', display_name: 'Kimi (Moonshot)', protocol: 'openai_compatible', default_base_url: 'https://api.moonshot.cn/v1', supports_tool_choice: true, default_max_tokens: 8192 },
{ provider: 'vllm', display_name: 'vLLM', protocol: 'openai_compatible', default_base_url: 'http://localhost:8000/v1', supports_tool_choice: true, default_max_tokens: 4096 },
{ provider: 'ollama', display_name: 'Ollama', protocol: 'openai_compatible', default_base_url: 'http://localhost:11434/v1', supports_tool_choice: true, default_max_tokens: 4096 },
Expand Down