feat(agent_runtime): align SDK model with agentrun-20250910 & support workspace_name#102
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the agentrun.agent_runtime SDK to align with the official agentrun-20250910 model/schema while adding a convenience feature that lets callers specify workspace_name (resolved internally to workspace_id) when creating and listing Agent Runtimes.
Changes:
- Added workspace name → ID resolution helper (
_workspace.py) with in-memory caching and integrated it intoAgentRuntimeClientcreate/list flows. - Aligned Agent Runtime models/inputs with
agentrun-20250910(new config sub-models, new fields,tagsremoval in favor ofsystem_tags, routing weight type change). - Updated unit tests to cover new models and workspace resolution behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
agentrun/agent_runtime/_workspace.py |
New resolver module to map workspace_name/workspace_names to IDs with caching and Tea-exception mapping. |
agentrun/agent_runtime/client.py |
Integrates workspace resolution and adds mutual-exclusion validation for workspace fields in create/list. |
agentrun/agent_runtime/model.py |
Adds/updates models and fields to match agentrun-20250910 (registry/protocol/NAS/OSS/scaling, tags→system_tags, weight float, workspace_name field). |
agentrun/agent_runtime/runtime.py |
Updates high-level AgentRuntime.list_all* filter parameters to match new model fields (system_tags, status, workspace filters). |
agentrun/agent_runtime/__client_async_template.py |
Updates codegen template to include workspace resolution logic for async client methods. |
agentrun/agent_runtime/__runtime_async_template.py |
Updates codegen template for list_all_async signature/args to match new filters. |
tests/unittests/agent_runtime/test_workspace.py |
New unit tests covering resolver behavior plus client create/list integration paths. |
tests/unittests/agent_runtime/test_model.py |
Adds coverage for new/updated models and serialization aliases. |
tests/unittests/agent_runtime/test_runtime.py |
Updates list_all filter test to use system_tags and new filters. |
Comments suppressed due to low confidence (4)
agentrun/agent_runtime/client.py:163
- Same issue as async create: workspace_name resolution ignores
self.configwhenconfigarg is None (can resolve with wrong credentials/region), and the truthy check skips empty-string validation/clearing. Merge configs before resolving and treat empty strings as invalid (or normalize/strip then validate).
if input.workspace_id and input.workspace_name:
raise ValueError(
"workspace_id and workspace_name are mutually exclusive; please"
" only set one of them."
)
if input.workspace_name:
input.workspace_id = resolve_workspace_id_by_name(
input.workspace_name, config
)
input.workspace_name = None
agentrun/agent_runtime/client.py:405
- list_async workspace_name(s) resolution ignores
self.configwhenconfigarg is None, which can resolve workspace IDs using the wrong account/region compared to the subsequent API call (which uses the client’s configured control_api). Also,if input.workspace_name:/if input.workspace_names:skips empty-string validation/normalization. Merge configs before resolving and validate non-empty strings.
if input.workspace_id and input.workspace_name:
raise ValueError(
"workspace_id and workspace_name are mutually exclusive;"
" please only set one of them."
)
if input.workspace_ids and input.workspace_names:
raise ValueError(
"workspace_ids and workspace_names are mutually exclusive;"
" please only set one of them."
)
if input.workspace_name:
input.workspace_id = await resolve_workspace_id_by_name_async(
input.workspace_name, config
)
input.workspace_name = None
if input.workspace_names:
input.workspace_ids = (
await resolve_workspace_ids_by_names_async(
input.workspace_names, config
)
)
input.workspace_names = None
agentrun/agent_runtime/client.py:463
- Same issue as list_async: workspace_name(s) resolution should use the effective config (merge
self.configwith the overrideconfig) so lookup runs under the same credentials/region as the API call, and it should not silently accept empty strings due to truthy checks.
if input.workspace_id and input.workspace_name:
raise ValueError(
"workspace_id and workspace_name are mutually exclusive;"
" please only set one of them."
)
if input.workspace_ids and input.workspace_names:
raise ValueError(
"workspace_ids and workspace_names are mutually exclusive;"
" please only set one of them."
)
if input.workspace_name:
input.workspace_id = resolve_workspace_id_by_name(
input.workspace_name, config
)
input.workspace_name = None
if input.workspace_names:
input.workspace_ids = resolve_workspace_ids_by_names(
input.workspace_names, config
)
input.workspace_names = None
agentrun/agent_runtime/__client_async_template.py:256
- Template: list_async workspace_name(s) resolution ignores
self.configwhenconfigis None, and truthy checks skip empty-string validation. Merge configs before resolving and validate/normalize non-empty workspace_name(s) so the resolver runs under the same account/region as the API call.
try:
if input is None:
input = AgentRuntimeListInput()
if input.workspace_id and input.workspace_name:
raise ValueError(
"workspace_id and workspace_name are mutually exclusive;"
" please only set one of them."
)
if input.workspace_ids and input.workspace_names:
raise ValueError(
"workspace_ids and workspace_names are mutually exclusive;"
" please only set one of them."
)
if input.workspace_name:
input.workspace_id = await resolve_workspace_id_by_name_async(
input.workspace_name, config
)
input.workspace_name = None
if input.workspace_names:
input.workspace_ids = (
await resolve_workspace_ids_by_names_async(
input.workspace_names, config
)
)
input.workspace_names = None
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if input.workspace_name: | ||
| input.workspace_id = await resolve_workspace_id_by_name_async( | ||
| input.workspace_name, config |
| if input.workspace_name: | ||
| input.workspace_id = await resolve_workspace_id_by_name_async( | ||
| input.workspace_name, config |
补齐 AgentRuntime / Endpoint / List 入参与官方 SDK 的字段差异,并把 ``tags`` 字段下线(已被原生 ``system_tags`` 覆盖,与 super_agent 模块统一)。 字段变更: - AgentRuntimeMutableProps: + disk_size, enable_session_isolation, nas_config, oss_mount_config; - tags - AgentRuntimeListInput: + status, workspace_ids; - tags - AgentRuntimeEndpointMutableProps: + disable_public_network_access, scaling_config; - tags - AgentRuntimeEndpointUpdateInput: + delete_scaling_config - AgentRuntimeContainer: + acr_instance_id, image_registry_type, port, registry_config - AgentRuntimeProtocolConfig: + protocol_settings - AgentRuntimeEndpointRoutingWeight.weight: int -> float 新增辅助模型(一比一对齐官方): - NASConfig / NASMountConfig - OSSMountConfig / OSSMountPoint - ScalingConfig / ScheduledPolicy - RegistryConfig / RegistryAuthConfig / RegistryCertConfig / RegistryNetworkConfig - ProtocolSettings list_all / list_all_async 形参同步更新:删 tags,加 system_tags / status / workspace_id / workspace_ids;runtime.py 通过 make codegen 重新生成。 测试:agent_runtime model.py 100% 行/分支覆盖;3444 全量单测通过; mypy --config-file mypy.ini agentrun/agent_runtime/ 无 issue。 Signed-off-by: Sodawyx <sodawyx@126.com>
…lve to workspace_id) 让用户在创建 / 查询 Agent Runtime 时可以直接填 workspace 名称, SDK 自动调用官方 ListWorkspaces 解析为 workspace_id 再下发, 无需用户手动查 ID。 模型变更(agentrun/agent_runtime/model.py): - AgentRuntimeImmutableProps: 新增 workspace_name(流入 CreateInput) - AgentRuntimeListInput: 新增 workspace_name / workspace_names 新增 agentrun/agent_runtime/_workspace.py: - resolve_workspace_id_by_name(_async) 精确名字匹配 + (ak, region, name) 缓存 - resolve_workspace_ids_by_names(_async) 批量名字 -> 逗号分隔 ID - 找不到抛 ResourceNotExistError,重名抛 ValueError, Tea ClientException/ServerException 转 SDK 内置 ClientError/ServerError client / runtime(async 模板 + codegen 同步生成): - AgentRuntimeClient.create / list:调底层 API 前自动解析 workspace_name(s) - 同时传 workspace_id+workspace_name(或复数版本)抛 ValueError - AgentRuntime.list_all:透传 workspace_name / workspace_names 示例:examples/quickstart_runtime.py - 演示通过镜像部署 AgentRuntime,并使用 workspace_name 选择工作空间 测试(tests/unittests/agent_runtime/test_workspace.py,新增 25 用例): - 精确匹配 / 缓存 / 空名 / 找不到 / 重名 / Tea 异常透传 - client.create 与 client.list 在 sync + async 路径下的解析与互斥校验 校验: - 全量 3469 单测通过 - agentrun.agent_runtime 总覆盖率 99%,_workspace.py 95% - mypy --config-file mypy.ini agentrun/agent_runtime/ 无 issue Signed-off-by: Sodawyx <sodawyx@126.com>
9170897 to
6cc91e1
Compare
PR #102 Copilot review surfaced two issues in the new workspace_name resolution path: 1. workspace_name resolution only used the method-level `config`, ignoring `self.config`. When a caller built `AgentRuntimeClient(config=...)` and then called `create(input)` without a method-level config, the resolver fell back to env credentials/region while the subsequent OpenAPI call used `self.config` — possibly resolving a workspace under the wrong account/region. Now passes `Config.with_configs(self.config, config)` to the resolver, matching the merge done in `ControlAPI._get_client`. 2. `if input.workspace_name:` silently skipped resolution for empty strings instead of surfacing the resolver's existing `ValueError("workspace_name must be non-empty")`. Switched the singular checks to `is not None` so empty strings now raise. Applied to create / create_async / list / list_async in client.py and mirrored into the codegen template. Plural `workspace_names` keeps the truthy check since empty-string plural is a benign "no filter". Added 8 unit tests covering effective-config propagation (client.config used when method config=None; method config overrides client.config) and empty-string ValueError across all four entry points. agent_runtime suite 262 passed / 2 skipped; `_workspace.py` 95.24% line/branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Sodawyx <sodawyx@126.com>
OhYee
left a comment
There was a problem hiding this comment.
Review: PR #102 — SDK Model Alignment & workspace_name Support
整体质量不错,模型对齐清晰,测试覆盖充分(99%),CI 全绿。以下几点建议:
1. 🔴 Workspace 分页硬编码 page_size=50
_workspace.py 中 ListWorkspacesRequest(name=name, page_size="50") — 如果用户有超过 50 个 workspace,resolver 可能找不到目标 workspace(API 返回的是 name 模糊匹配还是精确匹配?如果是模糊匹配,50 条可能不够)。建议至少做翻页兜底,或明确文档说明限制。
2. 🟡 resolve_workspace_id_by_name 不继承 client 的 config
Copilot 已经指出了这个问题。client.py:103 和 __client_async_template.py:93 中 workspace 解析只用了方法级 config,没有合并 self.config。如果调用者不传 config 但 client 构造时传了,resolver 会用默认凭证,可能解析到错误的 workspace。
3. 🟡 create() 中 mutate 了 input 对象
input.workspace_name = None # client.py调用者传入的 input 对象被修改了,这可能出乎意料。建议做浅拷贝或只在内部变量上操作。
4. 🟡 Module-level cache 无 TTL / 大小限制
_RESOLVE_CACHE 是全局 dict,无过期无上限。长期运行的服务中如果 workspace 被重命名或删除,缓存会返回过时的 ID。建议考虑使用 functools.lru_cache 或加 TTL。
5. ℹ️ tags → system_tags 是 breaking change
PR description 已说明,确认这是有意为之。建议在 changelog 中明确标注 BREAKING,方便用户升级。
6. ℹ️ resolve_workspace_ids_by_names_async 串行解析
多个 workspace name 用 for 循环逐个 await,可以用 asyncio.gather 并行。非阻塞建议。
总体:模型对齐部分扎实,workspace_name 是好的 DX 改进。建议至少处理 #1 (分页) 和 #3 (input mutation),其余可 follow-up。
Address two issues raised in PR #102 review by Ohyee: 1. Workspace lookup was hardcoded to a single page (page_size=50). If the upstream `name=` filter is server-side prefix/fuzzy match, a busy account can easily exceed 50 same-prefix workspaces and the resolver would silently miss the target. `_lookup_sync` / `_lookup_async` now paginate, accumulating candidates across pages and short-circuiting when a page returns fewer than `_PAGE_SIZE` rows; a `_MAX_PAGES=20` safety cap (1000 candidates) prevents runaway loops on misbehaving upstreams. 2. The workspace_name resolution path mutated the caller's input object (`input.workspace_id = ...; input.workspace_name = None`), which is surprising for a side-effect-free convenience field. Replaced with `input = input.model_copy(update={...})` so the caller's instance is untouched. Applied to create / create_async / list / list_async in client.py and mirrored into the codegen template. Tests: - 4 new pagination cases (multi-page accumulation, short-page early stop, MAX_PAGES cap, async multi-page). - 4 existing mutation assertions flipped to "caller's input stays unchanged; OpenAPI request carries the resolved id". agent_runtime suite 266 passed / 2 skipped; `_workspace.py` 95.62% line/branch. Full repo 3481 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Sodawyx <sodawyx@126.com>
Summary
AgentRuntime/Endpoint/List入参字段差异,下线已被system_tags覆盖的tags字段;新增NASConfig/OSSMountConfig/ScalingConfig/RegistryConfig/ProtocolSettings等辅助模型一比一对齐。ListWorkspaces解析为workspace_id(带(ak, region, name)缓存),无需手查 ID;同时传workspace_id+workspace_name时抛ValueError,找不到抛ResourceNotExistError,重名抛ValueError。examples/quickstart_runtime.py演示通过镜像部署 AgentRuntime 并以workspace_name选择工作空间。主要字段变更
AgentRuntimeMutableProps: +disk_size/enable_session_isolation/nas_config/oss_mount_config; −tagsAgentRuntimeListInput: +status/workspace_ids/workspace_name/workspace_names; −tagsAgentRuntimeEndpointMutableProps: +disable_public_network_access/scaling_config; −tagsAgentRuntimeEndpointUpdateInput: +delete_scaling_configAgentRuntimeContainer: +acr_instance_id/image_registry_type/port/registry_configAgentRuntimeProtocolConfig: +protocol_settingsAgentRuntimeEndpointRoutingWeight.weight:int→floatAgentRuntimeImmutableProps: +workspace_name(流入CreateInput)Test plan
agentrun.agent_runtime总覆盖率 99%,_workspace.py95%,model.py100% 行/分支覆盖mypy --config-file mypy.ini agentrun/agent_runtime/无 issue