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.
Files changed (289) hide show
  1. {iac_code-0.2.2 → iac_code-0.3.0}/PKG-INFO +9 -1
  2. {iac_code-0.2.2 → iac_code-0.3.0}/README.md +8 -0
  3. {iac_code-0.2.2 → iac_code-0.3.0}/pyproject.toml +4 -0
  4. {iac_code-0.2.2 → iac_code-0.3.0}/setup.py +11 -10
  5. iac_code-0.3.0/src/iac_code/__init__.py +2 -0
  6. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/agent_card.py +16 -0
  7. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/app.py +8 -1
  8. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/client.py +1 -1
  9. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/events.py +37 -8
  10. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/executor.py +40 -13
  11. iac_code-0.3.0/src/iac_code/a2a/exposure.py +66 -0
  12. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/parts.py +3 -2
  13. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push.py +6 -13
  14. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_queue.py +3 -8
  15. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_secrets.py +4 -9
  16. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/base.py +13 -0
  17. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/dispatcher.py +5 -0
  18. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/http.py +1 -1
  19. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/stdio.py +62 -1
  20. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/__init__.py +3 -1
  21. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/session.py +21 -8
  22. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/system_prompt.py +12 -23
  23. iac_code-0.3.0/src/iac_code/cli/install_git_bash.py +85 -0
  24. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/main.py +206 -9
  25. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/auth.py +457 -177
  26. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/config.py +2 -2
  27. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/__init__.py +33 -8
  28. iac_code-0.3.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  29. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +413 -249
  30. iac_code-0.3.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  31. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +418 -249
  32. iac_code-0.3.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  33. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +420 -249
  34. iac_code-0.3.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  35. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +398 -249
  36. iac_code-0.3.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  37. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +414 -249
  38. iac_code-0.3.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  39. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +392 -249
  40. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/memory_manager.py +2 -2
  41. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/manager.py +3 -3
  42. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/auto_detect.py +3 -2
  43. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/multimodal.py +1 -1
  44. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/loader.py +1 -1
  45. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/providers/aliyun.py +3 -3
  46. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/__init__.py +13 -0
  47. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/client.py +27 -6
  48. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/identity.py +32 -1
  49. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +14 -13
  50. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/__init__.py +1 -1
  51. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +5 -3
  52. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/discovery.py +2 -1
  53. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/renderer.py +33 -7
  54. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/bash_tool.py +25 -8
  55. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/path_validation.py +21 -2
  56. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/safety_checks.py +31 -8
  57. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +2 -2
  58. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/api_hooks.py +8 -3
  59. iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +76 -0
  60. iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +157 -0
  61. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_stack.py +16 -6
  62. iac_code-0.3.0/src/iac_code/tools/cloud/aliyun/ros_yaml.py +102 -0
  63. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/edit_file.py +2 -1
  64. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/glob.py +2 -1
  65. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/grep.py +2 -1
  66. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/list_files.py +2 -1
  67. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/read_file.py +2 -1
  68. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/write_file.py +2 -1
  69. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/banner.py +1 -1
  70. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/prompt_input.py +86 -30
  71. iac_code-0.3.0/src/iac_code/ui/core/raw_input.py +325 -0
  72. iac_code-0.3.0/src/iac_code/ui/core/raw_input_win.py +165 -0
  73. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/global_search.py +6 -3
  74. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/renderer.py +83 -33
  75. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/shell_history_provider.py +16 -1
  76. iac_code-0.3.0/src/iac_code/utils/console.py +37 -0
  77. iac_code-0.3.0/src/iac_code/utils/file_security.py +50 -0
  78. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/log.py +20 -6
  79. iac_code-0.3.0/src/iac_code/utils/platform.py +183 -0
  80. iac_code-0.3.0/src/iac_code/utils/project_paths.py +109 -0
  81. iac_code-0.3.0/src/iac_code/utils/signals.py +63 -0
  82. iac_code-0.3.0/src/iac_code/utils/windows_paths.py +42 -0
  83. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/PKG-INFO +9 -1
  84. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/SOURCES.txt +10 -0
  85. iac_code-0.2.2/src/iac_code/__init__.py +0 -2
  86. iac_code-0.2.2/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  87. iac_code-0.2.2/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  88. iac_code-0.2.2/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  89. iac_code-0.2.2/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  90. iac_code-0.2.2/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  91. iac_code-0.2.2/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  92. iac_code-0.2.2/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -57
  93. iac_code-0.2.2/src/iac_code/ui/core/raw_input.py +0 -319
  94. iac_code-0.2.2/src/iac_code/utils/project_paths.py +0 -74
  95. {iac_code-0.2.2 → iac_code-0.3.0}/LICENSE +0 -0
  96. {iac_code-0.2.2 → iac_code-0.3.0}/MANIFEST.in +0 -0
  97. {iac_code-0.2.2 → iac_code-0.3.0}/setup.cfg +0 -0
  98. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/__init__.py +0 -0
  99. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/artifacts.py +0 -0
  100. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/metrics.py +0 -0
  101. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/persistence.py +0 -0
  102. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/push_worker.py +0 -0
  103. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/router.py +0 -0
  104. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/signing.py +0 -0
  105. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/task_store.py +0 -0
  106. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transport.py +0 -0
  107. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/__init__.py +0 -0
  108. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/grpc.py +0 -0
  109. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
  110. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/redis_streams.py +0 -0
  111. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/unix.py +0 -0
  112. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/transports/websocket.py +0 -0
  113. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/a2a/types.py +0 -0
  114. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/convert.py +0 -0
  115. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/http_sse.py +0 -0
  116. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/mcp.py +0 -0
  117. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/metrics.py +0 -0
  118. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/server.py +0 -0
  119. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/slash_registry.py +0 -0
  120. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/state.py +0 -0
  121. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/tools.py +0 -0
  122. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/types.py +0 -0
  123. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/acp/version.py +0 -0
  124. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/__init__.py +0 -0
  125. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_loop.py +0 -0
  126. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_tool.py +0 -0
  127. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/agent_types.py +0 -0
  128. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/agent/message.py +0 -0
  129. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/__init__.py +0 -0
  130. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/headless.py +0 -0
  131. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/cli/output_formats.py +0 -0
  132. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/__init__.py +0 -0
  133. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/clear.py +0 -0
  134. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/compact.py +0 -0
  135. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/debug.py +0 -0
  136. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/effort.py +0 -0
  137. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/exit.py +0 -0
  138. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/help.py +0 -0
  139. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/model.py +0 -0
  140. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/registry.py +0 -0
  141. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/resume.py +0 -0
  142. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/commands/tasks.py +0 -0
  143. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/__init__.py +0 -0
  144. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/memory/memory_tools.py +0 -0
  145. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/__init__.py +0 -0
  146. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/anthropic_provider.py +0 -0
  147. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/azure_openai_provider.py +0 -0
  148. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/base.py +0 -0
  149. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/dashscope_provider.py +0 -0
  150. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/deepseek_provider.py +0 -0
  151. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/gemini_provider.py +0 -0
  152. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/kimi_provider.py +0 -0
  153. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/lmstudio_provider.py +0 -0
  154. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/minimax_provider.py +0 -0
  155. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/modelscope_provider.py +0 -0
  156. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/ollama_provider.py +0 -0
  157. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/openai_provider.py +0 -0
  158. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/openrouter_provider.py +0 -0
  159. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/registry.py +0 -0
  160. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/retry.py +0 -0
  161. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/siliconflow_provider.py +0 -0
  162. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/stream_watchdog.py +0 -0
  163. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/thinking.py +0 -0
  164. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/volcengine_provider.py +0 -0
  165. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/providers/zhipu_provider.py +0 -0
  166. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/__init__.py +0 -0
  167. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/agent_factory.py +0 -0
  168. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/capabilities/__init__.py +0 -0
  169. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/cloud_credentials.py +0 -0
  170. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/context_manager.py +0 -0
  171. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/__init__.py +0 -0
  172. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/pipeline.py +0 -0
  173. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/permissions/storage.py +0 -0
  174. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/providers/__init__.py +0 -0
  175. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/qwenpaw_source.py +0 -0
  176. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/session_index.py +0 -0
  177. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/session_storage.py +0 -0
  178. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/attributes.py +0 -0
  179. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/config.py +0 -0
  180. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/constants.py +0 -0
  181. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/content_serializer.py +0 -0
  182. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/events.py +0 -0
  183. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/fallback.py +0 -0
  184. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/metrics.py +0 -0
  185. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/names.py +0 -0
  186. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/sanitize.py +0 -0
  187. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/sink.py +0 -0
  188. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/tracing.py +0 -0
  189. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/telemetry/types.py +0 -0
  190. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/token_budget.py +0 -0
  191. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/services/token_counter.py +0 -0
  192. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/__init__.py +0 -0
  193. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/__init__.py +0 -0
  194. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
  195. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
  196. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
  197. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
  198. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
  199. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
  200. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
  201. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
  202. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
  203. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/bundled/simplify.py +0 -0
  204. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/frontmatter.py +0 -0
  205. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/listing.py +0 -0
  206. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/loader.py +0 -0
  207. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/processor.py +0 -0
  208. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/skill_definition.py +0 -0
  209. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/skills/skill_tool.py +0 -0
  210. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/state/__init__.py +0 -0
  211. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/state/app_state.py +0 -0
  212. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/__init__.py +0 -0
  213. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/notification_queue.py +0 -0
  214. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/task_state.py +0 -0
  215. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tasks/task_tools.py +0 -0
  216. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/__init__.py +0 -0
  217. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/base.py +0 -0
  218. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/__init__.py +0 -0
  219. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/command_parser.py +0 -0
  220. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/mode_validation.py +0 -0
  221. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/permissions.py +0 -0
  222. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/readonly_commands.py +0 -0
  223. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/bash/rule_matching.py +0 -0
  224. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/__init__.py +0 -0
  225. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
  226. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
  227. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
  228. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
  229. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_client.py +0 -0
  230. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +0 -0
  231. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/base_api.py +0 -0
  232. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/base_stack.py +0 -0
  233. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/registry.py +0 -0
  234. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/cloud/types.py +0 -0
  235. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/result_storage.py +0 -0
  236. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/tool_executor.py +0 -0
  237. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/tools/web_fetch.py +0 -0
  238. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/__init__.py +0 -0
  239. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/permissions.py +0 -0
  240. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/skill_source.py +0 -0
  241. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/types/stream_events.py +0 -0
  242. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/__init__.py +0 -0
  243. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/__init__.py +0 -0
  244. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/dialog.py +0 -0
  245. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/divider.py +0 -0
  246. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
  247. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/progress_bar.py +0 -0
  248. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/search_box.py +0 -0
  249. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/select.py +0 -0
  250. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/status_icon.py +0 -0
  251. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/components/tabs.py +0 -0
  252. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/__init__.py +0 -0
  253. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/in_place_render.py +0 -0
  254. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/input_history.py +0 -0
  255. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/key_event.py +0 -0
  256. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/core/screen.py +0 -0
  257. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/__init__.py +0 -0
  258. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/history_search.py +0 -0
  259. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/model_picker.py +0 -0
  260. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/quick_open.py +0 -0
  261. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/dialogs/resume_picker.py +0 -0
  262. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/keybindings/__init__.py +0 -0
  263. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/keybindings/manager.py +0 -0
  264. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/repl.py +0 -0
  265. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/spinner.py +0 -0
  266. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/__init__.py +0 -0
  267. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/aggregator.py +0 -0
  268. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/command_provider.py +0 -0
  269. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
  270. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/file_provider.py +0 -0
  271. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/token_extractor.py +0 -0
  272. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/suggestions/types.py +0 -0
  273. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/ui/transcript_view.py +0 -0
  274. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/__init__.py +0 -0
  275. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/background_housekeeping.py +0 -0
  276. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/cleanup.py +0 -0
  277. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/__init__.py +0 -0
  278. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/clipboard.py +0 -0
  279. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/format_detect.py +0 -0
  280. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/pasted_content.py +0 -0
  281. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/processor.py +0 -0
  282. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/resizer.py +0 -0
  283. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/image/store.py +0 -0
  284. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/json_utils.py +0 -0
  285. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code/utils/tool_input_parser.py +0 -0
  286. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/dependency_links.txt +0 -0
  287. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/entry_points.txt +0 -0
  288. {iac_code-0.2.2 → iac_code-0.3.0}/src/iac_code.egg-info/requires.txt +0 -0
  289. {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.2.2
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
- try:
62
- _run(["apt-get", "update", "-qq"])
63
- _run(["apt-get", "install", "-y", "-qq", "python3-babel"])
64
- result = _try_import_babel()
65
- if result:
66
- return result
67
- except Exception as exc:
68
- attempts.append("apt-get install python3-babel -> %s" % exc)
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:
@@ -0,0 +1,2 @@
1
+ __version__ = "0.3.0"
2
+ __release_date__ = "2026-05-29"
@@ -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": [{"kind": "text", "text": prompt}],
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=TaskState.TASK_STATE_INPUT_REQUIRED if event.is_retryable else TaskState.TASK_STATE_FAILED,
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
- async for event in runtime.agent_loop.run_streaming(prompt):
212
- text_chunk = await publish_stream_event(
213
- event_queue,
214
- task_id=task_id,
215
- context_id=context_id,
216
- event=event,
217
- artifact_store=self._artifact_store,
218
- permission_resolver=self._permission_resolver,
219
- auto_approve_permissions=self._auto_approve_permissions,
220
- )
221
- if text_chunk:
222
- task.output_text.append(text_chunk)
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 "An internal error occurred."
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
- _chmod_private(self._root, directory=True)
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
- _chmod_private(path.parent, directory=True)
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
- _chmod_private(tmp_path, directory=False)
319
- os.replace(tmp_path, path)
320
- _chmod_private(path, directory=False)
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
- self._chmod_private(path, directory=True)
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
- self._chmod_private(path, directory=False)
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
- _chmod_private(self._path.parent, directory=True)
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
- _chmod_private(self._path, directory=False)
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