Skip to content

SDK 0.3.0: cloud workspace client (xagent_sdk.cloud.WorkspaceClient)#4

Merged
rogercloud merged 9 commits into
xorbitsai:mainfrom
AlexLiu190625:feat/0.3.0-cloud-workspace
Jun 2, 2026
Merged

SDK 0.3.0: cloud workspace client (xagent_sdk.cloud.WorkspaceClient)#4
rogercloud merged 9 commits into
xorbitsai:mainfrom
AlexLiu190625:feat/0.3.0-cloud-workspace

Conversation

@AlexLiu190625

Copy link
Copy Markdown
Collaborator

What this does

Adds an additive xagent_sdk.cloud submodule for the hosted workspace
surface. 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_sdk does not load cloud, and the
top-level public surface is unchanged. Self-hosted users (UserClient /
AgentClient) are unaffected.

Surface

from xagent_sdk.cloud import WorkspaceClient
from xagent_sdk import AgentClient

with WorkspaceClient(workspace_key="xag_workspace_...") as ws:
    created = ws.agents.create_from_template(
        "support-email-agent", name="HR Leave Assistant"
    )
    runtime_key = created.runtime_full_key          # one-time secret

with AgentClient(api_key=runtime_key) as agent:
    print(agent.tasks.run(agent_id=created.agent_id, message="Hi").output)
  • WorkspaceClient — third _BaseClient sibling; auth via
    XAGENT_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

  • Reuses the shared transport (_BaseClient), error model, response
    parsers, and dataclasses; the workspace namespaces only differ in
    their paths and request bodies. No data/transport/error code is
    duplicated.
  • Response shapes match the personal-key V1 surface, so the existing
    parsers apply unchanged.
  • create / create_from_template accept the agent-config fields the
    workspace endpoints take (no metadata); optional fields are spread
    flat and dropped when None. Both fail closed (MalformedResponse) if a
    runtime key was requested but the response carried none.
  • _BaseClient gains a _DEFAULT_BASE_URL hook so a client bound to a
    fixed 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).
  • Top-level xagent_sdk.__all__ is unchanged.

Validation

  • Full unit suite green; mypy strict + ruff + codespell clean.
  • Cloud module pinned: xagent_sdk.cloud.__all__ == {"WorkspaceClient"},
    not re-exported at the top level, import xagent_sdk does not load it.
  • Mechanical pins for the workspace request contract: /v1/workspace/*
    paths, flat from-template body (no overrides wrapper), no metadata
    parameter, list params typed as list[str] (a bare string is
    rejected), fail-closed on a missing runtime key.
  • e2e (tests/e2e/test_cloud_smoke.py, gated on XAGENT_WORKSPACE_KEY):
    template list → create-from-template → run via AgentClient, plus
    unknown-template, bad-key, and no-runtime-key paths.

_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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread python/src/xagent_sdk/cloud/_templates.py Outdated
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.
@AlexLiu190625 AlexLiu190625 requested a review from rogercloud June 1, 2026 04:10

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head and found three issues that should be addressed before release.

Comment thread python/pyproject.toml
[project]
name = "xagent-sdk"
version = "0.2.0"
version = "0.3.0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread python/src/xagent_sdk/_base.py Outdated
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread python/README.md Outdated
)
runtime_key = created.runtime_full_key # one-time secret

with AgentClient(api_key=runtime_key) as agent: # run on the same surface

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@AlexLiu190625 AlexLiu190625 requested a review from rogercloud June 1, 2026 07:56

@rogercloud rogercloud left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after re-reviewing the updated head. The previous comments are addressed and the latest checks are green.

@rogercloud rogercloud merged commit 157c001 into xorbitsai:main Jun 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants