agentnode-sdk 0.13.0__tar.gz → 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/CHANGELOG.md +83 -0
  2. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/__init__.py +1 -1
  4. agentnode_sdk-0.15.0/agentnode_sdk/cli/setup_wizard.py +340 -0
  5. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/config.py +9 -1
  6. agentnode_sdk-0.15.0/agentnode_sdk/llm_providers.py +116 -0
  7. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_runner.py +65 -44
  8. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/pyproject.toml +1 -1
  9. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_config.py +31 -0
  10. agentnode_sdk-0.15.0/tests/test_llm_providers.py +79 -0
  11. agentnode_sdk-0.15.0/tests/test_llm_vault_binding.py +306 -0
  12. agentnode_sdk-0.15.0/tests/test_setup_wizard.py +342 -0
  13. agentnode_sdk-0.13.0/agentnode_sdk/cli/setup_wizard.py +0 -128
  14. agentnode_sdk-0.13.0/tests/test_llm_vault_binding.py +0 -143
  15. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/.env.example +0 -0
  16. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/.gitignore +0 -0
  17. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/README.md +0 -0
  18. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  19. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/REGISTRY_SIGNING_SPEC.md +0 -0
  20. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/THREAT_MODEL.md +0 -0
  21. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/TRUST_STACK.md +0 -0
  22. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode.lock +0 -0
  23. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/_fileutil.py +0 -0
  24. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/async_client.py +0 -0
  25. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/capability_graph.py +0 -0
  26. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/capability_taxonomy.py +0 -0
  27. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/__init__.py +0 -0
  28. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/__main__.py +0 -0
  29. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/audit.py +0 -0
  30. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/auth.py +0 -0
  31. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
  32. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/commands.py +0 -0
  33. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/complements.py +0 -0
  34. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/init.py +0 -0
  35. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/main.py +0 -0
  36. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
  37. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_status.py +0 -0
  38. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
  39. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
  40. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/output.py +0 -0
  41. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/publish.py +0 -0
  42. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/record_cases.py +0 -0
  43. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  44. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/serve.py +0 -0
  45. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/smart_run.py +0 -0
  46. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/templates.py +0 -0
  47. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/validate.py +0 -0
  48. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/cli/verify_local.py +0 -0
  49. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/client.py +0 -0
  50. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/compatibility.py +0 -0
  51. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_handle.py +0 -0
  52. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_resolver.py +0 -0
  53. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/credential_store.py +0 -0
  54. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/detect.py +0 -0
  55. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/exceptions.py +0 -0
  56. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/guard.py +0 -0
  57. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/input_guard.py +0 -0
  58. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/installer.py +0 -0
  59. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/key_status.py +0 -0
  60. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/lock_integrity.py +0 -0
  61. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/mcp_server.py +0 -0
  62. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/models.py +0 -0
  63. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/planner.py +0 -0
  64. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/policy.py +0 -0
  65. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/references.py +0 -0
  66. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/registry_trust.py +0 -0
  67. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/resolve.py +0 -0
  68. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/resource_provider.py +0 -0
  69. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/risk_profile.py +0 -0
  70. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/run_log.py +0 -0
  71. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runner.py +0 -0
  72. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtime.py +0 -0
  73. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/__init__.py +0 -0
  74. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
  75. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
  76. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
  77. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  78. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
  79. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  80. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/__init__.py +0 -0
  81. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
  82. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
  83. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
  84. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/backend.py +0 -0
  85. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
  86. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/policy.py +0 -0
  87. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/sandbox/types.py +0 -0
  88. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/signature.py +0 -0
  89. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/signing_key.py +0 -0
  90. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/agentnode_sdk/skill.py +0 -0
  91. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/sandbox-image/Dockerfile +0 -0
  92. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/sandbox-image/README.md +0 -0
  93. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/analyze_scores.py +0 -0
  94. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/batch_verify.py +0 -0
  95. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/ci_smoke_test.py +0 -0
  96. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/generate_compatibility_artifacts.py +0 -0
  97. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/verify_toolcalls.py +0 -0
  98. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/scripts/weekly_retest.sh +0 -0
  99. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/README.md +0 -0
  100. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
  101. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
  102. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
  103. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
  104. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
  105. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/__init__.py +0 -0
  106. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/conftest.py +0 -0
  107. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_llm_broker.py +0 -0
  108. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_llm_policy.py +0 -0
  109. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_rpc.py +0 -0
  110. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_runner.py +0 -0
  111. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_e2e.py +0 -0
  112. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_routing.py +0 -0
  113. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_sandbox_spike.py +0 -0
  114. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_agent_session_container.py +0 -0
  115. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_async_client.py +0 -0
  116. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_audit_ux.py +0 -0
  117. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_auth_cli_vault.py +0 -0
  118. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_auto_upgrade_policy.py +0 -0
  119. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_cli.py +0 -0
  120. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_cli_lock.py +0 -0
  121. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_cli_run_resolution.py +0 -0
  122. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_client.py +0 -0
  123. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_client_json_guard.py +0 -0
  124. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_client_sprint_b.py +0 -0
  125. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_credential_handle.py +0 -0
  126. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_credential_integration.py +0 -0
  127. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_credential_resolver.py +0 -0
  128. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_credential_store.py +0 -0
  129. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_credential_vault.py +0 -0
  130. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_detect.py +0 -0
  131. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_detect_and_install.py +0 -0
  132. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_e2e_runtime.py +0 -0
  133. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_edge_cases.py +0 -0
  134. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard.py +0 -0
  135. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_check.py +0 -0
  136. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_config_cache.py +0 -0
  137. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_policy.py +0 -0
  138. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_preview.py +0 -0
  139. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_schema.py +0 -0
  140. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_set.py +0 -0
  141. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_status.py +0 -0
  142. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override.py +0 -0
  143. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override_audit.py +0 -0
  144. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_tool_override_cli.py +0 -0
  145. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_guard_ux.py +0 -0
  146. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_input_guard.py +0 -0
  147. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_input_guard_escalation.py +0 -0
  148. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_install_hardening.py +0 -0
  149. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_installer_sprint_b.py +0 -0
  150. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_intelligence.py +0 -0
  151. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_key_status.py +0 -0
  152. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_llm_binding.py +0 -0
  153. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_llm_call_runlog.py +0 -0
  154. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_lock_integrity.py +0 -0
  155. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_lock_runtime.py +0 -0
  156. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_mcp_audit.py +0 -0
  157. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_mcp_doctor.py +0 -0
  158. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_mcp_sandbox.py +0 -0
  159. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_mcp_server.py +0 -0
  160. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_observability.py +0 -0
  161. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_planner.py +0 -0
  162. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_policy.py +0 -0
  163. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_policy_integration.py +0 -0
  164. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_prompt_specs.py +0 -0
  165. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_provider_matrix.py +0 -0
  166. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_publish.py +0 -0
  167. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_references.py +0 -0
  168. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_registry_trust.py +0 -0
  169. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_remote_hardening.py +0 -0
  170. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_remote_runner.py +0 -0
  171. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_resource_provider.py +0 -0
  172. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_resource_specs.py +0 -0
  173. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_risk_profile.py +0 -0
  174. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_run_log.py +0 -0
  175. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_runner.py +0 -0
  176. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_runtime.py +0 -0
  177. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_runtime_audit.py +0 -0
  178. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_backend.py +0 -0
  179. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_doctor.py +0 -0
  180. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_e2e.py +0 -0
  181. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_sandbox_gate.py +0 -0
  182. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_security_hardening.py +0 -0
  183. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_signature.py +0 -0
  184. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_signing_key.py +0 -0
  185. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_skill.py +0 -0
  186. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_smart.py +0 -0
  187. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_stability.py +0 -0
  188. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_toolpack_sandbox.py +0 -0
  189. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_v02.py +0 -0
  190. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_validate.py +0 -0
  191. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/test_validate_skill.py +0 -0
  192. {agentnode_sdk-0.13.0 → agentnode_sdk-0.15.0}/tests/validation_lockfile.json +0 -0
