test_anthropic_protocol.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import base64
  2. from agent.llm import openrouter, yescode
  3. from agent.llm.anthropic_protocol import (
  4. ANTHROPIC_MODEL_EXACT,
  5. ANTHROPIC_MODEL_FUZZY,
  6. build_anthropic_result,
  7. normalize_tool_call_ids,
  8. parse_anthropic_response,
  9. to_anthropic_messages,
  10. )
  11. def test_provider_model_tables_share_one_source_of_truth():
  12. assert yescode.MODEL_EXACT is ANTHROPIC_MODEL_EXACT
  13. assert yescode.MODEL_FUZZY is ANTHROPIC_MODEL_FUZZY
  14. assert openrouter._OR_MODEL_EXACT is ANTHROPIC_MODEL_EXACT
  15. assert openrouter._OR_MODEL_FUZZY is ANTHROPIC_MODEL_FUZZY
  16. assert yescode._resolve_model("anthropic/claude-sonnet-4.5") == (
  17. "claude-sonnet-4-5-20250929"
  18. )
  19. assert openrouter._resolve_openrouter_model("claude-sonnet-4.5") == (
  20. "anthropic/claude-sonnet-4-5-20250929"
  21. )
  22. def test_unknown_model_fallback_remains_provider_specific():
  23. assert yescode._resolve_model("vendor/new-model") == "new-model"
  24. assert openrouter._resolve_openrouter_model("vendor/new-model") == (
  25. "vendor/new-model"
  26. )
  27. def test_tool_call_id_normalization_links_assistant_and_tool_without_mutation():
  28. messages = [
  29. {
  30. "role": "assistant",
  31. "tool_calls": [{"id": "call_old", "function": {"name": "read"}}],
  32. },
  33. {"role": "tool", "tool_call_id": "call_old", "content": "ok"},
  34. ]
  35. normalized = normalize_tool_call_ids(messages, "toolu")
  36. assert normalized[0]["tool_calls"][0]["id"] == "toolu_000000"
  37. assert normalized[1]["tool_call_id"] == "toolu_000000"
  38. assert messages[0]["tool_calls"][0]["id"] == "call_old"
  39. assert normalize_tool_call_ids(normalized, "toolu") is normalized
  40. def test_yescode_and_openrouter_keep_their_image_nesting_contracts():
  41. png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 8 + (2).to_bytes(4, "big") + (
  42. 3
  43. ).to_bytes(4, "big")
  44. uri = "data:image/png;base64," + base64.b64encode(png).decode()
  45. messages = [
  46. {
  47. "role": "tool",
  48. "tool_call_id": "toolu_1",
  49. "content": [{"type": "image_url", "image_url": {"url": uri}}],
  50. }
  51. ]
  52. _, yescode_messages = yescode._convert_messages_to_anthropic(messages)
  53. _, shared_messages = to_anthropic_messages(messages)
  54. nested = yescode_messages[0]["content"][0]["content"][0]
  55. assert nested["type"] == "image"
  56. assert "_image_meta" not in nested
  57. sibling = shared_messages[0]["content"][1]
  58. assert sibling["type"] == "image"
  59. assert sibling["_image_meta"] == {"width": 2, "height": 3}
  60. def test_anthropic_response_and_result_shape_remain_compatible():
  61. parsed = parse_anthropic_response(
  62. {
  63. "content": [
  64. {"type": "text", "text": "done"},
  65. {"type": "tool_use", "id": "1", "name": "read", "input": {}},
  66. ],
  67. "stop_reason": "tool_use",
  68. "usage": {"input_tokens": 4, "output_tokens": 2},
  69. }
  70. )
  71. result = build_anthropic_result(parsed, "claude-sonnet-4-6")
  72. assert result["content"] == "done"
  73. assert result["tool_calls"][0]["function"]["name"] == "read"
  74. assert result["finish_reason"] == "tool_calls"
  75. assert result["prompt_tokens"] == 4
  76. assert result["completion_tokens"] == 2
  77. assert "cost" in result