iac-code 0.2.3__tar.gz → 0.3.1__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 (295) hide show
  1. {iac_code-0.2.3 → iac_code-0.3.1}/PKG-INFO +23 -1
  2. {iac_code-0.2.3 → iac_code-0.3.1}/README.md +22 -0
  3. {iac_code-0.2.3 → iac_code-0.3.1}/pyproject.toml +7 -2
  4. {iac_code-0.2.3 → iac_code-0.3.1}/setup.py +11 -10
  5. iac_code-0.3.1/src/iac_code/__init__.py +2 -0
  6. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/agent_card.py +16 -0
  7. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/app.py +8 -1
  8. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/client.py +46 -12
  9. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/events.py +27 -0
  10. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/executor.py +18 -12
  11. iac_code-0.3.1/src/iac_code/a2a/exposure.py +66 -0
  12. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/parts.py +3 -2
  13. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/push.py +6 -13
  14. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/push_queue.py +3 -8
  15. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/push_secrets.py +4 -9
  16. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/base.py +22 -1
  17. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/dispatcher.py +5 -0
  18. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/http.py +1 -1
  19. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/stdio.py +62 -1
  20. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/__init__.py +3 -1
  21. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/session.py +11 -9
  22. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/slash_registry.py +1 -1
  23. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/agent_loop.py +47 -0
  24. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/system_prompt.py +12 -23
  25. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/cli/headless.py +54 -0
  26. iac_code-0.3.1/src/iac_code/cli/install_git_bash.py +85 -0
  27. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/cli/main.py +81 -11
  28. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/cli/output_formats.py +6 -1
  29. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/auth.py +457 -177
  30. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/clear.py +1 -1
  31. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/registry.py +8 -4
  32. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/config.py +30 -11
  33. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/__init__.py +33 -8
  34. iac_code-0.3.1/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  35. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +510 -283
  36. iac_code-0.3.1/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  37. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +510 -283
  38. iac_code-0.3.1/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  39. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +510 -283
  40. iac_code-0.3.1/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  41. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +495 -283
  42. iac_code-0.3.1/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  43. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +509 -283
  44. iac_code-0.3.1/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  45. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +491 -283
  46. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/memory/memory_manager.py +51 -16
  47. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/registry.py +1 -1
  48. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/agent_factory.py +1 -0
  49. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/capabilities/auto_detect.py +3 -2
  50. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/capabilities/multimodal.py +1 -1
  51. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/permissions/loader.py +12 -6
  52. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/providers/aliyun.py +3 -3
  53. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/session_storage.py +8 -4
  54. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/__init__.py +2 -0
  55. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/client.py +1 -1
  56. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/config.py +9 -0
  57. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/fallback.py +4 -2
  58. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/identity.py +32 -1
  59. iac_code-0.3.1/src/iac_code/services/update_checker.py +567 -0
  60. iac_code-0.3.1/src/iac_code/skills/auto_trigger.py +115 -0
  61. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/__init__.py +2 -0
  62. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +4 -2
  63. iac_code-0.3.1/src/iac_code/skills/bundled/iac_aliyun/__init__.py +20 -0
  64. iac_code-0.3.1/src/iac_code/skills/bundled/iac_aliyun/auto_trigger.py +88 -0
  65. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +5 -3
  66. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/discovery.py +44 -27
  67. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/frontmatter.py +4 -0
  68. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/renderer.py +33 -7
  69. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/skill_definition.py +4 -0
  70. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/base.py +9 -0
  71. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/bash_tool.py +25 -8
  72. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/path_validation.py +21 -2
  73. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/safety_checks.py +31 -8
  74. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +7 -2
  75. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_client.py +5 -0
  76. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_stack.py +1 -1
  77. iac_code-0.3.1/src/iac_code/tools/cloud/aliyun/user_agent.py +23 -0
  78. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/edit_file.py +12 -3
  79. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/glob.py +2 -1
  80. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/grep.py +2 -1
  81. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/list_files.py +2 -1
  82. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/read_file.py +5 -1
  83. iac_code-0.3.1/src/iac_code/tools/result_storage.py +64 -0
  84. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/write_file.py +9 -2
  85. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/banner.py +38 -1
  86. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/input_history.py +36 -6
  87. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/prompt_input.py +33 -8
  88. iac_code-0.3.1/src/iac_code/ui/core/raw_input.py +383 -0
  89. iac_code-0.3.1/src/iac_code/ui/core/raw_input_win.py +216 -0
  90. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/global_search.py +6 -3
  91. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/renderer.py +96 -35
  92. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/repl.py +208 -9
  93. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/spinner.py +0 -2
  94. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/shell_history_provider.py +16 -1
  95. iac_code-0.3.1/src/iac_code/ui/suggestions/skill_provider.py +47 -0
  96. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/token_extractor.py +12 -1
  97. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/types.py +3 -3
  98. iac_code-0.3.1/src/iac_code/utils/console.py +37 -0
  99. iac_code-0.3.1/src/iac_code/utils/file_security.py +64 -0
  100. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/store.py +4 -2
  101. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/json_utils.py +38 -0
  102. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/log.py +26 -10
  103. iac_code-0.3.1/src/iac_code/utils/platform.py +183 -0
  104. iac_code-0.3.1/src/iac_code/utils/project_paths.py +132 -0
  105. iac_code-0.3.1/src/iac_code/utils/signals.py +63 -0
  106. iac_code-0.3.1/src/iac_code/utils/windows_paths.py +42 -0
  107. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code.egg-info/PKG-INFO +23 -1
  108. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code.egg-info/SOURCES.txt +13 -0
  109. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code.egg-info/requires.txt +3 -2
  110. iac_code-0.2.3/src/iac_code/__init__.py +0 -2
  111. iac_code-0.2.3/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  112. iac_code-0.2.3/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  113. iac_code-0.2.3/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  114. iac_code-0.2.3/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  115. iac_code-0.2.3/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  116. iac_code-0.2.3/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  117. iac_code-0.2.3/src/iac_code/skills/bundled/iac_aliyun/__init__.py +0 -16
  118. iac_code-0.2.3/src/iac_code/tools/result_storage.py +0 -39
  119. iac_code-0.2.3/src/iac_code/ui/core/raw_input.py +0 -319
  120. iac_code-0.2.3/src/iac_code/utils/project_paths.py +0 -74
  121. {iac_code-0.2.3 → iac_code-0.3.1}/LICENSE +0 -0
  122. {iac_code-0.2.3 → iac_code-0.3.1}/MANIFEST.in +0 -0
  123. {iac_code-0.2.3 → iac_code-0.3.1}/setup.cfg +0 -0
  124. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/__init__.py +0 -0
  125. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/artifacts.py +0 -0
  126. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/metrics.py +0 -0
  127. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/persistence.py +0 -0
  128. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/push_worker.py +0 -0
  129. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/router.py +0 -0
  130. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/signing.py +0 -0
  131. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/task_store.py +0 -0
  132. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transport.py +0 -0
  133. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/__init__.py +0 -0
  134. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/grpc.py +0 -0
  135. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
  136. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/redis_streams.py +0 -0
  137. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/unix.py +0 -0
  138. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/transports/websocket.py +0 -0
  139. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/a2a/types.py +0 -0
  140. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/convert.py +0 -0
  141. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/http_sse.py +0 -0
  142. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/mcp.py +0 -0
  143. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/metrics.py +0 -0
  144. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/server.py +0 -0
  145. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/state.py +0 -0
  146. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/tools.py +0 -0
  147. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/types.py +0 -0
  148. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/acp/version.py +0 -0
  149. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/__init__.py +0 -0
  150. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/agent_tool.py +0 -0
  151. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/agent_types.py +0 -0
  152. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/agent/message.py +0 -0
  153. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/cli/__init__.py +0 -0
  154. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/__init__.py +0 -0
  155. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/compact.py +0 -0
  156. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/debug.py +0 -0
  157. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/effort.py +0 -0
  158. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/exit.py +0 -0
  159. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/help.py +0 -0
  160. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/model.py +0 -0
  161. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/resume.py +0 -0
  162. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/commands/tasks.py +0 -0
  163. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/memory/__init__.py +0 -0
  164. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/memory/memory_tools.py +0 -0
  165. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/__init__.py +0 -0
  166. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/anthropic_provider.py +0 -0
  167. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/azure_openai_provider.py +0 -0
  168. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/base.py +0 -0
  169. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/dashscope_provider.py +0 -0
  170. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/deepseek_provider.py +0 -0
  171. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/gemini_provider.py +0 -0
  172. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/kimi_provider.py +0 -0
  173. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/lmstudio_provider.py +0 -0
  174. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/manager.py +0 -0
  175. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/minimax_provider.py +0 -0
  176. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/modelscope_provider.py +0 -0
  177. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/ollama_provider.py +0 -0
  178. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/openai_provider.py +0 -0
  179. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/openrouter_provider.py +0 -0
  180. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/retry.py +0 -0
  181. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/siliconflow_provider.py +0 -0
  182. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/stream_watchdog.py +0 -0
  183. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/thinking.py +0 -0
  184. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/volcengine_provider.py +0 -0
  185. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/providers/zhipu_provider.py +0 -0
  186. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/__init__.py +0 -0
  187. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/capabilities/__init__.py +0 -0
  188. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/cloud_credentials.py +0 -0
  189. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/context_manager.py +0 -0
  190. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/permissions/__init__.py +0 -0
  191. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/permissions/pipeline.py +0 -0
  192. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/permissions/storage.py +0 -0
  193. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/providers/__init__.py +0 -0
  194. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/qwenpaw_source.py +0 -0
  195. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/session_index.py +0 -0
  196. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/attributes.py +0 -0
  197. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/constants.py +0 -0
  198. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/content_serializer.py +0 -0
  199. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/events.py +0 -0
  200. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/metrics.py +0 -0
  201. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/names.py +0 -0
  202. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/sanitize.py +0 -0
  203. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/sink.py +0 -0
  204. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/tracing.py +0 -0
  205. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/telemetry/types.py +0 -0
  206. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/token_budget.py +0 -0
  207. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/services/token_counter.py +0 -0
  208. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/__init__.py +0 -0
  209. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
  210. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
  211. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
  212. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
  213. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
  214. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
  215. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
  216. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
  217. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
  218. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/bundled/simplify.py +0 -0
  219. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/listing.py +0 -0
  220. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/loader.py +0 -0
  221. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/processor.py +0 -0
  222. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/skills/skill_tool.py +0 -0
  223. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/state/__init__.py +0 -0
  224. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/state/app_state.py +0 -0
  225. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tasks/__init__.py +0 -0
  226. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tasks/notification_queue.py +0 -0
  227. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tasks/task_state.py +0 -0
  228. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tasks/task_tools.py +0 -0
  229. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/__init__.py +0 -0
  230. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/__init__.py +0 -0
  231. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/command_parser.py +0 -0
  232. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/mode_validation.py +0 -0
  233. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/permissions.py +0 -0
  234. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/readonly_commands.py +0 -0
  235. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/bash/rule_matching.py +0 -0
  236. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/__init__.py +0 -0
  237. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
  238. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
  239. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/api_hooks.py +0 -0
  240. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
  241. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
  242. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +0 -0
  243. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -0
  244. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +0 -0
  245. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/aliyun/ros_yaml.py +0 -0
  246. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/base_api.py +0 -0
  247. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/base_stack.py +0 -0
  248. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/registry.py +0 -0
  249. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/cloud/types.py +0 -0
  250. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/tool_executor.py +0 -0
  251. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/tools/web_fetch.py +0 -0
  252. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/types/__init__.py +0 -0
  253. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/types/permissions.py +0 -0
  254. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/types/skill_source.py +0 -0
  255. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/types/stream_events.py +0 -0
  256. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/__init__.py +0 -0
  257. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/__init__.py +0 -0
  258. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/dialog.py +0 -0
  259. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/divider.py +0 -0
  260. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
  261. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/progress_bar.py +0 -0
  262. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/search_box.py +0 -0
  263. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/select.py +0 -0
  264. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/status_icon.py +0 -0
  265. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/components/tabs.py +0 -0
  266. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/__init__.py +0 -0
  267. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/in_place_render.py +0 -0
  268. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/key_event.py +0 -0
  269. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/core/screen.py +0 -0
  270. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/__init__.py +0 -0
  271. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/history_search.py +0 -0
  272. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/model_picker.py +0 -0
  273. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/quick_open.py +0 -0
  274. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/dialogs/resume_picker.py +0 -0
  275. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/keybindings/__init__.py +0 -0
  276. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/keybindings/manager.py +0 -0
  277. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/__init__.py +0 -0
  278. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/aggregator.py +0 -0
  279. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/command_provider.py +0 -0
  280. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
  281. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/suggestions/file_provider.py +0 -0
  282. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/ui/transcript_view.py +0 -0
  283. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/__init__.py +0 -0
  284. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/background_housekeeping.py +0 -0
  285. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/cleanup.py +0 -0
  286. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/__init__.py +0 -0
  287. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/clipboard.py +0 -0
  288. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/format_detect.py +0 -0
  289. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/pasted_content.py +0 -0
  290. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/processor.py +0 -0
  291. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/image/resizer.py +0 -0
  292. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code/utils/tool_input_parser.py +0 -0
  293. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code.egg-info/dependency_links.txt +0 -0
  294. {iac_code-0.2.3 → iac_code-0.3.1}/src/iac_code.egg-info/entry_points.txt +0 -0
  295. {iac_code-0.2.3 → iac_code-0.3.1}/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.3
