from __future__ import annotations from skills_learning.models import FlowStep, FlowTemplateSkill, SkillMetadata from skills_learning.store import SkillStore from skills_learning.synthesis import extract_tool_calls, synthesize_flow_skill def _record( action: str, args: dict[str, object] | None = None, *, result: dict[str, object] | None = None, ) -> dict[str, object]: return { "tool_call": { "action": action, "args": args or {}, }, "result": result or {}, } def test_extract_tool_calls_filters_read_only_tools_in_order() -> None: records = [ _record("describe_screen"), _record("launch_app", {"app_id": "com.example"}), _record("screenshot"), _record("tap", {"x": 1, "y": 2}), _record("find_text", {"query": "Send"}), _record("input_text", {"text": "coffee"}), ] steps = extract_tool_calls("", records) assert steps == [ FlowStep("launch_app", {"app_id": "com.example"}), FlowStep("tap", {"x": 1, "y": 2}), FlowStep("input_text", {"text": "coffee"}), ] def test_first_time_synthesis_has_literal_steps_and_no_parameters() -> None: skill = synthesize_flow_skill( "search coffee", [_record("input_text", {"text": "coffee"})], ) assert skill.name == "search coffee" assert skill.steps == [FlowStep("input_text", {"text": "coffee"})] assert skill.parameters == {} def test_second_execution_promotes_differing_argument_to_parameter() -> None: store = SkillStore() store.create_version( FlowTemplateSkill( metadata=SkillMetadata( name="search", description="Learned search", originating_goal="search coffee", ), steps=[FlowStep("input_text", {"text": "coffee"})], parameters={}, ) ) records = [ _record( "input_text", {"text": "tea"}, result={ "semantic_scene": { "page": "Search", "intents": ["search"], "widgets": [ { "element_id": "search-input", "purpose": "search field", } ], } }, ) ] skill = synthesize_flow_skill("search tea", records, store=store) assert skill.name == "search" assert skill.steps == [FlowStep("input_text", {"text": "{search_field}"})] assert "search_field" in skill.parameters def test_identical_repeat_does_not_add_parameters() -> None: store = SkillStore() store.create_version( FlowTemplateSkill( metadata=SkillMetadata( name="search", description="Learned search", originating_goal="search coffee", ), steps=[FlowStep("input_text", {"text": "coffee"})], parameters={}, ) ) skill = synthesize_flow_skill( "search coffee again", [_record("input_text", {"text": "coffee"})], store=store, ) assert skill.steps == [FlowStep("input_text", {"text": "coffee"})] assert skill.parameters == {}