feat(planner): persist reusable action semantics
Tests / Test passed: 879

This commit is contained in:
2026-07-15 18:14:28 +08:00
parent 361dada276
commit d69be48f96
41 changed files with 733 additions and 116 deletions
+66
View File
@@ -632,6 +632,36 @@ def test_anthropic_client_tool_only_response_has_none_thinking_and_text_output()
assert decision.text_output is None
def test_anthropic_client_separates_required_action_metadata_from_arguments() -> None:
messages = FakeMessages(
response={
"content": [
{
"type": "tool_use",
"name": "tap",
"input": {
"x": 10,
"y": 20,
"purpose": "Open the account screen.",
"expected_outcome": "The account screen is visible.",
},
}
]
}
)
client = AnthropicToolCallingClient(
model="test-model", transport=FakeTransport(messages)
)
decision = client.decide(
system_prompt="s", user_prompt="u", screenshot=None, tools=[TAP_SPEC], timeout=1
)
assert decision.arguments == {"x": 10, "y": 20}
assert decision.purpose == "Open the account screen."
assert decision.expected_outcome == "The account screen is visible."
def test_openai_client_captures_reasoning_content() -> None:
completions = FakeCompletions(
response={
@@ -691,6 +721,42 @@ def test_openai_client_captures_message_content_as_text_output() -> None:
assert decision.text_output == "Previous step succeeded. Now tapping login."
def test_openai_client_separates_required_action_metadata_from_arguments() -> None:
completions = FakeCompletions(
response={
"choices": [
{
"message": {
"tool_calls": [
{
"function": {
"name": "tap",
"arguments": (
'{"x": 5, "y": 6, '
'"purpose": "Open settings.", '
'"expected_outcome": "Settings is visible."}'
),
}
}
]
}
}
]
}
)
client = OpenAIToolCallingClient(
model="test-model", transport=FakeOpenAITransport(completions)
)
decision = client.decide(
system_prompt="s", user_prompt="u", screenshot=None, tools=[TAP_SPEC], timeout=1
)
assert decision.arguments == {"x": 5, "y": 6}
assert decision.purpose == "Open settings."
assert decision.expected_outcome == "Settings is visible."
def test_openai_client_no_reasoning_content_gives_none_thinking() -> None:
completions = FakeCompletions(
response={