3
+ Version: 0.3.1
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
  ```
@@ -66,6 +74,20 @@ Reading from stdin is also supported:
66
74
  echo "Create an OSS Bucket" | iac-code --prompt -
67
75
  ```
68
76
 
77
+ ## Contributing
78
+
79
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then:
80
+
81
+ ```bash
82
+ make install # install dependencies and pre-commit hooks
83
+ make dev # run in debug mode
84
+ make test # run tests
85
+ make lint # run linters
86
+ make format # format code
87
+ ```
88
+
89
+ See the [Contributing Guide](https://aliyun.github.io/iac-code/getting-started/contributing) for details.
90
+
69
91
  ## Contact Us
70
92
 
71
93
  | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [Discord](https://discord.gg/qECFuFBwF) |
@@ -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
  ```
@@ -47,6 +55,20 @@ Reading from stdin is also supported:
47
55
  echo "Create an OSS Bucket" | iac-code --prompt -
48
56
  ```
49
57
 
58
+ ## Contributing
59
+
60
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then:
61
+
62
+ ```bash
63
+ make install # install dependencies and pre-commit hooks
64
+ make dev # run in debug mode
65
+ make test # run tests
66
+ make lint # run linters
67
+ make format # format code
68
+ ```
69
+
70
+ See the [Contributing Guide](https://aliyun.github.io/iac-code/getting-started/contributing) for details.
71
+
50
72
  ## Contact Us
51
73
 
52
74
  | [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [Discord](https://discord.gg/qECFuFBwF) |
@@ -21,6 +21,7 @@ dependencies = [
21
21
  "pyperclip>=1.8.0",
22
22
  "openai>=1.50",
23
23
  "httpx>=0.27.0",
24
+ "packaging>=24.0",
24
25
  "tiktoken>=0.7.0",
25
26
  "jsonschema>=4.20",
26
27
  "alibabacloud-ros20190910>=3.0.0",
@@ -32,8 +33,8 @@ dependencies = [
32
33
  "pillow==12.2.0",
33
34
  "cryptography>=42.0",
34
35
  "keyring>=25.0",
35
- "tree-sitter>=0.23",
36
- "tree-sitter-bash>=0.23",
36
+ "tree-sitter>=0.25,<0.26",
37
+ "tree-sitter-bash>=0.25,<0.26",
37
38
  ]
38
39
 
39
40
  [project.optional-dependencies]
@@ -68,6 +69,7 @@ dev = [
68
69
  "pre-commit>=4.0",
69
70
  "pytest-xdist>=3.0",
70
71
  "pytest-cov>=5.0",
72
+ "pytest-timeout>=2.3.1",
71
73
  "setuptools>=68.0",
72
74
  "wheel",
73
75
  "tomli>=2.0; python_version<\"3.11\"",
@@ -111,6 +113,9 @@ index-url = "https://mirrors.aliyun.com/pypi/simple/"
111
113
  url = "https://mirrors.aliyun.com/pypi/simple/"
112
114
  default = true
113
115
 
116
+ [tool.pytest.ini_options]
117
+ timeout = 30
118
+
114
119
  [tool.coverage.run]
115
120
  source_pkgs = ["iac_code"]
116
121
  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.1"
2
+ __release_date__ = "2026-06-02"
@@ -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:
@@ -7,6 +7,7 @@ from time import monotonic
7
7
  from typing import Any, AsyncIterator
8
8
 
9
9
  import httpx
10
+ from a2a.types import Role
10
11
 
11
12
  from iac_code.a2a.signing import AgentCardSignature, agent_card_signature_jwks_url, verify_agent_card_dict
12
13
  from iac_code.a2a.transport import A2AAuthConfig, A2ATransportBinding, UnsupportedA2ATransportError, headers_for_auth
@@ -31,19 +32,52 @@ class A2AClientResponse:
31
32
  if not isinstance(result, dict):
32
33
  return ""
33
34
  text = result.get("text")
34
- if isinstance(text, str):
35
+ if isinstance(text, str) and text:
35
36
  return text
36
37
  status = result.get("status")
37
- if not isinstance(status, dict):
38
- return ""
39
- message = status.get("message")
40
- if not isinstance(message, dict):
41
- return ""
42
- parts = message.get("parts")
43
- if not isinstance(parts, list) or not parts or not isinstance(parts[0], dict):
44
- return ""
45
- value = parts[0].get("text")
46
- return value if isinstance(value, str) else ""
38
+ if isinstance(status, dict):
39
+ extracted = _extract_parts_text(status.get("message"))
40
+ if extracted:
41
+ return extracted
42
+ task = result.get("task")
43
+ if isinstance(task, dict):
44
+ task_status = task.get("status")
45
+ if isinstance(task_status, dict):
46
+ extracted = _extract_parts_text(task_status.get("message"))
47
+ if extracted:
48
+ return extracted
49
+ history = task.get("history")
50
+ if isinstance(history, list):
51
+ for entry in reversed(history):
52
+ extracted = _extract_agent_entry_text(entry)
53
+ if extracted:
54
+ return extracted
55
+ return ""
56
+
57
+
58
+ _AGENT_ROLE_NAME = Role.Name(Role.ROLE_AGENT)
59
+
60
+
61
+ def _extract_agent_entry_text(entry: Any) -> str:
62
+ if not isinstance(entry, dict) or entry.get("role") != _AGENT_ROLE_NAME:
63
+ return ""
64
+ return _extract_parts_text(entry)
65
+
66
+
67
+ def _extract_parts_text(message: Any) -> str:
68
+ if not isinstance(message, dict):
69
+ return ""
70
+ parts = message.get("parts")
71
+ if not isinstance(parts, list):
72
+ return ""
73
+ pieces: list[str] = []
74
+ for part in parts:
75
+ if not isinstance(part, dict):
76
+ continue
77
+ value = part.get("text")
78
+ if isinstance(value, str):
79
+ pieces.append(value)
80
+ return "".join(pieces)
47
81
 
48
82
 
49
83
  class A2AClient:
@@ -302,7 +336,7 @@ class A2AClient:
302
336
  message: dict[str, Any] = {
303
337
  "messageId": str(uuid.uuid4()),
304
338
  "role": "ROLE_USER",
305
- "parts": [{"kind": "text", "text": prompt}],
339
+ "parts": [{"text": prompt}],
306
340
  "metadata": {"iac_code": {"cwd": cwd}},
307
341
  }
308
342
  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,
@@ -170,7 +171,10 @@ async def publish_stream_event(
170
171
  artifact_store: Any | None = None,
171
172
  permission_resolver: A2APermissionResolver | None = None,
172
173
  auto_approve_permissions: bool = False,
174
+ exposure_types: Any = None,
173
175
  ) -> str | None:
176
+ enabled_exposure_types = normalize_a2a_exposure_types(exposure_types)
177
+
174
178
  if isinstance(event, TextDeltaEvent):
175
179
  if not event.text:
176
180
  return None
@@ -184,9 +188,20 @@ async def publish_stream_event(
184
188
  return event.text
185
189
 
186
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
+ )
187
200
  return None
188
201
 
189
202
  if isinstance(event, ToolUseStartEvent):
203
+ if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
204
+ return None
190
205
  await _enqueue_status(
191
206
  event_queue,
192
207
  task_id=task_id,
@@ -197,6 +212,8 @@ async def publish_stream_event(
197
212
  return None
198
213
 
199
214
  if isinstance(event, ToolInputDeltaEvent):
215
+ if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
216
+ return None
200
217
  await _enqueue_status(
201
218
  event_queue,
202
219
  task_id=task_id,
@@ -215,6 +232,8 @@ async def publish_stream_event(
215
232
  return None
216
233
 
217
234
  if isinstance(event, ToolUseEndEvent):
235
+ if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
236
+ return None
218
237
  await _enqueue_status(
219
238
  event_queue,
220
239
  task_id=task_id,
@@ -235,6 +254,12 @@ async def publish_stream_event(
235
254
 
236
255
  if isinstance(event, ToolResultEvent):
237
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
238
263
  tool_metadata = {
239
264
  "status": "failed" if event.is_error else "completed",
240
265
  "toolUseId": event.tool_use_id,
@@ -262,6 +287,8 @@ async def publish_stream_event(
262
287
  approved = bool(await decision) if inspect.isawaitable(decision) else bool(decision)
263
288
  if event.response_future is not None and not event.response_future.done():
264
289
  event.response_future.set_result(approved)
290
+ if A2AExposureType.TOOL_TRACE not in enabled_exposure_types:
291
+ return None
265
292
  await _enqueue_status(
266
293
  event_queue,
267
294
  task_id=task_id,
@@ -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,6 +27,7 @@ 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
@@ -61,6 +63,7 @@ class IacCodeA2AExecutor(AgentExecutor):
61
63
  push_notifier: Any | None = None,
62
64
  permission_resolver: A2APermissionResolver | None = None,
63
65
  auto_approve_permissions: bool = False,
66
+ thinking_exposure_types: Any = None,
64
67
  ) -> None:
65
68
  self._task_store = task_store
66
69
  self._model = model
@@ -69,6 +72,7 @@ class IacCodeA2AExecutor(AgentExecutor):
69
72
  self._push_notifier = push_notifier
70
73
  self._permission_resolver = permission_resolver
71
74
  self._auto_approve_permissions = auto_approve_permissions
75
+ self._thinking_exposure_types = normalize_a2a_exposure_types(thinking_exposure_types)
72
76
 
73
77
  async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
74
78
  task_id = context.task_id or "task-" + uuid.uuid4().hex[:12]
@@ -218,18 +222,20 @@ class IacCodeA2AExecutor(AgentExecutor):
218
222
  context_id=context_id,
219
223
  state=TaskState.TASK_STATE_WORKING,
220
224
  )
221
- async for event in runtime.agent_loop.run_streaming(prompt):
222
- text_chunk = await publish_stream_event(
223
- event_queue,
224
- task_id=task_id,
225
- context_id=context_id,
226
- event=event,
227
- artifact_store=self._artifact_store,
228
- permission_resolver=self._permission_resolver,
229
- auto_approve_permissions=self._auto_approve_permissions,
230
- )
231
- if text_chunk:
232
- 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)
233
239
  task.state = TASK_STATE_INPUT_REQUIRED
234
240
  await self._publish_status(
235
241
  event_queue,
@@ -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__(