From 9164562fb924c2c98fc8055224fc8af32846b310 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:22:20 +0000 Subject: [PATCH 1/2] Revert "docs: add get_llm() and get_secrets() to OpenHands Cloud Workspace guide (#401)" This reverts the documentation for get_llm() and get_secrets() SaaS credential inheritance as the underlying get_secrets() functionality is not yet deployed. The docs will be re-introduced once the feature is available. Co-authored-by: openhands --- sdk/guides/agent-server/cloud-workspace.mdx | 179 +------------------- 1 file changed, 1 insertion(+), 178 deletions(-) diff --git a/sdk/guides/agent-server/cloud-workspace.mdx b/sdk/guides/agent-server/cloud-workspace.mdx index 43cc07e9..cf4164d2 100644 --- a/sdk/guides/agent-server/cloud-workspace.mdx +++ b/sdk/guides/agent-server/cloud-workspace.mdx @@ -1,6 +1,6 @@ --- title: OpenHands Cloud Workspace -description: Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance. +description: Connect to OpenHands Cloud for fully managed sandbox environments. --- > A ready-to-run example is available [here](#ready-to-run-example)! @@ -78,48 +78,6 @@ logger.info(f"Command completed: {result.exit_code}, {result.stdout}") This verifies connectivity to the cloud sandbox and ensures the environment is ready. -### Inheriting SaaS Credentials - -Instead of providing your own `LLM_API_KEY`, you can inherit the LLM configuration and secrets from your OpenHands Cloud account. This means you only need `OPENHANDS_CLOUD_API_KEY` — no separate LLM key required. - -#### `get_llm()` - -Fetches your account's LLM settings (model, API key, base URL) and returns a ready-to-use `LLM` instance: - -```python icon="python" focus={2-3} -with OpenHandsCloudWorkspace(...) as workspace: - llm = workspace.get_llm() - agent = Agent(llm=llm, tools=get_default_tools()) -``` - -You can override any parameter: - -```python icon="python" -llm = workspace.get_llm(model="gpt-4o", temperature=0.5) -``` - -Under the hood, `get_llm()` calls `GET /api/v1/users/me?expose_secrets=true`, sending your Cloud API key in the `Authorization` header plus the sandbox's `X-Session-API-Key`. That session key is issued by OpenHands Cloud for the running sandbox, so it scopes the request to that sandbox rather than acting like a separately provisioned second credential. - -#### `get_secrets()` - -Builds `LookupSecret` references for your SaaS-configured secrets. Raw values **never transit through the SDK client** — they are resolved lazily by the agent-server inside the sandbox: - -```python icon="python" focus={2-3} -with OpenHandsCloudWorkspace(...) as workspace: - secrets = workspace.get_secrets() - conversation.update_secrets(secrets) -``` - -You can also filter to specific secrets: - -```python icon="python" -gh_secrets = workspace.get_secrets(names=["GITHUB_TOKEN"]) -``` - - -See the [SaaS Credentials example](#saas-credentials-example) below for a complete working example. - - ## Comparison with Other Workspace Types | Feature | OpenHandsCloudWorkspace | APIRemoteWorkspace | DockerWorkspace | @@ -248,141 +206,6 @@ cd agent-sdk uv run python examples/02_remote_agent_server/07_convo_with_cloud_workspace.py ``` -## SaaS Credentials Example - - -This example is available on GitHub: [examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py) - - -This example demonstrates the simplified flow where your OpenHands Cloud account's LLM configuration and secrets are inherited automatically — no need to provide `LLM_API_KEY` separately: - -```python icon="python" expandable examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py -"""Example: Inherit SaaS credentials via OpenHandsCloudWorkspace. - -This example shows the simplified flow where your OpenHands Cloud account's -LLM configuration and secrets are inherited automatically — no need to -provide LLM_API_KEY separately. - -Compared to 07_convo_with_cloud_workspace.py (which requires a separate -LLM_API_KEY), this approach uses: - - workspace.get_llm() → fetches LLM config from your SaaS account - - workspace.get_secrets() → builds lazy LookupSecret references for your secrets - -Raw secret values never transit through the SDK client. The agent-server -inside the sandbox resolves them on demand. - -Usage: - uv run examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py - -Requirements: - - OPENHANDS_CLOUD_API_KEY: API key for OpenHands Cloud (the only credential needed) - -Optional: - - OPENHANDS_CLOUD_API_URL: Override the Cloud API URL (default: https://app.all-hands.dev) - - LLM_MODEL: Override the model from your SaaS settings -""" - -import os -import time - -from openhands.sdk import ( - Conversation, - RemoteConversation, - get_logger, -) -from openhands.tools.preset.default import get_default_agent -from openhands.workspace import OpenHandsCloudWorkspace - - -logger = get_logger(__name__) - - -cloud_api_key = os.getenv("OPENHANDS_CLOUD_API_KEY") -if not cloud_api_key: - logger.error("OPENHANDS_CLOUD_API_KEY required") - exit(1) - -cloud_api_url = os.getenv("OPENHANDS_CLOUD_API_URL", "https://app.all-hands.dev") -logger.info(f"Using OpenHands Cloud API: {cloud_api_url}") - -with OpenHandsCloudWorkspace( - cloud_api_url=cloud_api_url, - cloud_api_key=cloud_api_key, -) as workspace: - # --- LLM from SaaS account settings --- - # get_llm() calls GET /users/me?expose_secrets=true, - # sending your Cloud API key plus the sandbox session - # key that OpenHands Cloud issued for this workspace. - # It returns a fully configured LLM instance. - # Override any parameter: workspace.get_llm(model="gpt-4o") - llm = workspace.get_llm() - logger.info(f"LLM configured: model={llm.model}") - - # --- Secrets from SaaS account --- - # get_secrets() fetches secret *names* (not values) and builds LookupSecret - # references. Values are resolved lazily inside the sandbox. - secrets = workspace.get_secrets() - logger.info(f"Available secrets: {list(secrets.keys())}") - - # Build agent and conversation - agent = get_default_agent(llm=llm, cli_mode=True) - received_events: list = [] - last_event_time = {"ts": time.time()} - - def event_callback(event) -> None: - received_events.append(event) - last_event_time["ts"] = time.time() - - conversation = Conversation( - agent=agent, workspace=workspace, callbacks=[event_callback] - ) - assert isinstance(conversation, RemoteConversation) - - # Inject SaaS secrets into the conversation - if secrets: - conversation.update_secrets(secrets) - logger.info(f"Injected {len(secrets)} secrets into conversation") - - # Build a prompt that exercises the injected secrets by asking the agent to - # print the last 50% of each token — proves values resolved without leaking - # full secrets in logs. - secret_names = list(secrets.keys()) if secrets else [] - if secret_names: - names_str = ", ".join(f"${name}" for name in secret_names) - prompt = ( - f"For each of these environment variables: {names_str} — " - "print the variable name and the LAST 50% of its value " - "(i.e. the second half of the string). " - "Then write a short summary into SECRETS_CHECK.txt." - ) - else: - # No secret was configured on OpenHands Cloud - prompt = "Tell me, is there any secret configured for you?" - - try: - conversation.send_message(prompt) - conversation.run() - - while time.time() - last_event_time["ts"] < 2.0: - time.sleep(0.1) - - cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost - print(f"EXAMPLE_COST: {cost}") - finally: - conversation.close() - - logger.info("✅ Conversation completed successfully.") - logger.info(f"Total {len(received_events)} events received during conversation.") -``` - -```bash Running the SaaS Credentials Example -export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key" -# Optional: override LLM model from your SaaS settings -# export LLM_MODEL="gpt-4o" -cd agent-sdk -uv run python examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py -``` - ## Next Steps - **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Connect to Runtime API service From f02656bd389d4cbd012f0addb76b30b41bf6b22e Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 15:23:00 +0000 Subject: [PATCH 2/2] docs: re-introduce get_llm() and get_secrets() SaaS credentials documentation Re-adds the documentation for get_llm() and get_secrets() methods in the OpenHands Cloud Workspace guide. This was temporarily reverted before the v1.14.1 release since get_secrets() was not yet deployed. This PR should be merged AFTER the get_secrets() functionality is deployed. Co-authored-by: openhands --- sdk/guides/agent-server/cloud-workspace.mdx | 179 +++++++++++++++++++- 1 file changed, 178 insertions(+), 1 deletion(-) diff --git a/sdk/guides/agent-server/cloud-workspace.mdx b/sdk/guides/agent-server/cloud-workspace.mdx index cf4164d2..43cc07e9 100644 --- a/sdk/guides/agent-server/cloud-workspace.mdx +++ b/sdk/guides/agent-server/cloud-workspace.mdx @@ -1,6 +1,6 @@ --- title: OpenHands Cloud Workspace -description: Connect to OpenHands Cloud for fully managed sandbox environments. +description: Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance. --- > A ready-to-run example is available [here](#ready-to-run-example)! @@ -78,6 +78,48 @@ logger.info(f"Command completed: {result.exit_code}, {result.stdout}") This verifies connectivity to the cloud sandbox and ensures the environment is ready. +### Inheriting SaaS Credentials + +Instead of providing your own `LLM_API_KEY`, you can inherit the LLM configuration and secrets from your OpenHands Cloud account. This means you only need `OPENHANDS_CLOUD_API_KEY` — no separate LLM key required. + +#### `get_llm()` + +Fetches your account's LLM settings (model, API key, base URL) and returns a ready-to-use `LLM` instance: + +```python icon="python" focus={2-3} +with OpenHandsCloudWorkspace(...) as workspace: + llm = workspace.get_llm() + agent = Agent(llm=llm, tools=get_default_tools()) +``` + +You can override any parameter: + +```python icon="python" +llm = workspace.get_llm(model="gpt-4o", temperature=0.5) +``` + +Under the hood, `get_llm()` calls `GET /api/v1/users/me?expose_secrets=true`, sending your Cloud API key in the `Authorization` header plus the sandbox's `X-Session-API-Key`. That session key is issued by OpenHands Cloud for the running sandbox, so it scopes the request to that sandbox rather than acting like a separately provisioned second credential. + +#### `get_secrets()` + +Builds `LookupSecret` references for your SaaS-configured secrets. Raw values **never transit through the SDK client** — they are resolved lazily by the agent-server inside the sandbox: + +```python icon="python" focus={2-3} +with OpenHandsCloudWorkspace(...) as workspace: + secrets = workspace.get_secrets() + conversation.update_secrets(secrets) +``` + +You can also filter to specific secrets: + +```python icon="python" +gh_secrets = workspace.get_secrets(names=["GITHUB_TOKEN"]) +``` + + +See the [SaaS Credentials example](#saas-credentials-example) below for a complete working example. + + ## Comparison with Other Workspace Types | Feature | OpenHandsCloudWorkspace | APIRemoteWorkspace | DockerWorkspace | @@ -206,6 +248,141 @@ cd agent-sdk uv run python examples/02_remote_agent_server/07_convo_with_cloud_workspace.py ``` +## SaaS Credentials Example + + +This example is available on GitHub: [examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py](https://github.com/OpenHands/software-agent-sdk/blob/main/examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py) + + +This example demonstrates the simplified flow where your OpenHands Cloud account's LLM configuration and secrets are inherited automatically — no need to provide `LLM_API_KEY` separately: + +```python icon="python" expandable examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py +"""Example: Inherit SaaS credentials via OpenHandsCloudWorkspace. + +This example shows the simplified flow where your OpenHands Cloud account's +LLM configuration and secrets are inherited automatically — no need to +provide LLM_API_KEY separately. + +Compared to 07_convo_with_cloud_workspace.py (which requires a separate +LLM_API_KEY), this approach uses: + - workspace.get_llm() → fetches LLM config from your SaaS account + - workspace.get_secrets() → builds lazy LookupSecret references for your secrets + +Raw secret values never transit through the SDK client. The agent-server +inside the sandbox resolves them on demand. + +Usage: + uv run examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py + +Requirements: + - OPENHANDS_CLOUD_API_KEY: API key for OpenHands Cloud (the only credential needed) + +Optional: + - OPENHANDS_CLOUD_API_URL: Override the Cloud API URL (default: https://app.all-hands.dev) + - LLM_MODEL: Override the model from your SaaS settings +""" + +import os +import time + +from openhands.sdk import ( + Conversation, + RemoteConversation, + get_logger, +) +from openhands.tools.preset.default import get_default_agent +from openhands.workspace import OpenHandsCloudWorkspace + + +logger = get_logger(__name__) + + +cloud_api_key = os.getenv("OPENHANDS_CLOUD_API_KEY") +if not cloud_api_key: + logger.error("OPENHANDS_CLOUD_API_KEY required") + exit(1) + +cloud_api_url = os.getenv("OPENHANDS_CLOUD_API_URL", "https://app.all-hands.dev") +logger.info(f"Using OpenHands Cloud API: {cloud_api_url}") + +with OpenHandsCloudWorkspace( + cloud_api_url=cloud_api_url, + cloud_api_key=cloud_api_key, +) as workspace: + # --- LLM from SaaS account settings --- + # get_llm() calls GET /users/me?expose_secrets=true, + # sending your Cloud API key plus the sandbox session + # key that OpenHands Cloud issued for this workspace. + # It returns a fully configured LLM instance. + # Override any parameter: workspace.get_llm(model="gpt-4o") + llm = workspace.get_llm() + logger.info(f"LLM configured: model={llm.model}") + + # --- Secrets from SaaS account --- + # get_secrets() fetches secret *names* (not values) and builds LookupSecret + # references. Values are resolved lazily inside the sandbox. + secrets = workspace.get_secrets() + logger.info(f"Available secrets: {list(secrets.keys())}") + + # Build agent and conversation + agent = get_default_agent(llm=llm, cli_mode=True) + received_events: list = [] + last_event_time = {"ts": time.time()} + + def event_callback(event) -> None: + received_events.append(event) + last_event_time["ts"] = time.time() + + conversation = Conversation( + agent=agent, workspace=workspace, callbacks=[event_callback] + ) + assert isinstance(conversation, RemoteConversation) + + # Inject SaaS secrets into the conversation + if secrets: + conversation.update_secrets(secrets) + logger.info(f"Injected {len(secrets)} secrets into conversation") + + # Build a prompt that exercises the injected secrets by asking the agent to + # print the last 50% of each token — proves values resolved without leaking + # full secrets in logs. + secret_names = list(secrets.keys()) if secrets else [] + if secret_names: + names_str = ", ".join(f"${name}" for name in secret_names) + prompt = ( + f"For each of these environment variables: {names_str} — " + "print the variable name and the LAST 50% of its value " + "(i.e. the second half of the string). " + "Then write a short summary into SECRETS_CHECK.txt." + ) + else: + # No secret was configured on OpenHands Cloud + prompt = "Tell me, is there any secret configured for you?" + + try: + conversation.send_message(prompt) + conversation.run() + + while time.time() - last_event_time["ts"] < 2.0: + time.sleep(0.1) + + cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost + print(f"EXAMPLE_COST: {cost}") + finally: + conversation.close() + + logger.info("✅ Conversation completed successfully.") + logger.info(f"Total {len(received_events)} events received during conversation.") +``` + +```bash Running the SaaS Credentials Example +export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key" +# Optional: override LLM model from your SaaS settings +# export LLM_MODEL="gpt-4o" +cd agent-sdk +uv run python examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py +``` + ## Next Steps - **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Connect to Runtime API service