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.
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/CHANGELOG.md +44 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/PKG-INFO +1 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/__init__.py +1 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/auth.py +105 -36
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/setup_wizard.py +49 -19
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_store.py +12 -5
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_runner.py +16 -7
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/pyproject.toml +1 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_auth_cli_vault.py +129 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_vault_binding.py +4 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_setup_wizard.py +56 -1
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/.env.example +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/.gitignore +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/README.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/REGISTRY_SIGNING_ACTIVATION.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/REGISTRY_SIGNING_SPEC.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/THREAT_MODEL.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/TRUST_STACK.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode.lock +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/_fileutil.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/async_client.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/capability_graph.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/capability_taxonomy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/__init__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/__main__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/audit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/cassette_audit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/commands.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/complements.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/init.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/main.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_commands.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_status.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_submit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/mcp_verify.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/output.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/publish.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/record_cases.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/sandbox_commands.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/serve.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/smart_run.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/templates.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/validate.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/cli/verify_local.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/client.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/compatibility.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/config.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_handle.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/credential_resolver.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/detect.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/exceptions.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/guard.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/input_guard.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/installer.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/key_status.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/llm_providers.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/lock_integrity.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/mcp_server.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/models.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/planner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/references.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/registry_trust.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/resolve.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/resource_provider.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/risk_profile.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/run_log.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtime.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/__init__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_llm_broker.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_llm_policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/agent_sandbox.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/mcp_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/python_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/runtimes/remote_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/__init__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_container_wrapper.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_rpc.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/agent_session.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/backend.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/container_backend.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/sandbox/types.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/signature.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/signing_key.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/agentnode_sdk/skill.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/sandbox-image/Dockerfile +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/sandbox-image/README.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/analyze_scores.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/batch_verify.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/ci_smoke_test.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/generate_compatibility_artifacts.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/verify_toolcalls.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/scripts/weekly_retest.sh +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/README.md +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/__init__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/container_agent_wrapper.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/fake_llm.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/host_driver.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/spikes/agent_sandbox_routing/trivial_agent.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/__init__.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/conftest.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_llm_broker.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_llm_policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_rpc.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_routing.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_sandbox_spike.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_agent_session_container.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_async_client.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_audit_ux.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_auto_upgrade_policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli_lock.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_cli_run_resolution.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client_json_guard.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_client_sprint_b.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_config.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_handle.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_integration.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_resolver.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_store.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_credential_vault.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_detect.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_detect_and_install.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_e2e_runtime.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_edge_cases.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_check.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_config_cache.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_preview.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_schema.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_set.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_status.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override_audit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_tool_override_cli.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_guard_ux.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_input_guard.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_input_guard_escalation.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_install_hardening.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_installer_sprint_b.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_intelligence.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_key_status.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_binding.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_call_runlog.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_llm_providers.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_lock_integrity.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_lock_runtime.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_audit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_doctor.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_sandbox.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_mcp_server.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_observability.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_planner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_policy.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_policy_integration.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_prompt_specs.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_provider_matrix.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_publish.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_references.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_registry_trust.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_remote_hardening.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_remote_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_resource_provider.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_resource_specs.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_risk_profile.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_run_log.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runner.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runtime.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_runtime_audit.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_backend.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_doctor.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_e2e.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_sandbox_gate.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_security_hardening.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_signature.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_signing_key.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_skill.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_smart.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_stability.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_toolpack_sandbox.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_v02.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_validate.py +0 -0
- {agentnode_sdk-0.15.0 → agentnode_sdk-0.16.0}/tests/test_validate_skill.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
#
|
|
187
|
-
#
|
|
188
|
-
#
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
218
|
-
if
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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} ({
|
|
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} ({
|
|
242
|
-
f"
|
|
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
|
-
|
|
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
|
|
69
|
+
from agentnode_sdk.credential_store import set_credential
|
|
63
70
|
|
|
64
|
-
|
|
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).
|
|
106
|
-
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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.
|
|
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):
|