feat: add Claude and OpenRouter AI providers

OpenRouter is used when no provider flag is set. Claude is selected with .cl, and GLM stays available via .glm.
This commit is contained in:
Azimkin
2026-10-13 08:56:16 +00:00
parent c6b93c9aa8
commit a84467c385
10 changed files with 712 additions and 20 deletions

View File

@@ -13,6 +13,7 @@ from app.agentic import (
_build_context_messages,
_create_llm,
_download_message_media,
_provider,
render_prompt_template,
parse_agent_invocation,
run_agent_turn,
@@ -28,9 +29,13 @@ class AgentInvocationTests(unittest.TestCase):
with (
patch("app.agentic.ChatCompletionsLLM") as chat_llm,
patch("app.agentic.OpenAIBasedLLM") as responses_llm,
patch("app.agentic.AnthropicMessagesLLM") as claude_llm,
patch("app.agentic.OpenRouterChatLLM") as openrouter_llm,
):
_create_llm(provider, parse_agent_invocation("prompt", AiConfig()))
_create_llm(provider, parse_agent_invocation(".glm prompt", AiConfig()))
_create_llm(provider, parse_agent_invocation(".ds prompt", AiConfig()))
_create_llm(provider, parse_agent_invocation(".cl prompt", AiConfig()))
_create_llm(provider, parse_agent_invocation("prompt", AiConfig()))
chat_llm.assert_called_once_with(
api_url="https://example.test",
@@ -42,6 +47,16 @@ class AgentInvocationTests(unittest.TestCase):
api_key="key",
model="model",
)
claude_llm.assert_called_once_with(
api_url="https://example.test",
api_key="key",
model="model",
)
openrouter_llm.assert_called_once_with(
api_url="https://example.test",
api_key="key",
model="model",
)
def test_prompt_comments_are_removed_without_breaking_urls(self) -> None:
value = strip_prompt_comments(
@@ -72,9 +87,50 @@ class AgentInvocationTests(unittest.TestCase):
self.assertEqual(invocation.context_messages, 12)
self.assertTrue(invocation.use_deepseek)
self.assertFalse(invocation.use_claude)
self.assertFalse(invocation.use_openrouter)
self.assertTrue(invocation.thinking)
self.assertEqual(invocation.prompt, "explain this")
claude = parse_agent_invocation("3 .ds .cl .th summarize", AiConfig())
self.assertFalse(claude.use_deepseek)
self.assertTrue(claude.use_claude)
self.assertFalse(claude.use_openrouter)
self.assertTrue(claude.thinking)
self.assertEqual(claude.prompt, "summarize")
self.assertEqual(claude.context_messages, 3)
selected = _provider(
AiConfig(claude=AiProviderConfig("https://api.anthropic.com", "key", "claude-haiku-5-5")),
claude,
)
self.assertEqual(selected.model, "claude-haiku-5-5")
plain = parse_agent_invocation("just ask", AiConfig())
self.assertTrue(plain.use_openrouter)
self.assertFalse(plain.use_deepseek)
self.assertFalse(plain.use_claude)
self.assertEqual(
_provider(AiConfig(), plain).api_url,
"https://openrouter.ai/api/v1",
)
routed = parse_agent_invocation(".cl .or .th route this", AiConfig())
self.assertFalse(routed.use_claude)
self.assertTrue(routed.use_openrouter)
self.assertEqual(routed.prompt, "route this")
selected_router = _provider(
AiConfig(
openrouter=AiProviderConfig(
"https://openrouter.ai/api/v1",
"key",
"anthropic/claude-haiku-5.5",
)
),
routed,
)
self.assertEqual(selected_router.api_url, "https://openrouter.ai/api/v1")
self.assertEqual(selected_router.model, "anthropic/claude-haiku-5.5")
def test_ignores_unknown_leading_dot_arguments(self) -> None:
invocation = parse_agent_invocation(".unknown .ds prompt", AiConfig())