@@ -1,5 +1,88 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.0 — Generic OpenAI-compatible LLM providers
4
+
5
+ ### Added
6
+
7
+ - **Generic OpenAI-compatible provider registry.** The LLM runtime can now
8
+ bind any endpoint that speaks the OpenAI-compatible protocol. Custom
9
+ endpoints are plain config entries — `llm.providers.<name>` with
10
+ `base_url`, `model`, and optionally `api_key_env`/`requires_key` — no code
11
+ changes needed per provider.
12
+ - **Built-in presets** for OpenRouter, DeepSeek, Mistral, Qwen, Gemini
13
+ (Google's OpenAI-compatible endpoint) and Ollama, each with the canonical
14
+ base URL, API-key environment variable, and a sensible default model
15
+ (overridable via `llm.providers.<name>.model`).
16
+ - **Ollama as a key-free local provider** — the first agent path with no
17
+ account and no per-token cost. Opt-in only: select it explicitly with
18
+ `agentnode config set llm.default_provider ollama` (or configure it under
19
+ `llm.providers`); AgentNode never probes or binds it by surprise.
20
+ - Per-provider credentials: each provider's environment variable (e.g.
21
+ `DEEPSEEK_API_KEY`, `GEMINI_API_KEY`) and stored vault credentials both
22
+ work; the environment always overrides the stored key.
23
+
24
+ ### Changed
25
+
26
+ - The OpenRouter binding now goes through the shared provider registry
27
+ (same behavior, including the namespaced default model — one code path
28
+ instead of a special case).
29
+ - The sandboxed-agent LLM broker automatically inherits every compatible
30
+ provider: keys stay host-side, the container still sees only RPC
31
+ completions, and the C1/C2 access policy applies unchanged.
32
+
33
+ ### Hardened
34
+
35
+ - No provider keys in logs or error messages (client failures are reported
36
+ by exception type only).
37
+ - A custom provider without a configured model is skipped instead of
38
+ guessing — a wrong default model would fail confusingly at call time.
39
+ - Keyless providers are never attempted unless explicitly selected or
40
+ configured.
41
+
42
+ ### BREAKING / Upgrade Notes
43
+
44
+ - None. Existing OpenAI/Anthropic setups (env vars, vault, per-agent config)
45
+ behave identically; the new providers are purely additive.
46
+
47
+ ## 0.14.0 — Setup wizard: guided credentials + sandbox onboarding
48
+
49
+ ### Added
50
+
51
+ - **`agentnode setup` now guides through optional LLM credentials.** A new
52
+ wizard screen offers storing a provider key (OpenAI, Anthropic, OpenRouter —
53
+ or Skip, the default). Entry is via hidden getpass or an import from an
54
+ existing environment variable; the honest storage label (OS keychain /
55
+ plaintext file) is shown, and an optional key test runs against a free
56
+ endpoint (no completion call) without ever blocking the wizard.
57
+ - **`agentnode setup` now includes a "Local sandbox" screen.** It shows the
58
+ doctor's diagnosis (runtime, daemon, pinned image, digest) with clear next
59
+ steps. If only the image is missing, the wizard offers the digest-pinned
60
+ pull — on a TTY and only after an explicit Yes (default No).
61
+ - **Optional agent-sandbox enable prompt.** Only when the sandbox is fully
62
+ ready, the wizard asks "Enable sandboxed community agents now? [y/N]"
63
+ (default No). The choice is persisted with the wizard's normal save step;
64
+ cancelling saves nothing.
65
+ - The summary and finish screens show credentials, sandbox status, and the
66
+ exact follow-up commands (`agentnode auth status`, `agentnode sandbox
67
+ doctor`, `agentnode sandbox pull`, `agentnode config set
68
+ agent_sandbox.enabled true`).
69
+
70
+ ### Hardened
71
+
72
+ - No keys in wizard output (masked last-4 only), no keys in config files, no
73
+ keys via CLI arguments — entry is getpass or env import only.
74
+ - No automatic Docker pull: the only pull path is the existing, fully guarded
75
+ `agentnode sandbox pull`, offered solely on a TTY after an explicit Yes.
76
+ - The sandbox flag is never enabled without an explicit Yes, and never offered
77
+ when the sandbox is not ready.
78
+ - Pull failures never abort setup; non-interactive sessions never block
79
+ (credential and sandbox prompts skip themselves with guidance).
80
+
81
+ ### BREAKING / Upgrade Notes
82
+
83
+ - None. The wizard changes are additive; all defaults preserve previous
84
+ behavior (credentials skipped, sandbox flag stays off). No new dependency.
85
+
3
86
  ## 0.13.0 — Credential vault for LLM providers
