Skip to content

Commit ddf87b2

Browse files
authored
Merge pull request #94 from Serverless-Devs/claude/adoring-sammet-04753f
Claude/adoring sammet 04753f
2 parents 0624498 + ee439fd commit ddf87b2

12 files changed

Lines changed: 703 additions & 4 deletions

File tree

agentrun/agent_runtime/model.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ class AgentRuntimeMutableProps(BaseModel):
257257

258258

259259
class AgentRuntimeImmutableProps(BaseModel):
260-
pass
260+
workspace_id: Optional[str] = None
261+
"""Agent Runtime 所属的工作空间标识符;可选项,不填则使用默认工作空间
262+
/ Workspace identifier the Agent Runtime belongs to; optional, defaults to the default workspace if not provided"""
261263

262264

263265
class AgentRuntimeSystemProps(BaseModel):
@@ -329,6 +331,9 @@ class AgentRuntimeListInput(PageableInput):
329331
"""系统标签过滤, 多个标签用逗号分隔"""
330332
search_mode: Optional[str] = None
331333
"""搜索模式"""
334+
workspace_id: Optional[str] = None
335+
"""按工作空间标识符过滤
336+
/ Filter by workspace identifier"""
332337

333338

334339
class AgentRuntimeEndpointCreateInput(

agentrun/credential/model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ class CredentialMutableProps(BaseModel):
189189
class CredentialImmutableProps(BaseModel):
190190
credential_name: Optional[str] = None
191191
"""凭证名称"""
192+
workspace_id: Optional[str] = None
193+
"""凭证所属的工作空间标识符;可选项,不填则使用默认工作空间
194+
/ Workspace identifier the credential belongs to; optional, defaults to the default workspace if not provided"""
192195

193196

194197
class CredentialSystemProps(CredentialConfigInner):
@@ -221,6 +224,9 @@ class CredentialListInput(PageableInput):
221224
"""凭证来源类型(必填)"""
222225
provider: Optional[str] = None
223226
"""提供商"""
227+
workspace_id: Optional[str] = None
228+
"""按工作空间标识符过滤
229+
/ Filter by workspace identifier"""
224230

225231

226232
class CredentialListOutput(BaseModel):
@@ -232,6 +238,9 @@ class CredentialListOutput(BaseModel):
232238
enabled: Optional[bool] = None
233239
related_resource_count: Optional[int] = None
234240
updated_at: Optional[str] = None
241+
workspace_id: Optional[str] = None
242+
"""凭证所属的工作空间标识符
243+
/ Workspace identifier the credential belongs to"""
235244

236245
async def to_credential_async(self, config: Optional[Config] = None):
237246
from .client import CredentialClient

agentrun/knowledgebase/model.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,12 @@ class KnowledgeBaseImmutableProps(BaseModel):
312312
"""知识库名称 / KnowledgeBase name"""
313313
provider: Optional[Union[KnowledgeBaseProvider, str]] = None
314314
"""提供商 / Provider"""
315+
workspace_id: Optional[str] = None
316+
"""知识库所属的 AgentRun 工作空间标识符;可选项,不填则使用默认工作空间。
317+
注意:与 ``BailianProviderSettings.workspace_id`` 不同,后者指百炼侧的 workspace。
318+
/ Workspace identifier the knowledge base belongs to in AgentRun; optional,
319+
defaults to the default workspace if not provided. Distinct from
320+
``BailianProviderSettings.workspace_id`` which refers to the Bailian-side workspace."""
315321

316322

317323
class KnowledgeBaseSystemProps(BaseModel):
@@ -354,6 +360,9 @@ class KnowledgeBaseListInput(PageableInput):
354360

355361
provider: Optional[Union[KnowledgeBaseProvider, str]] = None
356362
"""提供商 / Provider"""
363+
workspace_id: Optional[str] = None
364+
"""按 AgentRun 工作空间标识符过滤
365+
/ Filter by AgentRun workspace identifier"""
357366

358367

359368
class KnowledgeBaseListOutput(BaseModel):
@@ -377,6 +386,9 @@ class KnowledgeBaseListOutput(BaseModel):
377386
"""创建时间 / Created at"""
378387
last_updated_at: Optional[str] = None
379388
"""最后更新时间 / Last updated at"""
389+
workspace_id: Optional[str] = None
390+
"""知识库所属的 AgentRun 工作空间标识符
391+
/ AgentRun workspace identifier the knowledge base belongs to"""
380392

381393
async def to_knowledge_base_async(self, config: Optional[Config] = None):
382394
"""转换为知识库对象(异步)/ Convert to KnowledgeBase object (async)

agentrun/memory_collection/model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class MemoryCollectionImmutableProps(BaseModel):
122122
"""Memory Collection 名称"""
123123
type: Optional[str] = None
124124
"""类型"""
125+
workspace_id: Optional[str] = None
126+
"""Memory Collection 所属的工作空间标识符;可选项,不填则使用默认工作空间
127+
/ Workspace identifier the memory collection belongs to; optional, defaults to the default workspace if not provided"""
125128

126129

127130
class MemoryCollectionSystemProps(BaseModel):
@@ -158,6 +161,9 @@ class MemoryCollectionListInput(PageableInput):
158161
"""状态 / Status"""
159162
type: Optional[str] = None
160163
"""类型 / Type"""
164+
workspace_id: Optional[str] = None
165+
"""按工作空间标识符过滤
166+
/ Filter by workspace identifier"""
161167

162168

163169
class MemoryCollectionListOutput(BaseModel):
@@ -169,6 +175,9 @@ class MemoryCollectionListOutput(BaseModel):
169175
type: Optional[str] = None
170176
created_at: Optional[str] = None
171177
last_updated_at: Optional[str] = None
178+
workspace_id: Optional[str] = None
179+
"""Memory Collection 所属的工作空间标识符
180+
/ Workspace identifier the memory collection belongs to"""
172181

173182
async def to_memory_collection_async(self, config: Optional[Config] = None):
174183
"""转换为完整的 MemoryCollection 对象(异步)"""

agentrun/model/model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ class CommonModelMutableProps(BaseModel):
160160

161161
class CommonModelImmutableProps(BaseModel):
162162
model_type: Optional[ModelType] = None
163+
workspace_id: Optional[str] = None
164+
"""模型资源所属的工作空间标识符;可选项,不填则使用默认工作空间
165+
/ Workspace identifier the model resource belongs to; optional, defaults to the default workspace if not provided"""
163166

164167

165168
class CommonModelSystemProps:
@@ -220,6 +223,9 @@ class ModelServiceUpdateInput(ModelServiceMutableProps):
220223
class ModelServiceListInput(PageableInput):
221224
model_type: Optional[ModelType] = None
222225
provider: Optional[str] = None
226+
workspace_id: Optional[str] = None
227+
"""按工作空间标识符过滤
228+
/ Filter by workspace identifier"""
223229

224230

225231
class ModelProxyCreateInput(ModelProxyMutableProps, ModelProxyImmutableProps):
@@ -233,3 +239,6 @@ class ModelProxyUpdateInput(ModelProxyMutableProps):
233239
class ModelProxyListInput(PageableInput):
234240
proxy_mode: Optional[str] = None
235241
status: Optional[Status] = None
242+
workspace_id: Optional[str] = None
243+
"""按工作空间标识符过滤
244+
/ Filter by workspace identifier"""

agentrun/sandbox/__template_async_template.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ class Template(BaseModel):
8080
"""MCP 状态 / MCP State"""
8181
allow_anonymous_manage: Optional[bool] = None
8282
"""是否允许匿名管理 / Whether to allow anonymous management"""
83+
workspace_id: Optional[str] = None
84+
"""Template 所属的工作空间标识符
85+
/ Workspace identifier the template belongs to"""
8386
created_at: Optional[str] = None
8487
"""创建时间 / Creation Time"""
8588
last_updated_at: Optional[str] = None

agentrun/sandbox/model.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ class TemplateInput(BaseModel):
294294
"""磁盘大小(GB) / Disk Size (GB)"""
295295
allow_anonymous_manage: Optional[bool] = None
296296
"""是否允许匿名管理 / Whether to allow anonymous management"""
297+
workspace_id: Optional[str] = None
298+
"""Template 所属的工作空间标识符;可选项,不填则使用默认工作空间
299+
/ Workspace identifier the template belongs to; optional, defaults to the default workspace if not provided"""
297300

298301
@model_validator(mode="before")
299302
@classmethod
@@ -392,3 +395,6 @@ class PageableInput(BaseModel):
392395
page_size: Optional[int] = 10
393396
"""每页大小 / Page Size"""
394397
template_type: Optional[TemplateType] = None
398+
workspace_id: Optional[str] = None
399+
"""按工作空间标识符过滤
400+
/ Filter by workspace identifier"""

agentrun/sandbox/template.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ class Template(BaseModel):
9090
"""MCP 状态 / MCP State"""
9191
allow_anonymous_manage: Optional[bool] = None
9292
"""是否允许匿名管理 / Whether to allow anonymous management"""
93+
workspace_id: Optional[str] = None
94+
"""Template 所属的工作空间标识符
95+
/ Workspace identifier the template belongs to"""
9396
created_at: Optional[str] = None
9497
"""创建时间 / Creation Time"""
9598
last_updated_at: Optional[str] = None
@@ -115,7 +118,9 @@ async def create_async(
115118
)
116119

117120
@classmethod
118-
def create(cls, input: TemplateInput, config: Optional[Config] = None):
121+
def create(
122+
cls, input: TemplateInput, config: Optional[Config] = None
123+
):
119124
return cls.__get_client(config=config).create_template(
120125
input, config=config
121126
)
@@ -167,7 +172,9 @@ async def get_by_name_async(
167172
)
168173

169174
@classmethod
170-
def get_by_name(cls, template_name: str, config: Optional[Config] = None):
175+
def get_by_name(
176+
cls, template_name: str, config: Optional[Config] = None
177+
):
171178
return cls.__get_client(config=config).get_template(
172179
template_name=template_name, config=config
173180
)
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""
2+
workspace_id 跨模块 E2E 测试 / Cross-module workspace_id E2E test
3+
4+
验证 SDK 在 create / get / list 接口上正确传递和回填 ``workspace_id``。
5+
Verifies the SDK correctly passes and back-fills ``workspace_id`` across
6+
create / get / list interfaces for resource modules.
7+
8+
环境变量 / Environment variables:
9+
- ``AGENTRUN_TEST_WORKSPACE_ID``:用于本测试的工作空间 ID。未配置则跳过整个文件。
10+
Workspace ID to use for this test; the entire file is skipped if not set.
11+
"""
12+
13+
import os
14+
15+
import pytest
16+
17+
from agentrun.credential import (
18+
Credential,
19+
CredentialClient,
20+
CredentialConfig,
21+
CredentialCreateInput,
22+
CredentialListInput,
23+
)
24+
from agentrun.sandbox import Template
25+
from agentrun.sandbox.model import PageableInput, TemplateInput, TemplateType
26+
from agentrun.utils.exception import ResourceNotExistError
27+
28+
WORKSPACE_ID = os.getenv("AGENTRUN_TEST_WORKSPACE_ID")
29+
30+
pytestmark = pytest.mark.skipif(
31+
not WORKSPACE_ID,
32+
reason=(
33+
"AGENTRUN_TEST_WORKSPACE_ID not configured; skipping workspace_id E2E"
34+
),
35+
)
36+
37+
38+
class TestWorkspaceId:
39+
"""workspace_id 跨模块 E2E 测试"""
40+
41+
@pytest.fixture
42+
def credential_name(self, unique_name: str) -> str:
43+
return f"{unique_name}-ws-cred"
44+
45+
@pytest.fixture
46+
def template_name(self, unique_name: str) -> str:
47+
return f"{unique_name}-ws-tpl"
48+
49+
async def test_credential_with_workspace_id_async(
50+
self, credential_name: str
51+
):
52+
"""凭证创建时指定 workspace_id,回读与列举均能拿到该 workspace_id"""
53+
client = CredentialClient()
54+
ws = WORKSPACE_ID # type: ignore[assignment]
55+
assert ws is not None
56+
57+
cred: Credential | None = None
58+
try:
59+
# 1. 创建带 workspace_id 的凭证
60+
cred = await Credential.create_async(
61+
CredentialCreateInput(
62+
credential_name=credential_name,
63+
description="E2E workspace_id test",
64+
credential_config=CredentialConfig.inbound_api_key(
65+
"sk-test-ws-e2e"
66+
),
67+
workspace_id=ws,
68+
)
69+
)
70+
assert cred.credential_name == credential_name
71+
assert (
72+
cred.workspace_id == ws
73+
), f"create 返回的 workspace_id 不匹配: {cred.workspace_id!r}"
74+
75+
# 2. get 接口回读 workspace_id
76+
cred_fetched = await client.get_async(
77+
credential_name=credential_name
78+
)
79+
assert (
80+
cred_fetched.workspace_id == ws
81+
), f"get 返回的 workspace_id 不匹配: {cred_fetched.workspace_id!r}"
82+
83+
# 3. list 接口按 workspace_id 过滤,本次创建的资源应在结果中
84+
list_results = await client.list_async(
85+
CredentialListInput(workspace_id=ws)
86+
)
87+
names = [item.credential_name for item in list_results]
88+
assert credential_name in names, (
89+
f"list(workspace_id={ws!r}) 未返回刚创建的凭证"
90+
f" {credential_name!r},"
91+
f"实际返回 {names!r}"
92+
)
93+
# 列表项的 workspace_id 也应该是同一个
94+
for item in list_results:
95+
if item.credential_name == credential_name:
96+
assert item.workspace_id == ws
97+
finally:
98+
if cred is not None:
99+
try:
100+
await cred.delete_async()
101+
except ResourceNotExistError:
102+
pass
103+
104+
async def test_template_with_workspace_id_async(self, template_name: str):
105+
"""Sandbox Template 创建时指定 workspace_id,回读与列举均能拿到该 workspace_id"""
106+
ws = WORKSPACE_ID # type: ignore[assignment]
107+
assert ws is not None
108+
109+
template: Template | None = None
110+
try:
111+
# 1. 创建带 workspace_id 的 Template
112+
template = await Template.create_async(
113+
TemplateInput(
114+
template_name=template_name,
115+
template_type=TemplateType.CODE_INTERPRETER,
116+
description="E2E workspace_id test",
117+
cpu=2.0,
118+
memory=4096,
119+
disk_size=512,
120+
sandbox_idle_timeout_in_seconds=600,
121+
sandbox_ttlin_seconds=600,
122+
workspace_id=ws,
123+
)
124+
)
125+
assert template.template_name == template_name
126+
assert (
127+
template.workspace_id == ws
128+
), f"create 返回的 workspace_id 不匹配: {template.workspace_id!r}"
129+
130+
# 2. get 接口回读 workspace_id
131+
template_fetched = await Template.get_by_name_async(template_name)
132+
assert (
133+
template_fetched.workspace_id == ws
134+
), f"get 返回的 workspace_id 不匹配: {template_fetched.workspace_id!r}"
135+
136+
# 3. list 接口按 workspace_id 过滤
137+
list_results = await Template.list_templates_async(
138+
PageableInput(workspace_id=ws, page_size=100)
139+
)
140+
names = [t.template_name for t in list_results or []]
141+
assert template_name in names, (
142+
f"list_templates(workspace_id={ws!r}) 未返回刚创建的"
143+
f" Template {template_name!r},实际返回 {names!r}"
144+
)
145+
finally:
146+
if template is not None:
147+
try:
148+
await Template.delete_by_name_async(template_name)
149+
except ResourceNotExistError:
150+
pass

tests/e2e/conftest.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515

1616
def auto_load_env():
1717
folder = Path(__file__).parent
18-
while folder != "/":
18+
# 一直向上查找 .env 文件,直到根目录为止
19+
# / Walk up to root looking for a .env file
20+
while True:
1921
dotfile = folder / ".env"
2022
if dotfile.exists():
2123
load_dotenv(dotfile)
2224
print("load .env:", dotfile)
2325
break
26+
if folder.parent == folder:
27+
# 已到根目录,未找到 .env,依赖外部环境变量
28+
# / Reached the filesystem root with no .env found; rely on env vars
29+
break
2430
folder = folder.parent
2531

2632

0 commit comments

Comments
 (0)