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.
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/PKG-INFO +1 -1
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/__init__.py +9 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/agent_card.py +8 -0
- tagword_codeagent-1.2.5/codeagent/server/a2a/client.py +448 -0
- tagword_codeagent-1.2.5/codeagent/server/a2a/client_card.py +136 -0
- tagword_codeagent-1.2.5/codeagent/server/a2a/context.py +201 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/handlers.py +29 -7
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/models.py +48 -1
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/a2a/streaming.py +21 -5
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/auto_continue.py +64 -6
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/chat_turn.py +32 -11
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/pyproject.toml +1 -1
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/PKG-INFO +1 -1
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/SOURCES.txt +5 -0
- tagword_codeagent-1.2.5/tests/test_a2a.py +401 -0
- tagword_codeagent-1.2.5/tests/test_a2a_context.py +210 -0
- tagword_codeagent-1.2.5/tests/test_a2a_tool.py +289 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auto_continue.py +100 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auto_continue_wiring.py +88 -1
- tagword_codeagent-1.2.1/tests/test_a2a.py +0 -213
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/LICENSE +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/README.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/__main__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/entrypoint.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/cli/main.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/attachments.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/audio_models.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/bootstrap.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/default_presets.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/env.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/folder_picker.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/image_understanding.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/models.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/paths.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/pricing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/process_ports.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/seed_bridge.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/settings.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/speech_synth.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/team_config.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/team_manager.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/token_counter.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/tts_voices.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/usage_billing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/core/vision_models.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/memory/diary.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/README.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/agent.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/identity.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/memory.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/skills.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/soul.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/tools.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/persona_defaults/user.md +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_bootstrap.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_context.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_handle.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/agent_runtime.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/compact_prompt.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/compact_state.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/llm_worker.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/orchestrator.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/prompt_enrichment.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/session_manager.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/task_split.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/turn_loop.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/runtime/worker.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/app_factory.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/attachment_api.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/evidence.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/hub.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/icon.png +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/self_healing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/team_engine.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/team_worker.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/tts_api.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_api_app.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_agentmeta.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_agents.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_auth.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_cronallow.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_envllm.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_files.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_git.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_health.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_mcp.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_projects.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_sessions.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_skills.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/server/webui_handlers_teams.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/skills/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/skills/select.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/tools/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/tools/agent_tools.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__init__.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/__init__.cpython-311.pyc +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/auth_impl.cpython-311.pyc +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/auth_impl.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-marked.min.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-mermaid.min.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-purify.min.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-storage.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/00-utils.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01-theme.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01a-scroll.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01b-think.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-identity.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-thinking.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-tree-menus.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-tree.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-session-utilities.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01c-vision-model.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01d-image-gen-model.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01e-audio-model.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01f-compose-ui.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01g-tts-settings.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01h-music-model.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01i-model-stack.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01j-video-gen-model.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01k-model-stack-bootstrap.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01k-model-stack-state.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/01q-inflight.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/02-reset.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/02-session-ui.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03-layout-p1.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03-layout-p2.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03a-links-stream.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03b-stream-text.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03c-render-bubble.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03d-agent-bubble.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03e-tool-trace.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03f-timeline.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/03g-bubble-tts.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p1.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p2b.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p3.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p4.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p5.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p6.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p7.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p8.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-components-p9.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-ws-connect.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/04-ws-helpers.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p1.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p2.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05-chat-p3.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05a-session-history.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05b-session-list.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05c-dialog.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05d-project-create.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/05e-project-remote.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06-chat.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06-utilities.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06b-attachments.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/06c-camera.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/07-activity-bar.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09-git-p1.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09-git-p2.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09a-init-bind.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09b-panel-toggle.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09c-api-refresh.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09d-status-log.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09e-commit.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/09f-remote.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11a-cron-parse.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11b-cron-panel.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11c-cron-card.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11d-cron-form.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11e-env-config.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-a-state.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-b-form.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11f-env-llm-c-board.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11g-env-paths-git.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-a-state.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-b-form.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/11h-env-mcp-c-board.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/12-plugins.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13a-skills.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13a-todo-utils.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13b-todo-fetch.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13b-tools.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13c-skills-panel.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13c-todo-crud.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/13d-file-browser.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/14-activity-bar.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/15-plans.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/15-plans.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/16-mobile.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/16-mobile.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-agents-list.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-agents-list.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/17-preset-ux.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/18-agent-mgr.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/18-agent-mgr.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/24-health-dashboard.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/25-teams-panel.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/25-teams-panel.js +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/99-brand-polish.css +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/body.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_form.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_head.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_tail.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/static/setup/setup_test.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/web_login.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/codeagent/web/webui.html +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/setup.cfg +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/dependency_links.txt +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/entry_points.txt +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/requires.txt +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tagword_codeagent.egg-info/top_level.txt +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_a2a_streaming.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_discovery_hollow.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_file_safety.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_registry_bridge.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_agent_root_consistency.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_archive_probe.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_attachment_safety.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_attachments.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auth_pure_fns.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_auth_routes.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_cli_chat_project_id.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_compact_state.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_default_presets.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_env.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_env_chat.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_evidence.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_git_provider.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_git_ssh_helpers.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_history_json_safe.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_history_rows.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_hub.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_image_understanding.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_list_agents_tool.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_listen_port.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_metas.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_nudge_edge.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_orchestrator.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_paths_files.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_paths_safety.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persist_summarize.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_fallback.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_hash.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_persona_init.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_preset_support.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_pricing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_project_dirs.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_prompt_enrichment.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_reply_summary.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_self_healing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_selfheal_heal.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_selfheal_health.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_server_init.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_server_pure.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_session_history.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_settings.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_skills_select.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_speech_synth_pure.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_task_split.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_concurrency.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_config.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_team_engine.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_token_counter.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_tts_voices.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_usage_billing.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_auth_cookie.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_pure_fns.py +0 -0
- {tagword_codeagent-1.2.1 → tagword_codeagent-1.2.5}/tests/test_webui_pure_fns2.py +0 -0
- {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.
|
|
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)
|