read_guide (api/mcp_guide.py) hands the AI one topic at a time: the README's parts between <!-- guide:name --> marks (both languages, same names; the Chinese one carries the words the Chinese window shows), Model Check's rules by section, and the fields of show_panel's blocks. Long topics come in parts that fit an answer. Nothing is written twice: the topics are the README's and the rules' own text. To make room for what 1.64 adds next: show_panel names its blocks and the guide has their fields (a bad block's error points there), update_panel's details moved there too, download_status became run_status(downloads=true), track_run's text is shorter. The tool list went from 43,957 to 42,574 characters with one tool more (53 tools); show_panel no longer needs a longer-description exception in the budget test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.3 KiB
Python
51 lines
2.3 KiB
Python
"""The tool budget: what AI apps are handed stays small enough for a light model (Gemini Flash is the
|
|
yardstick). On 2026-10-26 Gemini 3.8 Flash ran about 570 calls over the full set in Antigravity with
|
|
hardly a wrong one, so the set as it is, is the ceiling: a new tool, or a longer description, has to
|
|
make room by merging or cutting. Raise a number here only together with a new Flash run that
|
|
shows it still goes well (docs/architecture/tool-exam.md)."""
|
|
import json
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
PLUGIN = Path(__file__).resolve().parents[1]
|
|
sys.path[:0] = [str(PLUGIN), str(PLUGIN.parents[1])]
|
|
|
|
from api import mcp_server # noqa: E402
|
|
|
|
MAX_TOOLS = 53
|
|
MAX_CHARS = 44000 # the full list as AI apps get it (about 11k tokens)
|
|
MAX_DESCRIPTION = 700 # a tool's own description; details an AI needs only now and then go to read_guide
|
|
MAX_REQUIRED = 2 # what a call needs at least; the rest has a default
|
|
INSTRUCTIONS_CHARS = 3400
|
|
|
|
|
|
def listed():
|
|
return mcp_server._tool_list(True)["tools"]
|
|
|
|
|
|
class BudgetTests(unittest.TestCase):
|
|
def test_the_set_does_not_grow(self):
|
|
tools = listed()
|
|
self.assertLessEqual(len(tools), MAX_TOOLS, "a new tool: merge or cut one")
|
|
self.assertLessEqual(len(json.dumps(tools, ensure_ascii=False)), MAX_CHARS)
|
|
self.assertLessEqual(len(mcp_server.INSTRUCTIONS), INSTRUCTIONS_CHARS)
|
|
|
|
def test_each_tool_is_short_and_needs_little(self):
|
|
for tool in listed():
|
|
with self.subTest(tool=tool["name"]):
|
|
self.assertLessEqual(len(tool.get("description") or ""), MAX_DESCRIPTION)
|
|
self.assertLessEqual(len(tool["inputSchema"].get("required") or []), MAX_REQUIRED)
|
|
self.assertTrue(tool.get("description"), "every tool says what it is for")
|
|
|
|
def test_each_step_has_a_name_in_both_languages(self):
|
|
# The timeline names a step by aiTool_<tool> (web/modules/ai_task_view.js toolKey); the card's own calls are not steps.
|
|
words = (PLUGIN / "web" / "modules" / "locales.js").read_text(encoding="utf-8")
|
|
for name in set(mcp_server.TOOLS) - set(mcp_server.mcp_apps.APP_TOOLS):
|
|
with self.subTest(tool=name):
|
|
self.assertEqual(words.count(f"aiTool_{name}:"), 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|