feat: workspace.get_llm() and get_secrets() for SaaS credential inheritance#2409
Draft
feat: workspace.get_llm() and get_secrets() for SaaS credential inheritance#2409
Conversation
Add methods to OpenHandsCloudWorkspace that call the new SaaS API endpoints to retrieve the user's LLM configuration and custom secrets: - get_llm(**llm_kwargs): Fetches LLM settings from the user's SaaS account and returns a configured LLM instance. User kwargs override SaaS defaults. - get_secrets(names=None): Fetches custom secrets and returns a dict[str, str] compatible with conversation.update_secrets(). These methods enable SDK users to inherit their SaaS credentials while retaining full control over agent customization. Depends on OpenHands/OpenHands#13306 for the server-side API endpoints. Related: OpenHands/OpenHands#13268 Co-authored-by: openhands <openhands@all-hands.dev>
Contributor
API breakage checks (Griffe)Result: Passed |
Contributor
Agent server REST API breakage checks (OpenAPI)Result: Passed |
…t-backed configs SDK LLM changes: - LLM.api_key now accepts str | SecretStr | SecretSource | None - Validator passes through SecretSource instances; deserialises dicts - Serializer delegates to SecretSource.model_dump() for round-tripping - _get_litellm_api_key_value() resolves SecretSource.get_value() lazily - _init_model_info_and_caps() skips network for SecretSource api_key OpenHandsCloudWorkspace changes: - get_llm() calls sandbox-scoped /settings/llm (SESSION_API_KEY auth) and returns LLM with api_key=LookupSecret — raw key never reaches client - get_secrets() calls /settings/secrets for names, returns dict of LookupSecret instances pointing to per-secret endpoints - Added _send_settings_request() for SESSION_API_KEY-authenticated calls Co-authored-by: openhands <openhands@all-hands.dev>
Contributor
Coverage Report •
|
||||||||||||||||||||||||||||||
LookupSecret now supports env_headers — a mapping of header name to environment variable name. Headers are resolved from os.environ at get_value() call time. This ensures the SESSION_API_KEY is never embedded in the serialized LookupSecret; only the env var *name* travels over the wire. Resolution only succeeds inside the sandbox where the env var is set. - LookupSecret: add env_headers field, merge into headers in get_value() - LLM: add assert to narrow dict type for pyright - Workspace: use env_headers instead of raw headers in get_llm/get_secrets - Tests: 3 new env_headers enforcement tests (28 total SDK tests) - Fix pyright errors in examples and tests Co-authored-by: openhands <openhands@all-hands.dev>
Per feedback, LookupSecret is only needed for secrets — not LLM config. Reverted all LLM.api_key SecretSource changes (api_key stays str|SecretStr|None). workspace.get_llm() now returns a real LLM with the raw api_key. Deleted test_llm_secret_source_api_key.py (no longer applicable). - LLM class: reverted to main (no SecretSource support) - workspace.get_llm(): builds LLM from plain JSON response - workspace.get_secrets(): still uses LookupSecret+env_headers - 8 workspace tests pass Co-authored-by: openhands <openhands@all-hands.dev>
…ings/llm The OpenHands app server already has GET /api/v1/users/me which returns full user settings including llm_model, llm_api_key, llm_base_url. A new expose_secrets query param returns the raw api_key. get_llm() now calls /users/me?expose_secrets=true via _send_api_request (Bearer token auth) instead of /settings/llm via _send_settings_request (X-Session-API-Key auth), eliminating the need for a dedicated LLM settings endpoint. Co-authored-by: openhands <openhands@all-hands.dev>
The server now requires a valid session key when expose_secrets=true. get_llm() includes the sandbox session key header alongside the Bearer token, proving there is an active sandbox owned by the caller. Co-authored-by: openhands <openhands@all-hands.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
get_llm()andget_secrets()methods toOpenHandsCloudWorkspace, enabling SDK-created conversations to inherit the user's SaaS credentials.Design
get_llm(**kwargs): CallsGET /api/v1/users/me?expose_secrets=truewith both Bearer token andX-Session-API-Keyheaders (dual auth). Extractsllm_model,llm_api_key,llm_base_urland returns a fully usableLLMinstance. User-provided kwargs override SaaS settings.get_secrets(names=None): CallsGET /sandboxes/{id}/settings/secrets(X-Session-API-Key auth) for names only, then buildsLookupSecretreferences withenv_headers. Raw secret values never transit through the SDK client — they are resolved lazily by the agent-server inside the sandbox.Security
get_llm()sends both the Bearer token (user identity) and the sandbox session key (active sandbox proof). The server verifies the session key belongs to a sandbox owned by the same user. This prevents a leaked API key from being used to extract raw LLM credentials.Usage
Companion PR
expose_secretsparam on/users/me+ secrets endpoints)Tests
Resolves OpenHands/OpenHands#13268
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22golang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:78ff036-pythonRun
All tags pushed for this build
About Multi-Architecture Support
78ff036-python) is a multi-arch manifest supporting both amd64 and arm6478ff036-python-amd64) are also available if needed