agentnode-sdk 0.15.0__tar.gz → 0.16.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 (190) hide show
  1. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/CHANGELOG.md +44 -0
  2. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/PKG-INFO +1 -1
  3. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/__init__.py +1 -1
  4. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/auth.py +105 -36
  5. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/setup_wizard.py +49 -19
  6. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_store.py +12 -5
  7. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_runner.py +16 -7
  8. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/pyproject.toml +1 -1
  9. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_auth_cli_vault.py +129 -1
  10. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_vault_binding.py +4 -1
  11. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_setup_wizard.py +56 -1
  12. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/.env.example +0 -0
  13. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/.gitignore +0 -0
  14. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/README.md +0 -0
  15. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
  16. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/REGISTRY_SIGNING_SPEC.md +0 -0
  17. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/THREAT_MODEL.md +0 -0
  18. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/TRUST_STACK.md +0 -0
  19. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode.lock +0 -0
  20. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/_fileutil.py +0 -0
  21. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/async_client.py +0 -0
  22. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/capability_graph.py +0 -0
  23. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/capability_taxonomy.py +0 -0
  24. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/__init__.py +0 -0
  25. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/__main__.py +0 -0
  26. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/audit.py +0 -0
  27. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
  28. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/commands.py +0 -0
  29. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/complements.py +0 -0
  30. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/init.py +0 -0
  31. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/main.py +0 -0
  32. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
  33. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_status.py +0 -0
  34. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
  35. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
  36. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/output.py +0 -0
  37. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/publish.py +0 -0
  38. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/record_cases.py +0 -0
  39. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
  40. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/serve.py +0 -0
  41. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/smart_run.py +0 -0
  42. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/templates.py +0 -0
  43. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/validate.py +0 -0
  44. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/verify_local.py +0 -0
  45. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/client.py +0 -0
  46. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/compatibility.py +0 -0
  47. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/config.py +0 -0
  48. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_handle.py +0 -0
  49. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_resolver.py +0 -0
  50. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/detect.py +0 -0
  51. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/exceptions.py +0 -0
  52. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/guard.py +0 -0
  53. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/input_guard.py +0 -0
  54. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/installer.py +0 -0
  55. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/key_status.py +0 -0
  56. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/llm_providers.py +0 -0
  57. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/lock_integrity.py +0 -0
  58. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/mcp_server.py +0 -0
  59. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/models.py +0 -0
  60. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/planner.py +0 -0
  61. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/policy.py +0 -0
  62. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/references.py +0 -0
  63. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/registry_trust.py +0 -0
  64. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/resolve.py +0 -0
  65. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/resource_provider.py +0 -0
  66. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/risk_profile.py +0 -0
  67. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/run_log.py +0 -0
  68. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runner.py +0 -0
  69. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtime.py +0 -0
  70. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/__init__.py +0 -0
  71. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
  72. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
  73. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
  74. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
  75. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
  76. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
  77. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/__init__.py +0 -0
  78. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
  79. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
  80. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
  81. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/backend.py +0 -0
  82. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
  83. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/policy.py +0 -0
  84. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/types.py +0 -0
  85. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/signature.py +0 -0
  86. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/signing_key.py +0 -0
  87. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/skill.py +0 -0
  88. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/sandbox-image/Dockerfile +0 -0
  89. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/sandbox-image/README.md +0 -0
  90. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/analyze_scores.py +0 -0
  91. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/batch_verify.py +0 -0
  92. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/ci_smoke_test.py +0 -0
  93. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/generate_compatibility_artifacts.py +0 -0
  94. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/verify_toolcalls.py +0 -0
  95. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/weekly_retest.sh +0 -0
  96. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/README.md +0 -0
  97. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
  98. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
  99. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
  100. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
  101. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
  102. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/__init__.py +0 -0
  103. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/conftest.py +0 -0
  104. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_llm_broker.py +0 -0
  105. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_llm_policy.py +0 -0
  106. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_rpc.py +0 -0
  107. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_runner.py +0 -0
  108. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_e2e.py +0 -0
  109. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_routing.py +0 -0
  110. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_spike.py +0 -0
  111. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_session_container.py +0 -0
  112. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_async_client.py +0 -0
  113. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_audit_ux.py +0 -0
  114. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_auto_upgrade_policy.py +0 -0
  115. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli.py +0 -0
  116. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli_lock.py +0 -0
  117. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli_run_resolution.py +0 -0
  118. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client.py +0 -0
  119. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client_json_guard.py +0 -0
  120. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client_sprint_b.py +0 -0
  121. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_config.py +0 -0
  122. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_handle.py +0 -0
  123. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_integration.py +0 -0
  124. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_resolver.py +0 -0
  125. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_store.py +0 -0
  126. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_vault.py +0 -0
  127. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_detect.py +0 -0
  128. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_detect_and_install.py +0 -0
  129. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_e2e_runtime.py +0 -0
  130. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_edge_cases.py +0 -0
  131. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard.py +0 -0
  132. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_check.py +0 -0
  133. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_config_cache.py +0 -0
  134. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_policy.py +0 -0
  135. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_preview.py +0 -0
  136. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_schema.py +0 -0
  137. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_set.py +0 -0
  138. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_status.py +0 -0
  139. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override.py +0 -0
  140. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override_audit.py +0 -0
  141. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override_cli.py +0 -0
  142. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_ux.py +0 -0
  143. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_input_guard.py +0 -0
  144. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_input_guard_escalation.py +0 -0
  145. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_install_hardening.py +0 -0
  146. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_installer_sprint_b.py +0 -0
  147. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_intelligence.py +0 -0
  148. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_key_status.py +0 -0
  149. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_binding.py +0 -0
  150. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_call_runlog.py +0 -0
  151. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_providers.py +0 -0
  152. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_lock_integrity.py +0 -0
  153. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_lock_runtime.py +0 -0
  154. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_audit.py +0 -0
  155. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_doctor.py +0 -0
  156. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_sandbox.py +0 -0
  157. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_server.py +0 -0
  158. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_observability.py +0 -0
  159. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_planner.py +0 -0
  160. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_policy.py +0 -0
  161. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_policy_integration.py +0 -0
  162. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_prompt_specs.py +0 -0
  163. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_provider_matrix.py +0 -0
  164. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_publish.py +0 -0
  165. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_references.py +0 -0
  166. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_registry_trust.py +0 -0
  167. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_remote_hardening.py +0 -0
  168. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_remote_runner.py +0 -0
  169. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_resource_provider.py +0 -0
  170. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_resource_specs.py +0 -0
  171. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_risk_profile.py +0 -0
  172. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_run_log.py +0 -0
  173. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runner.py +0 -0
  174. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runtime.py +0 -0
  175. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runtime_audit.py +0 -0
  176. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_backend.py +0 -0
  177. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_doctor.py +0 -0
  178. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_e2e.py +0 -0
  179. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_gate.py +0 -0
  180. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_security_hardening.py +0 -0
  181. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_signature.py +0 -0
  182. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_signing_key.py +0 -0
  183. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_skill.py +0 -0
  184. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_smart.py +0 -0
  185. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_stability.py +0 -0
  186. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_toolpack_sandbox.py +0 -0
  187. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_v02.py +0 -0
  188. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_validate.py +0 -0
  189. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_validate_skill.py +0 -0
  190. {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/validation_lockfile.json +0 -0
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 — Registry providers in auth CLI and setup wizard
4
+
5
+ ### Added
6
+
7
+ - **`agentnode auth status` now surfaces all registry providers** — OpenAI,
8
+ Anthropic, OpenRouter, DeepSeek, Mistral, Qwen, Gemini — with their storage
9
+ backend and effective source, plus **Ollama as a local keyless provider**
10
+ (shown as selected/configured/not selected instead of "missing"). Custom
11
+ providers configured under `llm.providers.<name>` appear automatically.
12
+ - **`agentnode auth test` supports the registry providers.** Compatible
13
+ providers are validated with a free, no-completion `GET {base_url}/models`
14
+ probe; `agentnode auth test ollama` is a keyless reachability check
15
+ (exit 0 reachable, exit 3 unreachable — never "rejected", and it never
16
+ starts Ollama). Custom configured endpoints can be tested the same way.
17
+ - **The setup wizard lists all registry providers**, grouped for readability
18
+ (Recommended / More / Local), with Skip remaining the default. Selecting
19
+ Ollama never asks for an API key — it sets `llm.default_provider` through
20
+ the wizard's normal save step.
21
+ - Additive `vendor` field in host-side LLM bindings: logs and tooling can now
22
+ show the real provider name; the protocol discriminator used by the broker
23
+ is unchanged.
24
+
25
+ ### Changed
26
+
27
+ - The auth CLI and the setup wizard now use the provider registry as their
28
+ single source of truth — no hardcoded provider lists or environment
29
+ variable names remain in those surfaces.
30
+ - OpenRouter keeps its dedicated `/auth/key` validation path (its `/models`
31
+ endpoint is unauthenticated and validates nothing).
32
+
33
+ ### Hardened
34
+
35
+ - Provider response bodies are never printed (they can echo key fragments);
36
+ keys are never printed, logged, or exposed in exceptions.
37
+ - Ollama is never started or probed automatically — only an explicit
38
+ `agentnode auth test ollama` performs a localhost reachability check.
39
+ - The `vendor` field stays host-side and does not change the broker/sandbox
40
+ wire shape.
41
+
42
+ ### BREAKING / Upgrade Notes
43
+
44
+ - None. All changes are additive; existing `auth` and `setup` flows behave
45
+ identically for the previously supported providers.
46
+
3
47
  ## 0.15.0 — Generic OpenAI-compatible LLM providers
4
48
 
5
49
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentnode-sdk
3
- Version: 0.15.0
3
+ Version: 0.16.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.15.0"
41
+ __version__ = "0.16.0"
42
42
  __all__ = [
43
43
  "AgentNode",
44
44
  "AsyncAgentNode",
@@ -10,7 +10,6 @@ import os
10
10
  import sys
11
11
 
12
12
  from agentnode_sdk.credential_store import (
13
- LLM_PROVIDER_ENV,
14
13
  has_credential,
15
14
  list_credentials,
16
15
  load_credentials,
@@ -30,17 +29,28 @@ def _storage_short(storage: str) -> str:
30
29
  return "OS keychain" if storage == "keyring" else "plaintext file"
31
30
 
32
31
 
33
- def _llm_effective_source(provider: str) -> tuple[str | None, str]:
32
+ def _host_config() -> dict:
33
+ try:
34
+ from agentnode_sdk.config import load_config
35
+ return load_config() or {}
36
+ except Exception:
37
+ return {}
38
+
39
+
40
+ def _llm_effective_source(provider: str, host_config: dict | None = None) -> tuple[str | None, str]:
34
41
  """(effective key or None, human-readable source) for an LLM provider.
35
42
 
36
43
  Mirrors the runtime's resolution: env (incl. ~/.agentnode/.env) overrides
37
- the stored credential. Uses metadata only for the stored check here —
44
+ the stored credential. Env var names come from the provider REGISTRY
45
+ (single source of truth). Uses metadata only for the stored check here —
38
46
  the real keychain read happens in ``auth test``."""
47
+ from agentnode_sdk.llm_providers import resolve_provider_spec
39
48
  from agentnode_sdk.runtimes.agent_runner import _load_agentnode_env
40
49
  _load_agentnode_env()
41
50
 
42
- env_var = LLM_PROVIDER_ENV[provider]
43
- env_key = os.environ.get(env_var) or None
51
+ spec = resolve_provider_spec(provider, host_config) or {}
52
+ env_var = spec.get("api_key_env") or ""
53
+ env_key = (os.environ.get(env_var) or None) if env_var else None
44
54
  meta = load_credentials().get("providers", {}).get(provider)
45
55
  stored = isinstance(meta, dict)
46
56
  storage = (meta or {}).get("storage", "file")
@@ -54,6 +64,19 @@ def _llm_effective_source(provider: str) -> tuple[str | None, str]:
54
64
  return None, "not configured"
55
65
 
56
66
 
67
+ def _registry_key_providers(host_config: dict | None) -> list[str]:
68
+ """Key-requiring providers from the registry (presets + custom config
69
+ entries), in stable registry order."""
70
+ from agentnode_sdk.llm_providers import known_provider_names, resolve_provider_spec
71
+
72
+ names: list[str] = []
73
+ for name in known_provider_names(host_config):
74
+ spec = resolve_provider_spec(name, host_config)
75
+ if spec and spec.get("requires_key", True):
76
+ names.append(name)
77
+ return names
78
+
79
+
57
80
  def _resolve_auth_type(provider: str) -> str:
58
81
  """Read auth_type from lockfile connector entry. Falls back to 'api_key'.
59
82
 
@@ -132,15 +155,25 @@ def cmd_auth_remove(provider: str) -> int:
132
155
 
133
156
  def cmd_auth_status() -> int:
134
157
  # --- LLM providers (host runtime / sandbox broker) -----------------------
158
+ host_cfg = _host_config()
135
159
  print()
136
160
  print(section("LLM Providers"))
137
161
  print(f" {'Provider':<14}{'Status':<16}{'Effective source'}")
138
162
  print(f" {'--------':<14}{'------':<16}{'----------------'}")
139
- for provider in sorted(LLM_PROVIDER_ENV):
140
- key, source = _llm_effective_source(provider)
163
+ for provider in _registry_key_providers(host_cfg):
164
+ key, source = _llm_effective_source(provider, host_cfg)
141
165
  configured = key is not None or source not in ("not configured",)
142
166
  status = "configured" if configured else "missing"
143
167
  print(f" {provider:<14}{status:<16}{source}")
168
+ # ollama: keyless local provider — selection is config, not a credential
169
+ llm_cfg = host_cfg.get("llm") or {}
170
+ if llm_cfg.get("default_provider") == "ollama":
171
+ ollama_source = "selected via llm.default_provider"
172
+ elif "ollama" in (llm_cfg.get("providers") or {}):
173
+ ollama_source = "configured via llm.providers"
174
+ else:
175
+ ollama_source = "not selected — agentnode config set llm.default_provider ollama"
176
+ print(f" {'ollama':<14}{'local (keyless)':<16}{ollama_source}")
144
177
  print(dim(" Env vars always override stored credentials. "
145
178
  "Test a key: agentnode auth test <provider>"))
146
179
 
@@ -183,61 +216,97 @@ def cmd_auth_status() -> int:
183
216
 
184
217
 
185
218
  # Free, cost-less validation endpoints (no completion call, no charge).
186
- # anthropic REQUIRES the anthropic-version header (a 400 without it would be
187
- # misread as a bad key); openrouter's /models is UNAUTHENTICATED (validates
188
- # nothing) /auth/key is the correct probe.
189
- _TEST_REQUESTS = {
190
- "openai": lambda key: (
219
+ # GENERIC rule for OpenAI-compatible providers: GET {base_url}/models with a
220
+ # Bearer token (authenticated on deepseek/mistral/qwen/gemini and most custom
221
+ # endpoints). EXCEPTIONS: openai (official URL), anthropic (REQUIRES the
222
+ # anthropic-version header — a 400 without it would be misread as a bad key),
223
+ # openrouter (its /models is UNAUTHENTICATED, validates nothing — /auth/key is
224
+ # the correct probe).
225
+ _TEST_EXCEPTIONS = {
226
+ "openai": lambda key, spec: (
191
227
  "https://api.openai.com/v1/models",
192
228
  {"Authorization": f"Bearer {key}"},
193
229
  ),
194
- "anthropic": lambda key: (
230
+ "anthropic": lambda key, spec: (
195
231
  "https://api.anthropic.com/v1/models",
196
232
  {"x-api-key": key, "anthropic-version": "2023-06-01"},
197
233
  ),
198
- "openrouter": lambda key: (
234
+ "openrouter": lambda key, spec: (
199
235
  "https://openrouter.ai/api/v1/auth/key",
200
236
  {"Authorization": f"Bearer {key}"},
201
237
  ),
202
238
  }
203
239
 
204
240
 
241
+ def _test_request(provider: str, key: str | None, spec: dict) -> tuple[str, dict] | None:
242
+ if provider in _TEST_EXCEPTIONS:
243
+ return _TEST_EXCEPTIONS[provider](key, spec)
244
+ base = (spec.get("base_url") or "").rstrip("/")
245
+ if not base:
246
+ return None # no compat endpoint to probe
247
+ headers = {"Authorization": f"Bearer {key}"} if key else {}
248
+ return (base + "/models", headers)
249
+
250
+
205
251
  def cmd_auth_test(provider: str) -> int:
206
252
  """Validate the EFFECTIVE key for an LLM provider (env beats vault) via a
207
- free endpoint. Exit codes: 0 valid / 1 rejected (401/403) / 2 not
208
- configured or unsupported / 3 indeterminate (network/5xx never reported
209
- as invalid). Never prints the key (masked last-4 only) and never echoes
210
- the provider's response body (it can contain key fragments)."""
253
+ free endpoint registry-driven, so custom ``llm.providers`` entries work
254
+ too. Exit codes: 0 valid (key providers) / reachable (keyless) · 1
255
+ rejected (401/403, key providers only) · 2 not configured or unsupported ·
256
+ 3 indeterminate (network/5xx never reported as invalid; for keyless
257
+ providers this means unreachable). Never prints the key (masked last-4
258
+ only), never echoes provider response bodies (they can contain key
259
+ fragments), never starts anything."""
211
260
  provider = provider.lower().strip()
212
- if provider not in _TEST_REQUESTS:
213
- supported = ", ".join(sorted(_TEST_REQUESTS))
214
- print(f" auth test supports: {supported}", file=sys.stderr)
261
+ host_cfg = _host_config()
262
+ from agentnode_sdk.llm_providers import resolve_provider_spec
263
+ spec = resolve_provider_spec(provider, host_cfg)
264
+ if spec is None:
265
+ print(f" Unknown provider '{provider}'. Known: see `agentnode auth status`; "
266
+ "custom endpoints go under llm.providers.<name>.", file=sys.stderr)
215
267
  return 2
216
268
 
217
- key, source = _llm_effective_source(provider)
218
- if key is None:
219
- # No env override — resolve the stored credential (real keychain read).
220
- from agentnode_sdk.credential_store import get_llm_api_key
221
- key = get_llm_api_key(provider)
222
- if not key:
223
- print(f"\n {provider}: not configured. Run `agentnode auth set {provider}`.\n")
269
+ requires_key = spec.get("requires_key", True)
270
+ if requires_key:
271
+ key, source = _llm_effective_source(provider, host_cfg)
272
+ if key is None:
273
+ # No env override — resolve the stored credential (real keychain read).
274
+ from agentnode_sdk.credential_store import get_llm_api_key
275
+ key = get_llm_api_key(provider)
276
+ if not key:
277
+ print(f"\n {provider}: not configured. Run `agentnode auth set {provider}`.\n")
278
+ return 2
279
+ shown = f"{_masked(key)}, {source}"
280
+ else:
281
+ key, shown = None, "local (keyless)"
282
+
283
+ req = _test_request(provider, key, spec)
284
+ if req is None:
285
+ print(f"\n {provider}: no endpoint to probe (no base_url configured).\n")
224
286
  return 2
287
+ url, headers = req
225
288
 
226
- url, headers = _TEST_REQUESTS[provider](key)
227
289
  import httpx
228
290
  try:
229
291
  resp = httpx.get(url, headers=headers, timeout=10.0)
230
292
  except Exception:
231
- print(f"\n {provider} ({_masked(key)}, {source}): could not reach the "
232
- "provider key validity unknown.\n")
293
+ if not requires_key:
294
+ print(f"\n {provider} ({shown}): could not reach {url} — "
295
+ "is the local server (e.g. Ollama) running?\n")
296
+ else:
297
+ print(f"\n {provider} ({shown}): could not reach the "
298
+ "provider — key validity unknown.\n")
233
299
  return 3
234
300
  if resp.status_code == 200:
235
- print(f"\n {provider} ({_masked(key)}, {source}): key is valid.\n")
301
+ if requires_key:
302
+ print(f"\n {provider} ({shown}): key is valid.\n")
303
+ else:
304
+ print(f"\n {provider} ({shown}): endpoint is reachable.\n")
236
305
  return 0
237
- if resp.status_code in (401, 403):
238
- print(f"\n {provider} ({_masked(key)}, {source}): key was rejected by "
306
+ if requires_key and resp.status_code in (401, 403):
307
+ print(f"\n {provider} ({shown}): key was rejected by "
239
308
  f"the provider (HTTP {resp.status_code}).\n")
240
309
  return 1
241
- print(f"\n {provider} ({_masked(key)}, {source}): unexpected provider "
242
- f"response (HTTP {resp.status_code}) — key validity unknown.\n")
310
+ print(f"\n {provider} ({shown}): unexpected response "
311
+ f"(HTTP {resp.status_code}) — status unknown.\n")
243
312
  return 3
@@ -22,7 +22,14 @@ from agentnode_sdk.config import (
22
22
  )
23
23
  from agentnode_sdk.cli.output import bold, dim, kv, section
24
24
 
25
- _LLM_CHOICES = {"1": "openai", "2": "anthropic", "3": "openrouter"}
25
+ # Key-provider menu (grouped for readability); choice 8 = ollama (keyless,
26
+ # handled separately — never a key prompt), choice 9 = skip (the default).
27
+ _LLM_CHOICES = {
28
+ "1": "openai", "2": "anthropic", "3": "openrouter",
29
+ "4": "deepseek", "5": "mistral", "6": "qwen", "7": "gemini",
30
+ }
31
+ _OLLAMA_CHOICE = "8"
32
+ _SKIP_CHOICE = "9"
26
33
 
27
34
 
28
35
  def run_wizard() -> int:
@@ -59,15 +66,16 @@ def _store_llm_key(provider: str) -> str | None:
59
66
  import os
60
67
 
61
68
  from agentnode_sdk.cli.auth import _masked, _storage_short, cmd_auth_test
62
- from agentnode_sdk.credential_store import LLM_PROVIDER_ENV, set_credential
69
+ from agentnode_sdk.credential_store import set_credential
63
70
 
64
- env_var = LLM_PROVIDER_ENV[provider]
71
+ from agentnode_sdk.llm_providers import resolve_provider_spec
72
+ env_var = (resolve_provider_spec(provider) or {}).get("api_key_env") or ""
65
73
  try:
66
74
  from agentnode_sdk.runtimes.agent_runner import _load_agentnode_env
67
75
  _load_agentnode_env()
68
76
  except Exception:
69
77
  pass
70
- env_val = (os.environ.get(env_var) or "").strip()
78
+ env_val = (os.environ.get(env_var) or "").strip() if env_var else ""
71
79
 
72
80
  token = ""
73
81
  if env_val:
@@ -101,9 +109,11 @@ def _store_llm_key(provider: str) -> str | None:
101
109
  return _storage_short(storage)
102
110
 
103
111
 
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."""
112
+ def _credentials_screen(cfg: dict) -> list[tuple[str, str]]:
113
+ """Screen 5: LLM credentials (optional, default = skip). Key providers
114
+ come from the registry-backed menu; ollama is a keyless CONFIG selection
115
+ (written to the wizard cfg, persisted only via the normal Save confirm).
116
+ Returns the (provider, storage label) pairs selected in this run."""
107
117
  import sys
108
118
 
109
119
  print()
@@ -120,19 +130,38 @@ def _credentials_screen() -> list[tuple[str, str]]:
120
130
  return []
121
131
 
122
132
  stored: list[tuple[str, str]] = []
133
+ all_options = list(_LLM_CHOICES) + [_OLLAMA_CHOICE, _SKIP_CHOICE]
123
134
  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":
135
+ print(" Recommended: [1] OpenAI [2] Anthropic [3] OpenRouter")
136
+ print(" More: [4] DeepSeek [5] Mistral [6] Qwen [7] Gemini")
137
+ print(" Local: [8] Ollama keyless, requires a running Ollama")
138
+ print(" [9] Skip for now")
139
+ c = _choice(" Choice", all_options, _SKIP_CHOICE)
140
+ if c == _SKIP_CHOICE:
127
141
  break
128
- provider = _LLM_CHOICES[c]
129
- if any(p == provider for p, _ in stored):
130
- print(f" {provider} already added in this run.")
142
+ if c == _OLLAMA_CHOICE:
143
+ # keyless local provider: a config selection, NOT a credential.
144
+ # No key prompt, no probing, no starting anything.
145
+ if any(p == "ollama" for p, _ in stored):
146
+ print(" ollama already selected in this run.")
147
+ else:
148
+ print(" Ollama runs models locally — no API key, no per-token cost.")
149
+ print(dim(" Requires a running Ollama (https://ollama.com); the wizard"))
150
+ print(dim(" does not start or probe it."))
151
+ use = _prompt(" Use Ollama as your default LLM provider? [Y/n]: ", "y")
152
+ if use.lower() != "n":
153
+ cfg["llm"]["default_provider"] = "ollama"
154
+ stored.append(("ollama", "local, keyless"))
155
+ print(dim(" Selected. Test later with `agentnode auth test ollama`."))
131
156
  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):
157
+ provider = _LLM_CHOICES[c]
158
+ if any(p == provider for p, _ in stored):
159
+ print(f" {provider} already added in this run.")
160
+ else:
161
+ label = _store_llm_key(provider)
162
+ if label is not None:
163
+ stored.append((provider, label))
164
+ if len(stored) >= len(_LLM_CHOICES) + 1:
136
165
  break
137
166
  more = _prompt(" Add another provider? [y/N]: ", "n")
138
167
  if more.lower() != "y":
@@ -283,8 +312,9 @@ def _wizard_flow() -> dict | None:
283
312
  c = _choice(" Choice", ["1", "2", "3"], "1")
284
313
  cfg["trust"]["minimum_trust_level"] = {"1": "verified", "2": "trusted", "3": "curated"}[c]
285
314
 
286
- # Screen 5: LLM credentials (optional — UX-3A)
287
- stored_providers = _credentials_screen()
315
+ # Screen 5: LLM credentials (optional — UX-3A; provider list from the
316
+ # registry incl. keyless ollama — Endpoint-B)
317
+ stored_providers = _credentials_screen(cfg)
288
318
 
289
319
  # Screen 6: Local sandbox (UX-3B)
290
320
  sandbox_status, image_missing = _sandbox_screen(cfg)
@@ -47,12 +47,19 @@ CREDENTIALS_FILE = "credentials.json"
47
47
  CURRENT_VERSION = 1
48
48
 
49
49
  # Canonical env vars for the LLM providers the host runtime understands.
50
+ # DERIVED from the provider registry (single source of truth, Endpoint-B).
50
51
  # Env always OVERRIDES the stored credential (explicit/CI intent wins).
51
- LLM_PROVIDER_ENV: dict[str, str] = {
52
- "openai": "OPENAI_API_KEY",
53
- "anthropic": "ANTHROPIC_API_KEY",
54
- "openrouter": "OPENROUTER_API_KEY",
55
- }
52
+ def _derive_llm_provider_env() -> dict[str, str]:
53
+ from agentnode_sdk.llm_providers import KNOWN_PROVIDERS
54
+
55
+ return {
56
+ name: spec["api_key_env"]
57
+ for name, spec in KNOWN_PROVIDERS.items()
58
+ if spec.get("api_key_env")
59
+ }
60
+
61
+
62
+ LLM_PROVIDER_ENV: dict[str, str] = _derive_llm_provider_env()
56
63
 
57
64
  # One keychain item per provider: service "agentnode:<provider>", a fixed
58
65
  # username. (NOT service="agentnode" + username=provider — Windows WinVault
@@ -216,7 +216,10 @@ def _create_llm_from_config(llm_config: dict) -> dict | None:
216
216
  kwargs: dict = {"api_key": api_key}
217
217
  if provider == "openrouter":
218
218
  kwargs["base_url"] = "https://openrouter.ai/api/v1"
219
- return {"client": OpenAI(**kwargs), "provider": "openai", "model": model}
219
+ # "provider" is the PROTOCOL discriminator (broker compat);
220
+ # "vendor" carries the real provider name for logs/status.
221
+ return {"client": OpenAI(**kwargs), "provider": "openai",
222
+ "model": model, "vendor": provider}
220
223
  except ImportError:
221
224
  logger.debug("openai package not installed")
222
225
  except Exception as exc:
@@ -224,7 +227,8 @@ def _create_llm_from_config(llm_config: dict) -> dict | None:
224
227
  elif provider == "anthropic":
225
228
  try:
226
229
  from anthropic import Anthropic
227
- return {"client": Anthropic(api_key=api_key), "provider": "anthropic", "model": model}
230
+ return {"client": Anthropic(api_key=api_key), "provider": "anthropic",
231
+ "model": model, "vendor": "anthropic"}
228
232
  except ImportError:
229
233
  logger.debug("anthropic package not installed")
230
234
  except Exception as exc:
@@ -232,7 +236,8 @@ def _create_llm_from_config(llm_config: dict) -> dict | None:
232
236
  elif provider == "gemini":
233
237
  try:
234
238
  from google import genai
235
- return {"client": genai.Client(api_key=api_key), "provider": "gemini", "model": model}
239
+ return {"client": genai.Client(api_key=api_key), "provider": "gemini",
240
+ "model": model, "vendor": "gemini"}
236
241
  except ImportError:
237
242
  logger.debug("google-genai package not installed")
238
243
  except Exception as exc:
@@ -294,7 +299,8 @@ def _llm_binding_from_providers() -> dict | None:
294
299
  if name == "anthropic":
295
300
  try:
296
301
  from anthropic import Anthropic
297
- return {"client": Anthropic(api_key=key), "provider": "anthropic", "model": ""}
302
+ return {"client": Anthropic(api_key=key), "provider": "anthropic",
303
+ "model": "", "vendor": "anthropic"}
298
304
  except ImportError:
299
305
  logger.debug("key for anthropic set but anthropic package not installed")
300
306
  except Exception as exc:
@@ -313,7 +319,10 @@ def _llm_binding_from_providers() -> dict | None:
313
319
  kwargs: dict = {"api_key": key or "ollama"}
314
320
  if base_url:
315
321
  kwargs["base_url"] = base_url
316
- return {"client": OpenAI(**kwargs), "provider": "openai", "model": model}
322
+ # "provider" = protocol (broker compat); "vendor" = real name for
323
+ # logs/status. Host-side only — never crosses into the sandbox.
324
+ return {"client": OpenAI(**kwargs), "provider": "openai",
325
+ "model": model, "vendor": name}
317
326
  except ImportError:
318
327
  logger.debug("key for %s set but openai package not installed", name)
319
328
  except Exception as exc:
@@ -353,7 +362,7 @@ def _auto_detect_llm(slug: str = "") -> dict | None:
353
362
  try:
354
363
  from openai import OpenAI
355
364
  client = OpenAI(api_key=openai_key)
356
- return {"client": client, "provider": "openai", "model": ""}
365
+ return {"client": client, "provider": "openai", "model": "", "vendor": "openai"}
357
366
  except ImportError:
358
367
  logger.debug("OPENAI_API_KEY set but openai package not installed")
359
368
  except Exception as exc:
@@ -364,7 +373,7 @@ def _auto_detect_llm(slug: str = "") -> dict | None:
364
373
  try:
365
374
  from anthropic import Anthropic
366
375
  client = Anthropic(api_key=anthropic_key)
367
- return {"client": client, "provider": "anthropic", "model": ""}
376
+ return {"client": client, "provider": "anthropic", "model": "", "vendor": "anthropic"}
368
377
  except ImportError:
369
378
  logger.debug("ANTHROPIC_API_KEY set but anthropic package not installed")
370
379
  except Exception as exc:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "agentnode-sdk"
7
- version = "0.15.0"
7
+ version = "0.16.0"
8
8
  description = "Python SDK for AgentNode — the open upgrade and discovery infrastructure for AI agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -44,7 +44,9 @@ def _isolated(tmp_path, monkeypatch):
44
44
  # keep the test hermetic
45
45
  monkeypatch.setattr(
46
46
  "agentnode_sdk.runtimes.agent_runner._load_agentnode_env", lambda: None)
47
- for var in ("OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENROUTER_API_KEY"):
47
+ for var in ("OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENROUTER_API_KEY",
48
+ "DEEPSEEK_API_KEY", "MISTRAL_API_KEY", "DASHSCOPE_API_KEY",
49
+ "GEMINI_API_KEY", "OLLAMA_API_KEY"):
48
50
  monkeypatch.delenv(var, raising=False)
49
51
  yield
50
52
 
@@ -207,6 +209,132 @@ def test_list_shows_storage_column_no_leak(monkeypatch, capsys):
207
209
  assert SECRET not in out
208
210
 
209
211
 
212
+ # --- Endpoint-B: registry-driven status/test ---------------------------------
213
+
214
+ def test_status_shows_all_registry_providers(monkeypatch, capsys):
215
+ _use_keyring(monkeypatch, None)
216
+ assert cmd_auth_status() == 0
217
+ out = capsys.readouterr().out
218
+ for provider in ("openai", "anthropic", "openrouter", "deepseek",
219
+ "mistral", "qwen", "gemini"):
220
+ assert provider in out
221
+ assert "ollama" in out
222
+ assert "local (keyless)" in out # never shown as "missing"
223
+ assert "not selected" in out
224
+
225
+
226
+ def test_status_shows_ollama_selected(monkeypatch, capsys):
227
+ monkeypatch.setattr("agentnode_sdk.config.load_config",
228
+ lambda: {"llm": {"default_provider": "ollama"}})
229
+ assert cmd_auth_status() == 0
230
+ out = capsys.readouterr().out
231
+ assert "selected via llm.default_provider" in out
232
+
233
+
234
+ def test_status_shows_custom_provider(monkeypatch, capsys):
235
+ monkeypatch.setattr(
236
+ "agentnode_sdk.config.load_config",
237
+ lambda: {"llm": {"providers": {"myvllm": {
238
+ "base_url": "http://10.0.0.5:8000/v1", "model": "llama"}}}})
239
+ assert cmd_auth_status() == 0
240
+ assert "myvllm" in capsys.readouterr().out
241
+
242
+
243
+ @respx.mock
244
+ def test_auth_test_deepseek_generic_models_probe(monkeypatch, capsys):
245
+ _use_keyring(monkeypatch, None)
246
+ cs.set_credential("deepseek", SECRET, auth_type="api_key")
247
+ route = respx.get("https://api.deepseek.com/v1/models").mock(
248
+ return_value=httpx.Response(200, json={"data": []}))
249
+ assert cmd_auth_test("deepseek") == 0
250
+ assert route.calls.last.request.headers["Authorization"] == f"Bearer {SECRET}"
251
+ out = capsys.readouterr().out
252
+ assert SECRET not in out and "valid" in out
253
+
254
+
255
+ @respx.mock
256
+ def test_auth_test_deepseek_rejected_no_body_echo(monkeypatch, capsys):
257
+ _use_keyring(monkeypatch, None)
258
+ cs.set_credential("deepseek", SECRET, auth_type="api_key")
259
+ respx.get("https://api.deepseek.com/v1/models").mock(
260
+ return_value=httpx.Response(401, json={"error": f"bad key {SECRET}"}))
261
+ assert cmd_auth_test("deepseek") == 1
262
+ out = capsys.readouterr().out
263
+ assert SECRET not in out # body never echoed
264
+
265
+
266
+ @respx.mock
267
+ def test_auth_test_gemini_uses_compat_endpoint(monkeypatch):
268
+ _use_keyring(monkeypatch, None)
269
+ cs.set_credential("gemini", SECRET, auth_type="api_key")
270
+ route = respx.get(
271
+ "https://generativelanguage.googleapis.com/v1beta/openai/models").mock(
272
+ return_value=httpx.Response(200, json={"data": []}))
273
+ assert cmd_auth_test("gemini") == 0
274
+ assert route.called
275
+
276
+
277
+ @respx.mock
278
+ def test_auth_test_openrouter_still_auth_key_endpoint(monkeypatch):
279
+ # /models is unauthenticated on OpenRouter — the exception must survive
280
+ _use_keyring(monkeypatch, None)
281
+ cs.set_credential("openrouter", SECRET, auth_type="api_key")
282
+ route = respx.get("https://openrouter.ai/api/v1/auth/key").mock(
283
+ return_value=httpx.Response(200, json={"data": {}}))
284
+ assert cmd_auth_test("openrouter") == 0
285
+ assert route.called
286
+
287
+
288
+ @respx.mock
289
+ def test_auth_test_ollama_reachable_exit_0(monkeypatch, capsys):
290
+ route = respx.get("http://localhost:11434/v1/models").mock(
291
+ return_value=httpx.Response(200, json={"data": []}))
292
+ assert cmd_auth_test("ollama") == 0 # keyless: no exit-2 gate
293
+ assert route.called
294
+ out = capsys.readouterr().out
295
+ assert "reachable" in out
296
+ assert "authorization" not in route.calls.last.request.headers # keyless probe
297
+
298
+
299
+ @respx.mock
300
+ def test_auth_test_ollama_unreachable_exit_3_never_rejected(monkeypatch, capsys):
301
+ respx.get("http://localhost:11434/v1/models").mock(
302
+ side_effect=httpx.ConnectError("refused"))
303
+ assert cmd_auth_test("ollama") == 3 # unreachable ≠ rejected
304
+ out = capsys.readouterr().out
305
+ assert "running" in out # "is ... Ollama running?"
306
+
307
+
308
+ @respx.mock
309
+ def test_auth_test_custom_provider_via_config(monkeypatch):
310
+ _use_keyring(monkeypatch, None)
311
+ monkeypatch.setattr(
312
+ "agentnode_sdk.config.load_config",
313
+ lambda: {"llm": {"providers": {"myvllm": {
314
+ "base_url": "http://10.0.0.5:8000/v1", "model": "llama",
315
+ "requires_key": False}}}})
316
+ route = respx.get("http://10.0.0.5:8000/v1/models").mock(
317
+ return_value=httpx.Response(200, json={"data": []}))
318
+ assert cmd_auth_test("myvllm") == 0
319
+ assert route.called
320
+
321
+
322
+ def test_auth_test_unknown_provider_exit_2_b(monkeypatch):
323
+ assert cmd_auth_test("skynet") == 2
324
+
325
+
326
+ def test_registry_is_single_source_of_truth():
327
+ """auth CLI and wizard must not hardcode provider env vars — those come
328
+ from the registry only."""
329
+ import agentnode_sdk
330
+ from pathlib import Path
331
+ for mod in ("cli/auth.py", "cli/setup_wizard.py"):
332
+ src = (Path(agentnode_sdk.__file__).parent / mod).read_text(encoding="utf-8")
333
+ for literal in ("DEEPSEEK_API_KEY", "MISTRAL_API_KEY", "DASHSCOPE_API_KEY",
334
+ "GEMINI_API_KEY", "OPENROUTER_API_KEY", "OLLAMA_API_KEY"):
335
+ assert literal not in src, f"{literal} hardcoded in {mod}"
336
+
337
+
210
338
  def test_no_secret_in_logs(monkeypatch, caplog):
211
339
  """No log record in the store paths ever carries the token."""
212
340
  class WriteFails(FakeKeyring):