tagword-codeagent 1.2.1__tar.gz → 1.2.5__tar.gz

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.
Files changed (277) hide show
  1. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/PKG-INFO +1 -1
  2. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/__init__.py +9 -0
  3. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/agent_card.py +8 -0
  4. tagword_codeagent-1.2.5/codeagent/server/a2a/client.py +448 -0
  5. tagword_codeagent-1.2.5/codeagent/server/a2a/client_card.py +136 -0
  6. tagword_codeagent-1.2.5/codeagent/server/a2a/context.py +201 -0
  7. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/handlers.py +29 -7
  8. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/models.py +48 -1
  9. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/streaming.py +21 -5
  10. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/auto_continue.py +64 -6
  11. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/chat_turn.py +32 -11
  12. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/pyproject.toml +1 -1
  13. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/PKG-INFO +1 -1
  14. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/SOURCES.txt +5 -0
  15. tagword_codeagent-1.2.5/tests/test_a2a.py +401 -0
  16. tagword_codeagent-1.2.5/tests/test_a2a_context.py +210 -0
  17. tagword_codeagent-1.2.5/tests/test_a2a_tool.py +289 -0
  18. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auto_continue.py +100 -0
  19. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auto_continue_wiring.py +88 -1
  20. tagword_codeagent-1.2.1/tests/test_a2a.py +0 -213
  21. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/LICENSE +0 -0
  22. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/README.md +0 -0
  23. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/__init__.py +0 -0
  24. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/__main__.py +0 -0
  25. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/__init__.py +0 -0
  26. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/entrypoint.py +0 -0
  27. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/main.py +0 -0
  28. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/__init__.py +0 -0
  29. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/attachments.py +0 -0
  30. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/audio_models.py +0 -0
  31. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/bootstrap.py +0 -0
  32. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/default_presets.py +0 -0
  33. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/env.py +0 -0
  34. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/folder_picker.py +0 -0
  35. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/image_understanding.py +0 -0
  36. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/models.py +0 -0
  37. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/paths.py +0 -0
  38. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/pricing.py +0 -0
  39. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/process_ports.py +0 -0
  40. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/seed_bridge.py +0 -0
  41. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/settings.py +0 -0
  42. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/speech_synth.py +0 -0
  43. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/team_config.py +0 -0
  44. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/team_manager.py +0 -0
  45. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/token_counter.py +0 -0
  46. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/tts_voices.py +0 -0
  47. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/usage_billing.py +0 -0
  48. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/vision_models.py +0 -0
  49. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/memory/diary.py +0 -0
  50. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/README.md +0 -0
  51. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/agent.md +0 -0
  52. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/identity.md +0 -0
  53. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/memory.md +0 -0
  54. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/skills.md +0 -0
  55. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/soul.md +0 -0
  56. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/tools.md +0 -0
  57. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/user.md +0 -0
  58. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/__init__.py +0 -0
  59. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_bootstrap.py +0 -0
  60. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_context.py +0 -0
  61. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_handle.py +0 -0
  62. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_runtime.py +0 -0
  63. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/compact_prompt.py +0 -0
  64. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/compact_state.py +0 -0
  65. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/llm_worker.py +0 -0
  66. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/orchestrator.py +0 -0
  67. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/prompt_enrichment.py +0 -0
  68. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/session_manager.py +0 -0
  69. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/task_split.py +0 -0
  70. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/turn_loop.py +0 -0
  71. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/worker.py +0 -0
  72. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/__init__.py +0 -0
  73. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/app_factory.py +0 -0
  74. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/attachment_api.py +0 -0
  75. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/evidence.py +0 -0
  76. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/hub.py +0 -0
  77. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/icon.png +0 -0
  78. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/self_healing.py +0 -0
  79. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/team_engine.py +0 -0
  80. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/team_worker.py +0 -0
  81. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/tts_api.py +0 -0
  82. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_api_app.py +0 -0
  83. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_agentmeta.py +0 -0
  84. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_agents.py +0 -0
  85. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_auth.py +0 -0
  86. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_cronallow.py +0 -0
  87. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_envllm.py +0 -0
  88. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_files.py +0 -0
  89. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_git.py +0 -0
  90. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_health.py +0 -0
  91. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_mcp.py +0 -0
  92. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_projects.py +0 -0
  93. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_sessions.py +0 -0
  94. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_skills.py +0 -0
  95. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_teams.py +0 -0
  96. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/skills/__init__.py +0 -0
  97. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/skills/select.py +0 -0
  98. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/tools/__init__.py +0 -0
  99. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/tools/agent_tools.py +0 -0
  100. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__init__.py +0 -0
  101. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/__init__.cpython-311.pyc +0 -0
  102. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  103. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  104. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/auth_impl.cpython-311.pyc +0 -0
  105. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  106. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/auth_impl.py +0 -0
  107. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-marked.min.js +0 -0
  108. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-mermaid.min.js +0 -0
  109. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-purify.min.js +0 -0
  110. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-storage.js +0 -0
  111. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-utils.js +0 -0
  112. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01-theme.css +0 -0
  113. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01a-scroll.js +0 -0
  114. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01b-think.js +0 -0
  115. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-identity.js +0 -0
  116. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-thinking.js +0 -0
  117. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-tree-menus.js +0 -0
  118. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-tree.js +0 -0
  119. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-utilities.js +0 -0
  120. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-vision-model.js +0 -0
  121. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01d-image-gen-model.js +0 -0
  122. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01e-audio-model.js +0 -0
  123. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01f-compose-ui.js +0 -0
  124. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01g-tts-settings.js +0 -0
  125. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01h-music-model.js +0 -0
  126. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01i-model-stack.js +0 -0
  127. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01j-video-gen-model.js +0 -0
  128. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01k-model-stack-bootstrap.js +0 -0
  129. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01k-model-stack-state.js +0 -0
  130. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01q-inflight.js +0 -0
  131. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/02-reset.css +0 -0
  132. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/02-session-ui.js +0 -0
  133. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03-layout-p1.css +0 -0
  134. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03-layout-p2.css +0 -0
  135. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03a-links-stream.js +0 -0
  136. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03b-stream-text.js +0 -0
  137. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03c-render-bubble.js +0 -0
  138. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03d-agent-bubble.js +0 -0
  139. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03e-tool-trace.js +0 -0
  140. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03f-timeline.js +0 -0
  141. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03g-bubble-tts.js +0 -0
  142. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p1.css +0 -0
  143. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p2b.css +0 -0
  144. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p3.css +0 -0
  145. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p4.css +0 -0
  146. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p5.css +0 -0
  147. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p6.css +0 -0
  148. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p7.css +0 -0
  149. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p8.css +0 -0
  150. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p9.css +0 -0
  151. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-ws-connect.js +0 -0
  152. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-ws-helpers.js +0 -0
  153. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p1.css +0 -0
  154. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p2.css +0 -0
  155. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p3.css +0 -0
  156. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05a-session-history.js +0 -0
  157. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05b-session-list.js +0 -0
  158. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05c-dialog.js +0 -0
  159. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05d-project-create.js +0 -0
  160. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05e-project-remote.js +0 -0
  161. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06-chat.js +0 -0
  162. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06-utilities.css +0 -0
  163. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06b-attachments.js +0 -0
  164. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06c-camera.js +0 -0
  165. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/07-activity-bar.css +0 -0
  166. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09-git-p1.css +0 -0
  167. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09-git-p2.css +0 -0
  168. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09a-init-bind.js +0 -0
  169. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09b-panel-toggle.js +0 -0
  170. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09c-api-refresh.js +0 -0
  171. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09d-status-log.js +0 -0
  172. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09e-commit.js +0 -0
  173. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09f-remote.js +0 -0
  174. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11a-cron-parse.js +0 -0
  175. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11b-cron-panel.js +0 -0
  176. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11c-cron-card.js +0 -0
  177. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11d-cron-form.js +0 -0
  178. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11e-env-config.js +0 -0
  179. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-a-state.js +0 -0
  180. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-b-form.js +0 -0
  181. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-c-board.js +0 -0
  182. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11g-env-paths-git.js +0 -0
  183. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-a-state.js +0 -0
  184. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-b-form.js +0 -0
  185. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-c-board.js +0 -0
  186. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/12-plugins.js +0 -0
  187. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13a-skills.js +0 -0
  188. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13a-todo-utils.js +0 -0
  189. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13b-todo-fetch.js +0 -0
  190. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13b-tools.js +0 -0
  191. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13c-skills-panel.js +0 -0
  192. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13c-todo-crud.js +0 -0
  193. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13d-file-browser.js +0 -0
  194. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/14-activity-bar.js +0 -0
  195. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/15-plans.css +0 -0
  196. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/15-plans.js +0 -0
  197. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/16-mobile.css +0 -0
  198. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/16-mobile.js +0 -0
  199. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-agents-list.css +0 -0
  200. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-agents-list.js +0 -0
  201. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-preset-ux.css +0 -0
  202. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/18-agent-mgr.css +0 -0
  203. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/18-agent-mgr.js +0 -0
  204. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/24-health-dashboard.js +0 -0
  205. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/25-teams-panel.css +0 -0
  206. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/25-teams-panel.js +0 -0
  207. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/99-brand-polish.css +0 -0
  208. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/body.html +0 -0
  209. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_form.html +0 -0
  210. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_head.html +0 -0
  211. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_tail.html +0 -0
  212. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_test.html +0 -0
  213. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/web_login.html +0 -0
  214. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/webui.html +0 -0
  215. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/setup.cfg +0 -0
  216. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/dependency_links.txt +0 -0
  217. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/entry_points.txt +0 -0
  218. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/requires.txt +0 -0
  219. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/top_level.txt +0 -0
  220. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_a2a_streaming.py +0 -0
  221. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_discovery_hollow.py +0 -0
  222. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_file_safety.py +0 -0
  223. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_registry_bridge.py +0 -0
  224. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_root_consistency.py +0 -0
  225. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_archive_probe.py +0 -0
  226. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_attachment_safety.py +0 -0
  227. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_attachments.py +0 -0
  228. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auth_pure_fns.py +0 -0
  229. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auth_routes.py +0 -0
  230. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_cli_chat_project_id.py +0 -0
  231. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_compact_state.py +0 -0
  232. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_default_presets.py +0 -0
  233. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_env.py +0 -0
  234. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_env_chat.py +0 -0
  235. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_evidence.py +0 -0
  236. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_git_provider.py +0 -0
  237. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_git_ssh_helpers.py +0 -0
  238. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_history_json_safe.py +0 -0
  239. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_history_rows.py +0 -0
  240. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_hub.py +0 -0
  241. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_image_understanding.py +0 -0
  242. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_list_agents_tool.py +0 -0
  243. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_listen_port.py +0 -0
  244. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_metas.py +0 -0
  245. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_nudge_edge.py +0 -0
  246. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_orchestrator.py +0 -0
  247. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_paths_files.py +0 -0
  248. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_paths_safety.py +0 -0
  249. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persist_summarize.py +0 -0
  250. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_fallback.py +0 -0
  251. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_hash.py +0 -0
  252. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_init.py +0 -0
  253. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_preset_support.py +0 -0
  254. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_pricing.py +0 -0
  255. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_project_dirs.py +0 -0
  256. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_prompt_enrichment.py +0 -0
  257. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_reply_summary.py +0 -0
  258. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_self_healing.py +0 -0
  259. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_selfheal_heal.py +0 -0
  260. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_selfheal_health.py +0 -0
  261. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_server_init.py +0 -0
  262. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_server_pure.py +0 -0
  263. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_session_history.py +0 -0
  264. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_settings.py +0 -0
  265. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_skills_select.py +0 -0
  266. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_speech_synth_pure.py +0 -0
  267. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_task_split.py +0 -0
  268. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_concurrency.py +0 -0
  269. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_config.py +0 -0
  270. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_engine.py +0 -0
  271. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_token_counter.py +0 -0
  272. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_tts_voices.py +0 -0
  273. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_usage_billing.py +0 -0
  274. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_auth_cookie.py +0 -0
  275. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_pure_fns.py +0 -0
  276. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_pure_fns2.py +0 -0
  277. {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_write_env.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tagword-codeagent
3
- Version: 1.2.1
3
+ Version: 1.2.5
4
4
  Summary: Autonomous AI coding agent driven by Markdown config — DeepSeek-ready, CLI + Web UI, 45+ tools, self-hosted
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/tagword/codeagent
@@ -13,9 +13,14 @@
13
13
  - ``agent_card`` Agent Card 构建(发现端点)
14
14
  - ``handlers`` JSON-RPC 处理器(message/send、message/stream、tasks/get)
15
15
  - ``streaming`` SSE 流式支持(message/stream)
16
+ - ``context`` 统一上下文解析(contextId / taskId 反查 / projectId 校验)
17
+ - ``client_card`` Agent Card 解析模型(发送端)
18
+ - ``client`` A2A 客户端(发现 → 协商 → 调用),供 ``a2a`` 工具使用
16
19
  """
17
20
 
18
21
  from .agent_card import build_agent_card
22
+ from .client import A2AClient, A2AError, StreamEvent
23
+ from .client_card import AgentCard
19
24
  from .handlers import a2a_agent_card, a2a_jsonrpc
20
25
  from .streaming import stream_message, stream_response
21
26
 
@@ -25,4 +30,8 @@ __all__ = [
25
30
  "a2a_jsonrpc",
26
31
  "stream_message",
27
32
  "stream_response",
33
+ "A2AClient",
34
+ "A2AError",
35
+ "StreamEvent",
36
+ "AgentCard",
28
37
  ]
@@ -36,6 +36,14 @@ def build_agent_card() -> dict[str, Any]:
36
36
  ),
37
37
  "url": f"{base}/a2a",
38
38
  "preferredTransport": "JSONRPC",
39
+ # 规范 8.3.1:按优先级声明支持的传输组合,客户端据此选择。
40
+ # 首个条目即首选接口(8.3.2 客户端 MUST 选第一个支持的传输)。
41
+ "supportedInterfaces": [
42
+ {
43
+ "url": f"{base}/a2a",
44
+ "transport": "JSONRPC",
45
+ }
46
+ ],
39
47
  "version": "1.0.0",
40
48
  "provider": {
41
49
  "organization": "CodeAgent RSI Lab",
@@ -0,0 +1,448 @@
1
+ """A2A 客户端 — 让本 Agent 作为标准 A2A Client 调用外部 Agent。
2
+
3
+ 为什么需要它
4
+ ------------
5
+ 此前与外部 agent 通信靠「手撸脚本直接 POST /a2a」:跳过发现、硬编码 URL、
6
+ 假设对方支持 JSONRPC 与流式。对方一换地址/传输/认证方式,脚本立刻失效。
7
+
8
+ A2A 是双向对等协议。服务端(``handlers``/``streaming``)解决「被调用」,
9
+ 本模块解决「主动调用」,两者合起来才是完整实现。
10
+
11
+ 标准流程(规范 8.2 / 8.3.2)
12
+ ----------------------------
13
+ 1. **发现**:GET ``/.well-known/agent-card.json`` 拿名片
14
+ 2. **协商**:读 ``supportedInterfaces``,选第一个自己支持的传输
15
+ 3. **调用**:按名片声明的 URL / 认证方式 / 流式能力发请求
16
+
17
+ 本客户端强制走完 1→2,**不允许跳过发现直接调用**——这是与手撸脚本的本质区别。
18
+
19
+ 用法::
20
+
21
+ async with A2AClient.discover("http://127.0.0.1:18898") as client:
22
+ task = await client.send("把 X 改成 Y", project_id="proj-1")
23
+ print(task.text())
24
+
25
+ # 流式
26
+ async for ev in client.stream("写个脚本"):
27
+ if ev.kind == "artifact":
28
+ print(ev.text, end="")
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import logging
35
+ from collections.abc import AsyncIterator
36
+ from dataclasses import dataclass, field
37
+ from typing import Any
38
+
39
+ import httpx
40
+
41
+ from .client_card import AgentCard
42
+ from .models import (
43
+ STATE_CANCELED,
44
+ STATE_COMPLETED,
45
+ STATE_FAILED,
46
+ TERMINAL_STATES,
47
+ Message,
48
+ Part,
49
+ Task,
50
+ new_id,
51
+ )
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+ WELL_KNOWN_PATH = "/.well-known/agent-card.json"
56
+
57
+ # JSON-RPC 标准错误码 → 可读文案
58
+ _RPC_ERRORS = {
59
+ -32700: "解析错误:请求不是合法 JSON",
60
+ -32600: "非法请求",
61
+ -32601: "方法不存在(对方可能不支持该操作)",
62
+ -32602: "参数非法",
63
+ -32603: "对方内部错误",
64
+ }
65
+
66
+
67
+ class A2AError(Exception):
68
+ """A2A 调用失败(网络 / 协议 / 业务错误统一入口)。"""
69
+
70
+ def __init__(self, message: str, *, code: int | None = None) -> None:
71
+ super().__init__(message)
72
+ self.code = code
73
+
74
+
75
+ @dataclass
76
+ class StreamEvent:
77
+ """流式事件(把 SSE 的 status / artifact 归一成一种结构)。"""
78
+
79
+ kind: str # status | artifact | error
80
+ state: str = ""
81
+ text: str = ""
82
+ final: bool = False
83
+ # 协议语义:append=True 表示增量片段,False 表示全量快照(覆盖已累积内容)
84
+ append: bool = False
85
+ task_id: str = ""
86
+ context_id: str = ""
87
+ metadata: dict[str, Any] = field(default_factory=dict)
88
+ raw: dict[str, Any] = field(default_factory=dict)
89
+
90
+
91
+ class A2AClient:
92
+ """标准 A2A 客户端。
93
+
94
+ 构造后所有调用都基于**名片协商出的接口**,不硬编码 URL。
95
+ """
96
+
97
+ def __init__(
98
+ self,
99
+ card: AgentCard,
100
+ *,
101
+ token: str = "",
102
+ timeout: float = 300.0,
103
+ transport: httpx.AsyncBaseTransport | None = None,
104
+ ) -> None:
105
+ self.card = card
106
+ self._iface = card.select_interface() # 规范 8.3.2:协商失败即报错
107
+ self._token = token
108
+ self._timeout = timeout
109
+ self._transport = transport
110
+ self._client: httpx.AsyncClient | None = None
111
+
112
+ # ── 发现 ───────────────────────────────────────────────────
113
+ @classmethod
114
+ async def discover(
115
+ cls,
116
+ base_url: str,
117
+ *,
118
+ token: str = "",
119
+ timeout: float = 30.0,
120
+ transport: httpx.AsyncBaseTransport | None = None,
121
+ ) -> A2AClient:
122
+ """规范 8.2 Well-Known URI 发现:拉名片并构造客户端。"""
123
+ url = base_url.rstrip("/") + WELL_KNOWN_PATH
124
+ headers = {"Accept": "application/json"}
125
+ if token:
126
+ headers["Authorization"] = f"Bearer {token}"
127
+ try:
128
+ async with httpx.AsyncClient(
129
+ timeout=timeout, transport=transport, follow_redirects=True
130
+ ) as http:
131
+ resp = await http.get(url, headers=headers)
132
+ except httpx.HTTPError as e:
133
+ raise A2AError(f"发现失败:无法访问 {url}({e})") from e
134
+
135
+ if resp.status_code != 200:
136
+ raise A2AError(
137
+ f"发现失败:{url} 返回 HTTP {resp.status_code}",
138
+ code=resp.status_code,
139
+ )
140
+ try:
141
+ data = resp.json()
142
+ except json.JSONDecodeError as e:
143
+ raise A2AError(f"发现失败:{url} 返回的不是合法 JSON") from e
144
+ if not isinstance(data, dict):
145
+ raise A2AError(f"发现失败:{url} 返回的 Agent Card 不是对象")
146
+
147
+ return cls(data and AgentCard.from_dict(data), token=token, timeout=timeout, transport=transport)
148
+
149
+ # ── 生命周期 ───────────────────────────────────────────────
150
+ async def __aenter__(self) -> A2AClient:
151
+ self._client = httpx.AsyncClient(
152
+ timeout=self._timeout, transport=self._transport, follow_redirects=True
153
+ )
154
+ return self
155
+
156
+ async def __aexit__(self, *exc: Any) -> None:
157
+ if self._client is not None:
158
+ await self._client.aclose()
159
+ self._client = None
160
+
161
+ async def close(self) -> None:
162
+ await self.__aexit__(None, None, None)
163
+
164
+ # ── 调用 ───────────────────────────────────────────────────
165
+ async def send(
166
+ self,
167
+ text: str,
168
+ *,
169
+ context_id: str = "",
170
+ task_id: str = "",
171
+ project_id: str = "",
172
+ max_tool_rounds: int | None = None,
173
+ ) -> Task:
174
+ """``message/send`` — 同步执行,返回终态 Task。"""
175
+ params = self._build_params(
176
+ text,
177
+ context_id=context_id,
178
+ task_id=task_id,
179
+ project_id=project_id,
180
+ max_tool_rounds=max_tool_rounds,
181
+ )
182
+ result = await self._rpc("message/send", params)
183
+ if not isinstance(result, dict):
184
+ raise A2AError("message/send 返回结果不是对象")
185
+ return Task.from_dict(result)
186
+
187
+ async def stream(
188
+ self,
189
+ text: str,
190
+ *,
191
+ context_id: str = "",
192
+ task_id: str = "",
193
+ project_id: str = "",
194
+ max_tool_rounds: int | None = None,
195
+ ) -> AsyncIterator[StreamEvent]:
196
+ """``message/stream`` — SSE 流式执行。
197
+
198
+ 名片声明 ``capabilities.streaming=false`` 时直接报错,
199
+ 不静默降级——降级会让调用方以为拿到了流。
200
+ """
201
+ if not self.card.supports_streaming:
202
+ raise A2AError(
203
+ f"agent '{self.card.name}' 未声明 streaming 能力,请改用 send()"
204
+ )
205
+ params = self._build_params(
206
+ text,
207
+ context_id=context_id,
208
+ task_id=task_id,
209
+ project_id=project_id,
210
+ max_tool_rounds=max_tool_rounds,
211
+ )
212
+ body = {"jsonrpc": "2.0", "id": new_id("req"), "method": "message/stream", "params": params}
213
+
214
+ client = self._ensure_client()
215
+ try:
216
+ async with client.stream(
217
+ "POST", self._iface.url, json=body, headers=self._headers()
218
+ ) as resp:
219
+ if resp.status_code != 200:
220
+ detail = (await resp.aread()).decode("utf-8", "replace")[:300]
221
+ raise A2AError(
222
+ f"message/stream 返回 HTTP {resp.status_code}: {detail}",
223
+ code=resp.status_code,
224
+ )
225
+ async for ev in _iter_sse(resp):
226
+ yield ev
227
+ except httpx.HTTPError as e:
228
+ raise A2AError(f"message/stream 网络错误:{e}") from e
229
+
230
+ async def get_task(self, *, task_id: str = "", context_id: str = "") -> Task:
231
+ """``tasks/get`` — 查询任务状态。"""
232
+ if not task_id and not context_id:
233
+ raise A2AError("get_task 需要 task_id 或 context_id")
234
+ params: dict[str, Any] = {}
235
+ if task_id:
236
+ params["id"] = task_id
237
+ if context_id:
238
+ params["contextId"] = context_id
239
+ result = await self._rpc("tasks/get", params)
240
+ if not isinstance(result, dict):
241
+ raise A2AError("tasks/get 返回结果不是对象")
242
+ return Task.from_dict(result)
243
+
244
+ async def cancel(self, task_id: str) -> Task:
245
+ """``tasks/cancel`` — 取消任务。"""
246
+ result = await self._rpc("tasks/cancel", {"id": task_id})
247
+ if not isinstance(result, dict):
248
+ raise A2AError("tasks/cancel 返回结果不是对象")
249
+ return Task.from_dict(result)
250
+
251
+ async def wait(
252
+ self,
253
+ task: Task,
254
+ *,
255
+ poll_interval: float = 2.0,
256
+ max_wait: float = 600.0,
257
+ ) -> Task:
258
+ """轮询直到 Task 进入终态(对方不支持推送通知时的兜底)。"""
259
+ import asyncio
260
+
261
+ waited = 0.0
262
+ current = task
263
+ while current.status.state not in TERMINAL_STATES:
264
+ if waited >= max_wait:
265
+ raise A2AError(
266
+ f"等待任务超时({max_wait}s),当前状态 {current.status.state}"
267
+ )
268
+ await asyncio.sleep(poll_interval)
269
+ waited += poll_interval
270
+ current = await self.get_task(task_id=current.id, context_id=current.context_id)
271
+ return current
272
+
273
+ # ── 内部 ───────────────────────────────────────────────────
274
+ def _ensure_client(self) -> httpx.AsyncClient:
275
+ if self._client is None:
276
+ # 未用 async with 时自动建连接,调用方无需强制上下文管理
277
+ self._client = httpx.AsyncClient(
278
+ timeout=self._timeout, transport=self._transport, follow_redirects=True
279
+ )
280
+ return self._client
281
+
282
+ def _headers(self) -> dict[str, str]:
283
+ headers = {"Content-Type": "application/json", "Accept": "application/json"}
284
+ if self._token:
285
+ headers["Authorization"] = f"Bearer {self._token}"
286
+ return headers
287
+
288
+ def _build_params(
289
+ self,
290
+ text: str,
291
+ *,
292
+ context_id: str,
293
+ task_id: str,
294
+ project_id: str,
295
+ max_tool_rounds: int | None,
296
+ ) -> dict[str, Any]:
297
+ msg = Message(
298
+ role="user",
299
+ parts=[Part(kind="text", text=text)],
300
+ message_id=new_id("msg"),
301
+ context_id=context_id,
302
+ task_id=task_id,
303
+ metadata={"projectId": project_id} if project_id else {},
304
+ )
305
+ params: dict[str, Any] = {"message": _message_to_dict(msg)}
306
+ # 规范 8.3.2:接口声明了 tenant 就必须带上
307
+ if self._iface.tenant:
308
+ params["tenant"] = self._iface.tenant
309
+ if max_tool_rounds is not None:
310
+ params["configuration"] = {"maxToolRounds": max_tool_rounds}
311
+ return params
312
+
313
+ async def _rpc(self, method: str, params: dict[str, Any]) -> Any:
314
+ """发 JSON-RPC 请求并解包结果。"""
315
+ client = self._ensure_client()
316
+ body = {"jsonrpc": "2.0", "id": new_id("req"), "method": method, "params": params}
317
+ try:
318
+ resp = await client.post(self._iface.url, json=body, headers=self._headers())
319
+ except httpx.HTTPError as e:
320
+ raise A2AError(f"{method} 网络错误:{e}") from e
321
+
322
+ if resp.status_code != 200:
323
+ raise A2AError(
324
+ f"{method} 返回 HTTP {resp.status_code}: {resp.text[:300]}",
325
+ code=resp.status_code,
326
+ )
327
+ try:
328
+ payload = resp.json()
329
+ except json.JSONDecodeError as e:
330
+ raise A2AError(f"{method} 返回的不是合法 JSON") from e
331
+
332
+ if not isinstance(payload, dict):
333
+ raise A2AError(f"{method} 返回的 JSON-RPC 响应不是对象")
334
+
335
+ err = payload.get("error")
336
+ if isinstance(err, dict):
337
+ code = err.get("code")
338
+ msg = str(err.get("message") or "")
339
+ hint = _RPC_ERRORS.get(code, "") if isinstance(code, int) else ""
340
+ raise A2AError(
341
+ f"{method} 失败:{hint or msg}" + (f"({msg})" if hint and msg else ""),
342
+ code=code if isinstance(code, int) else None,
343
+ )
344
+ if "result" not in payload:
345
+ raise A2AError(f"{method} 响应缺少 result 字段")
346
+ return payload["result"]
347
+
348
+
349
+ def _message_to_dict(msg: Message) -> dict[str, Any]:
350
+ """Message → A2A wire 格式(models.Message 只有 from_dict,这里补序列化)。"""
351
+ out: dict[str, Any] = {
352
+ "role": msg.role,
353
+ "parts": [p.to_dict() for p in msg.parts],
354
+ "messageId": msg.message_id,
355
+ }
356
+ if msg.context_id:
357
+ out["contextId"] = msg.context_id
358
+ if msg.task_id:
359
+ out["taskId"] = msg.task_id
360
+ if msg.metadata:
361
+ out["metadata"] = msg.metadata
362
+ return out
363
+
364
+
365
+ async def _iter_sse(resp: httpx.Response) -> AsyncIterator[StreamEvent]:
366
+ """解析 A2A SSE 流(``event:`` / ``data:`` 帧)为 StreamEvent。"""
367
+ event_name = ""
368
+ async for line in resp.aiter_lines():
369
+ line = line.rstrip("\r")
370
+ if not line:
371
+ event_name = ""
372
+ continue
373
+ if line.startswith("event:"):
374
+ event_name = line[6:].strip()
375
+ continue
376
+ if not line.startswith("data:"):
377
+ continue
378
+ raw = line[5:].strip()
379
+ try:
380
+ data = json.loads(raw)
381
+ except json.JSONDecodeError:
382
+ logger.debug("a2a client: 跳过非法 SSE data: %s", raw[:120])
383
+ continue
384
+ if not isinstance(data, dict):
385
+ continue
386
+ yield _to_event(event_name, data)
387
+
388
+
389
+ def _to_event(event_name: str, data: dict[str, Any]) -> StreamEvent:
390
+ """把 A2A SSE 负载归一成 StreamEvent。"""
391
+ kind = str(data.get("kind") or event_name or "")
392
+ task_id = str(data.get("taskId") or "")
393
+ context_id = str(data.get("contextId") or "")
394
+
395
+ if kind == "status-update" or event_name == "status":
396
+ status = data.get("status") if isinstance(data.get("status"), dict) else {}
397
+ return StreamEvent(
398
+ kind="status",
399
+ state=str(status.get("state") or ""),
400
+ final=bool(data.get("final")),
401
+ task_id=task_id,
402
+ context_id=context_id,
403
+ metadata=data.get("metadata") if isinstance(data.get("metadata"), dict) else {},
404
+ raw=data,
405
+ )
406
+
407
+ if kind == "artifact-update" or event_name == "artifact":
408
+ artifact = data.get("artifact") if isinstance(data.get("artifact"), dict) else {}
409
+ text = "".join(
410
+ str(p.get("text") or "")
411
+ for p in (artifact.get("parts") or [])
412
+ if isinstance(p, dict) and p.get("kind") == "text"
413
+ )
414
+ return StreamEvent(
415
+ kind="artifact",
416
+ text=text,
417
+ final=bool(data.get("lastChunk")),
418
+ append=bool(data.get("append", True)),
419
+ task_id=task_id,
420
+ context_id=context_id,
421
+ raw=data,
422
+ )
423
+
424
+ if event_name == "error":
425
+ return StreamEvent(
426
+ kind="error",
427
+ text=str(data.get("message") or ""),
428
+ final=True,
429
+ raw=data,
430
+ )
431
+
432
+ return StreamEvent(kind=kind or "unknown", task_id=task_id, context_id=context_id, raw=data)
433
+
434
+
435
+ def is_terminal(task: Task) -> bool:
436
+ """Task 是否已进入终态。"""
437
+ return task.status.state in TERMINAL_STATES
438
+
439
+
440
+ __all__ = [
441
+ "A2AClient",
442
+ "A2AError",
443
+ "StreamEvent",
444
+ "is_terminal",
445
+ "STATE_CANCELED",
446
+ "STATE_COMPLETED",
447
+ "STATE_FAILED",
448
+ ]
@@ -0,0 +1,136 @@
1
+ """Agent Card 解析 — A2A 客户端侧的发现模型。
2
+
3
+ 服务端 ``agent_card.build_agent_card`` 是「写名片」,
4
+ 本模块是「读名片」:把对方返回的 JSON 解析成可查询的对象。
5
+
6
+ 规范 8.3.2 对客户端有 MUST 级要求:
7
+ - 解析 ``supportedInterfaces``,选第一个**自己支持**的传输
8
+ - 多个可选时优先靠前的条目
9
+ - 用所选接口的 URL,而非猜测
10
+ - 请求要带该接口声明的 ``tenant``(未声明则省略)
11
+
12
+ 因此本模块的核心价值是把「猜」变成「按声明读」。
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+ #: 本客户端实现的传输协议(目前只有 JSONRPC over HTTP)
21
+ SUPPORTED_TRANSPORTS = frozenset({"JSONRPC"})
22
+
23
+
24
+ @dataclass
25
+ class AgentInterface:
26
+ """规范 4.4.6:一个可调用的传输接口。"""
27
+
28
+ url: str
29
+ transport: str
30
+ tenant: str = ""
31
+
32
+ @classmethod
33
+ def from_dict(cls, d: dict[str, Any]) -> AgentInterface:
34
+ return cls(
35
+ url=str(d.get("url") or "").strip(),
36
+ transport=str(d.get("transport") or "").strip().upper(),
37
+ tenant=str(d.get("tenant") or "").strip(),
38
+ )
39
+
40
+
41
+ @dataclass
42
+ class AgentSkill:
43
+ """规范 4.4.5:Agent 声明的能力项。"""
44
+
45
+ id: str = ""
46
+ name: str = ""
47
+ description: str = ""
48
+ tags: list[str] = field(default_factory=list)
49
+
50
+ @classmethod
51
+ def from_dict(cls, d: dict[str, Any]) -> AgentSkill:
52
+ return cls(
53
+ id=str(d.get("id") or ""),
54
+ name=str(d.get("name") or ""),
55
+ description=str(d.get("description") or ""),
56
+ tags=[str(t) for t in (d.get("tags") or [])],
57
+ )
58
+
59
+
60
+ @dataclass
61
+ class AgentCard:
62
+ """规范 4.4.1:Agent 名片(客户端视角)。"""
63
+
64
+ name: str = ""
65
+ description: str = ""
66
+ url: str = ""
67
+ version: str = ""
68
+ protocol_version: str = ""
69
+ preferred_transport: str = ""
70
+ capabilities: dict[str, Any] = field(default_factory=dict)
71
+ skills: list[AgentSkill] = field(default_factory=list)
72
+ interfaces: list[AgentInterface] = field(default_factory=list)
73
+ security_schemes: dict[str, Any] = field(default_factory=dict)
74
+ security: list[dict[str, Any]] = field(default_factory=list)
75
+ raw: dict[str, Any] = field(default_factory=dict)
76
+
77
+ @classmethod
78
+ def from_dict(cls, d: dict[str, Any]) -> AgentCard:
79
+ raw_ifaces = d.get("supportedInterfaces")
80
+ interfaces: list[AgentInterface] = []
81
+ if isinstance(raw_ifaces, list):
82
+ interfaces = [
83
+ AgentInterface.from_dict(x) for x in raw_ifaces if isinstance(x, dict)
84
+ ]
85
+ # 兼容旧名片:没有 supportedInterfaces 时用顶层 url + preferredTransport
86
+ if not interfaces and d.get("url"):
87
+ interfaces = [
88
+ AgentInterface(
89
+ url=str(d.get("url") or ""),
90
+ transport=str(d.get("preferredTransport") or "JSONRPC").upper(),
91
+ )
92
+ ]
93
+
94
+ caps = d.get("capabilities")
95
+ return cls(
96
+ name=str(d.get("name") or ""),
97
+ description=str(d.get("description") or ""),
98
+ url=str(d.get("url") or ""),
99
+ version=str(d.get("version") or ""),
100
+ protocol_version=str(d.get("protocolVersion") or ""),
101
+ preferred_transport=str(d.get("preferredTransport") or "").upper(),
102
+ capabilities=caps if isinstance(caps, dict) else {},
103
+ skills=[AgentSkill.from_dict(s) for s in (d.get("skills") or []) if isinstance(s, dict)],
104
+ interfaces=interfaces,
105
+ security_schemes=d.get("securitySchemes") if isinstance(d.get("securitySchemes"), dict) else {},
106
+ security=[s for s in (d.get("security") or []) if isinstance(s, dict)],
107
+ raw=d,
108
+ )
109
+
110
+ # ── 能力查询 ────────────────────────────────────────────────
111
+ @property
112
+ def supports_streaming(self) -> bool:
113
+ return bool(self.capabilities.get("streaming"))
114
+
115
+ @property
116
+ def supports_push_notifications(self) -> bool:
117
+ return bool(self.capabilities.get("pushNotifications"))
118
+
119
+ def select_interface(self) -> AgentInterface:
120
+ """按规范 8.3.2 选择传输接口:第一个自己支持的。
121
+
122
+ 找不到时抛 ``ValueError``——**不猜测**,因为猜错 URL/传输
123
+ 比直接失败更难排查。
124
+ """
125
+ for iface in self.interfaces:
126
+ if iface.transport in SUPPORTED_TRANSPORTS and iface.url:
127
+ return iface
128
+ available = ", ".join(f"{i.transport}({i.url})" for i in self.interfaces) or "无"
129
+ raise ValueError(
130
+ f"agent '{self.name}' 没有本客户端支持的传输接口;"
131
+ f"对方声明: {available};本客户端支持: {sorted(SUPPORTED_TRANSPORTS)}"
132
+ )
133
+
134
+ def requires_auth(self) -> bool:
135
+ """是否需要认证(声明了 security 或 securitySchemes)。"""
136
+ return bool(self.security) or bool(self.security_schemes)