Skip to content

Commit f02656b

Browse files
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 <openhands@all-hands.dev>
1 parent 9164562 commit f02656b

1 file changed

Lines changed: 178 additions & 1 deletion

File tree

sdk/guides/agent-server/cloud-workspace.mdx

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: OpenHands Cloud Workspace
3-
description: Connect to OpenHands Cloud for fully managed sandbox environments.
3+
description: Connect to OpenHands Cloud for fully managed sandbox environments with optional SaaS credential inheritance.
44
---
55

66
> 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}")
7878

7979
This verifies connectivity to the cloud sandbox and ensures the environment is ready.
8080

81+
### Inheriting SaaS Credentials
82+
83+
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.
84+
85+
#### `get_llm()`
86+
87+
Fetches your account's LLM settings (model, API key, base URL) and returns a ready-to-use `LLM` instance:
88+
89+
```python icon="python" focus={2-3}
90+
with OpenHandsCloudWorkspace(...) as workspace:
91+
llm = workspace.get_llm()
92+
agent = Agent(llm=llm, tools=get_default_tools())
93+
```
94+
95+
You can override any parameter:
96+
97+
```python icon="python"
98+
llm = workspace.get_llm(model="gpt-4o", temperature=0.5)
99+
```
100+
101+
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.
102+
103+
#### `get_secrets()`
104+
105+
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:
106+
107+
```python icon="python" focus={2-3}
108+
with OpenHandsCloudWorkspace(...) as workspace:
109+
secrets = workspace.get_secrets()
110+
conversation.update_secrets(secrets)
111+
```
112+
113+
You can also filter to specific secrets:
114+
115+
```python icon="python"
116+
gh_secrets = workspace.get_secrets(names=["GITHUB_TOKEN"])
117+
```
118+
119+
<Tip>
120+
See the [SaaS Credentials example](#saas-credentials-example) below for a complete working example.
121+
</Tip>
122+
81123
## Comparison with Other Workspace Types
82124

83125
| Feature | OpenHandsCloudWorkspace | APIRemoteWorkspace | DockerWorkspace |
@@ -206,6 +248,141 @@ cd agent-sdk
206248
uv run python examples/02_remote_agent_server/07_convo_with_cloud_workspace.py
207249
```
208250

251+
## SaaS Credentials Example
252+
253+
<Note>
254+
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)
255+
</Note>
256+
257+
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:
258+
259+
```python icon="python" expandable examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py
260+
"""Example: Inherit SaaS credentials via OpenHandsCloudWorkspace.
261+
262+
This example shows the simplified flow where your OpenHands Cloud account's
263+
LLM configuration and secrets are inherited automatically — no need to
264+
provide LLM_API_KEY separately.
265+
266+
Compared to 07_convo_with_cloud_workspace.py (which requires a separate
267+
LLM_API_KEY), this approach uses:
268+
- workspace.get_llm() → fetches LLM config from your SaaS account
269+
- workspace.get_secrets() → builds lazy LookupSecret references for your secrets
270+
271+
Raw secret values never transit through the SDK client. The agent-server
272+
inside the sandbox resolves them on demand.
273+
274+
Usage:
275+
uv run examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py
276+
277+
Requirements:
278+
- OPENHANDS_CLOUD_API_KEY: API key for OpenHands Cloud (the only credential needed)
279+
280+
Optional:
281+
- OPENHANDS_CLOUD_API_URL: Override the Cloud API URL (default: https://app.all-hands.dev)
282+
- LLM_MODEL: Override the model from your SaaS settings
283+
"""
284+
285+
import os
286+
import time
287+
288+
from openhands.sdk import (
289+
Conversation,
290+
RemoteConversation,
291+
get_logger,
292+
)
293+
from openhands.tools.preset.default import get_default_agent
294+
from openhands.workspace import OpenHandsCloudWorkspace
295+
296+
297+
logger = get_logger(__name__)
298+
299+
300+
cloud_api_key = os.getenv("OPENHANDS_CLOUD_API_KEY")
301+
if not cloud_api_key:
302+
logger.error("OPENHANDS_CLOUD_API_KEY required")
303+
exit(1)
304+
305+
cloud_api_url = os.getenv("OPENHANDS_CLOUD_API_URL", "https://app.all-hands.dev")
306+
logger.info(f"Using OpenHands Cloud API: {cloud_api_url}")
307+
308+
with OpenHandsCloudWorkspace(
309+
cloud_api_url=cloud_api_url,
310+
cloud_api_key=cloud_api_key,
311+
) as workspace:
312+
# --- LLM from SaaS account settings ---
313+
# get_llm() calls GET /users/me?expose_secrets=true,
314+
# sending your Cloud API key plus the sandbox session
315+
# key that OpenHands Cloud issued for this workspace.
316+
# It returns a fully configured LLM instance.
317+
# Override any parameter: workspace.get_llm(model="gpt-4o")
318+
llm = workspace.get_llm()
319+
logger.info(f"LLM configured: model={llm.model}")
320+
321+
# --- Secrets from SaaS account ---
322+
# get_secrets() fetches secret *names* (not values) and builds LookupSecret
323+
# references. Values are resolved lazily inside the sandbox.
324+
secrets = workspace.get_secrets()
325+
logger.info(f"Available secrets: {list(secrets.keys())}")
326+
327+
# Build agent and conversation
328+
agent = get_default_agent(llm=llm, cli_mode=True)
329+
received_events: list = []
330+
last_event_time = {"ts": time.time()}
331+
332+
def event_callback(event) -> None:
333+
received_events.append(event)
334+
last_event_time["ts"] = time.time()
335+
336+
conversation = Conversation(
337+
agent=agent, workspace=workspace, callbacks=[event_callback]
338+
)
339+
assert isinstance(conversation, RemoteConversation)
340+
341+
# Inject SaaS secrets into the conversation
342+
if secrets:
343+
conversation.update_secrets(secrets)
344+
logger.info(f"Injected {len(secrets)} secrets into conversation")
345+
346+
# Build a prompt that exercises the injected secrets by asking the agent to
347+
# print the last 50% of each token — proves values resolved without leaking
348+
# full secrets in logs.
349+
secret_names = list(secrets.keys()) if secrets else []
350+
if secret_names:
351+
names_str = ", ".join(f"${name}" for name in secret_names)
352+
prompt = (
353+
f"For each of these environment variables: {names_str}"
354+
"print the variable name and the LAST 50% of its value "
355+
"(i.e. the second half of the string). "
356+
"Then write a short summary into SECRETS_CHECK.txt."
357+
)
358+
else:
359+
# No secret was configured on OpenHands Cloud
360+
prompt = "Tell me, is there any secret configured for you?"
361+
362+
try:
363+
conversation.send_message(prompt)
364+
conversation.run()
365+
366+
while time.time() - last_event_time["ts"] < 2.0:
367+
time.sleep(0.1)
368+
369+
cost = conversation.conversation_stats.get_combined_metrics().accumulated_cost
370+
print(f"EXAMPLE_COST: {cost}")
371+
finally:
372+
conversation.close()
373+
374+
logger.info("✅ Conversation completed successfully.")
375+
logger.info(f"Total {len(received_events)} events received during conversation.")
376+
```
377+
378+
```bash Running the SaaS Credentials Example
379+
export OPENHANDS_CLOUD_API_KEY="your-cloud-api-key"
380+
# Optional: override LLM model from your SaaS settings
381+
# export LLM_MODEL="gpt-4o"
382+
cd agent-sdk
383+
uv run python examples/02_remote_agent_server/10_cloud_workspace_share_credentials.py
384+
```
385+
209386
## Next Steps
210387

211388
- **[API-based Sandbox](/sdk/guides/agent-server/api-sandbox)** - Connect to Runtime API service

0 commit comments

Comments
 (0)