SDK 0.3.0: cloud workspace client (xagent_sdk.cloud.WorkspaceClient)#4
Conversation
_BaseClient resolved base_url from explicit arg then XAGENT_BASE_URL, with no way for a client bound to a fixed hosted service to supply a default. Add a `_DEFAULT_BASE_URL` class attribute (None by default) and consult it after the env var: explicit -> env -> class default -> raise. Self-hosted clients leave it None and still require an explicit or env base URL; a client targeting a hosted endpoint sets it to that URL. The None-only fallback is preserved: an explicitly empty base_url still reaches the guard and raises rather than resolving to the default.
Add an additive xagent_sdk.cloud submodule for the hosted workspace-key surface. WorkspaceClient is a third _BaseClient sibling authenticated by a workspace key (XAGENT_WORKSPACE_KEY), defaulting its base URL to the hosted endpoint via the new _DEFAULT_BASE_URL hook. WorkspaceAgentsAPI / WorkspaceTemplatesAPI target /v1/workspace/* and reuse the shared response parsers, dataclasses, and the _require_runtime_key fail-closed guard from the personal-key surface -- only the paths and the create request body differ. create()/create_from_template() accept the agent-config fields the workspace endpoints take (no metadata) and spread optional fields flat, dropping None. rotate_key() mints the agent's runtime key. The submodule is not imported by the top-level package: importing xagent_sdk does not load cloud, and the top-level public surface is unchanged.
Cover WorkspaceClient construction (env isolation to XAGENT_WORKSPACE_KEY,
empty-key no env fallback, hosted default base URL), the agents and
templates namespaces (path assertions, request body shape, parse,
fail-closed without a runtime key, flat from-template body with no
overrides wrapper, no metadata parameter), and the typed-error mappings.
Pin the cloud public surface to exactly {WorkspaceClient} and assert it
is not re-exported at the top level. Strip XAGENT_WORKSPACE_KEY in the
shared env-cleanup fixture so cloud tests start from a clean environment.
Add a WorkspaceClient section to the API reference (import path, the create -> runtime key -> AgentClient flow, env var, default URL) kept separate from the self-hosted sections. Add a workspace_client e2e fixture (gated on XAGENT_WORKSPACE_KEY) and test_cloud_smoke.py covering the full template -> create -> run flow plus unknown-template, bad-key, and no-runtime-key paths.
The workspace agent-config list params were typed Sequence[str] to dodge the `list` builtin being shadowed by the namespace's own `list()` method. But a bare str satisfies Sequence[str], so create(tool_categories="basic") passed type checking and put a scalar on the wire where the backend wants an array. Reference the builtin through a module-scope `_StrList = list[str]` alias instead -- it sidesteps the shadow without widening the type, so mypy now rejects a stray string. A test pins that these params never regress to a str-admitting Sequence type.
There was a problem hiding this comment.
Code Review
This pull request introduces the WorkspaceClient under xagent_sdk.cloud to support the hosted workspace surface. It adds workspace-scoped APIs for managing agents (WorkspaceAgentsAPI) and templates (WorkspaceTemplatesAPI), along with comprehensive unit and end-to-end tests. The base client was also updated to support class-level default base URLs. Feedback suggests URL-encoding the template_id parameter in the template detail endpoint to prevent potential routing issues with special characters.
templates.get() interpolated template_id straight into the path. httpx percent-encodes spaces but leaves "/", "?", "#" and "%" untouched, so a template id containing those could split the route or leak into the query string. Encode it with urllib.parse.quote(..., safe="") in both the personal-key and workspace template surfaces; ordinary slug ids pass through unchanged. Tests pin that a slash/query id stays in one path segment with an empty query.
rogercloud
left a comment
There was a problem hiding this comment.
Reviewed the current head and found three issues that should be addressed before release.
| [project] | ||
| name = "xagent-sdk" | ||
| version = "0.2.0" | ||
| version = "0.3.0" |
There was a problem hiding this comment.
This bumps the package to 0.3.0, but python/README.md still says the SDK status is 0.2.0 and the install command still pins @v0.2.0; the root README also still lists the Python client as 0.1.0. Anyone following the README will install a version that does not contain the new xagent_sdk.cloud.WorkspaceClient module, so the new example fails at import time. Please update the README status/install tag to v0.3.0 and sync the root README version as well.
| # default (None for self-hosted clients, a fixed URL for hosted ones). | ||
| if base_url is None: | ||
| base_url = os.environ.get("XAGENT_BASE_URL") | ||
| base_url = os.environ.get("XAGENT_BASE_URL") or self._DEFAULT_BASE_URL |
There was a problem hiding this comment.
This treats an explicitly configured empty XAGENT_BASE_URL as missing and falls back to the hosted default. In a staging/self-hosted deployment, a broken env value like XAGENT_BASE_URL="" would silently send WorkspaceClient traffic to https://cloud.xagent.run instead of failing fast. Please distinguish "env var not present" from "env var present but empty", and let the existing if not base_url guard raise for the empty-string case.
| ) | ||
| runtime_key = created.runtime_full_key # one-time secret | ||
|
|
||
| with AgentClient(api_key=runtime_key) as agent: # run on the same surface |
There was a problem hiding this comment.
This hosted WorkspaceClient example creates the workspace client without a base_url, relying on its cloud default, but then constructs AgentClient(api_key=runtime_key) without a base_url. AgentClient still requires either an explicit base_url or XAGENT_BASE_URL, so copying this quickstart fails with ValueError: base_url required after the agent is created. Please pass the same hosted URL explicitly here, or define a shared base_url variable and use it for both clients.
base_url resolution used `os.environ.get("XAGENT_BASE_URL") or
self._DEFAULT_BASE_URL`, so an env var set to "" (a broken staging
config) was treated as absent and silently fell back to the hosted
default -- routing self-hosted/staging traffic to production.
Extract a `_resolve(explicit, env_name, default)` helper used for both
api_key and base_url. It only falls through to the next source when a
value is genuinely absent (None); an explicit "" or an env var set to ""
is returned as-is so the existing `if not value` guard fails fast.
Centralizing it gives credential/URL resolution one None-vs-empty
definition instead of re-deriving the chain (and re-introducing the
falsy-`or` swallow) per field. Tests cover empty env base_url, empty env
api key, and the workspace empty-env-url case.
The package is 0.3.0 but the README status banner, install tags, version policy, User-Agent note, and the root README still said 0.2.0 / 0.1.0 -- following them installed a build without xagent_sdk.cloud, so the new example failed at import. Bump those to 0.3.0 (the Migration-from-0.1.0 section keeps its version references by design). The cloud quickstart created AgentClient without a base_url; AgentClient has no hosted default, so it raised ValueError after the agent was created. Use a shared base_url for both WorkspaceClient and AgentClient so the snippet runs as written.
rogercloud
left a comment
There was a problem hiding this comment.
Approved after re-reviewing the updated head. The previous comments are addressed and the latest checks are green.
What this does
Adds an additive
xagent_sdk.cloudsubmodule for the hosted workspacesurface. SaaS apps authenticate with a workspace key and manage
agents/templates scoped to a workspace, then run agents with the
existing
AgentClient.This is additive: importing
xagent_sdkdoes not loadcloud, and thetop-level public surface is unchanged. Self-hosted users (
UserClient/AgentClient) are unaffected.Surface
WorkspaceClient— third_BaseClientsibling; auth viaXAGENT_WORKSPACE_KEY, base URL defaults to the hosted endpoint(overridable by arg /
XAGENT_BASE_URL).ws.templates.{list,get}→GET /v1/workspace/templates*ws.agents.{list,create,create_from_template,rotate_key}→/v1/workspace/agents*Design
_BaseClient), error model, responseparsers, and dataclasses; the workspace namespaces only differ in
their paths and request bodies. No data/transport/error code is
duplicated.
parsers apply unchanged.
create/create_from_templateaccept the agent-config fields theworkspace endpoints take (no
metadata); optional fields are spreadflat and dropped when None. Both fail closed (
MalformedResponse) if aruntime key was requested but the response carried none.
_BaseClientgains a_DEFAULT_BASE_URLhook so a client bound to afixed hosted service can default its base URL; self-hosted clients
leave it unset and still require an explicit/env base URL. The
None-only env fallback is preserved (an explicit empty value raises).
New public surface
xagent_sdk.cloud.WorkspaceClient(the submodule exports only this).xagent_sdk.__all__is unchanged.Validation
xagent_sdk.cloud.__all__ == {"WorkspaceClient"},not re-exported at the top level,
import xagent_sdkdoes not load it./v1/workspace/*paths, flat from-template body (no
overrideswrapper), nometadataparameter, list params typed as
list[str](a bare string isrejected), fail-closed on a missing runtime key.
tests/e2e/test_cloud_smoke.py, gated onXAGENT_WORKSPACE_KEY):template list → create-from-template → run via
AgentClient, plusunknown-template, bad-key, and no-runtime-key paths.