iac-code 0.2.2__tar.gz → 0.3.0__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.
- {iac_code-0.2.2 → iac_code-0.3.0}/PKG-INFO +9 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/README.md +8 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/pyproject.toml +4 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/setup.py +11 -10
- iac_code-0.3.0/src/iac_code/__init__.py +2 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/agent_card.py +16 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/app.py +8 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/client.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/events.py +37 -8
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/executor.py +40 -13
- iac_code-0.3.0/src/iac_code/a2a/exposure.py +66 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/parts.py +3 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push.py +6 -13
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_queue.py +3 -8
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_secrets.py +4 -9
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/base.py +13 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/dispatcher.py +5 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/http.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/stdio.py +62 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/__init__.py +3 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/session.py +21 -8
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/system_prompt.py +12 -23
- iac_code-0.3.0/src/iac_code/cli/install_git_bash.py +85 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/main.py +206 -9
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/auth.py +457 -177
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/config.py +2 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/__init__.py +33 -8
- iac_code-0.3.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +413 -249
- iac_code-0.3.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +418 -249
- iac_code-0.3.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +420 -249
- iac_code-0.3.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +398 -249
- iac_code-0.3.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +414 -249
- iac_code-0.3.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +392 -249
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/memory_manager.py +2 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/manager.py +3 -3
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/auto_detect.py +3 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/multimodal.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/loader.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/providers/aliyun.py +3 -3
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/__init__.py +13 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/client.py +27 -6
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/identity.py +32 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +14 -13
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/__init__.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +5 -3
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/discovery.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/renderer.py +33 -7
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/bash_tool.py +25 -8
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/path_validation.py +21 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/safety_checks.py +31 -8
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +2 -2
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/api_hooks.py +8 -3
- iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +76 -0
- iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +157 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_stack.py +16 -6
- iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/ros_yaml.py +102 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/edit_file.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/glob.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/grep.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/list_files.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/read_file.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/write_file.py +2 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/banner.py +1 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/prompt_input.py +86 -30
- iac_code-0.3.0/src/iac_code/ui/core/raw_input.py +325 -0
- iac_code-0.3.0/src/iac_code/ui/core/raw_input_win.py +165 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/global_search.py +6 -3
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/renderer.py +83 -33
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/shell_history_provider.py +16 -1
- iac_code-0.3.0/src/iac_code/utils/console.py +37 -0
- iac_code-0.3.0/src/iac_code/utils/file_security.py +50 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/log.py +20 -6
- iac_code-0.3.0/src/iac_code/utils/platform.py +183 -0
- iac_code-0.3.0/src/iac_code/utils/project_paths.py +109 -0
- iac_code-0.3.0/src/iac_code/utils/signals.py +63 -0
- iac_code-0.3.0/src/iac_code/utils/windows_paths.py +42 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/PKG-INFO +9 -1
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/SOURCES.txt +10 -0
- iac_code-0.2.2/src/iac_code/__init__.py +0 -2
- iac_code-0.2.2/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.2.2/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -57
- iac_code-0.2.2/src/iac_code/ui/core/raw_input.py +0 -319
- iac_code-0.2.2/src/iac_code/utils/project_paths.py +0 -74
- {iac_code-0.2.2 → iac_code-0.3.0}/LICENSE +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/MANIFEST.in +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/setup.cfg +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/artifacts.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/metrics.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/persistence.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_worker.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/router.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/signing.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/task_store.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transport.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/grpc.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/redis_streams.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/unix.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/websocket.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/convert.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/http_sse.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/mcp.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/metrics.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/server.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/slash_registry.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/state.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/tools.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/version.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_loop.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_tool.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/message.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/headless.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/output_formats.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/clear.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/compact.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/debug.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/effort.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/exit.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/help.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/model.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/registry.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/resume.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/tasks.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/memory_tools.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/anthropic_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/azure_openai_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/base.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/dashscope_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/deepseek_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/gemini_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/kimi_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/lmstudio_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/minimax_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/modelscope_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/ollama_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/openai_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/openrouter_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/registry.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/retry.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/siliconflow_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/stream_watchdog.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/thinking.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/volcengine_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/zhipu_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/agent_factory.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/cloud_credentials.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/context_manager.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/pipeline.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/storage.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/providers/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/qwenpaw_source.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/session_index.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/session_storage.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/attributes.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/config.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/constants.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/content_serializer.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/events.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/fallback.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/metrics.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/names.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/sanitize.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/sink.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/tracing.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/token_budget.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/token_counter.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/simplify.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/frontmatter.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/listing.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/loader.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/processor.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/skill_definition.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/skill_tool.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/state/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/state/app_state.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/notification_queue.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/task_state.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/task_tools.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/base.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/command_parser.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/mode_validation.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/permissions.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/readonly_commands.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/rule_matching.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_client.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/base_api.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/base_stack.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/registry.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/result_storage.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/tool_executor.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/web_fetch.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/permissions.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/skill_source.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/stream_events.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/dialog.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/divider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/progress_bar.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/search_box.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/select.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/status_icon.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/tabs.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/in_place_render.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/input_history.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/key_event.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/screen.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/history_search.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/model_picker.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/quick_open.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/resume_picker.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/keybindings/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/keybindings/manager.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/repl.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/spinner.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/aggregator.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/command_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/file_provider.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/token_extractor.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/types.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/transcript_view.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/background_housekeeping.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/cleanup.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/__init__.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/clipboard.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/format_detect.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/pasted_content.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/processor.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/resizer.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/store.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/json_utils.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/tool_input_parser.py +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/dependency_links.txt +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/entry_points.txt +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/requires.txt +0 -0
- {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iac_code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Your AI-powered Infrastructure as Code assistant
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -34,8 +34,16 @@ License-File: LICENSE
|
|
|
34
34
|
|
|
35
35
|
> **Documentation**: [https://aliyun.github.io/iac-code/](https://aliyun.github.io/iac-code/)
|
|
36
36
|
|
|
37
|
+
<p align="center">
|
|
38
|
+
<img src="website/static/img/demo_en.gif" alt="iac-code demo" width="100%">
|
|
39
|
+
</p>
|
|
40
|
+
|
|
37
41
|
## Installation
|
|
38
42
|
|
|
43
|
+
IaC Code requires Python 3.10 or later. It supports macOS, Linux, and Windows.
|
|
44
|
+
|
|
45
|
+
> **Windows note**: On Windows, [Git for Windows](https://gitforwindows.org/) must be installed to provide the bash shell used by the tool execution environment. If Git Bash is installed but not on PATH, set the `IAC_CODE_GIT_BASH_PATH` environment variable.
|
|
46
|
+
|
|
39
47
|
```bash
|
|
40
48
|
pip install iac-code
|
|
41
49
|
```
|
|
@@ -15,8 +15,16 @@
|
|
|
15
15
|
|
|
16
16
|
> **Documentation**: [https://aliyun.github.io/iac-code/](https://aliyun.github.io/iac-code/)
|
|
17
17
|
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="website/static/img/demo_en.gif" alt="iac-code demo" width="100%">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
18
22
|
## Installation
|
|
19
23
|
|
|
24
|
+
IaC Code requires Python 3.10 or later. It supports macOS, Linux, and Windows.
|
|
25
|
+
|
|
26
|
+
> **Windows note**: On Windows, [Git for Windows](https://gitforwindows.org/) must be installed to provide the bash shell used by the tool execution environment. If Git Bash is installed but not on PATH, set the `IAC_CODE_GIT_BASH_PATH` environment variable.
|
|
27
|
+
|
|
20
28
|
```bash
|
|
21
29
|
pip install iac-code
|
|
22
30
|
```
|
|
@@ -68,6 +68,7 @@ dev = [
|
|
|
68
68
|
"pre-commit>=4.0",
|
|
69
69
|
"pytest-xdist>=3.0",
|
|
70
70
|
"pytest-cov>=5.0",
|
|
71
|
+
"pytest-timeout>=2.3.1",
|
|
71
72
|
"setuptools>=68.0",
|
|
72
73
|
"wheel",
|
|
73
74
|
"tomli>=2.0; python_version<\"3.11\"",
|
|
@@ -111,6 +112,9 @@ index-url = "https://mirrors.aliyun.com/pypi/simple/"
|
|
|
111
112
|
url = "https://mirrors.aliyun.com/pypi/simple/"
|
|
112
113
|
default = true
|
|
113
114
|
|
|
115
|
+
[tool.pytest.ini_options]
|
|
116
|
+
timeout = 30
|
|
117
|
+
|
|
114
118
|
[tool.coverage.run]
|
|
115
119
|
source_pkgs = ["iac_code"]
|
|
116
120
|
branch = true
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Custom build hook to inject __release_date__ at build time."""
|
|
2
2
|
|
|
3
|
+
import platform
|
|
3
4
|
import re
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
6
7
|
from datetime import date
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
import platform
|
|
9
9
|
|
|
10
10
|
from setuptools import setup
|
|
11
11
|
from setuptools.command.build_py import build_py
|
|
@@ -57,15 +57,16 @@ def _ensure_babel():
|
|
|
57
57
|
except Exception as exc:
|
|
58
58
|
attempts.append("ensurepip + pip -> %s" % exc)
|
|
59
59
|
|
|
60
|
-
# 3) apt-get install python3-babel (Debian/Ubuntu container with root)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
# 3) apt-get install python3-babel (Debian/Ubuntu container with root) — Linux only
|
|
61
|
+
if sys.platform == "linux":
|
|
62
|
+
try:
|
|
63
|
+
_run(["apt-get", "update", "-qq"])
|
|
64
|
+
_run(["apt-get", "install", "-y", "-qq", "python3-babel"])
|
|
65
|
+
result = _try_import_babel()
|
|
66
|
+
if result:
|
|
67
|
+
return result
|
|
68
|
+
except Exception as exc:
|
|
69
|
+
attempts.append("apt-get install python3-babel -> %s" % exc)
|
|
69
70
|
|
|
70
71
|
# 4) download get-pip.py via urllib, bootstrap pip, then pip install babel
|
|
71
72
|
try:
|
|
@@ -18,10 +18,12 @@ from a2a.types import (
|
|
|
18
18
|
from google.protobuf.json_format import ParseDict
|
|
19
19
|
|
|
20
20
|
from iac_code import __version__
|
|
21
|
+
from iac_code.a2a.exposure import format_a2a_exposure_types
|
|
21
22
|
from iac_code.a2a.parts import supported_input_mime_types
|
|
22
23
|
from iac_code.a2a.signing import sign_agent_card_dict
|
|
23
24
|
|
|
24
25
|
IAC_CODE_ARTIFACT_METADATA_EXTENSION_URI = "urn:iac-code:a2a:artifact-metadata:v1"
|
|
26
|
+
IAC_CODE_THINKING_EXPOSURE_EXTENSION_URI = "urn:iac-code:a2a:thinking-exposure:v1"
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def _base_url(host: str, port: int) -> str:
|
|
@@ -66,6 +68,7 @@ def build_agent_card(
|
|
|
66
68
|
push_notifications: bool = False,
|
|
67
69
|
supported_interfaces: list[dict[str, str]] | None = None,
|
|
68
70
|
agent_extensions: Any = None,
|
|
71
|
+
thinking_exposure_types: Any = None,
|
|
69
72
|
) -> AgentCard:
|
|
70
73
|
url = _base_url(host, port)
|
|
71
74
|
description = "AI-powered Infrastructure as Code assistant for Alibaba Cloud ROS and Terraform workflows."
|
|
@@ -152,6 +155,19 @@ def build_agent_card(
|
|
|
152
155
|
required=False,
|
|
153
156
|
)
|
|
154
157
|
)
|
|
158
|
+
if thinking_exposure_types is not None:
|
|
159
|
+
enabled_types = format_a2a_exposure_types(thinking_exposure_types)
|
|
160
|
+
if enabled_types:
|
|
161
|
+
extension = AgentExtension(
|
|
162
|
+
uri=IAC_CODE_THINKING_EXPOSURE_EXTENSION_URI,
|
|
163
|
+
description=(
|
|
164
|
+
"Optional iac-code metadata namespace for selected thinking exposure signals. "
|
|
165
|
+
"Raw thinking is emitted only when raw_thinking is enabled."
|
|
166
|
+
),
|
|
167
|
+
required=False,
|
|
168
|
+
)
|
|
169
|
+
ParseDict({"enabledTypes": enabled_types}, extension.params)
|
|
170
|
+
card.capabilities.extensions.append(extension)
|
|
155
171
|
for item in _iter_agent_extensions(agent_extensions):
|
|
156
172
|
card.capabilities.extensions.append(_agent_extension_from_dict(item))
|
|
157
173
|
|
|
@@ -166,6 +166,7 @@ def create_app(
|
|
|
166
166
|
supported_interfaces: list[dict[str, str]] | None = None,
|
|
167
167
|
agent_extensions: object | None = None,
|
|
168
168
|
auto_approve_permissions: bool = False,
|
|
169
|
+
thinking_exposure: object | None = None,
|
|
169
170
|
) -> Starlette:
|
|
170
171
|
from iac_code.a2a.transports.dispatcher import create_runtime_components
|
|
171
172
|
|
|
@@ -194,6 +195,7 @@ def create_app(
|
|
|
194
195
|
supported_interfaces=supported_interfaces,
|
|
195
196
|
agent_extensions=agent_extensions,
|
|
196
197
|
auto_approve_permissions=auto_approve_permissions,
|
|
198
|
+
thinking_exposure=thinking_exposure,
|
|
197
199
|
)
|
|
198
200
|
|
|
199
201
|
@asynccontextmanager
|
|
@@ -287,8 +289,9 @@ def run_server(
|
|
|
287
289
|
push_consumer_name: str | None = None,
|
|
288
290
|
push_lease_timeout_ms: int = 300_000,
|
|
289
291
|
auto_approve_permissions: bool = False,
|
|
292
|
+
thinking_exposure: object | None = None,
|
|
290
293
|
) -> None:
|
|
291
|
-
from iac_code.a2a.transports.base import normalize_transport_name
|
|
294
|
+
from iac_code.a2a.transports.base import normalize_transport_name, validate_transport_for_platform
|
|
292
295
|
|
|
293
296
|
normalized_transport = normalize_transport_name(transport)
|
|
294
297
|
if persistence_dir is None:
|
|
@@ -305,6 +308,8 @@ def run_server(
|
|
|
305
308
|
if push_queue == "redis-streams" and not push_redis_url:
|
|
306
309
|
raise RuntimeError("--push-redis-url is required for --push-queue redis-streams.")
|
|
307
310
|
|
|
311
|
+
validate_transport_for_platform(normalized_transport)
|
|
312
|
+
|
|
308
313
|
supported_interfaces = _supported_interfaces(
|
|
309
314
|
transport=normalized_transport,
|
|
310
315
|
host=host,
|
|
@@ -345,6 +350,7 @@ def run_server(
|
|
|
345
350
|
"push_lease_timeout_ms": push_lease_timeout_ms,
|
|
346
351
|
"supported_interfaces": supported_interfaces,
|
|
347
352
|
"auto_approve_permissions": auto_approve_permissions,
|
|
353
|
+
"thinking_exposure": thinking_exposure,
|
|
348
354
|
}
|
|
349
355
|
|
|
350
356
|
if normalized_transport == "stdio":
|
|
@@ -442,6 +448,7 @@ def run_server(
|
|
|
442
448
|
push_lease_timeout_ms=push_lease_timeout_ms,
|
|
443
449
|
supported_interfaces=supported_interfaces,
|
|
444
450
|
auto_approve_permissions=auto_approve_permissions,
|
|
451
|
+
thinking_exposure=thinking_exposure,
|
|
445
452
|
)
|
|
446
453
|
|
|
447
454
|
try:
|
|
@@ -302,7 +302,7 @@ class A2AClient:
|
|
|
302
302
|
message: dict[str, Any] = {
|
|
303
303
|
"messageId": str(uuid.uuid4()),
|
|
304
304
|
"role": "ROLE_USER",
|
|
305
|
-
"parts": [{"
|
|
305
|
+
"parts": [{"text": prompt}],
|
|
306
306
|
"metadata": {"iac_code": {"cwd": cwd}},
|
|
307
307
|
}
|
|
308
308
|
if context_id:
|
|
@@ -18,6 +18,7 @@ from a2a.types import (
|
|
|
18
18
|
)
|
|
19
19
|
from google.protobuf.json_format import ParseDict
|
|
20
20
|
|
|
21
|
+
from iac_code.a2a.exposure import A2AExposureType, normalize_a2a_exposure_types
|
|
21
22
|
from iac_code.types.stream_events import (
|
|
22
23
|
ErrorEvent,
|
|
23
24
|
MessageEndEvent,
|
|
@@ -31,6 +32,7 @@ from iac_code.types.stream_events import (
|
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
_METADATA_MAX_CHARS = 4000
|
|
35
|
+
_ERROR_TEXT_MAX_CHARS = 1000
|
|
34
36
|
_METADATA_MAX_DEPTH = 32
|
|
35
37
|
logger = logging.getLogger(__name__)
|
|
36
38
|
A2APermissionResolver: TypeAlias = Callable[[PermissionRequestEvent], "bool | Awaitable[bool]"]
|
|
@@ -169,7 +171,10 @@ async def publish_stream_event(
|
|
|
169
171
|
artifact_store: Any | None = None,
|
|
170
172
|
permission_resolver: A2APermissionResolver | None = None,
|
|
171
173
|
auto_approve_permissions: bool = False,
|
|
174
|
+
exposure_types: Any = None,
|
|
172
175
|
) -> str | None:
|
|
176
|
+
enabled_exposure_types = normalize_a2a_exposure_types(exposure_types)
|
|
177
|
+
|
|
173
178
|
if isinstance(event, TextDeltaEvent):
|
|
174
179
|
if not event.text:
|
|
175
180
|
return None
|
|
@@ -183,9 +188,20 @@ async def publish_stream_event(
|
|
|
183
188
|
return event.text
|
|
184
189
|
|
|
185
190
|
if isinstance(event, ThinkingDeltaEvent):
|
|
191
|
+
if A2AExposureType.RAW_THINKING not in enabled_exposure_types:
|
|
192
|
+
return None
|
|
193
|
+
await _enqueue_status(
|
|
194
|
+
event_queue,
|
|
195
|
+
task_id=task_id,
|
|
196
|
+
context_id=context_id,
|
|
197
|
+
state=TaskState.TASK_STATE_WORKING,
|
|
198
|
+
metadata={"iac_code": {"thinking": {"type": "raw_thinking", "text": _truncate(event.text)}}},
|
|
199
|
+
)
|
|
186
200
|
return None
|
|
187
201
|
|
|
188
202
|
if isinstance(event, ToolUseStartEvent):
|
|
203
|
+
if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
|
|
204
|
+
return None
|
|
189
205
|
await _enqueue_status(
|
|
190
206
|
event_queue,
|
|
191
207
|
task_id=task_id,
|
|
@@ -196,6 +212,8 @@ async def publish_stream_event(
|
|
|
196
212
|
return None
|
|
197
213
|
|
|
198
214
|
if isinstance(event, ToolInputDeltaEvent):
|
|
215
|
+
if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
|
|
216
|
+
return None
|
|
199
217
|
await _enqueue_status(
|
|
200
218
|
event_queue,
|
|
201
219
|
task_id=task_id,
|
|
@@ -214,6 +232,8 @@ async def publish_stream_event(
|
|
|
214
232
|
return None
|
|
215
233
|
|
|
216
234
|
if isinstance(event, ToolUseEndEvent):
|
|
235
|
+
if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
|
|
236
|
+
return None
|
|
217
237
|
await _enqueue_status(
|
|
218
238
|
event_queue,
|
|
219
239
|
task_id=task_id,
|
|
@@ -234,6 +254,12 @@ async def publish_stream_event(
|
|
|
234
254
|
|
|
235
255
|
if isinstance(event, ToolResultEvent):
|
|
236
256
|
artifact_metadata = _extract_artifact_metadata(event.result, artifact_store)
|
|
257
|
+
if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
|
|
258
|
+
if artifact_metadata is not None:
|
|
259
|
+
await event_queue.enqueue_event(
|
|
260
|
+
_artifact_update_event(task_id=task_id, context_id=context_id, metadata=artifact_metadata)
|
|
261
|
+
)
|
|
262
|
+
return None
|
|
237
263
|
tool_metadata = {
|
|
238
264
|
"status": "failed" if event.is_error else "completed",
|
|
239
265
|
"toolUseId": event.tool_use_id,
|
|
@@ -261,6 +287,8 @@ async def publish_stream_event(
|
|
|
261
287
|
approved = bool(await decision) if inspect.isawaitable(decision) else bool(decision)
|
|
262
288
|
if event.response_future is not None and not event.response_future.done():
|
|
263
289
|
event.response_future.set_result(approved)
|
|
290
|
+
if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
|
|
291
|
+
return None
|
|
264
292
|
await _enqueue_status(
|
|
265
293
|
event_queue,
|
|
266
294
|
task_id=task_id,
|
|
@@ -298,18 +326,19 @@ async def publish_stream_event(
|
|
|
298
326
|
return None
|
|
299
327
|
|
|
300
328
|
if isinstance(event, ErrorEvent):
|
|
329
|
+
if event.is_retryable:
|
|
330
|
+
text = "A temporary error occurred. Please retry."
|
|
331
|
+
state = TaskState.TASK_STATE_INPUT_REQUIRED
|
|
332
|
+
else:
|
|
333
|
+
raw = event.error or "Unknown error"
|
|
334
|
+
text = raw[:_ERROR_TEXT_MAX_CHARS]
|
|
335
|
+
state = TaskState.TASK_STATE_FAILED
|
|
301
336
|
await _enqueue_status(
|
|
302
337
|
event_queue,
|
|
303
338
|
task_id=task_id,
|
|
304
339
|
context_id=context_id,
|
|
305
|
-
state=
|
|
306
|
-
message=_agent_text_message(
|
|
307
|
-
task_id=task_id,
|
|
308
|
-
context_id=context_id,
|
|
309
|
-
text="A temporary error occurred. Please retry."
|
|
310
|
-
if event.is_retryable
|
|
311
|
-
else "An internal error occurred.",
|
|
312
|
-
),
|
|
340
|
+
state=state,
|
|
341
|
+
message=_agent_text_message(task_id=task_id, context_id=context_id, text=text),
|
|
313
342
|
)
|
|
314
343
|
return None
|
|
315
344
|
|
|
@@ -15,6 +15,7 @@ from a2a.types import Message, Role, Task, TaskState, TaskStatus, TaskStatusUpda
|
|
|
15
15
|
from google.protobuf.json_format import MessageToDict
|
|
16
16
|
|
|
17
17
|
from iac_code.a2a.events import make_text_part, publish_stream_event
|
|
18
|
+
from iac_code.a2a.exposure import normalize_a2a_exposure_types
|
|
18
19
|
from iac_code.a2a.metrics import A2AMetrics, NoOpA2AMetrics
|
|
19
20
|
from iac_code.a2a.parts import allowed_cwd_roots, is_relative_to, parts_to_prompt
|
|
20
21
|
from iac_code.a2a.task_store import A2ATaskStore
|
|
@@ -26,9 +27,20 @@ from iac_code.a2a.types import (
|
|
|
26
27
|
)
|
|
27
28
|
from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_runtime
|
|
28
29
|
from iac_code.services.session_storage import SessionStorage
|
|
30
|
+
from iac_code.services.telemetry import use_session_id
|
|
29
31
|
|
|
30
32
|
logger = logging.getLogger(__name__)
|
|
31
33
|
_CONTEXT_LOCK_ACQUIRE_TIMEOUT_SECONDS = 1
|
|
34
|
+
_ERROR_TEXT_MAX_CHARS = 1000
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _format_exception(exc: BaseException) -> str:
|
|
38
|
+
message = str(exc)
|
|
39
|
+
if not message:
|
|
40
|
+
return type(exc).__name__
|
|
41
|
+
return f"{type(exc).__name__}: {message[:_ERROR_TEXT_MAX_CHARS]}"
|
|
42
|
+
|
|
43
|
+
|
|
32
44
|
A2APermissionResolver: TypeAlias = Callable[[Any], "bool | Awaitable[bool]"]
|
|
33
45
|
|
|
34
46
|
|
|
@@ -51,6 +63,7 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
51
63
|
push_notifier: Any | None = None,
|
|
52
64
|
permission_resolver: A2APermissionResolver | None = None,
|
|
53
65
|
auto_approve_permissions: bool = False,
|
|
66
|
+
thinking_exposure_types: Any = None,
|
|
54
67
|
) -> None:
|
|
55
68
|
self._task_store = task_store
|
|
56
69
|
self._model = model
|
|
@@ -59,6 +72,7 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
59
72
|
self._push_notifier = push_notifier
|
|
60
73
|
self._permission_resolver = permission_resolver
|
|
61
74
|
self._auto_approve_permissions = auto_approve_permissions
|
|
75
|
+
self._thinking_exposure_types = normalize_a2a_exposure_types(thinking_exposure_types)
|
|
62
76
|
|
|
63
77
|
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
|
|
64
78
|
task_id = context.task_id or "task-" + uuid.uuid4().hex[:12]
|
|
@@ -208,18 +222,20 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
208
222
|
context_id=context_id,
|
|
209
223
|
state=TaskState.TASK_STATE_WORKING,
|
|
210
224
|
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
with use_session_id(ctx.session_id):
|
|
226
|
+
async for event in runtime.agent_loop.run_streaming(prompt):
|
|
227
|
+
text_chunk = await publish_stream_event(
|
|
228
|
+
event_queue,
|
|
229
|
+
task_id=task_id,
|
|
230
|
+
context_id=context_id,
|
|
231
|
+
event=event,
|
|
232
|
+
artifact_store=self._artifact_store,
|
|
233
|
+
permission_resolver=self._permission_resolver,
|
|
234
|
+
auto_approve_permissions=self._auto_approve_permissions,
|
|
235
|
+
exposure_types=self._thinking_exposure_types,
|
|
236
|
+
)
|
|
237
|
+
if text_chunk:
|
|
238
|
+
task.output_text.append(text_chunk)
|
|
223
239
|
task.state = TASK_STATE_INPUT_REQUIRED
|
|
224
240
|
await self._publish_status(
|
|
225
241
|
event_queue,
|
|
@@ -274,6 +290,17 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
274
290
|
ctx.touch()
|
|
275
291
|
task.touch()
|
|
276
292
|
self._task_store.mirror_context(ctx)
|
|
293
|
+
# Force-flush telemetry between tasks. The a2a server may run in
|
|
294
|
+
# an ephemeral sandbox that's destroyed immediately after the
|
|
295
|
+
# response is delivered, before the natural batch interval or
|
|
296
|
+
# process-exit graceful_shutdown can run. Synchronous flush is
|
|
297
|
+
# offloaded to a worker thread so the event loop is not blocked.
|
|
298
|
+
from iac_code.services.telemetry import flush_telemetry
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
await asyncio.to_thread(flush_telemetry)
|
|
302
|
+
except Exception:
|
|
303
|
+
logger.debug("flush_telemetry after task failed", exc_info=True)
|
|
277
304
|
finally:
|
|
278
305
|
lock.release()
|
|
279
306
|
|
|
@@ -338,7 +365,7 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
338
365
|
if status == 401:
|
|
339
366
|
return "Authentication required. Please configure your API credentials."
|
|
340
367
|
logger.exception("Unhandled A2A executor error")
|
|
341
|
-
return
|
|
368
|
+
return _format_exception(exc)
|
|
342
369
|
|
|
343
370
|
async def _publish_status(
|
|
344
371
|
self,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class A2AExposureType(str, Enum):
|
|
9
|
+
RAW_THINKING = "raw_thinking"
|
|
10
|
+
TOOL_TRACE = "tool_trace"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_EXPOSURE_TYPE_ORDER = (
|
|
14
|
+
A2AExposureType.RAW_THINKING,
|
|
15
|
+
A2AExposureType.TOOL_TRACE,
|
|
16
|
+
)
|
|
17
|
+
_SUPPORTED_TYPE_NAMES = ", ".join(item.value.replace("_", "-") for item in _EXPOSURE_TYPE_ORDER)
|
|
18
|
+
_DISABLED_ALIASES = frozenset({"", "none", "off", "false", "0"})
|
|
19
|
+
_ALL_ALIASES = frozenset({"all", "*"})
|
|
20
|
+
DEFAULT_A2A_EXPOSURE_TYPES = frozenset({A2AExposureType.TOOL_TRACE})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_a2a_exposure_types(value: Any) -> frozenset[A2AExposureType]:
|
|
24
|
+
if value is None:
|
|
25
|
+
return DEFAULT_A2A_EXPOSURE_TYPES
|
|
26
|
+
|
|
27
|
+
tokens = list(_iter_exposure_tokens(value))
|
|
28
|
+
if not tokens:
|
|
29
|
+
return frozenset()
|
|
30
|
+
if any(token in _ALL_ALIASES for token in tokens):
|
|
31
|
+
return frozenset(_EXPOSURE_TYPE_ORDER)
|
|
32
|
+
tokens = [token for token in tokens if token not in _DISABLED_ALIASES]
|
|
33
|
+
return frozenset(_exposure_type_from_token(token) for token in tokens)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def format_a2a_exposure_types(value: Any) -> list[str]:
|
|
37
|
+
enabled = normalize_a2a_exposure_types(value)
|
|
38
|
+
return [item.value for item in _EXPOSURE_TYPE_ORDER if item in enabled]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _iter_exposure_tokens(value: Any) -> Iterable[str]:
|
|
42
|
+
if isinstance(value, A2AExposureType):
|
|
43
|
+
yield value.value
|
|
44
|
+
return
|
|
45
|
+
if isinstance(value, str):
|
|
46
|
+
for item in value.replace(";", ",").split(","):
|
|
47
|
+
token = item.strip().lower().replace("-", "_")
|
|
48
|
+
if token:
|
|
49
|
+
yield token
|
|
50
|
+
return
|
|
51
|
+
if isinstance(value, Iterable) and not isinstance(value, dict):
|
|
52
|
+
for item in value:
|
|
53
|
+
yield from _iter_exposure_tokens(item)
|
|
54
|
+
return
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"A2A thinking exposure must be a string or list of strings. Supported values: {_SUPPORTED_TYPE_NAMES}."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _exposure_type_from_token(token: str) -> A2AExposureType:
|
|
61
|
+
try:
|
|
62
|
+
return A2AExposureType(token)
|
|
63
|
+
except ValueError as exc:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Unsupported A2A thinking exposure type {token!r}. Supported values: {_SUPPORTED_TYPE_NAMES}."
|
|
66
|
+
) from exc
|
|
@@ -9,6 +9,7 @@ from collections.abc import Iterable
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
from urllib.parse import unquote, urlparse
|
|
12
|
+
from urllib.request import url2pathname
|
|
12
13
|
|
|
13
14
|
from google.protobuf.json_format import MessageToDict
|
|
14
15
|
|
|
@@ -129,7 +130,7 @@ def _read_file_url_part(url: str, *, cwd: Path) -> str:
|
|
|
129
130
|
raise ValueError("A2A file URL parts must use local file:// URLs.")
|
|
130
131
|
|
|
131
132
|
cwd_path = cwd.resolve()
|
|
132
|
-
path = Path(unquote(parsed.path)).resolve()
|
|
133
|
+
path = Path(url2pathname(unquote(parsed.path))).resolve()
|
|
133
134
|
if not is_relative_to(path, cwd_path) or not any(is_relative_to(path, root) for root in allowed_cwd_roots()):
|
|
134
135
|
raise ValueError("A2A file URL part is outside the allowed workspace.")
|
|
135
136
|
if not path.is_file():
|
|
@@ -199,7 +200,7 @@ def _safe_file_url_path(url: str, *, cwd: Path) -> Path:
|
|
|
199
200
|
raise ValueError("A2A file URL parts must use local file:// URLs.")
|
|
200
201
|
|
|
201
202
|
cwd_path = cwd.resolve()
|
|
202
|
-
path = Path(unquote(parsed.path)).resolve()
|
|
203
|
+
path = Path(url2pathname(unquote(parsed.path))).resolve()
|
|
203
204
|
if not is_relative_to(path, cwd_path) or not any(is_relative_to(path, root) for root in allowed_cwd_roots()):
|
|
204
205
|
raise ValueError("A2A file URL part is outside the allowed workspace.")
|
|
205
206
|
if not path.is_file():
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import base64
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
|
-
import os
|
|
8
7
|
import uuid
|
|
9
8
|
from dataclasses import asdict, dataclass
|
|
10
9
|
from ipaddress import ip_address
|
|
@@ -26,6 +25,7 @@ from iac_code.a2a.persistence import A2APersistenceStore
|
|
|
26
25
|
from iac_code.a2a.push_queue import A2APushJob, A2APushQueue, LocalFileA2APushQueue
|
|
27
26
|
from iac_code.a2a.push_secrets import A2APushSecretKeyring
|
|
28
27
|
from iac_code.a2a.types import validate_protocol_id
|
|
28
|
+
from iac_code.utils.file_security import restrict_file_permissions, safe_replace
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class InvalidPushNotificationConfigError(ValueError):
|
|
@@ -74,7 +74,7 @@ class A2APushConfigStore(PushNotificationConfigStore):
|
|
|
74
74
|
self._root = Path(persistence.root) / "push_configs"
|
|
75
75
|
self._secret_keyring = secret_keyring or A2APushSecretKeyring(Path(persistence.root) / "push_keys.json")
|
|
76
76
|
self._root.mkdir(parents=True, exist_ok=True)
|
|
77
|
-
|
|
77
|
+
restrict_file_permissions(self._root, directory=True)
|
|
78
78
|
|
|
79
79
|
async def set_info(
|
|
80
80
|
self,
|
|
@@ -95,7 +95,7 @@ class A2APushConfigStore(PushNotificationConfigStore):
|
|
|
95
95
|
|
|
96
96
|
path = self._config_path(_owner(context), task_id, config.id)
|
|
97
97
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
-
|
|
98
|
+
restrict_file_permissions(path.parent, directory=True)
|
|
99
99
|
data = self._config_to_storage(config)
|
|
100
100
|
_write_json_atomic(path, data)
|
|
101
101
|
|
|
@@ -304,19 +304,12 @@ def _owner(context: ServerCallContext) -> str:
|
|
|
304
304
|
return resolve_user_scope(context)
|
|
305
305
|
|
|
306
306
|
|
|
307
|
-
def _chmod_private(path: Path, *, directory: bool) -> None:
|
|
308
|
-
try:
|
|
309
|
-
os.chmod(path, 0o700 if directory else 0o600)
|
|
310
|
-
except OSError:
|
|
311
|
-
return
|
|
312
|
-
|
|
313
|
-
|
|
314
307
|
def _write_json_atomic(path: Path, data: dict[str, Any]) -> None:
|
|
315
308
|
tmp_path = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
|
|
316
309
|
try:
|
|
317
310
|
tmp_path.write_text(json.dumps(data, ensure_ascii=False, sort_keys=True), encoding="utf-8")
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
311
|
+
restrict_file_permissions(tmp_path, directory=False)
|
|
312
|
+
safe_replace(str(tmp_path), str(path))
|
|
313
|
+
restrict_file_permissions(path, directory=False)
|
|
321
314
|
finally:
|
|
322
315
|
tmp_path.unlink(missing_ok=True)
|
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from typing import Any, Protocol
|
|
14
14
|
|
|
15
15
|
from iac_code.a2a.push_secrets import A2APushSecretKeyring
|
|
16
|
+
from iac_code.utils.file_security import restrict_file_permissions
|
|
16
17
|
|
|
17
18
|
_REDACTED_HEADERS = {"authorization", "x-a2a-notification-token", "x-api-key", "api-key"}
|
|
18
19
|
_ENCRYPTED_JOB_FIELD = "iacCodeEncryptedPushJob"
|
|
@@ -127,7 +128,7 @@ class LocalFileA2APushQueue:
|
|
|
127
128
|
self.dead_dir = self.root / "dead"
|
|
128
129
|
for path in (self.pending_dir, self.inflight_dir, self.dead_dir):
|
|
129
130
|
path.mkdir(parents=True, exist_ok=True)
|
|
130
|
-
|
|
131
|
+
restrict_file_permissions(path, directory=True)
|
|
131
132
|
|
|
132
133
|
async def enqueue(self, job: A2APushJob) -> None:
|
|
133
134
|
self._write(self.pending_dir / f"{job.job_id}.json", job)
|
|
@@ -163,7 +164,7 @@ class LocalFileA2APushQueue:
|
|
|
163
164
|
|
|
164
165
|
def _write(self, path: Path, job: A2APushJob) -> None:
|
|
165
166
|
path.write_text(_serialize_push_job(job, secret_keyring=self._secret_keyring), encoding="utf-8")
|
|
166
|
-
|
|
167
|
+
restrict_file_permissions(path, directory=False)
|
|
167
168
|
|
|
168
169
|
def _read(self, path: Path) -> A2APushJob:
|
|
169
170
|
return _deserialize_push_job(path.read_text(encoding="utf-8"), secret_keyring=self._secret_keyring)
|
|
@@ -180,12 +181,6 @@ class LocalFileA2APushQueue:
|
|
|
180
181
|
job.with_attempt(attempt=job.attempt, next_attempt_at=now, last_error="Delivery lease expired."),
|
|
181
182
|
)
|
|
182
183
|
|
|
183
|
-
def _chmod_private(self, path: Path, *, directory: bool) -> None:
|
|
184
|
-
try:
|
|
185
|
-
os.chmod(path, 0o700 if directory else 0o600)
|
|
186
|
-
except OSError:
|
|
187
|
-
return
|
|
188
|
-
|
|
189
184
|
|
|
190
185
|
class RedisStreamsA2APushQueue:
|
|
191
186
|
def __init__(
|
|
@@ -9,6 +9,8 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
from cryptography.fernet import Fernet, InvalidToken
|
|
11
11
|
|
|
12
|
+
from iac_code.utils.file_security import restrict_file_permissions
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class A2APushSecretError(ValueError):
|
|
14
16
|
pass
|
|
@@ -90,7 +92,7 @@ class A2APushSecretKeyring:
|
|
|
90
92
|
|
|
91
93
|
def _write(self) -> None:
|
|
92
94
|
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
-
|
|
95
|
+
restrict_file_permissions(self._path.parent, directory=True)
|
|
94
96
|
data = {
|
|
95
97
|
"activeKeyId": self._active_key_id,
|
|
96
98
|
"keys": [
|
|
@@ -99,15 +101,8 @@ class A2APushSecretKeyring:
|
|
|
99
101
|
],
|
|
100
102
|
}
|
|
101
103
|
self._path.write_text(json.dumps(data, sort_keys=True), encoding="utf-8")
|
|
102
|
-
|
|
104
|
+
restrict_file_permissions(self._path, directory=False)
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
def _new_key_id() -> str:
|
|
106
108
|
return f"push-{int(time.time())}-{uuid.uuid4().hex[:12]}"
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _chmod_private(path: Path, *, directory: bool) -> None:
|
|
110
|
-
try:
|
|
111
|
-
os.chmod(path, 0o700 if directory else 0o600)
|
|
112
|
-
except OSError:
|
|
113
|
-
return
|