4
87
 
5
88
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnode-sdk
3
- Version: 0.13.0
3
+ Version: 0.15.0
4
4
  Summary: Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents.
5
5
  Project-URL: Homepage, https://agentnode.net
6
6
  Project-URL: Repository, https://github.com/agentnode-ai/agentnode
@@ -38,7 +38,7 @@ from agentnode_sdk.runtime import AgentNodeRuntime
38
38
  Client = AgentNodeClient
39
39
  ToolError = AgentNodeToolError
40
40
 
41
- __version__ = "0.13.0"
41
+ __version__ = "0.15.0"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -0,0 +1,340 @@
1
+ """AgentNode setup wizard — interactive configuration.
2
+
3
+ UX-3A: the wizard offers (never requires) storing LLM provider keys via the
4
+ credential vault. All credential handling reuses the existing
5
+ auth/credential_store primitives: keys only via getpass or an env-var import
6
+ (never argv), output only masked, honest storage labels (never "encrypted"),
7
+ key tests non-blocking.
8
+
9
+ UX-3B: a "Local sandbox" screen reuses the doctor's read-only diagnosis. The
10
+ ONLY operative action is an image pull through the existing, fully guarded
11
+ ``cmd_sandbox_pull`` — offered solely on a TTY after an explicit Yes (default
12
+ No), and a pull failure never fails the wizard. The agent-sandbox flag is
13
+ offered ONLY when the sandbox is fully ready (default No) and is persisted via
14
+ the wizard's normal Save confirm. The wizard itself contains no docker calls.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ from agentnode_sdk.config import (
19
+ default_config,
20
+ installation_behavior_label,
21
+ save_config,
22
+ )
23
+ from agentnode_sdk.cli.output import bold, dim, kv, section
24
+
25
+ _LLM_CHOICES = {"1": "openai", "2": "anthropic", "3": "openrouter"}
26
+
27
+
28
+ def run_wizard() -> int:
29
+ """Run the setup wizard. Returns exit code."""
30
+ try:
31
+ cfg = _wizard_flow()
32
+ return 0 if cfg is not None else 1
33
+ except (KeyboardInterrupt, EOFError):
34
+ print("\n\nSetup cancelled.")
35
+ return 130
36
+
37
+
38
+ def _prompt(text: str, default: str = "") -> str:
39
+ result = input(text).strip()
40
+ return result if result else default
41
+
42
+
43
+ def _choice(prompt_text: str, options: list[str], default: str = "1") -> str:
44
+ result = _prompt(f"{prompt_text} [{default}]: ", default)
45
+ if result not in options:
46
+ print(f" Invalid choice. Using default: {default}")
47
+ return default
48
+ return result
49
+
50
+
51
+ def _store_llm_key(provider: str) -> str | None:
52
+ """Store one provider key via the vault. Returns the short storage label
53
+ ("OS keychain"/"plaintext file") on success, None when skipped.
54
+
55
+ Key entry is getpass-only (hidden, never argv); if the provider's env var
56
+ is already set (incl. ~/.agentnode/.env), offers to import it instead.
57
+ Output shows only the masked tail and the honest storage label."""
58
+ import getpass
59
+ import os
60
+
61
+ from agentnode_sdk.cli.auth import _masked, _storage_short, cmd_auth_test
62
+ from agentnode_sdk.credential_store import LLM_PROVIDER_ENV, set_credential
63
+
64
+ env_var = LLM_PROVIDER_ENV[provider]
65
+ try:
66
+ from agentnode_sdk.runtimes.agent_runner import _load_agentnode_env
67
+ _load_agentnode_env()
68
+ except Exception:
69
+ pass
70
+ env_val = (os.environ.get(env_var) or "").strip()
71
+
72
+ token = ""
73
+ if env_val:
74
+ print(f" Found {env_var} in your environment (env always overrides stored keys).")
75
+ imp = _prompt(f" Store this key from {env_var}? [Y/n]: ", "y")
76
+ if imp.lower() != "n":
77
+ token = env_val
78
+ if not token:
79
+ token = getpass.getpass(f" Enter API key for {provider} (input hidden): ").strip()
80
+ if not token:
81
+ print(" No key entered — skipped.")
82
+ return None
83
+
84
+ storage = set_credential(provider, token, auth_type="api_key")
85
+ if storage == "keyring":
86
+ where = "OS keychain"
87
+ else:
88
+ where = "plaintext file (0600) — OS keychain unavailable on this system"
89
+ print(f" Credential stored for {provider} ({_masked(token)}) — storage: {where}.")
90
+
91
+ t = _prompt(" Test the key now? (free endpoint, no completion call) [Y/n]: ", "y")
92
+ if t.lower() != "n":
93
+ try:
94
+ rc = cmd_auth_test(provider)
95
+ except Exception:
96
+ rc = 3
97
+ if rc not in (0, 1):
98
+ # indeterminate/network — never block the wizard on it
99
+ print(dim(f" Could not verify right now — test later with "
100
+ f"`agentnode auth test {provider}`."))
101
+ return _storage_short(storage)
102
+
103
+
104
+ def _credentials_screen() -> list[tuple[str, str]]:
105
+ """Screen 5: LLM credentials (optional, default = skip). Returns the
106
+ (provider, storage label) pairs stored in this run."""
107
+ import sys
108
+
109
+ print()
110
+ print(bold(" LLM credentials (optional)"))
111
+ print()
112
+ print(" Agents and the sandboxed-agent LLM broker need a provider API key.")
113
+ print(" Keys are stored in your OS keychain when available; otherwise in a")
114
+ print(" plaintext file (0600) — AgentNode will tell you which.")
115
+ print()
116
+
117
+ if not sys.stdin.isatty():
118
+ print(dim(" Non-interactive session — skipping. Add keys later with"))
119
+ print(dim(" `agentnode auth set <provider>`."))
120
+ return []
121
+
122
+ stored: list[tuple[str, str]] = []
123
+ while True:
124
+ print(" [1] OpenAI [2] Anthropic [3] OpenRouter [4] Skip for now")
125
+ c = _choice(" Choice", ["1", "2", "3", "4"], "4")
126
+ if c == "4":
127
+ break
128
+ provider = _LLM_CHOICES[c]
129
+ if any(p == provider for p, _ in stored):
130
+ print(f" {provider} already added in this run.")
131
+ else:
132
+ label = _store_llm_key(provider)
133
+ if label is not None:
134
+ stored.append((provider, label))
135
+ if len(stored) >= len(_LLM_CHOICES):
136
+ break
137
+ more = _prompt(" Add another provider? [y/N]: ", "n")
138
+ if more.lower() != "y":
139
+ break
140
+ return stored
141
+
142
+
143
+ def _sandbox_screen(cfg: dict) -> tuple[str, bool]:
144
+ """Screen 6: local sandbox (UX-3B).
145
+
146
+ Diagnosis via the doctor's ``_build_env_checks`` (pure, read-only); the
147
+ ONLY action is an optional, TTY-confirmed image pull via the existing
148
+ ``cmd_sandbox_pull`` — the wizard itself never talks to docker. The enable
149
+ prompt (default No) is offered ONLY when the sandbox is fully ready, and
150
+ the flag rides the wizard's normal Save confirm (cancel persists nothing).
151
+
152
+ Returns (status line for Summary/Success, image_still_missing)."""
153
+ import sys
154
+
155
+ from agentnode_sdk.cli.sandbox_commands import _build_env_checks, cmd_sandbox_pull
156
+
157
+ print()
158
+ print(bold(" Local sandbox"))
159
+ print()
160
+ print(" Community packages run in an isolated container sandbox.")
161
+ print(" Trusted/curated packages run without it.")
162
+ print()
163
+
164
+ checks, ready, image_missing = _build_env_checks()
165
+
166
+ def _render(check_list):
167
+ for c in check_list:
168
+ mark = "[OK]" if c["ok"] else ("[--]" if c["ok"] is None else "[!!]")
169
+ print(f" {mark} {c['check']}: {c['detail']}")
170
+ if c["ok"] is False and c.get("fix"):
171
+ print(dim(f" -> {c['fix']}"))
172
+
173
+ _render(checks)
174
+ print()
175
+
176
+ if image_missing and not ready:
177
+ if sys.stdin.isatty():
178
+ print(" Pull the pinned AgentNode sandbox image now? [y/N]")
179
+ print(dim(" This downloads the digest-pinned sandbox image."))
180
+ print(dim(" It does not install Docker/Podman."))
181
+ print(dim(" It does not enable auto-pull."))
182
+ answer = _prompt(" > ", "n")
183
+ if answer.lower() == "y":
184
+ rc = cmd_sandbox_pull()
185
+ if rc == 0:
186
+ checks, ready, image_missing = _build_env_checks()
187
+ else:
188
+ # never a wizard failure — the user can pull later
189
+ print(dim(" Pull failed or was skipped — the wizard continues."))
190
+ print(dim(" Run `agentnode sandbox pull` later."))
191
+ else:
192
+ print(dim(" Skipped. Run `agentnode sandbox pull` when ready."))
193
+ else:
194
+ print(dim(" Non-interactive session — run `agentnode sandbox doctor` later."))
195
+
196
+ if ready:
197
+ print()
198
+ print(" Sandbox ready — community packages run isolated.")
199
+ print()
200
+ print(" Sandboxed community agents are currently disabled (default).")
201
+ print(" Enabling lets verified/unverified community agents run isolated;")
202
+ print(" without it they are refused.")
203
+ if sys.stdin.isatty():
204
+ en = _prompt(" Enable sandboxed community agents now? [y/N]: ", "n")
205
+ if en.lower() == "y":
206
+ cfg["agent_sandbox"]["enabled"] = True
207
+ else:
208
+ print(dim(" You can enable it later with "
209
+ "`agentnode config set agent_sandbox.enabled true`."))
210
+ else:
211
+ print(dim(" Enable later with "
212
+ "`agentnode config set agent_sandbox.enabled true`."))
213
+ return "ready", False
214
+
215
+ from agentnode_sdk.cli.sandbox_commands import _first_failure
216
+ fail = _first_failure(checks)
217
+ detail = (fail or {}).get("detail", "not ready")
218
+ print(dim(" Not ready — details and guidance: agentnode sandbox doctor"))
219
+ return f"not ready — {detail}", image_missing
220
+
221
+
222
+ def _wizard_flow() -> dict | None:
223
+ cfg = default_config()
224
+
225
+ # Screen 1: Intro
226
+ print()
227
+ print(section("AgentNode Setup"))
228
+ print(" Configure how AgentNode manages capabilities for your agents.")
229
+ print(" You can change these settings later with `agentnode setup`.")
230
+ print()
231
+ print(dim(" Press Enter to continue..."))
232
+ _prompt("")
233
+
234
+ # Screen 2: Installation behavior
235
+ print()
236
+ print(bold(" Installation behavior"))
237
+ print()
238
+ print(" [1] Automatic — install verified capabilities without asking")
239
+ print(" [2] Review before install — ask before each installation")
240
+ print(" [3] Manual only — never install automatically")
241
+ print()
242
+ choice = _choice(" Choice", ["1", "2", "3"], "1")
243
+ if choice == "1":
244
+ cfg["auto_upgrade_policy"] = "safe"
245
+ cfg["install_confirmation"] = "auto"
246
+ elif choice == "2":
247
+ cfg["auto_upgrade_policy"] = "safe"
248
+ cfg["install_confirmation"] = "prompt"
249
+ else:
250
+ cfg["auto_upgrade_policy"] = "off"
251
+ cfg["install_confirmation"] = "auto"
252
+
253
+ # Screen 3: Permission defaults
254
+ print()
255
+ print(bold(" Permission defaults"))
256
+ print()
257
+ for perm_label, perm_key in [
258
+ ("Network", "network"),
259
+ ("Filesystem", "filesystem"),
260
+ ("Code execution", "code_execution"),
261
+ ]:
262
+ if perm_key == "code_execution":
263
+ print(f" {perm_label}: [1] sandboxed [2] prompt [3] deny")
264
+ c = _choice(" Choice", ["1", "2", "3"], "1")
265
+ cfg["permissions"][perm_key] = {"1": "sandboxed", "2": "prompt", "3": "deny"}[c]
266
+ else:
267
+ print(f" {perm_label}: [1] allow [2] prompt [3] deny")
268
+ c = _choice(" Choice", ["1", "2", "3"], "2")
269
+ cfg["permissions"][perm_key] = {"1": "allow", "2": "prompt", "3": "deny"}[c]
270
+
271
+ # Screen 4: Advanced (optional)
272
+ print()
273
+ print(bold(" Advanced settings"))
274
+ print()
275
+ adv = _prompt(" Configure trust level? [y/N]: ", "n")
276
+ if adv.lower() == "y":
277
+ print()
278
+ print(" Minimum trust level:")
279
+ print(" [1] verified — community-reviewed packages")
280
+ print(" [2] trusted — manually approved by AgentNode team")
281
+ print(" [3] curated — official AgentNode packages only")
282
+ print()
283
+ c = _choice(" Choice", ["1", "2", "3"], "1")
284
+ cfg["trust"]["minimum_trust_level"] = {"1": "verified", "2": "trusted", "3": "curated"}[c]
285
+
286
+ # Screen 5: LLM credentials (optional — UX-3A)
287
+ stored_providers = _credentials_screen()
288
+
289
+ # Screen 6: Local sandbox (UX-3B)
290
+ sandbox_status, image_missing = _sandbox_screen(cfg)
291
+
292
+ # Screen 7: Summary
293
+ print()
294
+ print(section("Summary"))
295
+ print(kv("Installation behavior", installation_behavior_label(cfg)))
296
+ print(kv("Trust level", cfg["trust"]["minimum_trust_level"]))
297
+ if stored_providers:
298
+ creds_line = ", ".join(f"{p} ({label})" for p, label in stored_providers)
299
+ else:
300
+ creds_line = "none — add later with `agentnode auth set <provider>`"
301
+ print(kv("LLM credentials", creds_line))
302
+ print(kv("Sandbox", sandbox_status))
303
+ print(kv("Agent sandbox",
304
+ "enabled" if cfg["agent_sandbox"]["enabled"] else "disabled"))
305
+ print()
306
+ print(" Permissions")
307
+ print(" " + "-" * 11)
308
+ print(kv("Network", cfg["permissions"]["network"]))
309
+ print(kv("Filesystem", cfg["permissions"]["filesystem"]))
310
+ print(kv("Code execution", cfg["permissions"]["code_execution"]))
311
+ print()
312
+ confirm = _prompt(" Save? [Y/n]: ", "y")
313
+ if confirm.lower() == "n":
314
+ print("\n Setup cancelled. No changes saved.")
315
+ return None
316
+
317
+ save_config(cfg)
318
+
319
+ # Screen 8: Success
320
+ print()
321
+ print(bold(" Configuration saved."))
322
+ print()
323
+ print(kv("Sandbox", sandbox_status))
324
+ print(dim(" Community packages need a local sandbox. Trusted/curated"))
325
+ print(dim(" packages can run without it."))
326
+ print()
327
+ print(dim(" Next steps:"))
328
+ print(dim(" agentnode auth status check your credentials"))
329
+ print(dim(" agentnode sandbox doctor check the sandbox runtime"))
330
+ if image_missing:
331
+ print(dim(" agentnode sandbox pull fetch the sandbox image"))
332
+ if not cfg["agent_sandbox"]["enabled"]:
333
+ print(dim(" agentnode config set agent_sandbox.enabled true"))
334
+ print(dim(" enable sandboxed community agents"))
335
+ print(dim(" agentnode install word-counter-pack install a first capability"))
336
+ print(dim(" agentnode run word-counter-pack --input '{\"text\":\"hello\"}'"))
337
+ print(dim(" agentnode doctor check your whole setup"))
338
+ print()
339
+
340
+ return cfg
@@ -46,6 +46,7 @@ DEFAULTS: dict[str, Any] = {
46
46
  },
47
47
  "llm": {
48
48
  "default_provider": "openai",
49
+ "providers": {},
49
50
  },
50
51
  }
51
52
 
@@ -68,7 +69,10 @@ VALID_VALUES: dict[str, tuple[str, ...]] = {
68
69
  "guard.compute": ("allow", "prompt", "deny"),
69
70
  "guard.unknown": ("allow", "prompt", "deny"),
70
71
  "agent_sandbox.enabled": ("true", "false"),
71
- "llm.default_provider": ("openai", "anthropic", "openrouter"),
72
+ "llm.default_provider": (
73
+ "openai", "anthropic", "openrouter", "deepseek", "mistral", "qwen",
74
+ "gemini", "ollama",
75
+ ),
72
76
  }
73
77
 
74
78
 
@@ -160,6 +164,10 @@ def _merge_defaults(data: dict) -> dict[str, Any]:
160
164
  for k in ("default_provider",):
161
165
  if k in data["llm"]:
162
166
  cfg["llm"][k] = data["llm"][k]
167
+ # llm.providers holds nested endpoint specs (base_url/model/...) —
168
+ # pass through verbatim, like agent_sandbox.llm above.
169
+ if isinstance(data["llm"].get("providers"), dict):
170
+ cfg["llm"]["providers"] = data["llm"]["providers"]
163
171
  return cfg
164
172
 
165
173
 
@@ -0,0 +1,116 @@
1
+ """OpenAI-compatible LLM provider registry (Endpoint-A).
2
+
3
+ One shared source of truth for the LLM endpoints AgentNode can bind: built-in
4
+ presets (OpenRouter, DeepSeek, Mistral, Qwen, Ollama — plus the official
5
+ OpenAI/Anthropic entries) and user-defined entries/overrides from
6
+ ``llm.providers.<name>`` in ~/.agentnode/config.json.
7
+
8
+ The runtime (``_auto_detect_llm``) builds standard OpenAI-compatible bindings
9
+ from these specs, so the host LLM broker — and therefore sandboxed agents —
10
+ inherit every provider automatically, with the key staying host-side and no
11
+ broker/sandbox change.
12
+
13
+ Compat presets MUST carry a default model: the broker's bare OpenAI default
14
+ ("gpt-4o-mini") would 404 on most compatible endpoints. ``requires_key:
15
+ False`` providers (Ollama) are bound only on explicit selection — never by
16
+ probing.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from typing import Any
21
+
22
+ # Built-in presets. base_url=None means the provider SDK's official endpoint.
23
+ # default_model="" lets the broker pick its own official default.
24
+ KNOWN_PROVIDERS: dict[str, dict[str, Any]] = {
25
+ "openai": {
26
+ "base_url": None,
27
+ "api_key_env": "OPENAI_API_KEY",
28
+ "default_model": "",
29
+ "requires_key": True,
30
+ },
31
+ "anthropic": {
32
+ "base_url": None,
33
+ "api_key_env": "ANTHROPIC_API_KEY",
34
+ "default_model": "",
35
+ "requires_key": True,
36
+ },
37
+ "openrouter": {
38
+ "base_url": "https://openrouter.ai/api/v1",
39
+ "api_key_env": "OPENROUTER_API_KEY",
40
+ "default_model": "openai/gpt-4o-mini",
41
+ "requires_key": True,
42
+ },
43
+ "deepseek": {
44
+ "base_url": "https://api.deepseek.com/v1",
45
+ "api_key_env": "DEEPSEEK_API_KEY",
46
+ "default_model": "deepseek-chat",
47
+ "requires_key": True,
48
+ },
49
+ "mistral": {
50
+ "base_url": "https://api.mistral.ai/v1",
51
+ "api_key_env": "MISTRAL_API_KEY",
52
+ "default_model": "mistral-small-latest",
53
+ "requires_key": True,
54
+ },
55
+ "qwen": {
56
+ "base_url": "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
57
+ "api_key_env": "DASHSCOPE_API_KEY",
58
+ "default_model": "qwen-plus",
59
+ "requires_key": True,
60
+ },
61
+ "gemini": {
62
+ # Google's own OpenAI-compatible endpoint (trailing slash required).
63
+ "base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
64
+ "api_key_env": "GEMINI_API_KEY",
65
+ "default_model": "gemini-2.0-flash",
66
+ "requires_key": True,
67
+ },
68
+ "ollama": {
69
+ # key-free local inference: the first agent path with no account and
70
+ # no per-token cost. Bound ONLY when explicitly selected (no probing).
71
+ "base_url": "http://localhost:11434/v1",
72
+ "api_key_env": "OLLAMA_API_KEY",
73
+ "default_model": "llama3.2",
74
+ "requires_key": False,
75
+ },
76
+ }
77
+
78
+ _SPEC_KEYS = ("base_url", "api_key_env", "default_model", "requires_key")
79
+
80
+
81
+ def _config_providers(host_config: dict | None) -> dict:
82
+ llm = (host_config or {}).get("llm") or {}
83
+ providers = llm.get("providers")
84
+ return providers if isinstance(providers, dict) else {}
85
+
86
+
87
+ def resolve_provider_spec(name: str, host_config: dict | None = None) -> dict | None:
88
+ """Effective spec for ``name`` = preset merged with the config override
89
+ (``llm.providers.<name>``), or None for an unknown name without a config
90
+ entry. ``model`` is accepted as a friendlier config alias for
91
+ ``default_model``."""
92
+ name = (name or "").lower().strip()
93
+ preset = KNOWN_PROVIDERS.get(name)
94
+ override = _config_providers(host_config).get(name)
95
+ if preset is None and not isinstance(override, dict):
96
+ return None
97
+
98
+ spec: dict[str, Any] = dict(preset) if preset else {
99
+ "base_url": None, "api_key_env": "", "default_model": "", "requires_key": True,
100
+ }
101
+ if isinstance(override, dict):
102
+ for k in _SPEC_KEYS:
103
+ if k in override:
104
+ spec[k] = override[k]
105
+ if "model" in override:
106
+ spec["default_model"] = override["model"]
107
+ return spec
108
+
109
+
110
+ def known_provider_names(host_config: dict | None = None) -> list[str]:
111
+ """Preset names first (stable order), then custom config-defined names."""
112
+ names = list(KNOWN_PROVIDERS)
113
+ for n in _config_providers(host_config):
114
+ if n not in names:
115
+ names.append(n)
116
+ return names