add mcp and online search

This commit is contained in:
Abdelrahman Abdallah
2026-04-05 02:35:49 +02:00
parent 3f31cee395
commit 783145fe6a
38 changed files with 8114 additions and 261 deletions
+88
View File
@@ -0,0 +1,88 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from src.account_runtime import AccountRuntime
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentRuntimeConfig
class AccountRuntimeTests(unittest.TestCase):
def test_account_runtime_discovers_profiles_and_persists_login(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-account.json').write_text(
(
'{"profiles":['
'{"name":"local","provider":"openai","identity":"dev@example.com","authMode":"api_key"},'
'{"name":"team","provider":"anthropic","identity":"team@example.com","org":"Harness"}'
']}'
),
encoding='utf-8',
)
with patch.dict('os.environ', {'OPENAI_API_KEY': 'local-token'}, clear=False):
runtime = AccountRuntime.from_workspace(workspace)
report = runtime.login('local')
restored = AccountRuntime.from_workspace(workspace)
self.assertEqual(len(runtime.profiles), 2)
self.assertTrue(report.logged_in)
self.assertEqual(report.profile_name, 'local')
self.assertIsNotNone(restored.active_session)
self.assertEqual(restored.active_session.profile_name, 'local')
self.assertIn('Credential env vars: OPENAI_API_KEY', restored.render_summary())
def test_account_runtime_logout_clears_active_session(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-account.json').write_text(
'{"profiles":[{"name":"local","provider":"openai","identity":"dev@example.com"}]}',
encoding='utf-8',
)
runtime = AccountRuntime.from_workspace(workspace)
runtime.login('local')
report = runtime.logout()
self.assertFalse(report.logged_in)
self.assertIn('Logged out dev@example.com', report.detail)
def test_account_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-account.json').write_text(
'{"profiles":[{"name":"local","provider":"openai","identity":"dev@example.com"}]}',
encoding='utf-8',
)
runtime = AccountRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
account_runtime=runtime,
)
list_result = execute_tool(
default_tool_registry(),
'account_list_profiles',
{},
context,
)
login_result = execute_tool(
default_tool_registry(),
'account_login',
{'target': 'local'},
context,
)
status_result = execute_tool(
default_tool_registry(),
'account_status',
{},
context,
)
self.assertTrue(list_result.ok)
self.assertIn('dev@example.com', list_result.content)
self.assertTrue(login_result.ok)
self.assertIn('profile=local', login_result.content)
self.assertTrue(status_result.ok)
self.assertIn('Configured account profiles: 1', status_result.content)
+65
View File
@@ -103,6 +103,71 @@ class AgentContextTests(unittest.TestCase):
self.assertIn('mcpRuntime', snapshot.user_context)
self.assertIn('Local MCP resources: 1', snapshot.user_context['mcpRuntime'])
def test_user_context_loads_search_runtime_summary(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir) / 'repo'
workspace.mkdir(parents=True)
(workspace / '.claw-search.json').write_text(
'{"providers":[{"name":"local-search","provider":"searxng","baseUrl":"http://127.0.0.1:8080"}]}',
encoding='utf-8',
)
snapshot = build_context_snapshot(AgentRuntimeConfig(cwd=workspace))
self.assertIn('searchRuntime', snapshot.user_context)
self.assertIn('Configured search providers: 1', snapshot.user_context['searchRuntime'])
self.assertIn('local-search', snapshot.user_context['searchRuntime'])
def test_user_context_loads_remote_runtime_summary(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir) / 'repo'
workspace.mkdir(parents=True)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":[{"name":"staging","mode":"ssh","target":"dev@staging",'
'"workspaceCwd":"/srv/app","sessionUrl":"wss://remote/session"}]}'
),
encoding='utf-8',
)
snapshot = build_context_snapshot(AgentRuntimeConfig(cwd=workspace))
self.assertIn('remoteRuntime', snapshot.user_context)
self.assertIn('Configured remote profiles: 1', snapshot.user_context['remoteRuntime'])
self.assertIn('staging', snapshot.user_context['remoteRuntime'])
def test_user_context_loads_account_runtime_summary(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir) / 'repo'
workspace.mkdir(parents=True)
(workspace / '.claw-account.json').write_text(
'{"profiles":[{"name":"local","provider":"openai","identity":"dev@example.com"}]}',
encoding='utf-8',
)
snapshot = build_context_snapshot(AgentRuntimeConfig(cwd=workspace))
self.assertIn('accountRuntime', snapshot.user_context)
self.assertIn('Configured account profiles: 1', snapshot.user_context['accountRuntime'])
self.assertIn('dev@example.com', snapshot.user_context['accountRuntime'])
def test_user_context_loads_config_runtime_summary(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir) / 'repo'
workspace.mkdir(parents=True)
claude_dir = workspace / '.claude'
claude_dir.mkdir()
(claude_dir / 'settings.json').write_text(
'{"review":{"mode":"strict"}}',
encoding='utf-8',
)
snapshot = build_context_snapshot(AgentRuntimeConfig(cwd=workspace))
self.assertIn('configRuntime', snapshot.user_context)
self.assertIn('Config sources: 1', snapshot.user_context['configRuntime'])
self.assertIn('Effective keys: 2', snapshot.user_context['configRuntime'])
def test_user_context_loads_task_runtime_summary(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir) / 'repo'
+1
View File
@@ -35,6 +35,7 @@ class AgentContextUsageTests(unittest.TestCase):
self.assertGreater(report.total_tokens, 0)
self.assertIn('## Context Usage', rendered)
self.assertIn('**Token counter:**', rendered)
self.assertIn('### System Prompt Sections', rendered)
self.assertIn('### Message Breakdown', rendered)
self.assertIn('#### Top Tools', rendered)
+82
View File
@@ -125,6 +125,88 @@ class AgentPromptingTests(unittest.TestCase):
prompt = render_system_prompt(parts)
self.assertIn('# MCP', prompt)
def test_prompt_builder_mentions_search_when_runtime_is_loaded(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
'{"providers":[{"name":"local-search","provider":"searxng","baseUrl":"http://127.0.0.1:8080"}]}',
encoding='utf-8',
)
runtime_config = AgentRuntimeConfig(cwd=workspace)
model_config = ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct')
prompt_context = build_prompt_context(runtime_config, model_config)
parts = build_system_prompt_parts(
prompt_context=prompt_context,
runtime_config=runtime_config,
tools=default_tool_registry(),
)
prompt = render_system_prompt(parts)
self.assertIn('# Search', prompt)
self.assertIn('web_search', prompt)
def test_prompt_builder_mentions_remote_when_manifest_is_loaded(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":[{"name":"staging","mode":"ssh","target":"dev@staging",'
'"workspaceCwd":"/srv/app"}]}'
),
encoding='utf-8',
)
runtime_config = AgentRuntimeConfig(cwd=workspace)
model_config = ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct')
prompt_context = build_prompt_context(runtime_config, model_config)
parts = build_system_prompt_parts(
prompt_context=prompt_context,
runtime_config=runtime_config,
tools=default_tool_registry(),
)
prompt = render_system_prompt(parts)
self.assertIn('# Remote', prompt)
def test_prompt_builder_mentions_account_when_runtime_is_loaded(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-account.json').write_text(
'{"profiles":[{"name":"local","provider":"openai","identity":"dev@example.com"}]}',
encoding='utf-8',
)
runtime_config = AgentRuntimeConfig(cwd=workspace)
model_config = ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct')
prompt_context = build_prompt_context(runtime_config, model_config)
parts = build_system_prompt_parts(
prompt_context=prompt_context,
runtime_config=runtime_config,
tools=default_tool_registry(),
)
prompt = render_system_prompt(parts)
self.assertIn('# Account', prompt)
def test_prompt_builder_mentions_config_when_runtime_is_loaded(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
claude_dir = workspace / '.claude'
claude_dir.mkdir()
(claude_dir / 'settings.json').write_text(
'{"review":{"mode":"strict"}}',
encoding='utf-8',
)
runtime_config = AgentRuntimeConfig(cwd=workspace)
model_config = ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct')
prompt_context = build_prompt_context(runtime_config, model_config)
parts = build_system_prompt_parts(
prompt_context=prompt_context,
runtime_config=runtime_config,
tools=default_tool_registry(),
)
prompt = render_system_prompt(parts)
self.assertIn('# Config', prompt)
def test_prompt_builder_mentions_tasks_when_runtime_is_loaded(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
+191
View File
@@ -2,6 +2,8 @@ from __future__ import annotations
import tempfile
import unittest
import json
import sys
from pathlib import Path
from unittest.mock import patch
@@ -12,6 +14,55 @@ from src.plan_runtime import PlanRuntime
from src.task_runtime import TaskRuntime
class _FakeHTTPResponse:
def __init__(self, payload: str) -> None:
self.payload = payload
def read(self) -> bytes:
return self.payload.encode('utf-8')
def __enter__(self) -> '_FakeHTTPResponse':
return self
def __exit__(self, exc_type, exc, tb) -> None:
return None
def _write_fake_mcp_server(workspace: Path) -> Path:
server_path = workspace / 'fake_mcp_server.py'
server_path.write_text(
(
'import json, sys\n'
'TOOLS = [{"name": "echo", "description": "Echo text", "inputSchema": {"type": "object", "properties": {"text": {"type": "string"}}}}]\n'
'for raw in sys.stdin:\n'
' raw = raw.strip()\n'
' if not raw:\n'
' continue\n'
' message = json.loads(raw)\n'
' method = message.get("method")\n'
' if method == "initialize":\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"protocolVersion": "2025-11-25", "capabilities": {"resources": {}, "tools": {}}, "serverInfo": {"name": "fake-remote", "version": "1.0.0"}}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "notifications/initialized":\n'
' continue\n'
' if method == "tools/list":\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"tools": TOOLS}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "tools/call":\n'
' text = message.get("params", {}).get("arguments", {}).get("text", "")\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"content": [{"type": "text", "text": "echo:" + text}], "isError": False}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"resources": []}}\n'
' print(json.dumps(response), flush=True)\n'
),
encoding='utf-8',
)
return server_path
class AgentSlashCommandTests(unittest.TestCase):
def test_parse_slash_command(self) -> None:
parsed = parse_slash_command('/context extra args')
@@ -84,6 +135,142 @@ class AgentSlashCommandTests(unittest.TestCase):
self.assertIn('mcp notes', resource_result.final_output)
self.assertIn('# MCP', legacy_mcp_result.final_output)
def test_mcp_tools_command_renders_transport_backed_tools(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
server_path = _write_fake_mcp_server(workspace)
(workspace / '.claw-mcp.json').write_text(
json.dumps(
{
'mcpServers': {
'remote': {
'command': sys.executable,
'args': ['-u', str(server_path)],
}
}
}
),
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
tools_result = agent.run('/mcp tools')
tool_result = agent.run('/mcp tool echo')
self.assertIn('# MCP Tools', tools_result.final_output)
self.assertIn('echo', tools_result.final_output)
self.assertIn('# MCP Tool Result', tool_result.final_output)
self.assertIn('echo:', tool_result.final_output)
def test_search_commands_render_and_update_local_search_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
(
'{"providers":['
'{"name":"local-search","provider":"searxng","baseUrl":"http://127.0.0.1:8080"},'
'{"name":"backup-search","provider":"searxng","baseUrl":"http://127.0.0.2:8080"}'
']}'
),
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
with patch(
'src.search_runtime.request.urlopen',
return_value=_FakeHTTPResponse(
'{"results":[{"title":"Alpha","url":"https://example.com/alpha","content":"Search snippet"}]}'
),
):
search_result = agent.run('/search alpha query')
providers_result = agent.run('/search providers')
activate_result = agent.run('/search use backup-search')
provider_result = agent.run('/search provider backup-search')
self.assertIn('# Web Search', search_result.final_output)
self.assertIn('Alpha', search_result.final_output)
self.assertIn('# Search Providers', providers_result.final_output)
self.assertIn('local-search', providers_result.final_output)
self.assertIn('backup-search', providers_result.final_output)
self.assertIn('provider=backup-search', activate_result.final_output)
self.assertIn('# Search Provider', provider_result.final_output)
self.assertIn('backup-search', provider_result.final_output)
def test_remote_commands_render_and_update_local_remote_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":[{"name":"staging","mode":"ssh","target":"dev@staging",'
'"workspaceCwd":"/srv/app","sessionUrl":"wss://remote/session"}]}'
),
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
remotes_result = agent.run('/remotes')
remote_result = agent.run('/remote')
ssh_result = agent.run('/ssh staging')
disconnect_result = agent.run('/disconnect')
self.assertIn('# Remote Profiles', remotes_result.final_output)
self.assertIn('staging', remotes_result.final_output)
self.assertIn('# Remote', remote_result.final_output)
self.assertIn('Configured remote profiles: 1', remote_result.final_output)
self.assertIn('mode=ssh', ssh_result.final_output)
self.assertIn('profile=staging', ssh_result.final_output)
self.assertIn('connected=False', disconnect_result.final_output)
def test_account_commands_render_and_update_local_account_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-account.json').write_text(
'{"profiles":[{"name":"local","provider":"openai","identity":"dev@example.com"}]}',
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
account_result = agent.run('/account')
profiles_result = agent.run('/account profiles')
login_result = agent.run('/login local')
logout_result = agent.run('/logout')
self.assertIn('# Account', account_result.final_output)
self.assertIn('Configured account profiles: 1', account_result.final_output)
self.assertIn('# Account Profiles', profiles_result.final_output)
self.assertIn('dev@example.com', profiles_result.final_output)
self.assertIn('profile=local', login_result.final_output)
self.assertIn('logged_in=False', logout_result.final_output)
def test_config_commands_render_local_reports(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
claude_dir = workspace / '.claude'
claude_dir.mkdir()
(claude_dir / 'settings.json').write_text(
'{"review":{"mode":"strict"}}',
encoding='utf-8',
)
agent = LocalCodingAgent(
model_config=ModelConfig(model='Qwen/Qwen3-Coder-30B-A3B-Instruct'),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
config_result = agent.run('/config')
effective_result = agent.run('/config effective')
value_result = agent.run('/config get review.mode')
source_result = agent.run('/settings source project')
self.assertIn('# Config', config_result.final_output)
self.assertIn('Config sources: 1', config_result.final_output)
self.assertIn('# Config Effective', effective_result.final_output)
self.assertIn('"review"', effective_result.final_output)
self.assertIn('# Config Value', value_result.final_output)
self.assertIn('"strict"', value_result.final_output)
self.assertIn('# Config Source', source_result.final_output)
def test_tasks_and_task_commands_render_local_reports(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
@@ -100,11 +287,14 @@ class AgentSlashCommandTests(unittest.TestCase):
tasks_result = agent.run('/tasks')
task_result = agent.run(f'/task {task_id}')
todo_result = agent.run('/todo in_progress')
next_result = agent.run('/task-next')
self.assertIn('# Tasks', tasks_result.final_output)
self.assertIn(task_id, tasks_result.final_output)
self.assertIn('# Task', task_result.final_output)
self.assertIn('in_progress', task_result.final_output)
self.assertIn('# Tasks', todo_result.final_output)
self.assertIn('# Next Tasks', next_result.final_output)
self.assertIn(task_id, next_result.final_output)
def test_plan_command_renders_local_report(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
@@ -133,6 +323,7 @@ class AgentSlashCommandTests(unittest.TestCase):
self.assertIn('# Tools', tools_result.final_output)
self.assertIn('`read_file`', tools_result.final_output)
self.assertIn('# Status', status_result.final_output)
self.assertIn('Token counter:', status_result.final_output)
self.assertIn('Last run: none', status_result.final_output)
def test_hooks_and_trust_commands_render_local_reports(self) -> None:
+82
View File
@@ -0,0 +1,82 @@
from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentPermissions, AgentRuntimeConfig
from src.config_runtime import ConfigRuntime
class ConfigRuntimeTests(unittest.TestCase):
def test_config_runtime_loads_and_merges_sources(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
claude_dir = workspace / '.claude'
claude_dir.mkdir()
(claude_dir / 'settings.json').write_text(
'{"model":{"name":"project-model","temperature":0.1},"review":{"strict":false}}',
encoding='utf-8',
)
(claude_dir / 'settings.local.json').write_text(
'{"model":{"temperature":0.0},"review":{"strict":true}}',
encoding='utf-8',
)
runtime = ConfigRuntime.from_workspace(workspace)
self.assertTrue(runtime.has_config())
self.assertEqual(runtime.get_value('model.name'), 'project-model')
self.assertEqual(runtime.get_value('model.temperature'), 0.0)
self.assertEqual(runtime.get_value('review.strict'), True)
self.assertIn('Config sources: 2', runtime.render_summary())
def test_config_runtime_persists_set_value(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
runtime = ConfigRuntime.from_workspace(workspace)
mutation = runtime.set_value('review.mode', 'strict', source='local')
restored = ConfigRuntime.from_workspace(workspace)
self.assertEqual(mutation.source_name, 'local')
self.assertEqual(restored.get_value('review.mode'), 'strict')
self.assertIn('review.mode', restored.render_keys())
self.assertEqual(json.loads(restored.render_value('review.mode')), 'strict')
def test_config_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
runtime = ConfigRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(
cwd=workspace,
permissions=AgentPermissions(allow_file_write=True),
),
config_runtime=runtime,
)
set_result = execute_tool(
default_tool_registry(),
'config_set',
{'key_path': 'review.mode', 'value': 'strict'},
context,
)
list_result = execute_tool(
default_tool_registry(),
'config_list',
{'prefix': 'review'},
context,
)
get_result = execute_tool(
default_tool_registry(),
'config_get',
{'key_path': 'review.mode'},
context,
)
self.assertTrue(set_result.ok)
self.assertEqual(set_result.metadata.get('source_name'), 'local')
self.assertIn('# Config Keys', list_result.content)
self.assertIn('review.mode', list_result.content)
self.assertIn('# Config Value', get_result.content)
self.assertIn('"strict"', get_result.content)
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentRuntimeConfig
class ExtendedToolTests(unittest.TestCase):
def test_web_fetch_reads_text_from_file_url(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
target = workspace / 'page.txt'
target.write_text('hello from web fetch\n', encoding='utf-8')
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
tool_registry=default_tool_registry(),
)
result = execute_tool(
default_tool_registry(),
'web_fetch',
{'url': target.resolve().as_uri()},
context,
)
self.assertTrue(result.ok)
self.assertIn('hello from web fetch', result.content)
self.assertEqual(result.metadata.get('action'), 'web_fetch')
def test_tool_search_lists_matching_tools(self) -> None:
registry = default_tool_registry()
with tempfile.TemporaryDirectory() as tmp_dir:
context = build_tool_context(
AgentRuntimeConfig(cwd=Path(tmp_dir)),
tool_registry=registry,
)
result = execute_tool(
registry,
'tool_search',
{'query': 'file'},
context,
)
self.assertTrue(result.ok)
self.assertIn('# Tool Search', result.content)
self.assertIn('read_file', result.content)
self.assertIn('write_file', result.content)
def test_sleep_tool_waits_briefly_and_returns_metadata(self) -> None:
registry = default_tool_registry()
with tempfile.TemporaryDirectory() as tmp_dir:
context = build_tool_context(
AgentRuntimeConfig(cwd=Path(tmp_dir)),
tool_registry=registry,
)
result = execute_tool(
registry,
'sleep',
{'seconds': 0.01},
context,
)
self.assertTrue(result.ok)
self.assertIn('slept for', result.content)
self.assertEqual(result.metadata.get('action'), 'sleep')
+40
View File
@@ -127,3 +127,43 @@ class MainCliTests(unittest.TestCase):
self.assertEqual(recorded_results, ['First chat reply.', 'Second chat reply.'])
self.assertIn('# Agent Chat', recorded_lines)
self.assertIn('chat_ended=user_exit', recorded_lines)
def test_parser_accepts_remote_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['remote-profiles', '--cwd', '.'])
self.assertEqual(args.command, 'remote-profiles')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_account_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['account-profiles', '--cwd', '.'])
self.assertEqual(args.command, 'account-profiles')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_search_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['search', 'repo query', '--cwd', '.', '--provider', 'local-search'])
self.assertEqual(args.command, 'search')
self.assertEqual(args.query, 'repo query')
self.assertEqual(args.provider, 'local-search')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_mcp_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['mcp-tools', '--cwd', '.', '--server', 'remote'])
self.assertEqual(args.command, 'mcp-tools')
self.assertEqual(args.server, 'remote')
self.assertEqual(args.cwd, '.')
def test_parser_accepts_daemon_subcommands(self) -> None:
parser = build_parser()
args = parser.parse_args(['daemon', 'ps'])
self.assertEqual(args.command, 'daemon')
self.assertEqual(args.daemon_command, 'ps')
def test_parser_accepts_config_runtime_commands(self) -> None:
parser = build_parser()
args = parser.parse_args(['config-get', 'review.mode', '--cwd', '.'])
self.assertEqual(args.command, 'config-get')
self.assertEqual(args.key_path, 'review.mode')
self.assertEqual(args.cwd, '.')
+192
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
import sys
import tempfile
import unittest
from pathlib import Path
@@ -36,6 +37,52 @@ def make_urlopen_side_effect(responses: list[dict[str, object]]):
class MCPRuntimeTests(unittest.TestCase):
def _write_fake_stdio_server(self, workspace: Path) -> Path:
server_path = workspace / 'fake_mcp_server.py'
server_path.write_text(
(
'import json, sys\n'
'RESOURCES = [{"uri": "mcp://remote/notes", "name": "Remote Notes", "mimeType": "text/plain"}]\n'
'TOOLS = [{"name": "echo", "description": "Echo text", "inputSchema": {"type": "object", "properties": {"text": {"type": "string"}}}}]\n'
'for raw in sys.stdin:\n'
' raw = raw.strip()\n'
' if not raw:\n'
' continue\n'
' message = json.loads(raw)\n'
' method = message.get("method")\n'
' if method == "initialize":\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"protocolVersion": "2025-11-25", "capabilities": {"resources": {}, "tools": {}}, "serverInfo": {"name": "fake-remote", "version": "1.0.0"}}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "notifications/initialized":\n'
' continue\n'
' if method == "resources/list":\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"resources": RESOURCES}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "resources/read":\n'
' uri = message.get("params", {}).get("uri")\n'
' text = "remote notes via stdio" if uri == "mcp://remote/notes" else "unknown resource"\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"contents": [{"uri": uri, "mimeType": "text/plain", "text": text}]}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "tools/list":\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"tools": TOOLS}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' if method == "tools/call":\n'
' params = message.get("params", {})\n'
' text = params.get("arguments", {}).get("text", "")\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "result": {"content": [{"type": "text", "text": "echo:" + text}], "isError": False}}\n'
' print(json.dumps(response), flush=True)\n'
' continue\n'
' response = {"jsonrpc": "2.0", "id": message.get("id"), "error": {"code": -32601, "message": "Method not found"}}\n'
' print(json.dumps(response), flush=True)\n'
),
encoding='utf-8',
)
return server_path
def test_runtime_discovers_and_reads_local_resources(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
@@ -55,6 +102,38 @@ class MCPRuntimeTests(unittest.TestCase):
self.assertEqual(runtime.read_resource('mcp://workspace/inline'), 'inline body')
self.assertIn('mcp notes', runtime.read_resource('mcp://workspace/notes'))
def test_runtime_discovers_stdio_server_and_remote_resources_and_tools(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
server_path = self._write_fake_stdio_server(workspace)
(workspace / '.claw-mcp.json').write_text(
json.dumps(
{
'mcpServers': {
'remote': {
'command': sys.executable,
'args': ['-u', str(server_path)],
}
}
}
),
encoding='utf-8',
)
runtime = MCPRuntime.from_workspace(workspace)
resources = runtime.list_resources()
tools = runtime.list_tools()
self.assertEqual(len(runtime.servers), 1)
self.assertTrue(runtime.has_transport_servers())
self.assertIn('Configured MCP servers: 1', runtime.render_summary())
self.assertEqual(len(resources), 1)
self.assertEqual(resources[0].uri, 'mcp://remote/notes')
self.assertIn('remote notes via stdio', runtime.read_resource('mcp://remote/notes'))
self.assertEqual(len(tools), 1)
self.assertEqual(tools[0].name, 'echo')
rendered, metadata = runtime.call_tool('echo', arguments={'text': 'hello'})
self.assertIn('echo:hello', rendered)
self.assertEqual(metadata.get('server_name'), 'remote')
def test_mcp_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
@@ -90,6 +169,47 @@ class MCPRuntimeTests(unittest.TestCase):
self.assertTrue(read_result.ok)
self.assertIn('mcp notes', read_result.content)
def test_mcp_transport_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
server_path = self._write_fake_stdio_server(workspace)
(workspace / '.claw-mcp.json').write_text(
json.dumps(
{
'mcpServers': {
'remote': {
'command': sys.executable,
'args': ['-u', str(server_path)],
}
}
}
),
encoding='utf-8',
)
runtime = MCPRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
mcp_runtime=runtime,
)
list_result = execute_tool(
default_tool_registry(),
'mcp_list_tools',
{},
context,
)
call_result = execute_tool(
default_tool_registry(),
'mcp_call_tool',
{'tool_name': 'echo', 'arguments': {'text': 'tool-run'}},
context,
)
self.assertTrue(list_result.ok)
self.assertIn('echo', list_result.content)
self.assertTrue(call_result.ok)
self.assertIn('echo:tool-run', call_result.content)
self.assertEqual(call_result.metadata.get('action'), 'mcp_call_tool')
def test_agent_can_use_mcp_tools_in_model_loop(self) -> None:
responses = [
{
@@ -156,3 +276,75 @@ class MCPRuntimeTests(unittest.TestCase):
if message.get('role') == 'tool'
)
self.assertIn('mcp notes', tool_message.get('content', ''))
def test_agent_can_use_transport_backed_mcp_call_tool_in_model_loop(self) -> None:
responses = [
{
'choices': [
{
'message': {
'role': 'assistant',
'content': 'I will call the remote MCP tool.',
'tool_calls': [
{
'id': 'call_1',
'type': 'function',
'function': {
'name': 'mcp_call_tool',
'arguments': '{"tool_name": "echo", "server": "remote", "arguments": {"text": "agent-call"}}',
},
}
],
},
'finish_reason': 'tool_calls',
}
],
'usage': {'prompt_tokens': 8, 'completion_tokens': 3},
},
{
'choices': [
{
'message': {
'role': 'assistant',
'content': 'The remote MCP tool replied with echo:agent-call.',
},
'finish_reason': 'stop',
}
],
'usage': {'prompt_tokens': 6, 'completion_tokens': 3},
},
]
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
server_path = self._write_fake_stdio_server(workspace)
(workspace / '.claw-mcp.json').write_text(
json.dumps(
{
'mcpServers': {
'remote': {
'command': sys.executable,
'args': ['-u', str(server_path)],
}
}
}
),
encoding='utf-8',
)
with patch('src.openai_compat.request.urlopen', side_effect=make_urlopen_side_effect(responses)):
agent = LocalCodingAgent(
model_config=ModelConfig(
model='Qwen/Qwen3-Coder-30B-A3B-Instruct',
base_url='http://127.0.0.1:8000/v1',
),
runtime_config=AgentRuntimeConfig(cwd=workspace),
)
result = agent.run('Call the remote MCP echo tool')
self.assertEqual(result.final_output, 'The remote MCP tool replied with echo:agent-call.')
self.assertEqual(result.tool_calls, 1)
tool_message = next(
message
for message in result.transcript
if message.get('role') == 'tool'
)
self.assertIn('echo:agent-call', tool_message.get('content', ''))
+5 -1
View File
@@ -51,7 +51,8 @@ class PlanRuntimeTests(unittest.TestCase):
},
{
'step': 'Patch the tool registry',
'status': 'pending',
'status': 'blocked',
'depends_on': ['plan_1'],
},
],
explanation='Work through the runtime in two phases.',
@@ -59,12 +60,15 @@ class PlanRuntimeTests(unittest.TestCase):
)
rendered_plan = plan_runtime.render_plan()
rendered_tasks = task_runtime.render_tasks()
rendered_task = task_runtime.render_task('plan_2')
self.assertEqual(mutation.after_count, 2)
self.assertEqual(mutation.synced_tasks, 2)
self.assertIn('Inspect the runtime loop', rendered_plan)
self.assertIn('Work through the runtime in two phases.', rendered_plan)
self.assertIn('depends_on: plan_1', rendered_plan)
self.assertIn('Inspect the runtime loop', rendered_tasks)
self.assertIn('Blocked By: plan_1', rendered_task)
def test_plan_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
+123
View File
@@ -0,0 +1,123 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentRuntimeConfig
from src.remote_runtime import (
RemoteRuntime,
run_deep_link_mode,
run_direct_connect_mode,
run_remote_mode,
run_ssh_mode,
run_teleport_mode,
)
class RemoteRuntimeTests(unittest.TestCase):
def test_remote_runtime_discovers_profiles_and_persists_connection(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":['
'{"name":"staging","mode":"ssh","target":"dev@staging","workspaceCwd":"/srv/app"},'
'{"name":"preview","mode":"deep-link","target":"preview://session"}'
']}'
),
encoding='utf-8',
)
runtime = RemoteRuntime.from_workspace(workspace)
report = runtime.connect('staging')
restored = RemoteRuntime.from_workspace(workspace)
self.assertEqual(len(runtime.profiles), 2)
self.assertTrue(report.connected)
self.assertEqual(report.profile_name, 'staging')
self.assertIsNotNone(restored.active_connection)
self.assertEqual(restored.active_connection.profile_name, 'staging')
self.assertIn('Configured remote profiles: 2', restored.render_summary())
def test_remote_runtime_disconnect_clears_active_connection(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
'{"profiles":[{"name":"staging","mode":"ssh","target":"dev@staging"}]}',
encoding='utf-8',
)
runtime = RemoteRuntime.from_workspace(workspace)
runtime.connect('staging')
report = runtime.disconnect()
self.assertFalse(report.connected)
self.assertIn('Disconnected ssh target dev@staging', report.detail)
def test_remote_mode_helpers_use_manifest_backed_profiles(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":['
'{"name":"workspace","mode":"remote","target":"remote://workspace"},'
'{"name":"sshbox","mode":"ssh","target":"dev@sshbox"},'
'{"name":"tele","mode":"teleport","target":"teleport://workspace"},'
'{"name":"direct","mode":"direct-connect","target":"direct://workspace"},'
'{"name":"link","mode":"deep-link","target":"deep://workspace"}'
']}'
),
encoding='utf-8',
)
remote_report = run_remote_mode('workspace', cwd=workspace)
ssh_report = run_ssh_mode('sshbox', cwd=workspace)
teleport_report = run_teleport_mode('tele', cwd=workspace)
direct_report = run_direct_connect_mode('direct', cwd=workspace)
deep_link_report = run_deep_link_mode('link', cwd=workspace)
self.assertEqual(remote_report.profile_name, 'workspace')
self.assertEqual(ssh_report.mode, 'ssh')
self.assertEqual(teleport_report.mode, 'teleport')
self.assertEqual(direct_report.mode, 'direct-connect')
self.assertEqual(deep_link_report.mode, 'deep-link')
def test_remote_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-remote.json').write_text(
(
'{"profiles":[{"name":"staging","mode":"ssh","target":"dev@staging",'
'"workspaceCwd":"/srv/app","sessionUrl":"wss://remote/session"}]}'
),
encoding='utf-8',
)
runtime = RemoteRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
remote_runtime=runtime,
)
list_result = execute_tool(
default_tool_registry(),
'remote_list_profiles',
{},
context,
)
connect_result = execute_tool(
default_tool_registry(),
'remote_connect',
{'target': 'staging'},
context,
)
status_result = execute_tool(
default_tool_registry(),
'remote_status',
{},
context,
)
self.assertTrue(list_result.ok)
self.assertIn('staging', list_result.content)
self.assertTrue(connect_result.ok)
self.assertIn('profile=staging', connect_result.content)
self.assertTrue(status_result.ok)
self.assertIn('Configured remote profiles: 1', status_result.content)
+132
View File
@@ -0,0 +1,132 @@
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from src.agent_tools import build_tool_context, default_tool_registry, execute_tool
from src.agent_types import AgentRuntimeConfig
from src.search_runtime import SearchRuntime
class FakeHTTPResponse:
def __init__(self, payload: str) -> None:
self.payload = payload
def read(self) -> bytes:
return self.payload.encode('utf-8')
def __enter__(self) -> 'FakeHTTPResponse':
return self
def __exit__(self, exc_type, exc, tb) -> None:
return None
class SearchRuntimeTests(unittest.TestCase):
def test_provider_activation_persists_across_reload(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
(
'{"providers":['
'{"name":"primary","provider":"searxng","baseUrl":"http://127.0.0.1:8080"},'
'{"name":"backup","provider":"searxng","baseUrl":"http://127.0.0.2:8080"}'
']}'
),
encoding='utf-8',
)
runtime = SearchRuntime.from_workspace(workspace)
report = runtime.activate_provider('backup')
reloaded = SearchRuntime.from_workspace(workspace)
self.assertEqual(report.provider_name, 'backup')
self.assertIsNotNone(reloaded.current_provider())
self.assertEqual(reloaded.current_provider().name, 'backup')
def test_search_runtime_loads_searxng_provider_from_env(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
with patch.dict('os.environ', {'SEARXNG_BASE_URL': 'http://127.0.0.1:8888'}, clear=False):
runtime = SearchRuntime.from_workspace(workspace)
provider = runtime.current_provider()
self.assertIsNotNone(provider)
self.assertEqual(provider.name, 'searxng')
self.assertEqual(provider.base_url, 'http://127.0.0.1:8888')
def test_search_runtime_parses_searxng_results(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
'{"providers":[{"name":"local-search","provider":"searxng","baseUrl":"http://127.0.0.1:8080"}]}',
encoding='utf-8',
)
runtime = SearchRuntime.from_workspace(workspace)
with patch(
'src.search_runtime.request.urlopen',
return_value=FakeHTTPResponse(
'{"results":[{"title":"Alpha","url":"https://example.com/alpha","content":"Snippet"}]}'
),
):
provider, results = runtime.search('alpha')
self.assertEqual(provider.name, 'local-search')
self.assertEqual(len(results), 1)
self.assertEqual(results[0].title, 'Alpha')
self.assertEqual(results[0].url, 'https://example.com/alpha')
def test_search_runtime_parses_brave_results(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
'{"providers":[{"name":"brave-local","provider":"brave","baseUrl":"https://api.search.brave.com/res/v1/web/search","apiKeyEnv":"BRAVE_SEARCH_API_KEY"}]}',
encoding='utf-8',
)
with patch.dict('os.environ', {'BRAVE_SEARCH_API_KEY': 'demo-key'}, clear=False):
runtime = SearchRuntime.from_workspace(workspace)
with patch(
'src.search_runtime.request.urlopen',
return_value=FakeHTTPResponse(
'{"web":{"results":[{"title":"Alpha","url":"https://example.com/alpha","description":"Snippet"}]}}'
),
):
provider, results = runtime.search('alpha', provider_name='brave-local')
self.assertEqual(provider.provider, 'brave')
self.assertEqual(len(results), 1)
self.assertEqual(results[0].snippet, 'Snippet')
def test_web_search_tool_uses_search_runtime(self) -> None:
registry = default_tool_registry()
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
(workspace / '.claw-search.json').write_text(
'{"providers":[{"name":"local-search","provider":"searxng","baseUrl":"http://127.0.0.1:8080"}]}',
encoding='utf-8',
)
runtime = SearchRuntime.from_workspace(workspace)
context = build_tool_context(
AgentRuntimeConfig(cwd=workspace),
tool_registry=registry,
search_runtime=runtime,
)
with patch(
'src.search_runtime.request.urlopen',
return_value=FakeHTTPResponse(
'{"results":[{"title":"Alpha","url":"https://example.com/alpha","content":"Snippet"}]}'
),
):
result = execute_tool(
registry,
'web_search',
{'query': 'alpha'},
context,
)
self.assertTrue(result.ok)
self.assertIn('# Web Search', result.content)
self.assertEqual(result.metadata.get('action'), 'web_search')
self.assertEqual(result.metadata.get('provider'), 'local-search')
self.assertEqual(result.metadata.get('result_count'), 1)
+84 -6
View File
@@ -46,12 +46,12 @@ class TaskRuntimeTests(unittest.TestCase):
status='in_progress',
)
assert created.task is not None
runtime.update_task(created.task.task_id, status='done')
runtime.update_task(created.task.task_id, status='completed')
rendered_tasks = runtime.render_tasks()
rendered_task = runtime.render_task(created.task.task_id)
self.assertIn('Implement task runtime', rendered_tasks)
self.assertIn('done', rendered_task)
self.assertIn('completed', rendered_task)
def test_task_tools_execute_against_runtime(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
@@ -67,11 +67,17 @@ class TaskRuntimeTests(unittest.TestCase):
create_result = execute_tool(
default_tool_registry(),
'task_create',
{'title': 'Review task tools', 'status': 'todo'},
{'title': 'Review task tools', 'status': 'pending'},
context,
)
self.assertTrue(create_result.ok)
task_id = str(create_result.metadata.get('task_id'))
next_result = execute_tool(
default_tool_registry(),
'task_next',
{},
context,
)
list_result = execute_tool(
default_tool_registry(),
'task_list',
@@ -87,7 +93,7 @@ class TaskRuntimeTests(unittest.TestCase):
update_result = execute_tool(
default_tool_registry(),
'task_update',
{'task_id': task_id, 'status': 'done'},
{'task_id': task_id, 'status': 'completed'},
context,
)
todo_result = execute_tool(
@@ -97,13 +103,85 @@ class TaskRuntimeTests(unittest.TestCase):
context,
)
self.assertIn('Review task tools', next_result.content)
self.assertIn(task_id, list_result.content)
self.assertIn('Review task tools', get_result.content)
self.assertTrue(update_result.ok)
self.assertEqual(update_result.metadata.get('task_status'), 'done')
self.assertEqual(update_result.metadata.get('task_status'), 'completed')
self.assertTrue(todo_result.ok)
self.assertEqual(todo_result.metadata.get('total_tasks'), 1)
def test_next_tasks_respects_dependencies_and_completion(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
runtime = TaskRuntime.from_workspace(workspace)
runtime.replace_tasks(
[
{'task_id': 'scan', 'title': 'Scan workspace', 'status': 'pending'},
{
'task_id': 'patch',
'title': 'Patch files',
'status': 'blocked',
'blocked_by': ['scan'],
},
]
)
first_next = runtime.render_next_tasks()
runtime.complete_task('scan')
second_next = runtime.render_next_tasks()
self.assertIn('Scan workspace', first_next)
self.assertNotIn('Patch files', first_next)
self.assertIn('Patch files', second_next)
def test_task_execution_tools_handle_block_start_and_complete(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
workspace = Path(tmp_dir)
runtime = TaskRuntime.from_workspace(workspace)
runtime.replace_tasks(
[
{'task_id': 'scan', 'title': 'Scan workspace', 'status': 'pending'},
{
'task_id': 'patch',
'title': 'Patch files',
'status': 'blocked',
'blocked_by': ['scan'],
},
]
)
context = build_tool_context(
AgentRuntimeConfig(
cwd=workspace,
permissions=AgentPermissions(allow_file_write=True),
),
task_runtime=runtime,
)
blocked_start = execute_tool(
default_tool_registry(),
'task_start',
{'task_id': 'patch'},
context,
)
complete_scan = execute_tool(
default_tool_registry(),
'task_complete',
{'task_id': 'scan'},
context,
)
start_patch = execute_tool(
default_tool_registry(),
'task_start',
{'task_id': 'patch', 'owner': 'agent_1', 'active_form': 'Patching files'},
context,
)
self.assertTrue(blocked_start.ok)
self.assertIn('[blocked]', blocked_start.content)
self.assertTrue(complete_scan.ok)
self.assertTrue(start_patch.ok)
self.assertIn('[in_progress]', start_patch.content)
self.assertEqual(runtime.get_task('patch').owner, 'agent_1')
def test_agent_can_use_task_tools_in_model_loop(self) -> None:
responses = [
{
@@ -118,7 +196,7 @@ class TaskRuntimeTests(unittest.TestCase):
'type': 'function',
'function': {
'name': 'task_create',
'arguments': '{"title": "Review runtime tasks", "status": "todo"}',
'arguments': '{"title": "Review runtime tasks", "status": "pending"}',
},
}
],
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import unittest
from unittest.mock import patch
from src.tokenizer_runtime import (
ResolvedTokenCounter,
TokenCounterInfo,
clear_token_counter_cache,
count_tokens,
describe_token_counter,
resolve_token_counter,
)
class TokenizerRuntimeTests(unittest.TestCase):
def tearDown(self) -> None:
clear_token_counter_cache()
def test_gpt_models_prefer_tiktoken_backend_when_available(self) -> None:
fake_counter = ResolvedTokenCounter(
info=TokenCounterInfo(
backend='tiktoken',
source='o200k_base',
accurate=True,
),
count_text=lambda text: len(text.split()),
)
with patch('src.tokenizer_runtime._try_build_tiktoken_counter', return_value=fake_counter):
with patch('src.tokenizer_runtime._try_build_transformers_counter', return_value=None):
info = describe_token_counter('gpt-4o-mini')
token_count = count_tokens('hello world from claw code', 'gpt-4o-mini')
self.assertEqual(info.backend, 'tiktoken')
self.assertTrue(info.accurate)
self.assertEqual(token_count, 5)
def test_transformers_backend_can_be_selected_with_env_override(self) -> None:
fake_counter = ResolvedTokenCounter(
info=TokenCounterInfo(
backend='transformers',
source='/tmp/fake-tokenizer (local_files_only)',
accurate=True,
),
count_text=lambda text: len(text.split()),
)
with patch.dict(
'os.environ',
{'CLAW_CODE_TOKENIZER_PATH': '/tmp/fake-tokenizer'},
clear=False,
):
with patch('src.tokenizer_runtime._try_build_transformers_counter', return_value=fake_counter):
info = describe_token_counter('Qwen/Qwen3-Coder-30B-A3B-Instruct')
token_count = count_tokens('one two three', 'Qwen/Qwen3-Coder-30B-A3B-Instruct')
self.assertEqual(info.backend, 'transformers')
self.assertTrue(info.accurate)
self.assertEqual(token_count, 3)
def test_fallback_backend_is_used_when_all_tokenizers_fail(self) -> None:
with patch('src.tokenizer_runtime._try_build_tiktoken_counter', return_value=None):
with patch('src.tokenizer_runtime._try_build_transformers_counter', return_value=None):
counter = resolve_token_counter('unknown-model')
token_count = count_tokens('abcd' * 5, 'unknown-model')
self.assertEqual(counter.info.backend, 'heuristic')
self.assertFalse(counter.info.accurate)
self.assertGreater(token_count, 0)