agentcore-cli 0.31.1__py3-none-any.whl

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 (655) hide show
  1. agentcore/__init__.py +1 -0
  2. agentcore/agent_inspector/NOTICE +9 -0
  3. agentcore/agent_inspector/favicon.svg +60 -0
  4. agentcore/agent_inspector/index.css +1 -0
  5. agentcore/agent_inspector/index.html +14 -0
  6. agentcore/agent_inspector/index.js +289 -0
  7. agentcore/assets/AGENTS.md +60 -0
  8. agentcore/assets/README.md +108 -0
  9. agentcore/assets/agents/AGENTS.md +218 -0
  10. agentcore/assets/cdk/.prettierrc +8 -0
  11. agentcore/assets/cdk/README.md +26 -0
  12. agentcore/assets/cdk/bin/cdk.ts +216 -0
  13. agentcore/assets/cdk/cdk.json +88 -0
  14. agentcore/assets/cdk/gitignore.template +9 -0
  15. agentcore/assets/cdk/jest.config.js +9 -0
  16. agentcore/assets/cdk/lib/cdk-stack.ts +291 -0
  17. agentcore/assets/cdk/npmignore.template +6 -0
  18. agentcore/assets/cdk/package.json +30 -0
  19. agentcore/assets/cdk/test/cdk.test.ts +31 -0
  20. agentcore/assets/cdk/tsconfig.json +28 -0
  21. agentcore/assets/container/python/Dockerfile +47 -0
  22. agentcore/assets/container/python/dockerignore.template +27 -0
  23. agentcore/assets/container/typescript/Dockerfile +25 -0
  24. agentcore/assets/container/typescript/dockerignore.template +24 -0
  25. agentcore/assets/datasets/predefined-v1.jsonl +3 -0
  26. agentcore/assets/datasets/simulated-v1.jsonl +3 -0
  27. agentcore/assets/evaluators/autoevals-lambda/execution-role-policy.json +15 -0
  28. agentcore/assets/evaluators/autoevals-lambda/lambda_function.py +37 -0
  29. agentcore/assets/evaluators/autoevals-lambda/pyproject.toml +22 -0
  30. agentcore/assets/evaluators/deepeval-lambda/execution-role-policy.json +15 -0
  31. agentcore/assets/evaluators/deepeval-lambda/lambda_function.py +29 -0
  32. agentcore/assets/evaluators/deepeval-lambda/pyproject.toml +19 -0
  33. agentcore/assets/evaluators/python-lambda/execution-role-policy.json +10 -0
  34. agentcore/assets/evaluators/python-lambda/lambda_function.py +19 -0
  35. agentcore/assets/evaluators/python-lambda/pyproject.toml +15 -0
  36. agentcore/assets/harness/invoke.py.template +74 -0
  37. agentcore/assets/mcp/python/README.md +27 -0
  38. agentcore/assets/mcp/python/pyproject.toml +22 -0
  39. agentcore/assets/mcp/python/server.py +117 -0
  40. agentcore/assets/mcp/python-lambda/README.md +22 -0
  41. agentcore/assets/mcp/python-lambda/handler.py +144 -0
  42. agentcore/assets/mcp/python-lambda/pyproject.toml +15 -0
  43. agentcore/assets/python/a2a/googleadk/base/README.md +22 -0
  44. agentcore/assets/python/a2a/googleadk/base/gitignore.template +41 -0
  45. agentcore/assets/python/a2a/googleadk/base/main.py +124 -0
  46. agentcore/assets/python/a2a/googleadk/base/model/__init__.py +1 -0
  47. agentcore/assets/python/a2a/googleadk/base/model/load.py +41 -0
  48. agentcore/assets/python/a2a/googleadk/base/pyproject.toml +22 -0
  49. agentcore/assets/python/a2a/langchain_langgraph/base/README.md +22 -0
  50. agentcore/assets/python/a2a/langchain_langgraph/base/gitignore.template +41 -0
  51. agentcore/assets/python/a2a/langchain_langgraph/base/main.py +144 -0
  52. agentcore/assets/python/a2a/langchain_langgraph/base/model/__init__.py +1 -0
  53. agentcore/assets/python/a2a/langchain_langgraph/base/model/load.py +123 -0
  54. agentcore/assets/python/a2a/langchain_langgraph/base/pyproject.toml +26 -0
  55. agentcore/assets/python/a2a/strands/base/README.md +22 -0
  56. agentcore/assets/python/a2a/strands/base/gitignore.template +41 -0
  57. agentcore/assets/python/a2a/strands/base/main.py +113 -0
  58. agentcore/assets/python/a2a/strands/base/model/__init__.py +1 -0
  59. agentcore/assets/python/a2a/strands/base/model/load.py +123 -0
  60. agentcore/assets/python/a2a/strands/base/pyproject.toml +23 -0
  61. agentcore/assets/python/a2a/strands/capabilities/memory/__init__.py +1 -0
  62. agentcore/assets/python/a2a/strands/capabilities/memory/session.py +46 -0
  63. agentcore/assets/python/agui/googleadk/base/README.md +30 -0
  64. agentcore/assets/python/agui/googleadk/base/gitignore.template +41 -0
  65. agentcore/assets/python/agui/googleadk/base/main.py +91 -0
  66. agentcore/assets/python/agui/googleadk/base/model/__init__.py +1 -0
  67. agentcore/assets/python/agui/googleadk/base/model/load.py +41 -0
  68. agentcore/assets/python/agui/googleadk/base/pyproject.toml +25 -0
  69. agentcore/assets/python/agui/langchain_langgraph/base/README.md +22 -0
  70. agentcore/assets/python/agui/langchain_langgraph/base/gitignore.template +41 -0
  71. agentcore/assets/python/agui/langchain_langgraph/base/main.py +130 -0
  72. agentcore/assets/python/agui/langchain_langgraph/base/model/__init__.py +1 -0
  73. agentcore/assets/python/agui/langchain_langgraph/base/model/load.py +123 -0
  74. agentcore/assets/python/agui/langchain_langgraph/base/pyproject.toml +31 -0
  75. agentcore/assets/python/agui/strands/base/README.md +22 -0
  76. agentcore/assets/python/agui/strands/base/gitignore.template +41 -0
  77. agentcore/assets/python/agui/strands/base/main.py +105 -0
  78. agentcore/assets/python/agui/strands/base/model/__init__.py +1 -0
  79. agentcore/assets/python/agui/strands/base/model/load.py +123 -0
  80. agentcore/assets/python/agui/strands/base/pyproject.toml +27 -0
  81. agentcore/assets/python/agui/strands/capabilities/memory/__init__.py +1 -0
  82. agentcore/assets/python/agui/strands/capabilities/memory/session.py +46 -0
  83. agentcore/assets/python/http/autogen/base/README.md +45 -0
  84. agentcore/assets/python/http/autogen/base/gitignore.template +41 -0
  85. agentcore/assets/python/http/autogen/base/main.py +145 -0
  86. agentcore/assets/python/http/autogen/base/mcp_client/__init__.py +1 -0
  87. agentcore/assets/python/http/autogen/base/mcp_client/client.py +28 -0
  88. agentcore/assets/python/http/autogen/base/model/__init__.py +1 -0
  89. agentcore/assets/python/http/autogen/base/model/load.py +136 -0
  90. agentcore/assets/python/http/autogen/base/pyproject.toml +34 -0
  91. agentcore/assets/python/http/bma/base/Dockerfile +41 -0
  92. agentcore/assets/python/http/bma/base/README.md +178 -0
  93. agentcore/assets/python/http/bma/base/client.py +200 -0
  94. agentcore/assets/python/http/bma/base/lifecycle/server.py +641 -0
  95. agentcore/assets/python/http/bma/base/otel/collector.yaml +69 -0
  96. agentcore/assets/python/http/bma/base/plugins/acr-report/.codex-plugin/plugin.json +6 -0
  97. agentcore/assets/python/http/bma/base/plugins/acr-report/skills/acr-report/SKILL.md +16 -0
  98. agentcore/assets/python/http/bma/base/policies/bma-acr-policy.json +11 -0
  99. agentcore/assets/python/http/bma/base/policies/bma-session-policy.json +35 -0
  100. agentcore/assets/python/http/bma/base/policies/bma-session-trust.json +20 -0
  101. agentcore/assets/python/http/bma/base/pyproject.toml +21 -0
  102. agentcore/assets/python/http/googleadk/base/README.md +45 -0
  103. agentcore/assets/python/http/googleadk/base/gitignore.template +41 -0
  104. agentcore/assets/python/http/googleadk/base/main.py +207 -0
  105. agentcore/assets/python/http/googleadk/base/mcp_client/__init__.py +1 -0
  106. agentcore/assets/python/http/googleadk/base/mcp_client/client.py +75 -0
  107. agentcore/assets/python/http/googleadk/base/model/__init__.py +1 -0
  108. agentcore/assets/python/http/googleadk/base/model/load.py +41 -0
  109. agentcore/assets/python/http/googleadk/base/pyproject.toml +24 -0
  110. agentcore/assets/python/http/langchain_langgraph/base/README.md +45 -0
  111. agentcore/assets/python/http/langchain_langgraph/base/gitignore.template +41 -0
  112. agentcore/assets/python/http/langchain_langgraph/base/main.py +227 -0
  113. agentcore/assets/python/http/langchain_langgraph/base/mcp_client/__init__.py +1 -0
  114. agentcore/assets/python/http/langchain_langgraph/base/mcp_client/client.py +79 -0
  115. agentcore/assets/python/http/langchain_langgraph/base/model/__init__.py +1 -0
  116. agentcore/assets/python/http/langchain_langgraph/base/model/load.py +123 -0
  117. agentcore/assets/python/http/langchain_langgraph/base/pyproject.toml +38 -0
  118. agentcore/assets/python/http/openaiagents/base/README.md +45 -0
  119. agentcore/assets/python/http/openaiagents/base/gitignore.template +41 -0
  120. agentcore/assets/python/http/openaiagents/base/main.py +200 -0
  121. agentcore/assets/python/http/openaiagents/base/mcp_client/__init__.py +1 -0
  122. agentcore/assets/python/http/openaiagents/base/mcp_client/client.py +74 -0
  123. agentcore/assets/python/http/openaiagents/base/model/__init__.py +1 -0
  124. agentcore/assets/python/http/openaiagents/base/model/load.py +37 -0
  125. agentcore/assets/python/http/openaiagents/base/pyproject.toml +21 -0
  126. agentcore/assets/python/http/strands/base/README.md +46 -0
  127. agentcore/assets/python/http/strands/base/gitignore.template +41 -0
  128. agentcore/assets/python/http/strands/base/main.py +714 -0
  129. agentcore/assets/python/http/strands/base/mcp_client/__init__.py +1 -0
  130. agentcore/assets/python/http/strands/base/mcp_client/client.py +116 -0
  131. agentcore/assets/python/http/strands/base/model/__init__.py +1 -0
  132. agentcore/assets/python/http/strands/base/model/load.py +239 -0
  133. agentcore/assets/python/http/strands/base/model/mantle_compat.py +21 -0
  134. agentcore/assets/python/http/strands/base/pyproject.toml +31 -0
  135. agentcore/assets/python/http/strands/base/skills/fetcher.py +279 -0
  136. agentcore/assets/python/http/strands/capabilities/execution-limits/hooks/execution_limits.py +54 -0
  137. agentcore/assets/python/http/strands/capabilities/memory/__init__.py +1 -0
  138. agentcore/assets/python/http/strands/capabilities/memory/session.py +47 -0
  139. agentcore/assets/python/http/strands/capabilities/payments/__init__.py +4 -0
  140. agentcore/assets/python/http/strands/capabilities/payments/payments.py +142 -0
  141. agentcore/assets/python/mcp/standalone/base/README.md +36 -0
  142. agentcore/assets/python/mcp/standalone/base/gitignore.template +41 -0
  143. agentcore/assets/python/mcp/standalone/base/main.py +83 -0
  144. agentcore/assets/python/mcp/standalone/base/pyproject.toml +16 -0
  145. agentcore/assets/typescript/.gitkeep +0 -0
  146. agentcore/assets/typescript/http/strands/base/README.md +42 -0
  147. agentcore/assets/typescript/http/strands/base/gitignore.template +22 -0
  148. agentcore/assets/typescript/http/strands/base/main.ts +149 -0
  149. agentcore/assets/typescript/http/strands/base/mcp_client/client.ts +11 -0
  150. agentcore/assets/typescript/http/strands/base/model/load.ts +102 -0
  151. agentcore/assets/typescript/http/strands/base/package.json +38 -0
  152. agentcore/assets/typescript/http/strands/base/tsconfig.json +19 -0
  153. agentcore/assets/typescript/http/strands/capabilities/memory/memory.ts +52 -0
  154. agentcore/assets/typescript/http/vercelai/base/README.md +42 -0
  155. agentcore/assets/typescript/http/vercelai/base/gitignore.template +22 -0
  156. agentcore/assets/typescript/http/vercelai/base/main.ts +70 -0
  157. agentcore/assets/typescript/http/vercelai/base/model/load.ts +121 -0
  158. agentcore/assets/typescript/http/vercelai/base/package.json +35 -0
  159. agentcore/assets/typescript/http/vercelai/base/tsconfig.json +19 -0
  160. agentcore/aws/__init__.py +263 -0
  161. agentcore/aws/_aws_errors.py +72 -0
  162. agentcore/aws/_http.py +176 -0
  163. agentcore/aws/_util.py +37 -0
  164. agentcore/aws/account.py +165 -0
  165. agentcore/aws/agentcore.py +971 -0
  166. agentcore/aws/agentcore_ab_tests.py +230 -0
  167. agentcore/aws/agentcore_batch_evaluation.py +227 -0
  168. agentcore/aws/agentcore_config_bundles.py +220 -0
  169. agentcore/aws/agentcore_control.py +994 -0
  170. agentcore/aws/agentcore_datasets.py +222 -0
  171. agentcore/aws/agentcore_harness.py +330 -0
  172. agentcore/aws/agentcore_payments.py +430 -0
  173. agentcore/aws/agentcore_recommendation.py +140 -0
  174. agentcore/aws/agui_parser.py +131 -0
  175. agentcore/aws/agui_types.py +342 -0
  176. agentcore/aws/api_client.py +87 -0
  177. agentcore/aws/arn.py +18 -0
  178. agentcore/aws/aws_context.py +23 -0
  179. agentcore/aws/bedrock.py +44 -0
  180. agentcore/aws/bedrock_agent.py +129 -0
  181. agentcore/aws/bedrock_import.py +252 -0
  182. agentcore/aws/bedrock_import_types.py +111 -0
  183. agentcore/aws/cloudwatch.py +136 -0
  184. agentcore/aws/connect_shell.py +266 -0
  185. agentcore/aws/iam.py +41 -0
  186. agentcore/aws/partition.py +58 -0
  187. agentcore/aws/policy_generation.py +116 -0
  188. agentcore/aws/region.py +54 -0
  189. agentcore/aws/retry.py +27 -0
  190. agentcore/aws/shell_framer.py +136 -0
  191. agentcore/aws/stage_endpoint.py +39 -0
  192. agentcore/aws/target_region.py +54 -0
  193. agentcore/aws/transaction_search.py +97 -0
  194. agentcore/cdk/__init__.py +10 -0
  195. agentcore/cdk/constants.py +13 -0
  196. agentcore/cdk/local_cdk_project.py +48 -0
  197. agentcore/cdk/toolkit.py +706 -0
  198. agentcore/cli/__init__.py +0 -0
  199. agentcore/cli/commands/__init__.py +0 -0
  200. agentcore/cli/commands/_jobs_common.py +39 -0
  201. agentcore/cli/commands/_run_wizards.py +639 -0
  202. agentcore/cli/commands/add/__init__.py +37 -0
  203. agentcore/cli/commands/add/_commander.py +52 -0
  204. agentcore/cli/commands/add/_common.py +47 -0
  205. agentcore/cli/commands/add/agent_wizard.py +528 -0
  206. agentcore/cli/commands/add/auth_options.py +154 -0
  207. agentcore/cli/commands/add/skill_action.py +105 -0
  208. agentcore/cli/commands/add/skill_command.py +87 -0
  209. agentcore/cli/commands/add/tool_action.py +160 -0
  210. agentcore/cli/commands/add/tool_command.py +117 -0
  211. agentcore/cli/commands/add/validate.py +333 -0
  212. agentcore/cli/commands/capacity_provider/__init__.py +86 -0
  213. agentcore/cli/commands/capacity_provider/action.py +114 -0
  214. agentcore/cli/commands/capacity_provider/constants.py +22 -0
  215. agentcore/cli/commands/config.py +91 -0
  216. agentcore/cli/commands/config_bundle.py +251 -0
  217. agentcore/cli/commands/create/__init__.py +643 -0
  218. agentcore/cli/commands/create/action.py +406 -0
  219. agentcore/cli/commands/create/dependencies.py +91 -0
  220. agentcore/cli/commands/create/harness_validate.py +82 -0
  221. agentcore/cli/commands/create/validate.py +195 -0
  222. agentcore/cli/commands/create/wizard.py +127 -0
  223. agentcore/cli/commands/dataset.py +119 -0
  224. agentcore/cli/commands/deploy/__init__.py +31 -0
  225. agentcore/cli/commands/deploy/_guards.py +25 -0
  226. agentcore/cli/commands/deploy/actions.py +840 -0
  227. agentcore/cli/commands/deploy/command.py +349 -0
  228. agentcore/cli/commands/deploy/progress.py +115 -0
  229. agentcore/cli/commands/deploy/types.py +68 -0
  230. agentcore/cli/commands/deploy/utils.py +43 -0
  231. agentcore/cli/commands/deploy/validate.py +21 -0
  232. agentcore/cli/commands/dev/__init__.py +5 -0
  233. agentcore/cli/commands/dev/_deps.py +50 -0
  234. agentcore/cli/commands/dev/browser_mode.py +270 -0
  235. agentcore/cli/commands/dev/command.py +450 -0
  236. agentcore/cli/commands/dev/terminal_mode.py +379 -0
  237. agentcore/cli/commands/exec/__init__.py +29 -0
  238. agentcore/cli/commands/exec/action.py +442 -0
  239. agentcore/cli/commands/exec/command.py +206 -0
  240. agentcore/cli/commands/exec/types.py +35 -0
  241. agentcore/cli/commands/export/__init__.py +114 -0
  242. agentcore/cli/commands/export/constants.py +21 -0
  243. agentcore/cli/commands/export/fetch_harness_spec.py +234 -0
  244. agentcore/cli/commands/export/harness_action.py +385 -0
  245. agentcore/cli/commands/export/harness_mapper.py +868 -0
  246. agentcore/cli/commands/export/harness_resolver.py +95 -0
  247. agentcore/cli/commands/export/types.py +90 -0
  248. agentcore/cli/commands/export/wizard.py +97 -0
  249. agentcore/cli/commands/feedback/__init__.py +69 -0
  250. agentcore/cli/commands/feedback/action.py +30 -0
  251. agentcore/cli/commands/feedback/consent_prompt.py +35 -0
  252. agentcore/cli/commands/feedback/wizard.py +49 -0
  253. agentcore/cli/commands/fetch/__init__.py +69 -0
  254. agentcore/cli/commands/fetch/action.py +62 -0
  255. agentcore/cli/commands/help.py +65 -0
  256. agentcore/cli/commands/import_/__init__.py +167 -0
  257. agentcore/cli/commands/import_/_js.py +72 -0
  258. agentcore/cli/commands/import_/actions.py +457 -0
  259. agentcore/cli/commands/import_/constants.py +45 -0
  260. agentcore/cli/commands/import_/import_evaluator.py +140 -0
  261. agentcore/cli/commands/import_/import_gateway.py +520 -0
  262. agentcore/cli/commands/import_/import_memory.py +128 -0
  263. agentcore/cli/commands/import_/import_online_eval.py +165 -0
  264. agentcore/cli/commands/import_/import_pipeline.py +145 -0
  265. agentcore/cli/commands/import_/import_runtime.py +214 -0
  266. agentcore/cli/commands/import_/import_utils.py +410 -0
  267. agentcore/cli/commands/import_/phase1_update.py +110 -0
  268. agentcore/cli/commands/import_/phase2_import.py +206 -0
  269. agentcore/cli/commands/import_/resource_import.py +228 -0
  270. agentcore/cli/commands/import_/template_utils.py +148 -0
  271. agentcore/cli/commands/import_/types.py +150 -0
  272. agentcore/cli/commands/import_/yaml_parser.py +265 -0
  273. agentcore/cli/commands/init.py +196 -0
  274. agentcore/cli/commands/invoke/__init__.py +32 -0
  275. agentcore/cli/commands/invoke/_deps.py +73 -0
  276. agentcore/cli/commands/invoke/action.py +840 -0
  277. agentcore/cli/commands/invoke/command.py +490 -0
  278. agentcore/cli/commands/invoke/constants.py +40 -0
  279. agentcore/cli/commands/invoke/guards.py +23 -0
  280. agentcore/cli/commands/invoke/interactive.py +604 -0
  281. agentcore/cli/commands/invoke/resolve.py +149 -0
  282. agentcore/cli/commands/invoke/resolve_prompt.py +60 -0
  283. agentcore/cli/commands/invoke/types.py +61 -0
  284. agentcore/cli/commands/invoke/validate.py +33 -0
  285. agentcore/cli/commands/job_actions.py +239 -0
  286. agentcore/cli/commands/job_history.py +380 -0
  287. agentcore/cli/commands/logs/__init__.py +53 -0
  288. agentcore/cli/commands/logs/action.py +159 -0
  289. agentcore/cli/commands/logs/filter_pattern.py +17 -0
  290. agentcore/cli/commands/logs/guards.py +51 -0
  291. agentcore/cli/commands/logs_evals.py +42 -0
  292. agentcore/cli/commands/package.py +118 -0
  293. agentcore/cli/commands/remove/__init__.py +172 -0
  294. agentcore/cli/commands/remove/skill_command.py +131 -0
  295. agentcore/cli/commands/remove/tool_command.py +59 -0
  296. agentcore/cli/commands/run.py +808 -0
  297. agentcore/cli/commands/run_ingest.py +97 -0
  298. agentcore/cli/commands/shared/__init__.py +0 -0
  299. agentcore/cli/commands/shared/arn_utils.py +9 -0
  300. agentcore/cli/commands/shared/filesystem_utils.py +421 -0
  301. agentcore/cli/commands/shared/header_utils.py +87 -0
  302. agentcore/cli/commands/shared/indexed_key_parser.py +76 -0
  303. agentcore/cli/commands/shared/lifecycle_utils.py +72 -0
  304. agentcore/cli/commands/shared/region_utils.py +20 -0
  305. agentcore/cli/commands/shared/vpc_utils.py +99 -0
  306. agentcore/cli/commands/status/__init__.py +30 -0
  307. agentcore/cli/commands/status/_guards.py +25 -0
  308. agentcore/cli/commands/status/action.py +741 -0
  309. agentcore/cli/commands/status/command.py +321 -0
  310. agentcore/cli/commands/status/constants.py +36 -0
  311. agentcore/cli/commands/status/format_knowledge_base.py +111 -0
  312. agentcore/cli/commands/telemetry.py +79 -0
  313. agentcore/cli/commands/traces/__init__.py +155 -0
  314. agentcore/cli/commands/traces/action.py +123 -0
  315. agentcore/cli/commands/update/__init__.py +63 -0
  316. agentcore/cli/commands/update/action.py +164 -0
  317. agentcore/cli/commands/validate.py +165 -0
  318. agentcore/cli/constants.py +96 -0
  319. agentcore/cli/errors.py +118 -0
  320. agentcore/cli/external_requirements/__init__.py +67 -0
  321. agentcore/cli/external_requirements/checks.py +280 -0
  322. agentcore/cli/external_requirements/detect.py +55 -0
  323. agentcore/cli/external_requirements/versions.py +57 -0
  324. agentcore/cli/groups.py +23 -0
  325. agentcore/cli/interactive.py +85 -0
  326. agentcore/cli/main.py +137 -0
  327. agentcore/cli/naming.py +11 -0
  328. agentcore/cli/notices.py +42 -0
  329. agentcore/cli/project.py +34 -0
  330. agentcore/cli/run_command.py +27 -0
  331. agentcore/cli/tree.py +135 -0
  332. agentcore/cli/update_notifier.py +123 -0
  333. agentcore/cli/variadic.py +50 -0
  334. agentcore/cloudformation/__init__.py +84 -0
  335. agentcore/cloudformation/bootstrap.py +64 -0
  336. agentcore/cloudformation/logical_ids.py +37 -0
  337. agentcore/cloudformation/outputs.py +566 -0
  338. agentcore/cloudformation/stack_discovery.py +75 -0
  339. agentcore/cloudformation/stack_events.py +107 -0
  340. agentcore/cloudformation/stack_status.py +98 -0
  341. agentcore/cloudformation/types.py +44 -0
  342. agentcore/lib/__init__.py +68 -0
  343. agentcore/lib/constants.py +77 -0
  344. agentcore/lib/dependency_management/__init__.py +23 -0
  345. agentcore/lib/dependency_management/messages.py +92 -0
  346. agentcore/lib/dependency_management/plan.py +98 -0
  347. agentcore/lib/dependency_management/semver.py +137 -0
  348. agentcore/lib/dependency_management/sync.py +139 -0
  349. agentcore/lib/dependency_management/types.py +56 -0
  350. agentcore/lib/errors/__init__.py +5 -0
  351. agentcore/lib/errors/types.py +352 -0
  352. agentcore/lib/errors/validation.py +121 -0
  353. agentcore/lib/packaging/__init__.py +81 -0
  354. agentcore/lib/packaging/build_args.py +23 -0
  355. agentcore/lib/packaging/build_context.py +26 -0
  356. agentcore/lib/packaging/build_context_dockerignore.py +45 -0
  357. agentcore/lib/packaging/container.py +95 -0
  358. agentcore/lib/packaging/helpers.py +289 -0
  359. agentcore/lib/packaging/node.py +153 -0
  360. agentcore/lib/packaging/python.py +135 -0
  361. agentcore/lib/packaging/types.py +69 -0
  362. agentcore/lib/packaging/uv.py +45 -0
  363. agentcore/lib/result.py +104 -0
  364. agentcore/lib/schemas/__init__.py +1 -0
  365. agentcore/lib/schemas/io/__init__.py +27 -0
  366. agentcore/lib/schemas/io/config_io.py +458 -0
  367. agentcore/lib/schemas/io/global_config.py +239 -0
  368. agentcore/lib/schemas/io/path_resolver.py +132 -0
  369. agentcore/lib/secrets/__init__.py +18 -0
  370. agentcore/lib/secrets/cipher.py +70 -0
  371. agentcore/lib/secrets/key_provider.py +163 -0
  372. agentcore/lib/secrets/sensitive_keys.py +55 -0
  373. agentcore/lib/time_constants.py +4 -0
  374. agentcore/lib/utils/__init__.py +47 -0
  375. agentcore/lib/utils/aws_account.py +15 -0
  376. agentcore/lib/utils/credentials.py +68 -0
  377. agentcore/lib/utils/dotenv.py +32 -0
  378. agentcore/lib/utils/env.py +105 -0
  379. agentcore/lib/utils/json_format.py +29 -0
  380. agentcore/lib/utils/json_rpc.py +22 -0
  381. agentcore/lib/utils/platform.py +33 -0
  382. agentcore/lib/utils/polling.py +86 -0
  383. agentcore/lib/utils/subprocess.py +135 -0
  384. agentcore/lib/utils/time_parser.py +48 -0
  385. agentcore/lib/utils/validation.py +106 -0
  386. agentcore/logging/__init__.py +10 -0
  387. agentcore/logging/_common.py +77 -0
  388. agentcore/logging/cdk_io_host.py +36 -0
  389. agentcore/logging/create_logger.py +107 -0
  390. agentcore/logging/dev_logger.py +63 -0
  391. agentcore/logging/exec_logger.py +159 -0
  392. agentcore/logging/invoke_logger.py +154 -0
  393. agentcore/logging/remove_logger.py +95 -0
  394. agentcore/operations/__init__.py +0 -0
  395. agentcore/operations/agent/__init__.py +0 -0
  396. agentcore/operations/agent/config_bundle_defaults.py +31 -0
  397. agentcore/operations/agent/generate/__init__.py +28 -0
  398. agentcore/operations/agent/generate/schema_mapper.py +319 -0
  399. agentcore/operations/agent/generate/types.py +172 -0
  400. agentcore/operations/agent/generate/write_agent_to_project.py +67 -0
  401. agentcore/operations/agent/import_/__init__.py +31 -0
  402. agentcore/operations/agent/import_/base_translator.py +449 -0
  403. agentcore/operations/agent/import_/constants.py +23 -0
  404. agentcore/operations/agent/import_/execute.py +127 -0
  405. agentcore/operations/agent/import_/langgraph_translator.py +294 -0
  406. agentcore/operations/agent/import_/pyproject_generator.py +73 -0
  407. agentcore/operations/agent/import_/strands_translator.py +289 -0
  408. agentcore/operations/config_bundle/__init__.py +1 -0
  409. agentcore/operations/config_bundle/bundle_name_variants.py +9 -0
  410. agentcore/operations/config_bundle/diff_versions.py +52 -0
  411. agentcore/operations/config_bundle/resolve_bundle.py +65 -0
  412. agentcore/operations/connectors/__init__.py +3 -0
  413. agentcore/operations/connectors/translators.py +63 -0
  414. agentcore/operations/dataset/__init__.py +21 -0
  415. agentcore/operations/dataset/delete.py +7 -0
  416. agentcore/operations/dataset/publish.py +16 -0
  417. agentcore/operations/dataset/pull.py +21 -0
  418. agentcore/operations/dataset/push.py +201 -0
  419. agentcore/operations/dataset/resolve_dataset.py +65 -0
  420. agentcore/operations/dataset/status.py +18 -0
  421. agentcore/operations/dataset/wait.py +25 -0
  422. agentcore/operations/deploy/__init__.py +152 -0
  423. agentcore/operations/deploy/backfill_vpc_id.py +90 -0
  424. agentcore/operations/deploy/change_detection.py +84 -0
  425. agentcore/operations/deploy/constants.py +58 -0
  426. agentcore/operations/deploy/dependency_sync.py +105 -0
  427. agentcore/operations/deploy/ensure_target.py +30 -0
  428. agentcore/operations/deploy/gateway_status.py +44 -0
  429. agentcore/operations/deploy/managed_memory_notice.py +36 -0
  430. agentcore/operations/deploy/payment_connector_authorization.py +106 -0
  431. agentcore/operations/deploy/post_deploy_datasets.py +96 -0
  432. agentcore/operations/deploy/post_deploy_knowledge_bases.py +104 -0
  433. agentcore/operations/deploy/post_deploy_observability.py +28 -0
  434. agentcore/operations/deploy/post_deploy_online_evals.py +60 -0
  435. agentcore/operations/deploy/pre_deploy_identity.py +505 -0
  436. agentcore/operations/deploy/preflight.py +385 -0
  437. agentcore/operations/deploy/region_override.py +45 -0
  438. agentcore/operations/deploy/teardown.py +153 -0
  439. agentcore/operations/dev/__init__.py +61 -0
  440. agentcore/operations/dev/_http.py +79 -0
  441. agentcore/operations/dev/_server.py +165 -0
  442. agentcore/operations/dev/codezip_dev_server.py +151 -0
  443. agentcore/operations/dev/config.py +128 -0
  444. agentcore/operations/dev/constants.py +9 -0
  445. agentcore/operations/dev/container_dev_server.py +237 -0
  446. agentcore/operations/dev/container_runtime.py +37 -0
  447. agentcore/operations/dev/dev_server.py +244 -0
  448. agentcore/operations/dev/gateway_env.py +25 -0
  449. agentcore/operations/dev/invoke.py +214 -0
  450. agentcore/operations/dev/invoke_a2a.py +204 -0
  451. agentcore/operations/dev/invoke_agui.py +88 -0
  452. agentcore/operations/dev/invoke_mcp.py +145 -0
  453. agentcore/operations/dev/invoke_types.py +30 -0
  454. agentcore/operations/dev/load_dev_env.py +26 -0
  455. agentcore/operations/dev/memory_env.py +19 -0
  456. agentcore/operations/dev/otel/__init__.py +5 -0
  457. agentcore/operations/dev/otel/collector.py +234 -0
  458. agentcore/operations/dev/otel/transforms.py +331 -0
  459. agentcore/operations/dev/payment_env.py +50 -0
  460. agentcore/operations/dev/server.py +28 -0
  461. agentcore/operations/dev/sse_transform.py +30 -0
  462. agentcore/operations/dev/utils.py +152 -0
  463. agentcore/operations/dev/web_ui/__init__.py +31 -0
  464. agentcore/operations/dev/web_ui/constants.py +25 -0
  465. agentcore/operations/dev/web_ui/handlers/__init__.py +36 -0
  466. agentcore/operations/dev/web_ui/handlers/_common.py +36 -0
  467. agentcore/operations/dev/web_ui/handlers/_deployed.py +37 -0
  468. agentcore/operations/dev/web_ui/handlers/a2a_proxy.py +37 -0
  469. agentcore/operations/dev/web_ui/handlers/cloudwatch_traces.py +71 -0
  470. agentcore/operations/dev/web_ui/handlers/harness_invocation.py +89 -0
  471. agentcore/operations/dev/web_ui/handlers/harness_utils.py +95 -0
  472. agentcore/operations/dev/web_ui/handlers/invocations.py +327 -0
  473. agentcore/operations/dev/web_ui/handlers/mcp_proxy.py +119 -0
  474. agentcore/operations/dev/web_ui/handlers/memory.py +84 -0
  475. agentcore/operations/dev/web_ui/handlers/resources.py +351 -0
  476. agentcore/operations/dev/web_ui/handlers/start.py +182 -0
  477. agentcore/operations/dev/web_ui/handlers/status.py +25 -0
  478. agentcore/operations/dev/web_ui/handlers/traces.py +50 -0
  479. agentcore/operations/dev/web_ui/run_web_ui.py +60 -0
  480. agentcore/operations/dev/web_ui/web_server.py +297 -0
  481. agentcore/operations/eval/__init__.py +8 -0
  482. agentcore/operations/eval/list_eval_runs.py +18 -0
  483. agentcore/operations/eval/logs_eval.py +119 -0
  484. agentcore/operations/eval/pause_resume.py +90 -0
  485. agentcore/operations/eval/run_eval.py +354 -0
  486. agentcore/operations/eval/shared/__init__.py +0 -0
  487. agentcore/operations/eval/shared/dataset_loader.py +68 -0
  488. agentcore/operations/eval/shared/dataset_session_provider.py +122 -0
  489. agentcore/operations/eval/shared/evaluator_runner.py +89 -0
  490. agentcore/operations/eval/shared/scenario_executor.py +71 -0
  491. agentcore/operations/eval/shared/span_collector.py +255 -0
  492. agentcore/operations/eval/storage.py +67 -0
  493. agentcore/operations/feedback/__init__.py +21 -0
  494. agentcore/operations/feedback/aperture_client.py +98 -0
  495. agentcore/operations/feedback/build_payload.py +97 -0
  496. agentcore/operations/feedback/constants.py +26 -0
  497. agentcore/operations/feedback/submit_feedback.py +150 -0
  498. agentcore/operations/fetch_access/__init__.py +36 -0
  499. agentcore/operations/fetch_access/_target.py +17 -0
  500. agentcore/operations/fetch_access/fetch_gateway_token.py +75 -0
  501. agentcore/operations/fetch_access/fetch_harness_token.py +72 -0
  502. agentcore/operations/fetch_access/fetch_runtime_token.py +76 -0
  503. agentcore/operations/fetch_access/list_agents.py +18 -0
  504. agentcore/operations/fetch_access/list_gateways.py +26 -0
  505. agentcore/operations/fetch_access/list_harnesses.py +23 -0
  506. agentcore/operations/fetch_access/oauth_token.py +162 -0
  507. agentcore/operations/fetch_access/types.py +34 -0
  508. agentcore/operations/harness/__init__.py +1 -0
  509. agentcore/operations/harness/orphan.py +84 -0
  510. agentcore/operations/harness/skill_utils.py +56 -0
  511. agentcore/operations/identity/__init__.py +33 -0
  512. agentcore/operations/identity/api_key_credential_provider.py +68 -0
  513. agentcore/operations/identity/oauth2_credential_provider.py +95 -0
  514. agentcore/operations/ingest/__init__.py +184 -0
  515. agentcore/operations/init/__init__.py +3 -0
  516. agentcore/operations/init/files.py +59 -0
  517. agentcore/operations/insights/__init__.py +21 -0
  518. agentcore/operations/insights/insights_storage.py +67 -0
  519. agentcore/operations/insights/run_insights.py +148 -0
  520. agentcore/operations/invoke/__init__.py +5 -0
  521. agentcore/operations/invoke/resolve_agent_context.py +85 -0
  522. agentcore/operations/jobs/__init__.py +38 -0
  523. agentcore/operations/jobs/ab_test/__init__.py +0 -0
  524. agentcore/operations/jobs/ab_test/build_options.py +126 -0
  525. agentcore/operations/jobs/ab_test/format.py +121 -0
  526. agentcore/operations/jobs/ab_test/handler.py +412 -0
  527. agentcore/operations/jobs/ab_test/promote.py +142 -0
  528. agentcore/operations/jobs/ab_test/resolve.py +221 -0
  529. agentcore/operations/jobs/batch_evaluation/__init__.py +0 -0
  530. agentcore/operations/jobs/batch_evaluation/build_source.py +68 -0
  531. agentcore/operations/jobs/batch_evaluation/dataset_phase1.py +95 -0
  532. agentcore/operations/jobs/batch_evaluation/format.py +58 -0
  533. agentcore/operations/jobs/batch_evaluation/handler.py +203 -0
  534. agentcore/operations/jobs/insights/__init__.py +0 -0
  535. agentcore/operations/jobs/insights/format.py +65 -0
  536. agentcore/operations/jobs/insights/handler.py +178 -0
  537. agentcore/operations/jobs/recommendation/__init__.py +0 -0
  538. agentcore/operations/jobs/recommendation/apply_to_bundle.py +76 -0
  539. agentcore/operations/jobs/recommendation/build_config.py +271 -0
  540. agentcore/operations/jobs/recommendation/fetch_session_spans.py +93 -0
  541. agentcore/operations/jobs/recommendation/format.py +77 -0
  542. agentcore/operations/jobs/recommendation/handler.py +331 -0
  543. agentcore/operations/jobs/shared/__init__.py +0 -0
  544. agentcore/operations/jobs/shared/constants.py +85 -0
  545. agentcore/operations/jobs/shared/engine.py +187 -0
  546. agentcore/operations/jobs/shared/format.py +104 -0
  547. agentcore/operations/jobs/shared/region.py +34 -0
  548. agentcore/operations/jobs/shared/resolve_agent_state.py +30 -0
  549. agentcore/operations/jobs/shared/storage.py +117 -0
  550. agentcore/operations/jobs/shared/wait.py +30 -0
  551. agentcore/operations/knowledge_base/__init__.py +33 -0
  552. agentcore/operations/knowledge_base/connector_config.py +105 -0
  553. agentcore/operations/knowledge_base/hydrate_data_sources.py +59 -0
  554. agentcore/operations/mcp/__init__.py +4 -0
  555. agentcore/operations/memory/__init__.py +13 -0
  556. agentcore/operations/memory/_records.py +72 -0
  557. agentcore/operations/memory/list_memory_records.py +45 -0
  558. agentcore/operations/memory/retrieve_memory_records.py +52 -0
  559. agentcore/operations/node/__init__.py +15 -0
  560. agentcore/operations/node/setup.py +36 -0
  561. agentcore/operations/python/__init__.py +17 -0
  562. agentcore/operations/python/setup.py +47 -0
  563. agentcore/operations/resolve_agent.py +159 -0
  564. agentcore/operations/session/__init__.py +86 -0
  565. agentcore/operations/traces/__init__.py +29 -0
  566. agentcore/operations/traces/get_trace.py +202 -0
  567. agentcore/operations/traces/insights_query.py +74 -0
  568. agentcore/operations/traces/list_traces.py +46 -0
  569. agentcore/operations/traces/trace_url.py +25 -0
  570. agentcore/operations/traces/types.py +55 -0
  571. agentcore/primitives/__init__.py +0 -0
  572. agentcore/primitives/_gateway_target_wizard.py +293 -0
  573. agentcore/primitives/_gateway_validation.py +442 -0
  574. agentcore/primitives/_harness_cli.py +557 -0
  575. agentcore/primitives/_harness_validation.py +163 -0
  576. agentcore/primitives/_harness_wizard.py +726 -0
  577. agentcore/primitives/_knowledge_base_wizard.py +259 -0
  578. agentcore/primitives/_payment_wizard.py +331 -0
  579. agentcore/primitives/_policy_form.py +160 -0
  580. agentcore/primitives/agent.py +817 -0
  581. agentcore/primitives/auth_utils.py +77 -0
  582. agentcore/primitives/base.py +178 -0
  583. agentcore/primitives/capacity_provider.py +501 -0
  584. agentcore/primitives/config_bundle.py +329 -0
  585. agentcore/primitives/credential.py +387 -0
  586. agentcore/primitives/credential_utils.py +45 -0
  587. agentcore/primitives/dataset.py +211 -0
  588. agentcore/primitives/deployed_gateways.py +34 -0
  589. agentcore/primitives/evaluator.py +909 -0
  590. agentcore/primitives/gateway.py +496 -0
  591. agentcore/primitives/gateway_target.py +988 -0
  592. agentcore/primitives/harness.py +706 -0
  593. agentcore/primitives/knowledge_base.py +477 -0
  594. agentcore/primitives/memory.py +387 -0
  595. agentcore/primitives/online_eval_config.py +365 -0
  596. agentcore/primitives/online_insights.py +275 -0
  597. agentcore/primitives/payment_connector.py +427 -0
  598. agentcore/primitives/payment_eligible.py +20 -0
  599. agentcore/primitives/payment_manager.py +605 -0
  600. agentcore/primitives/payment_validation.py +68 -0
  601. agentcore/primitives/policy.py +622 -0
  602. agentcore/primitives/policy_engine.py +358 -0
  603. agentcore/primitives/registry.py +56 -0
  604. agentcore/primitives/runtime_endpoint.py +361 -0
  605. agentcore/primitives/secret_flag_warning.py +17 -0
  606. agentcore/schema/__init__.py +14 -0
  607. agentcore/schema/_zod.py +395 -0
  608. agentcore/schema/constants.py +306 -0
  609. agentcore/schema/schemas/__init__.py +40 -0
  610. agentcore/schema/schemas/agent_env.py +545 -0
  611. agentcore/schema/schemas/agentcore_project.py +530 -0
  612. agentcore/schema/schemas/auth.py +286 -0
  613. agentcore/schema/schemas/aws_targets.py +72 -0
  614. agentcore/schema/schemas/connections.py +187 -0
  615. agentcore/schema/schemas/deployed_state.py +322 -0
  616. agentcore/schema/schemas/mcp.py +650 -0
  617. agentcore/schema/schemas/mcp_defs.py +54 -0
  618. agentcore/schema/schemas/primitives/__init__.py +52 -0
  619. agentcore/schema/schemas/primitives/ab_test.py +162 -0
  620. agentcore/schema/schemas/primitives/capacity_provider.py +214 -0
  621. agentcore/schema/schemas/primitives/config_bundle.py +48 -0
  622. agentcore/schema/schemas/primitives/dataset.py +55 -0
  623. agentcore/schema/schemas/primitives/evaluator.py +171 -0
  624. agentcore/schema/schemas/primitives/harness.py +710 -0
  625. agentcore/schema/schemas/primitives/knowledge_base.py +90 -0
  626. agentcore/schema/schemas/primitives/memory.py +113 -0
  627. agentcore/schema/schemas/primitives/online_eval_config.py +77 -0
  628. agentcore/schema/schemas/primitives/payment.py +114 -0
  629. agentcore/schema/schemas/primitives/policy.py +83 -0
  630. agentcore/schema/schemas/primitives/tags.py +50 -0
  631. agentcore/schema/schemas/zod_util.py +5 -0
  632. agentcore/schema/types/__init__.py +3 -0
  633. agentcore/schema/types/path.py +16 -0
  634. agentcore/templates/__init__.py +115 -0
  635. agentcore/templates/_handlebars.py +782 -0
  636. agentcore/templates/_js.py +164 -0
  637. agentcore/templates/base_renderer.py +107 -0
  638. agentcore/templates/cdk_renderer.py +129 -0
  639. agentcore/templates/evaluator_renderer.py +39 -0
  640. agentcore/templates/gateway_target_renderer.py +69 -0
  641. agentcore/templates/llm_context/README.md +16 -0
  642. agentcore/templates/llm_context/agentcore.ts +685 -0
  643. agentcore/templates/llm_context/aws-targets.ts +51 -0
  644. agentcore/templates/profiles.py +151 -0
  645. agentcore/templates/render.py +92 -0
  646. agentcore/templates/renderers.py +83 -0
  647. agentcore/templates/schema_assets.py +23 -0
  648. agentcore/templates/template_root.py +28 -0
  649. agentcore/templates/types.py +192 -0
  650. agentcore_cli-0.31.1.dist-info/METADATA +84 -0
  651. agentcore_cli-0.31.1.dist-info/RECORD +655 -0
  652. agentcore_cli-0.31.1.dist-info/WHEEL +4 -0
  653. agentcore_cli-0.31.1.dist-info/entry_points.txt +2 -0
  654. agentcore_cli-0.31.1.dist-info/licenses/LICENSE +175 -0
  655. agentcore_cli-0.31.1.dist-info/licenses/NOTICE +88 -0
