aixtools 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aixtools might be problematic. Click here for more details.

Files changed (50) hide show
  1. aixtools/_version.py +2 -2
  2. aixtools/a2a/app.py +1 -1
  3. aixtools/a2a/google_sdk/__init__.py +0 -0
  4. aixtools/a2a/google_sdk/card.py +27 -0
  5. aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +199 -0
  6. aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +26 -0
  7. aixtools/a2a/google_sdk/remote_agent_connection.py +88 -0
  8. aixtools/a2a/google_sdk/utils.py +59 -0
  9. aixtools/agents/prompt.py +97 -0
  10. aixtools/context.py +5 -0
  11. aixtools/google/client.py +25 -0
  12. aixtools/logging/logging_config.py +45 -0
  13. aixtools/mcp/client.py +274 -0
  14. aixtools/server/utils.py +3 -3
  15. aixtools/utils/config.py +13 -0
  16. aixtools/utils/files.py +17 -0
  17. aixtools/utils/utils.py +7 -0
  18. aixtools/vault/__init__.py +7 -0
  19. aixtools/vault/vault.py +73 -0
  20. aixtools-0.1.6.dist-info/METADATA +668 -0
  21. {aixtools-0.1.4.dist-info → aixtools-0.1.6.dist-info}/RECORD +48 -13
  22. {aixtools-0.1.4.dist-info → aixtools-0.1.6.dist-info}/top_level.txt +1 -0
  23. scripts/test.sh +23 -0
  24. tests/__init__.py +0 -0
  25. tests/unit/__init__.py +0 -0
  26. tests/unit/a2a/__init__.py +0 -0
  27. tests/unit/a2a/google_sdk/__init__.py +0 -0
  28. tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
  29. tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +188 -0
  30. tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +156 -0
  31. tests/unit/a2a/google_sdk/test_card.py +114 -0
  32. tests/unit/a2a/google_sdk/test_remote_agent_connection.py +413 -0
  33. tests/unit/a2a/google_sdk/test_utils.py +208 -0
  34. tests/unit/agents/__init__.py +0 -0
  35. tests/unit/agents/test_prompt.py +363 -0
  36. tests/unit/google/__init__.py +1 -0
  37. tests/unit/google/test_client.py +233 -0
  38. tests/unit/mcp/__init__.py +0 -0
  39. tests/unit/mcp/test_client.py +242 -0
  40. tests/unit/server/__init__.py +0 -0
  41. tests/unit/server/test_path.py +225 -0
  42. tests/unit/server/test_utils.py +362 -0
  43. tests/unit/utils/__init__.py +0 -0
  44. tests/unit/utils/test_files.py +146 -0
  45. tests/unit/vault/__init__.py +0 -0
  46. tests/unit/vault/test_vault.py +114 -0
  47. aixtools/a2a/__init__.py +0 -5
  48. aixtools-0.1.4.dist-info/METADATA +0 -355
  49. {aixtools-0.1.4.dist-info → aixtools-0.1.6.dist-info}/WHEEL +0 -0
  50. {aixtools-0.1.4.dist-info → aixtools-0.1.6.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  aixtools/__init__.py,sha256=9NGHm7LjsQmsvjTZvw6QFJexSvAU4bCoN_KBk9SCa00,260
2
- aixtools/_version.py,sha256=rLCrf4heo25FJtBY-2Ap7ZuWW-5FS7sqTjsolIUuI5c,704
2
+ aixtools/_version.py,sha256=riGXiVTWXmtdoju9hVCWvTxpszEMAAIK0sZZWoLKlnU,704
3
3
  aixtools/app.py,sha256=JzQ0nrv_bjDQokllIlGHOV0HEb-V8N6k_nGQH-TEsVU,5227
4
4
  aixtools/chainlit.md,sha256=yC37Ly57vjKyiIvK4oUvf4DYxZCwH7iocTlx7bLeGLU,761
5
- aixtools/context.py,sha256=XuvSGjG8f-QHBJlI_yCPdjYo4rm_WrswmUE8GjLoRqI,491
5
+ aixtools/context.py,sha256=I_MD40ZnvRm5WPKAKqBUAdXIf8YaurkYUUHSVVy-QvU,598
6
6
  aixtools/.chainlit/config.toml,sha256=jxpzXQ9l2IRqBKTFLdvpDG4DunUcOko6kIEb3dQw4HU,3788
7
7
  aixtools/.chainlit/translations/bn.json,sha256=eB36bL3SggncVGejh29UDnGYFd_MMDtQGBiMxQ9w1fM,16432
8
8
  aixtools/.chainlit/translations/en-US.json,sha256=V5pTnHxsR0dkzhUwlkxqKe0qvR-T39N9tJN0c1CLsAw,7118
@@ -17,15 +17,22 @@ aixtools/.chainlit/translations/nl.json,sha256=R3e-WxkQXAiuQgnnXjFWhwzpn1EA9xJ8g
17
17
  aixtools/.chainlit/translations/ta.json,sha256=pxa2uLEEDjiGiT6MFcCJ_kNh5KoFViHFptcJjc79Llc,17224
18
18
  aixtools/.chainlit/translations/te.json,sha256=0qGj-ODEHVOcxfVVX5IszS1QBCKSXuU1okANP_EbvBQ,16885
19
19
  aixtools/.chainlit/translations/zh-CN.json,sha256=EWxhT2_6CW9z0F6SI2llr3RsaL2omH1QZWHVG2n5POA,8664
20
- aixtools/a2a/__init__.py,sha256=UGeU-1JoE-QmgT3A_9r4Gzw7WQU2cZr4JxNGqm5ZZrg,191
21
- aixtools/a2a/app.py,sha256=VQAKJHYJJr5-qJRLXvRgc2pZtH_SPXvB293nJaEDXTw,5071
20
+ aixtools/a2a/app.py,sha256=p18G7fAInl9dcNYq6RStBjv1C3aD6oilQq3WXtBuk30,5069
22
21
  aixtools/a2a/utils.py,sha256=EHr3IyyBJn23ni-JcfAf6i3VpQmPs0g1TSnAZazvY_8,4039
22
+ aixtools/a2a/google_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ aixtools/a2a/google_sdk/card.py,sha256=P0L3bKbm28HaRkcIxIvjuSGUKOOc0ymyRAFHKm3a5GQ,996
24
+ aixtools/a2a/google_sdk/remote_agent_connection.py,sha256=oDCRSN3gfONY1Ibp8BrtysIVqfQQ-lWe5N7lr1ymHxY,2819
25
+ aixtools/a2a/google_sdk/utils.py,sha256=hjrNRZywJEUxHHaOttJFQU0FLzteg0Ggtm3qAeXMSVw,2430
26
+ aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py,sha256=MMbhbEnUL6NwSYnisJrDdHW8zJoSyJ3Pzzkt8jqwNdI,7066
27
+ aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py,sha256=nGoVL7MPoZJW7iVR71laqpUYP308yFKZIifJtvUgpiU,878
23
28
  aixtools/agents/__init__.py,sha256=MAW196S2_G7uGqv-VNjvlOETRfuV44WlU1leO7SiR0A,282
24
29
  aixtools/agents/agent.py,sha256=E1zu70t53RqIbcLI_R09wUtsiYZR1bTnElCQ5PrsrKw,6127
25
30
  aixtools/agents/agent_batch.py,sha256=0Zu9yNCRPAQZPjXQ-dIUAmP1uGTVbxVt7xvnMpoJMjU,2251
31
+ aixtools/agents/prompt.py,sha256=VCOVSnhNKsPIT347ouzwM1PH4I9UTm2cSnTh3ZpjRwk,3391
26
32
  aixtools/db/__init__.py,sha256=b8vRhme3egV-aUZbAntnOaDkSXB8UT0Xy5oqQhU_z0Q,399
27
33
  aixtools/db/database.py,sha256=caWe95GlxZYlxn2ubDmR-_cQUW0ulkpR3BHunKIaOsw,3369
28
34
  aixtools/db/vector_db.py,sha256=be4JGyXj3o8VEfy9L6SO1aAoDET_zazMJkYfjlYHTYQ,4133
35
+ aixtools/google/client.py,sha256=8yuv_zEZKlmUTI-zRxAb3vjLUrfiwrBhcpNe0hYsO0g,1078
29
36
  aixtools/log_view/__init__.py,sha256=0fWLCq9BMo8GoH3Z5WDgvf0-J2TP0XWqtef0f28SHBA,405
30
37
  aixtools/log_view/app.py,sha256=DZp3PUM_iS3DpMHqHfFXVACvbZ9PItbOCNMkDjIOfTc,6595
31
38
  aixtools/log_view/display.py,sha256=8ygvtfUjieD8JKov_cnAi5KNMsGoOCE4AeZmDwAa8DU,9971
@@ -37,12 +44,13 @@ aixtools/logfilters/__init__.py,sha256=pTD8ujCqjPWBCeB7yv7lmCtnA2KXOnkIv0HExDagk
37
44
  aixtools/logfilters/context_filter.py,sha256=zR3Bnv3fCqXLeb7bCFTmlnWhC6dFIvUb-u712tOnUPk,2259
38
45
  aixtools/logging/__init__.py,sha256=b5oYyGQDUHHxhRtzqKUaQPv8hQeWw54rzDXSV8lDY1w,613
39
46
  aixtools/logging/log_objects.py,sha256=hKMLIsWZEw9Q0JT3GCn9mS8O7DJCzsgMZTL7mlMObpM,6828
40
- aixtools/logging/logging_config.py,sha256=LBUjR5aDUh_G5U2EA2yjzw9IuOZHHUJli1y-TVpj9lA,3267
47
+ aixtools/logging/logging_config.py,sha256=LvxV3C75-I0096PpcCIbgM-Cp998LzWXeMM14HYbU20,4985
41
48
  aixtools/logging/mcp_log_models.py,sha256=7-H2GJXiiyLhpImuyLLftAGG4skxJal8Swax0ob04MY,3463
42
49
  aixtools/logging/mcp_logger.py,sha256=d2I5l4t0d6rQH17w23FpE1IUD8Ax-mSaKfByCH86q4I,6257
43
50
  aixtools/logging/model_patch_logging.py,sha256=MY2EvR7ZSctC4hJxNMe8iACeVayUJ2V5In2GAnKdgOo,2880
44
51
  aixtools/logging/open_telemetry.py,sha256=fJjF1ou_8GyfNfbyWDQPGK6JAUrUaPwURYPHhXEtDBE,1121
45
52
  aixtools/mcp/__init__.py,sha256=tLo2KZ1Ojo-rgEEJBGtZfUw-iOoopWoHDnYQTq3IzfE,163
53
+ aixtools/mcp/client.py,sha256=yUcurbNQ8bRaPc8-gK_ESpcPb26d5ckDOEDQrG_YW0c,11823
46
54
  aixtools/mcp/example_client.py,sha256=QCFGP3NCNJMOKWjUOnFwjnbJhUSb879IA1ZYmwjRnmc,889
47
55
  aixtools/mcp/example_server.py,sha256=1SWCyrLWsAnOa81HC4QbPJo_lBVu0b3SZBWI-qDh1vQ,458
48
56
  aixtools/mcp/fast_mcp_log.py,sha256=XYOS406dVjn5YTHyGRsRvVNQ0SKlRObfrKj6EeLFjHg,1057
@@ -51,7 +59,7 @@ aixtools/model_patch/model_patch.py,sha256=JT-oHubIn2LeoSwWbzEQ5vLH7crJmFUecHyQf
51
59
  aixtools/server/__init__.py,sha256=rwPx020YpOzCnrxA80Lc4yLLcIp-Mpe9hNqVO9wDPv0,448
52
60
  aixtools/server/app_mounter.py,sha256=0tJ0tC140ezAjnYdlhpLJQjY-TO8NVw7D8LseYCCVY8,3336
53
61
  aixtools/server/path.py,sha256=SaIJxvmhJy3kzx5zJ6d4cKP6kKu2wFFciQkOLGTA4gg,3056
54
- aixtools/server/utils.py,sha256=3kIkwpqvc3KsLr5Ja2aGaQ6xy13UaG6K75s2NJVjj-o,2237
62
+ aixtools/server/utils.py,sha256=tZWITIx6M-luV9yve4j3rPtYGSSA6zWS0JWEAySne_M,2276
55
63
  aixtools/testing/__init__.py,sha256=mlmaAR2gmS4SbsYNCxnIprmFpFp-syjgVUkpUszo3mE,166
56
64
  aixtools/testing/aix_test_model.py,sha256=dlI3sdyvmu4fUs_K4-oazs_a7cE6V-gnI6RQ0_fPVxg,5925
57
65
  aixtools/testing/mock_tool.py,sha256=4I0LxxSkLhGIKM2YxCP3cnYI8IYJjdKhfwGZ3dioXsM,2465
@@ -60,13 +68,16 @@ aixtools/tools/doctor/__init__.py,sha256=FPwYzC1eJyw8IH0-BP0wgxSprLy6Y_4yXCek749
60
68
  aixtools/tools/doctor/tool_doctor.py,sha256=flp00mbFwVI0-Ii_xC4YDW6Vrn-EAExA1TtQkY6cOZE,2583
61
69
  aixtools/tools/doctor/tool_recommendation.py,sha256=t-l5bm6kwnXs1NH-ZZVTWhVrEAmWa460M44bi_Bip4g,1463
62
70
  aixtools/utils/__init__.py,sha256=xT6almZBQYMfj4h7Hq9QXDHyVXbOOTxqLsmJsxYYnSw,757
63
- aixtools/utils/config.py,sha256=1_sGqjj9qahu2thdjJUo6RGi6W2b79UB10sefyfZNMs,4146
71
+ aixtools/utils/config.py,sha256=pNWVMC1V9Hn2KEqaXaLbhxCI_iwQfGiVKKaZLrRM4ug,4820
64
72
  aixtools/utils/config_util.py,sha256=3Ya4Qqhj1RJ1qtTTykQ6iayf5uxlpigPXgEJlTi1wn4,2229
65
73
  aixtools/utils/enum_with_description.py,sha256=zjSzWxG74eR4x7dpmb74pLTYCWNSMvauHd7_9LpDYIw,1088
74
+ aixtools/utils/files.py,sha256=8JnxwHJRJcjWCdFpjzWmo0po2fRg8esj4H7sOxElYXU,517
66
75
  aixtools/utils/persisted_dict.py,sha256=0jQzV7oF-A6Or-HjcU6V7aMXWQL67SOKpULgmtFwAfg,3110
67
- aixtools/utils/utils.py,sha256=4AkAPPnymYt4faThWz0QYSrAeQVys-l-YO8Kw302CcA,4703
76
+ aixtools/utils/utils.py,sha256=5911Ej1ES2NU_FKIWA3CWKhKnwgjvi1aDR2aiD6Xv3E,4880
68
77
  aixtools/utils/chainlit/cl_agent_show.py,sha256=vaRuowp4BRvhxEr5hw0zHEJ7iaSF_5bo_9BH7pGPPpw,4398
69
78
  aixtools/utils/chainlit/cl_utils.py,sha256=fxaxdkcZg6uHdM8uztxdPowg3a2f7VR7B26VPY4t-3c,5738
79
+ aixtools/vault/__init__.py,sha256=fsr_NuX3GZ9WZ7dGfe0gp_5-z3URxAfwVRXw7Xyc0dU,141
80
+ aixtools/vault/vault.py,sha256=JeAnRnsXm_etkTUntE9ABIiNlI8M81E2b85IQHfGehI,2817
70
81
  docker/mcp-base/Dockerfile,sha256=sSpbt0sasSBHHeGwPIpJpiEQMU5HGeXzerK8biVSt7Q,1547
71
82
  notebooks/example_faulty_mcp_server.ipynb,sha256=b2Cy3GXfj-gOBZ7SoUzj25F1rxp5u-32EWPHWQ-sxn8,1729
72
83
  notebooks/example_mcp_server_stdio.ipynb,sha256=ya4dRKNFU2vQxob-uIhKHGAzINXGQ6MehgKVmSCpHLk,1634
@@ -78,8 +89,32 @@ scripts/log_view.sh,sha256=bp8oXFRRbbHpyvHAN85wfDHTVK7vMJOYsBx_-bgECQc,511
78
89
  scripts/run_example_mcp_server.sh,sha256=f7m7h7O_wo6-nAsYlOXVWIASCOh3Qbuu0XWizlxMhl8,355
79
90
  scripts/run_faulty_mcp_server.sh,sha256=u_-8NbPDnJQt6IinNSjh8tc2ed-_MjGyipJXrUXaGR8,291
80
91
  scripts/run_server.sh,sha256=5iiB9bB5M2MuOgxVQqu7Oa_tBVtJpt0uB4z9uLu2J50,720
81
- aixtools-0.1.4.dist-info/METADATA,sha256=bf7ho9WYXQx1MchAPtz5HWkOi3-gDcOBLHrp9QWIBeg,10109
82
- aixtools-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
83
- aixtools-0.1.4.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
84
- aixtools-0.1.4.dist-info/top_level.txt,sha256=IPyw70hj9gVDyugaIr3LRlanLYXzostW4azlTgdlALo,34
85
- aixtools-0.1.4.dist-info/RECORD,,
92
+ scripts/test.sh,sha256=5akwfnX7P-98898WCXcQRFQt84UBgms5Y9fXC_ym1a4,662
93
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
+ tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
+ tests/unit/a2a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
+ tests/unit/a2a/google_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
+ tests/unit/a2a/google_sdk/test_card.py,sha256=g8OUIX9BCtApM9y8l_nL1Q7sm3yezxu5yBxrpy76mo4,4359
98
+ tests/unit/a2a/google_sdk/test_remote_agent_connection.py,sha256=nIY8eg32w96BAddaQ25mT-lr0ozPb6UrG-_Vpqx5RMY,17492
99
+ tests/unit/a2a/google_sdk/test_utils.py,sha256=-eHmIk2GJH57W2bAdTzfRrUUb5jnd9Pf-QSXJogN3g8,8312
100
+ tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
101
+ tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py,sha256=PcyCw0N3y-txu2KJzufzbCjs7ZfoBBCVjpZuRBqTmOw,7722
102
+ tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py,sha256=tb67pFfvyWSaDfKaiPDNBQfl6-o17WtCMZh3lQHrYxY,5468
103
+ tests/unit/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
+ tests/unit/agents/test_prompt.py,sha256=YWFZdH_F774hxw79gsWoTWBPVs8UjOAtJOgNXJ8N9gs,15384
105
+ tests/unit/google/__init__.py,sha256=eRYHldBi5cFWL7oo2_t5TErI8ESmIjNvBZIcp-w8hSA,45
106
+ tests/unit/google/test_client.py,sha256=fXR4Cozea7bdL2prM-1s9IqUQ9AheklQnHpN-4YM3gg,11005
107
+ tests/unit/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
+ tests/unit/mcp/test_client.py,sha256=n9sZvmzNzJfozvxoHweAg4M5ZLNhEizq16IjcZHGdj0,8838
109
+ tests/unit/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
+ tests/unit/server/test_path.py,sha256=1QKiKLLRga9GNxmaUEt_wEZ9U14yzB-7PIhAOgB4wwo,9523
111
+ tests/unit/server/test_utils.py,sha256=kvhzdgNfsJl5tqcRBWg2yTR5GPpyrFCOmEIOuHb3904,14848
112
+ tests/unit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
+ tests/unit/utils/test_files.py,sha256=AKFmXQqXstyKd2PreE4EmQyhQYeqOmu1Sp80MwHrf_Q,5782
114
+ tests/unit/vault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
+ tests/unit/vault/test_vault.py,sha256=YzmJ-JZo8OLGNcmc845knCh3zp8jhkANVgKT09cuhSU,4250
116
+ aixtools-0.1.6.dist-info/METADATA,sha256=6JFv550ISVltjdNxLIcsM0TNd-O7KS-g2ZsvCuanuJ0,18569
117
+ aixtools-0.1.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
118
+ aixtools-0.1.6.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
119
+ aixtools-0.1.6.dist-info/top_level.txt,sha256=ee4eF-0pqu45zCUVml0mWIhnXQgqMQper2-49BBVHLY,40
120
+ aixtools-0.1.6.dist-info/RECORD,,
@@ -2,3 +2,4 @@ aixtools
2
2
  docker
3
3
  notebooks
4
4
  scripts
5
+ tests
scripts/test.sh ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/bash -eu
2
+ set -o pipefail
3
+
4
+ # Get the directory of this script
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "${SCRIPT_DIR}/config.sh"
7
+
8
+ # Install required dependencies
9
+ echo "Installing test dependencies..."
10
+ uv pip install pytest pytest-asyncio pytest-mock pytest-cov
11
+
12
+ # Ensure .env exists
13
+ touch .env
14
+
15
+ # Add the project root to PYTHONPATH so imports work correctly
16
+ export PYTHONPATH="${PROJECT_DIR}:${PYTHONPATH:-}"
17
+
18
+ # Run all tests using pytest with coverage
19
+ echo "Running tests with coverage..."
20
+ pytest tests/ -v --cov=aixtools --cov-report=term-missing
21
+
22
+ # Generate HTML coverage report
23
+ # pytest tests/ --cov=aixtools --cov-report=html
tests/__init__.py ADDED
File without changes
tests/unit/__init__.py ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,188 @@
1
+ """Tests for the Pydantic AI adapter agent executor module."""
2
+
3
+ import unittest
4
+ from unittest.mock import AsyncMock, MagicMock, patch
5
+
6
+ from a2a.server.agent_execution import RequestContext
7
+ from a2a.server.events import EventQueue
8
+ from a2a.types import Message, TaskState, TaskStatusUpdateEvent
9
+ from pydantic_ai import Agent
10
+
11
+ from aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor import (
12
+ AgentParameters,
13
+ RunOutput,
14
+ PydanticAgentExecutor,
15
+ _task_failed_event,
16
+ )
17
+
18
+
19
+ class TestAgentParameters(unittest.TestCase):
20
+ """Tests for the AgentParameters model."""
21
+
22
+ def test_agent_parameters_creation(self):
23
+ """Test creating AgentParameters with valid data."""
24
+ params = AgentParameters(
25
+ system_prompt="Test prompt",
26
+ mcp_servers=["server1", "server2"]
27
+ )
28
+
29
+ self.assertEqual(params.system_prompt, "Test prompt")
30
+ self.assertEqual(params.mcp_servers, ["server1", "server2"])
31
+
32
+
33
+ class TestRunOutput(unittest.TestCase):
34
+ """Tests for the RunOutput model."""
35
+
36
+ def test_run_output_creation(self):
37
+ """Test creating RunOutput with valid data."""
38
+ output = RunOutput(
39
+ is_task_failed=False,
40
+ is_task_in_progress=True,
41
+ is_input_required=False,
42
+ output="Test output",
43
+ created_artifacts_paths=["path1", "path2"]
44
+ )
45
+
46
+ self.assertFalse(output.is_task_failed)
47
+ self.assertTrue(output.is_task_in_progress)
48
+ self.assertFalse(output.is_input_required)
49
+ self.assertEqual(output.output, "Test output")
50
+ self.assertEqual(output.created_artifacts_paths, ["path1", "path2"])
51
+
52
+
53
+ class TestTaskFailedEvent(unittest.TestCase):
54
+ """Tests for the _task_failed_event function."""
55
+
56
+ def test_task_failed_event_creation(self):
57
+ """Test creating a task failed event."""
58
+ text = "Test error message"
59
+ context_id = "ctx123"
60
+ task_id = "task456"
61
+
62
+ event = _task_failed_event(text, context_id, task_id)
63
+
64
+ self.assertIsInstance(event, TaskStatusUpdateEvent)
65
+ self.assertEqual(event.status.state, TaskState.failed)
66
+ self.assertEqual(event.context_id, context_id)
67
+ self.assertEqual(event.task_id, task_id)
68
+ self.assertTrue(event.final)
69
+
70
+ def test_task_failed_event_with_none_ids(self):
71
+ """Test creating a task failed event with None IDs."""
72
+ text = "Test error message"
73
+
74
+ event = _task_failed_event(text, "default_ctx", "default_task")
75
+
76
+ self.assertIsInstance(event, TaskStatusUpdateEvent)
77
+ self.assertEqual(event.status.state, TaskState.failed)
78
+ self.assertEqual(event.context_id, "default_ctx")
79
+ self.assertEqual(event.task_id, "default_task")
80
+ self.assertTrue(event.final)
81
+
82
+
83
+ class TestPydanticAgentExecutor(unittest.IsolatedAsyncioTestCase):
84
+ """Tests for the PydanticAgentExecutor class."""
85
+
86
+ def setUp(self):
87
+ self.agent_params = AgentParameters(
88
+ system_prompt="Test system prompt",
89
+ mcp_servers=["test_server"]
90
+ )
91
+ self.executor = PydanticAgentExecutor(self.agent_params)
92
+
93
+ def test_init(self):
94
+ """Test PydanticAgentExecutor initialization."""
95
+ self.assertEqual(self.executor._agent_parameters, self.agent_params)
96
+ self.assertIsNotNone(self.executor.history_storage)
97
+
98
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_message_text")
99
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_file_parts")
100
+ def test_convert_message_to_pydantic_parts_text_only(self, mock_get_file_parts, mock_get_message_text):
101
+ """Test converting message with text only."""
102
+ mock_get_message_text.return_value = "Test message"
103
+ mock_get_file_parts.return_value = []
104
+
105
+ mock_message = MagicMock(spec=Message)
106
+ mock_message.parts = []
107
+ session_tuple = ("user1", "session1")
108
+
109
+ result = self.executor._convert_message_to_pydantic_parts(session_tuple, mock_message)
110
+
111
+ self.assertEqual(result, "Test message")
112
+ mock_get_message_text.assert_called_once_with(mock_message)
113
+ mock_get_file_parts.assert_called_once_with([])
114
+
115
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.build_user_input")
116
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_message_text")
117
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_file_parts")
118
+ def test_convert_message_to_pydantic_parts_with_files(self, mock_get_file_parts, mock_get_message_text, mock_build_user_input):
119
+ """Test converting message with files."""
120
+ mock_get_message_text.return_value = "Test message"
121
+ mock_file_part = MagicMock()
122
+ mock_file_part.uri = "/test/path.txt"
123
+ mock_get_file_parts.return_value = [mock_file_part]
124
+ mock_build_user_input.return_value = ["processed", "input"]
125
+
126
+ mock_message = MagicMock(spec=Message)
127
+ mock_message.parts = [mock_file_part]
128
+ session_tuple = ("user1", "session1")
129
+
130
+ result = self.executor._convert_message_to_pydantic_parts(session_tuple, mock_message)
131
+
132
+ self.assertEqual(result, ["processed", "input"])
133
+ # Verify that build_user_input was called (exact args may vary due to filtering)
134
+ mock_build_user_input.assert_called_once()
135
+
136
+ def test_execute_no_message_validation(self):
137
+ """Test that execute validates message presence."""
138
+ # This is a simple validation test without complex integration
139
+ mock_context = MagicMock(spec=RequestContext)
140
+ mock_context.message = None
141
+
142
+ # The actual validation happens in the execute method
143
+ # We test the validation logic separately
144
+ self.assertIsNone(mock_context.message)
145
+
146
+ async def test_cancel_not_supported(self):
147
+ """Test that cancel raises an exception."""
148
+ mock_context = MagicMock(spec=RequestContext)
149
+ mock_event_queue = AsyncMock(spec=EventQueue)
150
+
151
+ with self.assertRaises(Exception) as context:
152
+ await self.executor.cancel(mock_context, mock_event_queue)
153
+
154
+ self.assertIn("cancel not supported", str(context.exception))
155
+
156
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_agent")
157
+ @patch("aixtools.a2a.google_sdk.pydantic_ai_adapter.agent_executor.get_configured_mcp_servers")
158
+ def test_build_agent(self, mock_get_mcp_servers, mock_get_agent):
159
+ """Test building an agent."""
160
+ mock_agent = MagicMock(spec=Agent)
161
+ mock_get_agent.return_value = mock_agent
162
+ mock_get_mcp_servers.return_value = ["mcp1", "mcp2"]
163
+
164
+ session_tuple = ("user1", "session1")
165
+ result = self.executor._build_agent(session_tuple)
166
+
167
+ self.assertEqual(result, mock_agent)
168
+ mock_get_mcp_servers.assert_called_once_with(session_tuple, ["test_server"])
169
+ mock_get_agent.assert_called_once_with(
170
+ system_prompt="Test system prompt",
171
+ toolsets=["mcp1", "mcp2"],
172
+ output_type=RunOutput
173
+ )
174
+
175
+ def test_task_creation_logic(self):
176
+ """Test task creation logic validation."""
177
+ # Test the basic logic without complex integration
178
+ mock_context = MagicMock(spec=RequestContext)
179
+ mock_context.current_task = None
180
+ mock_context.message = MagicMock(spec=Message)
181
+
182
+ # Verify that when current_task is None, we have a message to work with
183
+ self.assertIsNone(mock_context.current_task)
184
+ self.assertIsNotNone(mock_context.message)
185
+
186
+
187
+ if __name__ == '__main__':
188
+ unittest.main()
@@ -0,0 +1,156 @@
1
+ """Tests for the Pydantic AI adapter storage module."""
2
+
3
+ import unittest
4
+ from unittest.mock import MagicMock
5
+
6
+ from aixtools.a2a.google_sdk.pydantic_ai_adapter.storage import (
7
+ PydanticAiAgentHistoryStorage,
8
+ InMemoryHistoryStorage,
9
+ )
10
+
11
+
12
+ class TestInMemoryHistoryStorage(unittest.TestCase):
13
+ """Tests for the InMemoryHistoryStorage class."""
14
+
15
+ def setUp(self):
16
+ self.storage = InMemoryHistoryStorage()
17
+
18
+ def test_init(self):
19
+ """Test InMemoryHistoryStorage initialization."""
20
+ self.assertEqual(self.storage.storage, {})
21
+
22
+ def test_get_nonexistent_task(self):
23
+ """Test getting history for a task that doesn't exist."""
24
+ result = self.storage.get("nonexistent_task")
25
+ self.assertIsNone(result)
26
+
27
+ def test_store_and_get_messages(self):
28
+ """Test storing and retrieving messages."""
29
+ task_id = "test_task_1"
30
+ # Use simple mock objects that can be stored
31
+ messages = [MagicMock(), MagicMock()]
32
+
33
+ # Store the messages
34
+ self.storage.store(task_id, messages)
35
+
36
+ # Retrieve the messages
37
+ result = self.storage.get(task_id)
38
+
39
+ self.assertIsNotNone(result)
40
+ self.assertEqual(result, messages)
41
+ self.assertEqual(len(result), 2)
42
+
43
+ def test_store_overwrites_existing(self):
44
+ """Test that storing overwrites existing messages for the same task."""
45
+ task_id = "test_task_3"
46
+
47
+ # Store initial messages
48
+ initial_messages = [MagicMock()]
49
+ self.storage.store(task_id, initial_messages)
50
+
51
+ # Store new messages (should overwrite)
52
+ new_messages = [MagicMock(), MagicMock()]
53
+ self.storage.store(task_id, new_messages)
54
+
55
+ # Retrieve and verify new messages are stored
56
+ result = self.storage.get(task_id)
57
+
58
+ self.assertIsNotNone(result)
59
+ self.assertEqual(result, new_messages)
60
+ self.assertEqual(len(result), 2)
61
+ self.assertNotEqual(result, initial_messages)
62
+
63
+ def test_multiple_tasks(self):
64
+ """Test storing and retrieving messages for multiple tasks."""
65
+ task1_id = "task_1"
66
+ task2_id = "task_2"
67
+
68
+ task1_messages = [MagicMock()]
69
+ task2_messages = [MagicMock(), MagicMock()]
70
+
71
+ # Store messages for both tasks
72
+ self.storage.store(task1_id, task1_messages)
73
+ self.storage.store(task2_id, task2_messages)
74
+
75
+ # Retrieve and verify both tasks' messages
76
+ result1 = self.storage.get(task1_id)
77
+ result2 = self.storage.get(task2_id)
78
+
79
+ self.assertIsNotNone(result1)
80
+ self.assertIsNotNone(result2)
81
+ self.assertEqual(result1, task1_messages)
82
+ self.assertEqual(result2, task2_messages)
83
+ self.assertNotEqual(result1, result2)
84
+
85
+ def test_store_empty_list(self):
86
+ """Test storing an empty list of messages."""
87
+ task_id = "empty_task"
88
+ empty_messages = []
89
+
90
+ self.storage.store(task_id, empty_messages)
91
+ result = self.storage.get(task_id)
92
+
93
+ self.assertIsNotNone(result)
94
+ self.assertEqual(result, empty_messages)
95
+ self.assertEqual(len(result), 0)
96
+
97
+ def test_get_after_multiple_stores(self):
98
+ """Test that get returns the most recent store for a task."""
99
+ task_id = "update_task"
100
+
101
+ # Store multiple times
102
+ messages1 = [MagicMock()]
103
+ messages2 = [MagicMock()]
104
+ messages3 = [MagicMock(), MagicMock()]
105
+
106
+ self.storage.store(task_id, messages1)
107
+ self.storage.store(task_id, messages2)
108
+ self.storage.store(task_id, messages3)
109
+
110
+ result = self.storage.get(task_id)
111
+
112
+ self.assertIsNotNone(result)
113
+ self.assertEqual(result, messages3)
114
+
115
+ def test_storage_isolation(self):
116
+ """Test that different storage instances are isolated."""
117
+ storage1 = InMemoryHistoryStorage()
118
+ storage2 = InMemoryHistoryStorage()
119
+
120
+ task_id = "isolation_test"
121
+ messages1 = [MagicMock()]
122
+ messages2 = [MagicMock()]
123
+
124
+ storage1.store(task_id, messages1)
125
+ storage2.store(task_id, messages2)
126
+
127
+ result1 = storage1.get(task_id)
128
+ result2 = storage2.get(task_id)
129
+
130
+ self.assertIsNotNone(result1)
131
+ self.assertIsNotNone(result2)
132
+ self.assertEqual(result1, messages1)
133
+ self.assertEqual(result2, messages2)
134
+ self.assertNotEqual(result1, result2)
135
+
136
+
137
+ class TestPydanticAiAgentHistoryStorageInterface(unittest.TestCase):
138
+ """Tests for the PydanticAiAgentHistoryStorage abstract interface."""
139
+
140
+ def test_cannot_instantiate_abstract_class(self):
141
+ """Test that the abstract base class cannot be instantiated."""
142
+ with self.assertRaises(TypeError):
143
+ PydanticAiAgentHistoryStorage()
144
+
145
+ def test_inmemory_implements_interface(self):
146
+ """Test that InMemoryHistoryStorage properly implements the interface."""
147
+ storage = InMemoryHistoryStorage()
148
+
149
+ # Verify it's an instance of the abstract base class
150
+ self.assertIsInstance(storage, PydanticAiAgentHistoryStorage)
151
+
152
+ # Verify it has the required methods
153
+ self.assertTrue(hasattr(storage, 'get'))
154
+ self.assertTrue(hasattr(storage, 'store'))
155
+ self.assertTrue(callable(getattr(storage, 'get')))
156
+ self.assertTrue(callable(getattr(storage, 'store')))
@@ -0,0 +1,114 @@
1
+ """Tests for the A2A card module."""
2
+
3
+ import unittest
4
+ from unittest.mock import AsyncMock, MagicMock, patch
5
+
6
+ import httpx
7
+ from a2a.client import A2ACardResolver
8
+ from a2a.types import AgentCard
9
+
10
+ from aixtools.a2a.google_sdk.card import get_agent_card
11
+
12
+
13
+ class TestCard(unittest.IsolatedAsyncioTestCase):
14
+ """Tests for the A2A card module."""
15
+
16
+ def setUp(self):
17
+ self.test_agent_host = "http://localhost:9999"
18
+
19
+ @patch("aixtools.a2a.google_sdk.card.A2ACardResolver")
20
+ async def test_get_agent_card_success(self, mock_resolver_class):
21
+ """Test successful retrieval of agent card."""
22
+ # Setup
23
+ mock_client = AsyncMock(spec=httpx.AsyncClient)
24
+
25
+ mock_resolver = AsyncMock(spec=A2ACardResolver)
26
+ mock_resolver_class.return_value = mock_resolver
27
+
28
+ mock_card = MagicMock(spec=AgentCard)
29
+ mock_card.model_dump_json.return_value = '{"test": "data"}'
30
+ mock_resolver.get_agent_card.return_value = mock_card
31
+
32
+ # Call the function
33
+ result = await get_agent_card(mock_client, self.test_agent_host)
34
+
35
+ # Verify the result
36
+ self.assertEqual(result, mock_card)
37
+
38
+ # Verify the resolver was created correctly
39
+ mock_resolver_class.assert_called_once_with(
40
+ httpx_client=mock_client,
41
+ base_url=self.test_agent_host
42
+ )
43
+
44
+ # Verify the card was fetched
45
+ mock_resolver.get_agent_card.assert_called_once()
46
+
47
+ # Verify the URL was set
48
+ self.assertEqual(result.url, self.test_agent_host)
49
+
50
+ @patch("aixtools.a2a.google_sdk.card.A2ACardResolver")
51
+ async def test_get_agent_card_failure(self, mock_resolver_class):
52
+ """Test handling of errors when retrieving agent card."""
53
+ # Setup
54
+ mock_client = AsyncMock(spec=httpx.AsyncClient)
55
+
56
+ mock_resolver = AsyncMock(spec=A2ACardResolver)
57
+ mock_resolver_class.return_value = mock_resolver
58
+
59
+ # Make the resolver raise an exception
60
+ mock_resolver.get_agent_card.side_effect = Exception("Failed to fetch card")
61
+
62
+ # Call the function and expect an exception
63
+ with self.assertRaises(RuntimeError) as context:
64
+ await get_agent_card(mock_client, self.test_agent_host)
65
+
66
+ self.assertIn("Failed to fetch the public agent card", str(context.exception))
67
+
68
+ @patch("aixtools.a2a.google_sdk.card.logger")
69
+ @patch("aixtools.a2a.google_sdk.card.A2ACardResolver")
70
+ async def test_get_agent_card_logging(self, mock_resolver_class, mock_logger):
71
+ """Test that proper logging occurs during card retrieval."""
72
+ # Setup
73
+ mock_client = AsyncMock(spec=httpx.AsyncClient)
74
+
75
+ mock_resolver = AsyncMock(spec=A2ACardResolver)
76
+ mock_resolver_class.return_value = mock_resolver
77
+
78
+ mock_card = MagicMock(spec=AgentCard)
79
+ mock_card.model_dump_json.return_value = '{"test": "data"}'
80
+ mock_resolver.get_agent_card.return_value = mock_card
81
+
82
+ # Call the function
83
+ await get_agent_card(mock_client, self.test_agent_host)
84
+
85
+ # Verify logging calls
86
+ mock_logger.info.assert_called()
87
+ self.assertEqual(mock_logger.info.call_count, 2) # Two info calls in the function
88
+
89
+ @patch("aixtools.a2a.google_sdk.card.logger")
90
+ @patch("aixtools.a2a.google_sdk.card.A2ACardResolver")
91
+ async def test_get_agent_card_error_logging(self, mock_resolver_class, mock_logger):
92
+ """Test that errors are properly logged."""
93
+ # Setup
94
+ mock_client = AsyncMock(spec=httpx.AsyncClient)
95
+
96
+ mock_resolver = AsyncMock(spec=A2ACardResolver)
97
+ mock_resolver_class.return_value = mock_resolver
98
+
99
+ test_error = Exception("Test error")
100
+ mock_resolver.get_agent_card.side_effect = test_error
101
+
102
+ # Call the function and expect an exception
103
+ with self.assertRaises(RuntimeError):
104
+ await get_agent_card(mock_client, self.test_agent_host)
105
+
106
+ # Verify error logging
107
+ mock_logger.error.assert_called_once()
108
+ args, kwargs = mock_logger.error.call_args
109
+ self.assertIn("Critical error fetching public agent card", args[0])
110
+ self.assertTrue(kwargs.get('exc_info'))
111
+
112
+
113
+ if __name__ == '__main__':
114
+ unittest.main()