@@ -0,0 +1,458 @@
1
+ """Read/validate/write AgentCore config files (port of ``config-io.ts``).
2
+
3
+ Schema models come from ``agentcore.schema`` and are imported lazily. Reads return validated
4
+ Pydantic models; writes accept models or plain dicts, validate, and write
5
+ ``JSON.stringify(data, null, 2)``-identical output (2-space indent, no trailing newline)
6
+ atomically through a sibling temp file + rename.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import time
14
+ import uuid
15
+ from collections.abc import Callable, Mapping, Sequence
16
+ from contextlib import contextmanager
17
+ from typing import Any, Literal, TypeVar
18
+
19
+ from pydantic import BaseModel, TypeAdapter
20
+ from pydantic import ValidationError as PydanticValidationError
21
+
22
+ from agentcore.lib.errors.types import (
23
+ ConfigNotFoundError,
24
+ ConfigParseError,
25
+ ConfigReadError,
26
+ ConfigValidationError,
27
+ ConfigWriteError,
28
+ NoProjectError,
29
+ )
30
+ from agentcore.lib.schemas.io.path_resolver import PathConfig, PathResolver, find_config_root
31
+ from agentcore.lib.utils.aws_account import detect_aws_account
32
+ from agentcore.lib.utils.json_format import to_json_string
33
+
34
+ T = TypeVar("T")
35
+
36
+ SchemaVersion = Literal[1]
37
+
38
+ PROJECT_LOCK_RETRY_MS = 25
39
+ PROJECT_LOCK_TIMEOUT_MS = 45_000
40
+ PROJECT_LOCK_STALE_MS = 60_000
41
+
42
+ # Mirrors AgentCoreRegionSchema; used only if agentcore.schema doesn't expose the region type.
43
+ _FALLBACK_REGIONS = frozenset(
44
+ {
45
+ "ap-northeast-1", "ap-northeast-2", "ap-south-1", "ap-south-2", "ap-southeast-1", "ap-southeast-2",
46
+ "ap-southeast-5", "ap-southeast-7", "ca-central-1", "eu-central-1", "eu-north-1", "eu-south-1",
47
+ "eu-south-2", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2",
48
+ "us-west-1", "us-west-2", "us-gov-west-1",
49
+ }
50
+ ) # fmt: skip
51
+
52
+
53
+ def get_schema_url_for_version(version: SchemaVersion) -> str:
54
+ return f"https://schema.agentcore.aws.dev/v{version}/agentcore.json"
55
+
56
+
57
+ # --- Schema bridge -----------------------------------------------------------------------------
58
+
59
+ Validator = type[BaseModel] | TypeAdapter[Any]
60
+
61
+
62
+ def _schema_attr(name: str) -> Any:
63
+ import agentcore.schema as schema
64
+
65
+ try:
66
+ return getattr(schema, name)
67
+ except AttributeError as exc:
68
+ raise ImportError(f"agentcore.schema does not define {name}") from exc
69
+
70
+
71
+ def _targets_validator() -> Validator:
72
+ try:
73
+ import agentcore.schema as schema
74
+
75
+ adapter = getattr(schema, "AwsDeploymentTargetsSchema", None)
76
+ except ImportError:
77
+ adapter = None
78
+ if isinstance(adapter, TypeAdapter) or (isinstance(adapter, type) and issubclass(adapter, BaseModel)):
79
+ return adapter
80
+ return TypeAdapter(list[_schema_attr("AwsDeploymentTarget")]) # type: ignore[misc]
81
+
82
+
83
+ def to_jsonable(value: Any) -> Any:
84
+ """Models -> ``model_dump(mode="json", by_alias=True)``, recursively.
85
+
86
+ Not ``exclude_none``: the schema models already omit unset optionals, and ``exclude_none``
87
+ would also strip ``null``s inside free-form payloads that upstream preserves.
88
+ """
89
+ if isinstance(value, BaseModel):
90
+ return value.model_dump(mode="json", by_alias=True)
91
+ if isinstance(value, Mapping):
92
+ return {k: to_jsonable(v) for k, v in value.items()}
93
+ if isinstance(value, (list, tuple)):
94
+ return [to_jsonable(v) for v in value]
95
+ return value
96
+
97
+
98
+ def _run_validator(validator: Validator, data: Any) -> Any:
99
+ if isinstance(validator, TypeAdapter):
100
+ return validator.validate_python(data)
101
+ return validator.model_validate(data)
102
+
103
+
104
+ def _dump(validator: Validator, value: Any) -> Any:
105
+ if isinstance(validator, TypeAdapter):
106
+ return validator.dump_python(value, mode="json", by_alias=True)
107
+ return to_jsonable(value)
108
+
109
+
110
+ def _unique_by_name(items: Any, message: Callable[[str], str]) -> list[dict[str, Any]]:
111
+ issues: list[dict[str, Any]] = []
112
+ seen: set[str] = set()
113
+ for idx, item in enumerate(items or []):
114
+ name = item.get("name") if isinstance(item, Mapping) else getattr(item, "name", None)
115
+ if name in seen:
116
+ issues.append({"type": "custom", "loc": (idx,), "msg": message(str(name))})
117
+ seen.add(name)
118
+ return issues
119
+
120
+
121
+ class ConfigIO:
122
+ """Reads, writes and validates AgentCore configuration files."""
123
+
124
+ def __init__(self, path_config: PathConfig | Mapping[str, str] | None = None, *, base_dir: str | None = None):
125
+ if isinstance(path_config, Mapping):
126
+ base_dir = base_dir or path_config.get("base_dir") or path_config.get("baseDir")
127
+ elif isinstance(path_config, PathConfig):
128
+ base_dir = base_dir or path_config.base_dir
129
+
130
+ if base_dir:
131
+ # Explicit base dir (e.g. during project creation).
132
+ self._project_discovered = True
133
+ else:
134
+ discovered = find_config_root()
135
+ base_dir = discovered
136
+ self._project_discovered = discovered is not None
137
+
138
+ self._path_resolver = PathResolver(base_dir=base_dir)
139
+
140
+ def has_project(self) -> bool:
141
+ """False when no base dir was given and auto-discovery found no project."""
142
+ return self._project_discovered
143
+
144
+ def get_path_resolver(self) -> PathResolver:
145
+ return self._path_resolver
146
+
147
+ def set_base_dir(self, base_dir: str) -> None:
148
+ self._path_resolver.set_base_dir(base_dir)
149
+
150
+ def get_project_root(self) -> str:
151
+ return self._path_resolver.get_project_root()
152
+
153
+ def get_config_root(self) -> str:
154
+ return self._path_resolver.get_base_dir()
155
+
156
+ def get_agent_config_path(self) -> str:
157
+ return self._path_resolver.get_agent_config_path()
158
+
159
+ def get_harness_config_path(self, harness_name: str) -> str:
160
+ return self._path_resolver.get_harness_config_path(harness_name)
161
+
162
+ # --- Project spec ---
163
+
164
+ def read_project_spec(self) -> Any:
165
+ return self._read_and_validate(
166
+ self._path_resolver.get_agent_config_path(),
167
+ "AgentCore Project Config",
168
+ _schema_attr("AgentCoreProjectSpec"),
169
+ )
170
+
171
+ def write_project_spec(self, data: Any) -> None:
172
+ cleaned = to_jsonable(data)
173
+ # TODO (upstream): extend to all resource arrays so empty defaults never pollute agentcore.json
174
+ if isinstance(cleaned, dict):
175
+ for key in ("configBundles", "abTests"):
176
+ if cleaned.get(key) == []:
177
+ del cleaned[key]
178
+ self._validate_and_write(
179
+ self._path_resolver.get_agent_config_path(),
180
+ "AgentCore Project Config",
181
+ _schema_attr("AgentCoreProjectSpec"),
182
+ cleaned,
183
+ )
184
+
185
+ def update_project_spec(self, mutate: Callable[[Any], Any]) -> Any:
186
+ """Read, mutate, validate and write ``agentcore.json`` under a cross-process lock.
187
+
188
+ ``mutate`` may modify the project in place (returning ``None``) or return a replacement.
189
+ """
190
+ with self._file_lock(self._path_resolver.get_agent_config_path()):
191
+ project = self.read_project_spec()
192
+ result = mutate(project)
193
+ updated = project if result is None else result
194
+ self.write_project_spec(updated)
195
+ return updated
196
+
197
+ # --- AWS targets ---
198
+
199
+ def read_aws_deployment_targets(self) -> list[Any]:
200
+ """Read ``aws-targets.json``. With ``AWS_PROFILE`` set, the account comes from the credentials."""
201
+ targets = self._read_targets()
202
+ if os.environ.get("AWS_PROFILE"):
203
+ account = detect_aws_account()
204
+ if account:
205
+ targets = [_with_field(t, "account", account) for t in targets]
206
+ return targets
207
+
208
+ def resolve_aws_deployment_targets(self) -> list[Any]:
209
+ """Targets with region fallback: saved > AWS_REGION > AWS_DEFAULT_REGION > profile config."""
210
+ targets = self.read_aws_deployment_targets()
211
+ fallback_region = self._resolve_region_fallback()
212
+ if not fallback_region:
213
+ return targets
214
+ return [t if _get_field(t, "region") else _with_field(t, "region", fallback_region) for t in targets]
215
+
216
+ def _resolve_region_fallback(self) -> str | None:
217
+ env_region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION")
218
+ if env_region and _is_agentcore_region(env_region):
219
+ return env_region
220
+ try:
221
+ import botocore.session
222
+
223
+ profile = os.environ.get("AWS_PROFILE") or "default"
224
+ profiles = botocore.session.Session().full_config.get("profiles", {})
225
+ profile_region = profiles.get(profile, {}).get("region")
226
+ if profile_region and _is_agentcore_region(profile_region):
227
+ return profile_region
228
+ except Exception:
229
+ pass # config file not available
230
+ return None
231
+
232
+ def write_aws_deployment_targets(self, data: Sequence[Any]) -> None:
233
+ file_path = self._path_resolver.get_aws_targets_config_path()
234
+
235
+ def check(targets: Any) -> None:
236
+ issues = _unique_by_name(_as_list(targets), lambda n: f"Duplicate deployment target name: {n}")
237
+ if issues:
238
+ raise ConfigValidationError(file_path, "AWS Targets", issues)
239
+
240
+ self._validate_and_write(file_path, "AWS Targets", _targets_validator(), list(data), check=check)
241
+
242
+ def _read_targets(self) -> list[Any]:
243
+ file_path = self._path_resolver.get_aws_targets_config_path()
244
+ targets = self._read_and_validate(file_path, "AWS Targets", _targets_validator())
245
+ targets = _as_list(targets)
246
+ issues = _unique_by_name(targets, lambda n: f"Duplicate deployment target name: {n}")
247
+ if issues:
248
+ raise ConfigValidationError(file_path, "AWS Targets", issues)
249
+ return targets
250
+
251
+ # --- Deployed state ---
252
+
253
+ def _target_names(self) -> set[str]:
254
+ return {str(_get_field(t, "name")) for t in self.read_aws_deployment_targets()}
255
+
256
+ def read_deployed_state(self) -> Any:
257
+ """Read ``.cli/deployed-state.json``; every target key must exist in aws-targets."""
258
+ names = self._target_names()
259
+ file_path = self._path_resolver.get_state_path()
260
+ state = self._read_and_validate(file_path, "State", _schema_attr("DeployedState"))
261
+ self._check_state_targets(file_path, state, names)
262
+ return state
263
+
264
+ def write_deployed_state(self, data: Any) -> None:
265
+ names = self._target_names()
266
+ file_path = self._path_resolver.get_state_path()
267
+ self._validate_and_write(
268
+ file_path,
269
+ "State",
270
+ _schema_attr("DeployedState"),
271
+ data,
272
+ check=lambda state: self._check_state_targets(file_path, state, names),
273
+ )
274
+
275
+ def _check_state_targets(self, file_path: str, state: Any, names: set[str]) -> None:
276
+ targets = _get_field(state, "targets")
277
+ keys = list(targets.keys()) if isinstance(targets, Mapping) else []
278
+ if all(k in names for k in keys):
279
+ return
280
+ raise ConfigValidationError(
281
+ file_path,
282
+ "State",
283
+ [{"type": "custom", "loc": (), "msg": "Deployed state contains target names not present in aws-targets"}],
284
+ )
285
+
286
+ # --- MCP defs / harness ---
287
+
288
+ def read_mcp_defs(self) -> Any:
289
+ return self._read_and_validate(
290
+ self._path_resolver.get_mcp_defs_path(), "MCP Definitions", _schema_attr("AgentCoreCliMcpDefs")
291
+ )
292
+
293
+ def write_mcp_defs(self, data: Any) -> None:
294
+ self._validate_and_write(
295
+ self._path_resolver.get_mcp_defs_path(), "MCP Definitions", _schema_attr("AgentCoreCliMcpDefs"), data
296
+ )
297
+
298
+ def read_harness_spec(self, harness_name: str) -> Any:
299
+ return self._read_and_validate(
300
+ self._path_resolver.get_harness_config_path(harness_name), "Harness Spec", _schema_attr("HarnessSpec")
301
+ )
302
+
303
+ def write_harness_spec(self, harness_name: str, data: Any) -> None:
304
+ self._validate_and_write(
305
+ self._path_resolver.get_harness_config_path(harness_name), "Harness Spec", _schema_attr("HarnessSpec"), data
306
+ )
307
+
308
+ # --- Existence / init ---
309
+
310
+ def base_dir_exists(self) -> bool:
311
+ return os.path.exists(self._path_resolver.get_base_dir())
312
+
313
+ def config_exists(self, kind: Literal["project", "awsTargets", "state", "mcpDefs"]) -> bool:
314
+ paths = {
315
+ "project": self._path_resolver.get_agent_config_path,
316
+ "awsTargets": self._path_resolver.get_aws_targets_config_path,
317
+ "state": self._path_resolver.get_state_path,
318
+ "mcpDefs": self._path_resolver.get_mcp_defs_path,
319
+ }
320
+ return os.path.exists(paths[kind]())
321
+
322
+ def initialize_base_dir(self) -> None:
323
+ """Create ``agentcore/`` and ``agentcore/.cli/``. Requires a configured/discovered project."""
324
+ self._require_project()
325
+ base_dir = self._path_resolver.get_base_dir()
326
+ try:
327
+ os.makedirs(base_dir, exist_ok=True)
328
+ os.makedirs(self._path_resolver.get_cli_system_dir(), exist_ok=True)
329
+ except OSError as exc:
330
+ raise ConfigWriteError(base_dir, exc) from exc
331
+
332
+ # --- Internals ---
333
+
334
+ def _require_project(self) -> None:
335
+ if not self._project_discovered:
336
+ raise NoProjectError()
337
+
338
+ def _read_and_validate(self, file_path: str, file_type: str, validator: Validator) -> Any:
339
+ if not os.path.exists(file_path):
340
+ raise ConfigNotFoundError(file_path, file_type)
341
+ try:
342
+ with open(file_path, encoding="utf-8") as fh:
343
+ content = fh.read()
344
+ except (OSError, UnicodeDecodeError) as exc:
345
+ raise ConfigReadError(file_path, exc) from exc
346
+ try:
347
+ parsed = json.loads(content)
348
+ except ValueError as exc:
349
+ raise ConfigParseError(file_path, exc) from exc
350
+ try:
351
+ return _run_validator(validator, parsed)
352
+ except PydanticValidationError as exc:
353
+ raise ConfigValidationError(file_path, file_type, exc) from exc
354
+
355
+ def _validate_and_write(
356
+ self,
357
+ file_path: str,
358
+ file_type: str,
359
+ validator: Validator,
360
+ data: Any,
361
+ check: Callable[[Any], None] | None = None,
362
+ ) -> None:
363
+ self._require_project()
364
+ try:
365
+ validated = _run_validator(validator, to_jsonable(data))
366
+ except PydanticValidationError as exc:
367
+ raise ConfigValidationError(file_path, file_type, exc) from exc
368
+ if check is not None:
369
+ check(validated)
370
+
371
+ try:
372
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
373
+ except OSError as exc:
374
+ raise ConfigWriteError(file_path, exc) from exc
375
+
376
+ # Write a sibling temp file and rename so readers never observe partial JSON.
377
+ temp_path = f"{file_path}.{os.getpid()}.{uuid.uuid4()}.tmp"
378
+ try:
379
+ content = to_json_string(_dump(validator, validated), 2)
380
+ with open(temp_path, "w", encoding="utf-8", newline="") as fh:
381
+ fh.write(content)
382
+ os.replace(temp_path, file_path)
383
+ except Exception as exc:
384
+ try:
385
+ os.unlink(temp_path)
386
+ except OSError:
387
+ pass
388
+ raise ConfigWriteError(file_path, exc) from exc
389
+
390
+ @contextmanager
391
+ def _file_lock(self, file_path: str): # type: ignore[no-untyped-def]
392
+ """Exclusive ``<file>.lock`` via O_EXCL; stale locks (>60s) are broken, waits time out at 45s."""
393
+ lock_path = f"{file_path}.lock"
394
+ started = time.monotonic()
395
+ fd: int | None = None
396
+ while fd is None:
397
+ try:
398
+ fd = os.open(lock_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
399
+ except FileExistsError:
400
+ try:
401
+ if time.time() - os.stat(lock_path).st_mtime > PROJECT_LOCK_STALE_MS / 1000:
402
+ os.unlink(lock_path)
403
+ continue
404
+ except FileNotFoundError:
405
+ continue
406
+ except OSError as exc:
407
+ raise ConfigWriteError(file_path, exc) from exc
408
+ if (time.monotonic() - started) * 1000 >= PROJECT_LOCK_TIMEOUT_MS:
409
+ raise ConfigWriteError(
410
+ file_path, RuntimeError("Timed out waiting for another configuration update")
411
+ ) from None
412
+ time.sleep(PROJECT_LOCK_RETRY_MS / 1000)
413
+ except OSError as exc:
414
+ raise ConfigWriteError(file_path, exc) from exc
415
+ try:
416
+ yield
417
+ finally:
418
+ os.close(fd)
419
+ try:
420
+ os.unlink(lock_path)
421
+ except FileNotFoundError:
422
+ pass
423
+
424
+
425
+ def _as_list(value: Any) -> list[Any]:
426
+ return list(value.root) if hasattr(value, "root") else list(value)
427
+
428
+
429
+ def _get_field(obj: Any, key: str) -> Any:
430
+ if isinstance(obj, Mapping):
431
+ return obj.get(key)
432
+ return getattr(obj, key, None)
433
+
434
+
435
+ def _with_field(obj: Any, key: str, value: Any) -> Any:
436
+ if isinstance(obj, BaseModel):
437
+ return obj.model_copy(update={key: value})
438
+ if isinstance(obj, Mapping):
439
+ return {**obj, key: value}
440
+ return obj
441
+
442
+
443
+ def _is_agentcore_region(region: str) -> bool:
444
+ try:
445
+ region_type = _schema_attr("AgentCoreRegion")
446
+ except ImportError:
447
+ return region in _FALLBACK_REGIONS
448
+ try:
449
+ TypeAdapter(region_type).validate_python(region)
450
+ return True
451
+ except PydanticValidationError:
452
+ return False
453
+ except Exception:
454
+ return region in _FALLBACK_REGIONS
455
+
456
+
457
+ def create_config_io(path_config: PathConfig | Mapping[str, str] | None = None, **kwargs: Any) -> ConfigIO:
458
+ return ConfigIO(path_config, **kwargs)
@@ -0,0 +1,239 @@
1
+ """User-level CLI config at ``~/.agentcore/config.json`` (port of ``global-config.ts``).
2
+
3
+ The config is a plain ``dict`` keyed by the on-disk camelCase names. Reads are resilient
4
+ (invalid fields are dropped, unknown keys preserved); ``validate_global_config`` is strict.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import re
12
+ import uuid
13
+ from collections.abc import Callable, Mapping
14
+ from dataclasses import dataclass, field
15
+ from typing import Any, NotRequired, TypedDict
16
+
17
+ from agentcore.lib.result import Err, Ok
18
+ from agentcore.lib.utils.json_format import to_json_string
19
+
20
+
21
+ class TelemetryConfig(TypedDict, total=False):
22
+ enabled: bool
23
+ endpoint: str
24
+ audit: bool
25
+
26
+
27
+ class GlobalConfig(TypedDict, total=False):
28
+ installationId: str
29
+ uvDefaultIndex: str
30
+ uvIndex: str
31
+ disableDependencyManagement: bool
32
+ disableTransactionSearch: bool
33
+ transactionSearchIndexPercentage: int
34
+ telemetry: NotRequired[TelemetryConfig]
35
+
36
+
37
+ def get_global_config_dir() -> str:
38
+ env_dir = os.environ.get("AGENTCORE_CONFIG_DIR")
39
+ return env_dir if env_dir is not None else os.path.join(os.path.expanduser("~"), ".agentcore")
40
+
41
+
42
+ def get_global_config_file() -> str:
43
+ return os.path.join(get_global_config_dir(), "config.json")
44
+
45
+
46
+ # Snapshot at import time, like upstream. Functions resolve the location at call time instead
47
+ # so AGENTCORE_CONFIG_DIR changes (e.g. in tests) take effect.
48
+ GLOBAL_CONFIG_DIR = get_global_config_dir()
49
+ GLOBAL_CONFIG_FILE = get_global_config_file()
50
+
51
+
52
+ # --- Schema (strict, mirrors the Zod object) --------------------------------------------------
53
+
54
+ # Zod 4's z.string().uuid() pattern.
55
+ _UUID_RE = re.compile(
56
+ r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"
57
+ r"|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)"
58
+ )
59
+
60
+
61
+ class _Invalid(Exception):
62
+ pass
63
+
64
+
65
+ def _string(value: Any) -> str:
66
+ if not isinstance(value, str):
67
+ raise _Invalid("expected string")
68
+ return value
69
+
70
+
71
+ def _uuid(value: Any) -> str:
72
+ if not isinstance(value, str) or not _UUID_RE.fullmatch(value):
73
+ raise _Invalid("Invalid UUID")
74
+ return value
75
+
76
+
77
+ def _boolean(value: Any) -> bool:
78
+ if not isinstance(value, bool):
79
+ raise _Invalid("expected boolean")
80
+ return value
81
+
82
+
83
+ def _percentage(value: Any) -> int:
84
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
85
+ raise _Invalid("expected number")
86
+ if isinstance(value, float) and not value.is_integer():
87
+ raise _Invalid("expected int, received number")
88
+ if not 0 <= value <= 100:
89
+ raise _Invalid("Too small: expected number to be >=0" if value < 0 else "Too big: expected number to be <=100")
90
+ return int(value)
91
+
92
+
93
+ _Validator = Callable[[Any], Any]
94
+
95
+ _TELEMETRY_SHAPE: dict[str, _Validator] = {"enabled": _boolean, "endpoint": _string, "audit": _boolean}
96
+
97
+
98
+ def _strict_object(shape: Mapping[str, Any], value: Any, path: list[Any], issues: list[dict[str, Any]]) -> None:
99
+ if not isinstance(value, Mapping):
100
+ issues.append({"type": "dict_type", "loc": tuple(path), "msg": "expected object"})
101
+ return
102
+ for key, item in value.items():
103
+ if key not in shape:
104
+ issues.append({"type": "extra_forbidden", "loc": (*path, key), "msg": "Unrecognized key"})
105
+ continue
106
+ validator = shape[key]
107
+ if isinstance(validator, Mapping):
108
+ _strict_object(validator, item, [*path, key], issues)
109
+ continue
110
+ try:
111
+ validator(item)
112
+ except _Invalid as exc:
113
+ issues.append({"type": "value_error", "loc": (*path, key), "msg": str(exc), "input": item})
114
+
115
+
116
+ _GLOBAL_SHAPE: dict[str, Any] = {
117
+ "installationId": _uuid,
118
+ "uvDefaultIndex": _string,
119
+ "uvIndex": _string,
120
+ "disableDependencyManagement": _boolean,
121
+ "disableTransactionSearch": _boolean,
122
+ "transactionSearchIndexPercentage": _percentage,
123
+ "telemetry": _TELEMETRY_SHAPE,
124
+ }
125
+
126
+
127
+ @dataclass(frozen=True, slots=True)
128
+ class GlobalConfigValidation:
129
+ success: bool
130
+ issues: list[dict[str, Any]] = field(default_factory=list)
131
+
132
+
133
+ def validate_global_config(data: Any) -> GlobalConfigValidation:
134
+ """Strictly validate a (partial) config: unknown keys and wrong types fail."""
135
+ issues: list[dict[str, Any]] = []
136
+ _strict_object(_GLOBAL_SHAPE, data, [], issues)
137
+ return GlobalConfigValidation(success=not issues, issues=issues)
138
+
139
+
140
+ def _resilient(shape: Mapping[str, Any], data: Any) -> dict[str, Any]:
141
+ """Field-by-field parse: invalid known fields are dropped, unknown keys kept (upstream resilientParse)."""
142
+ if not isinstance(data, Mapping):
143
+ return {}
144
+ result: dict[str, Any] = {}
145
+ for key, validator in shape.items():
146
+ if key not in data:
147
+ continue
148
+ value = data[key]
149
+ if isinstance(validator, Mapping):
150
+ if isinstance(value, Mapping):
151
+ result[key] = _resilient(validator, value)
152
+ continue
153
+ try:
154
+ result[key] = validator(value)
155
+ except _Invalid:
156
+ pass
157
+ for key, value in data.items():
158
+ if key not in shape:
159
+ result[key] = value
160
+ return result
161
+
162
+
163
+ # --- I/O ---------------------------------------------------------------------------------------
164
+
165
+
166
+ def read_global_config(config_file: str | None = None) -> Ok[GlobalConfig] | Err[Exception]:
167
+ """Read the config. A missing file is ``Ok({})``; an unreadable/unparseable file is ``Err``."""
168
+ path = config_file or get_global_config_file()
169
+ if not os.path.exists(path):
170
+ return Ok(GlobalConfig())
171
+ try:
172
+ with open(path, encoding="utf-8") as fh:
173
+ parsed = json.load(fh)
174
+ if not isinstance(parsed, dict):
175
+ return Err(ValueError(f"Config at {path} is not a JSON object."))
176
+ return Ok(_resilient(_GLOBAL_SHAPE, parsed)) # type: ignore[arg-type]
177
+ except Exception as exc:
178
+ return Err(exc)
179
+
180
+
181
+ def read_global_config_sync(config_file: str | None = None) -> GlobalConfig:
182
+ """Best-effort read; any failure yields ``{}``."""
183
+ try:
184
+ with open(config_file or get_global_config_file(), encoding="utf-8") as fh:
185
+ return _resilient(_GLOBAL_SHAPE, json.load(fh)) # type: ignore[return-value]
186
+ except Exception:
187
+ return GlobalConfig()
188
+
189
+
190
+ def _merge_config(target: Mapping[str, Any], source: Mapping[str, Any]) -> dict[str, Any]:
191
+ merged = {**target, **source}
192
+ if "telemetry" in source:
193
+ merged["telemetry"] = {**(target.get("telemetry") or {}), **(source["telemetry"] or {})}
194
+ return merged
195
+
196
+
197
+ def update_global_config(
198
+ partial: Mapping[str, Any], config_dir: str | None = None, config_file: str | None = None
199
+ ) -> bool:
200
+ """Shallow-merge ``partial`` into the config (``telemetry`` merged one level deep)."""
201
+ config_dir = config_dir or get_global_config_dir()
202
+ config_file = config_file or get_global_config_file()
203
+ existing = read_global_config(config_file)
204
+ if not isinstance(existing, Ok):
205
+ return False
206
+ try:
207
+ merged = _merge_config(existing.value, partial)
208
+ os.makedirs(config_dir, exist_ok=True)
209
+ with open(config_file, "w", encoding="utf-8") as fh:
210
+ fh.write(to_json_string(merged, 2))
211
+ return True
212
+ except Exception:
213
+ return False
214
+
215
+
216
+ @dataclass(frozen=True, slots=True)
217
+ class InstallationId:
218
+ id: str
219
+ created: bool
220
+
221
+
222
+ def get_or_create_installation_id(
223
+ config_dir: str | None = None, config_file: str | None = None
224
+ ) -> Ok[InstallationId] | Err[Exception]:
225
+ """Return the installation id, generating and persisting one if absent or not a valid UUID.
226
+
227
+ Concurrent first runs may each generate an id; the last write wins.
228
+ """
229
+ config_file = config_file or get_global_config_file()
230
+ read = read_global_config(config_file)
231
+ if not isinstance(read, Ok):
232
+ return read
233
+ existing = read.value.get("installationId")
234
+ if existing:
235
+ return Ok(InstallationId(id=existing, created=False))
236
+ new_id = str(uuid.uuid4())
237
+ if not update_global_config({"installationId": new_id}, config_dir, config_file):
238
+ return Err(RuntimeError(f"Failed to persist installation id to {config_file}"))
239
+ return Ok(InstallationId(id=new_id, created=True))