Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ac00d45
Initial plan
Copilot Apr 20, 2026
7a82fe8
fix: make delete_sandbox idempotent when data plane returns sandbox n…
Copilot Apr 20, 2026
1c4a05a
Initial plan
Copilot Apr 20, 2026
8582d6d
Fix DataAPI config propagation to with_path and Sandbox.__get_client(…
Copilot Apr 20, 2026
562d231
fix: 扩展 config 透传修复至所有资源模块与 endpoint 调用点
OhYee Apr 25, 2026
e6d5dc9
style(sandbox): 应用 pyink 行宽规则
OhYee Apr 25, 2026
1cea6c9
fix(test): prevent infinite loop in E2E conftest auto_load_env
congxiao-wxx Apr 29, 2026
ee439fd
feat: support workspace_id in create/list inputs across resource modules
congxiao-wxx Apr 29, 2026
1f291b5
fix: standardize sandbox data api errors
TJKkking Apr 30, 2026
887c087
test(unittests): Add an automatic fixture for test environment variab…
TJKkking Apr 30, 2026
24e74fc
fix: use ClientError for non-JSON 2xx; document HTTPError.__str__ for…
Copilot Apr 30, 2026
ddf87b2
Merge pull request #94 from Serverless-Devs/claude/adoring-sammet-04753f
OhYee Apr 30, 2026
e0688e7
Merge pull request #95 from Serverless-Devs/dev-sb-responce
OhYee May 6, 2026
f35715f
Fix MySQL embedder dimension regression in memory_collection._build_m…
Copilot May 6, 2026
eec1443
fix: raise ResourceNotExistError for data-plane not-found, add bounda…
Copilot May 6, 2026
a31ba3b
style: unify __get_client() calls to keyword form across all resource…
Copilot May 6, 2026
eee4e8b
Merge pull request #89 from Serverless-Devs/copilot/fix-terminated-sa…
OhYee May 6, 2026
5523af3
Merge pull request #88 from Serverless-Devs/copilot/fix-sandbox-delet…
OhYee May 7, 2026
7ec9682
ci: skip-existing on PyPI publish to unblock concurrent PR pushes
OhYee May 7, 2026
1afdbf9
Merge pull request #96 from Serverless-Devs/fix/ci-skip-existing-publish
OhYee May 7, 2026
333f548
Update version to 0.0.35
actions-user May 7, 2026
cfe5829
ci: raise coverage thresholds from 0% to meaningful levels
congxiao-wxx May 9, 2026
1435314
Merge pull request #97 from Serverless-Devs/feat/strict-ci-gates
OhYee May 12, 2026
5981780
ci: add e2e workflow with OIDC keyless auth for Alibaba Cloud
congxiao-wxx May 12, 2026
c206f21
ci: use placeholder for API_KEY and WORKSPACE_ID in e2e workflow
congxiao-wxx May 12, 2026
83cc8f9
ci: fix e2e workflow env vars and exclude integration tests
congxiao-wxx May 13, 2026
36a15e7
ci: exclude tests requiring API_KEY from e2e workflow
congxiao-wxx May 13, 2026
da4c775
ci: exclude flaky process_get tests from e2e workflow
congxiao-wxx May 13, 2026
885d314
ci: exclude flaky browser sandbox tests from e2e workflow
congxiao-wxx May 13, 2026
4e36a23
fix: sanitize non-identifier field names in MCP/OpenAPI tool schemas
Sodawyx May 13, 2026
18f9357
ci: exclude flaky code interpreter filesystem tests from e2e workflow
congxiao-wxx May 13, 2026
185c367
ci: exclude flaky sandbox delete/lifecycle/connect tests from e2e wor…
congxiao-wxx May 13, 2026
e97f1ba
fix: address Copilot review on sanitize-non-identifier PR
Sodawyx May 13, 2026
f7111f0
ci: exclude flaky connect_sandbox_async and delete_nonexistent_sandbo…
congxiao-wxx May 13, 2026
aa2b77f
Merge pull request #99 from Serverless-Devs/fix/sanitize-non-identifi…
OhYee May 13, 2026
2c0ac13
Merge pull request #98 from Serverless-Devs/feat/e2e-oidc-ci
Sodawyx May 13, 2026
dd328f5
Delete docs directory
OhYee May 13, 2026
942e8a3
feat(agent_runtime): align SDK model with official agentrun-20250910
Sodawyx May 14, 2026
9170897
feat(agent_runtime): support workspace_name in create/list (auto-reso…
Sodawyx May 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ jobs:
with:
password: ${{ secrets.PYPI_API_TOKEN }}
verify-metadata: false
# 当 PR 多次 push 或并发 CI 跑出相同版本号时,跳过已存在版本而非报错。
# 根因是版本号自增逻辑依赖 PyPI API 拉取最新版本,存在缓存/并发窗口。
# 不阻塞 PR 合入即可,长期方案是在版本号中嵌入 commit SHA 实现唯一化。
skip-existing: true

- name: Create and push tag
if: steps.changes.outputs.agentrun_changed == 'true'
Expand Down
86 changes: 86 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
name: E2E Tests

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

# OIDC token generation requires id-token:write permission
permissions:
id-token: write
contents: read

# Prevent parallel e2e runs from conflicting on shared test resources
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
e2e:
runs-on: ubuntu-latest
# Skip e2e for PRs from forks (they cannot access OIDC secrets)
if: >-
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
strategy:
matrix:
python-version: ['3.10']

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
make setup PYTHON_VERSION=${{ matrix.python-version }}

# Obtain temporary Alibaba Cloud credentials via OIDC (keyless)
# This action exchanges the GitHub OIDC token for temporary AK/SK/SecurityToken
# and exports them as ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET,
# ALIBABA_CLOUD_SECURITY_TOKEN environment variables.
- name: Configure Alibaba Cloud credentials (OIDC)
uses: aliyun/configure-aliyun-credentials-action@v1
with:
role-to-assume: ${{ secrets.ALIBABA_CLOUD_OIDC_ROLE_ARN }}
oidc-provider-arn: ${{ secrets.ALIBABA_CLOUD_OIDC_PROVIDER_ARN }}
role-session-name: agentrun-e2e-${{ github.run_id }}
role-session-expiration: 3600

- name: Run E2E tests
env:
# Credentials are auto-injected by configure-aliyun-credentials-action:
# ALIBABA_CLOUD_ACCESS_KEY_ID
# ALIBABA_CLOUD_ACCESS_KEY_SECRET
# ALIBABA_CLOUD_SECURITY_TOKEN
AGENTRUN_ACCOUNT_ID: ${{ secrets.AGENTRUN_ACCOUNT_ID }}
AGENTRUN_REGION: ${{ secrets.AGENTRUN_REGION }}
AGENTRUN_CONTROL_ENDPOINT: ${{ secrets.AGENTRUN_CONTROL_ENDPOINT }}
AGENTRUN_DATA_ENDPOINT: ${{ secrets.AGENTRUN_DATA_ENDPOINT }}
# API_KEY and AGENTRUN_TEST_WORKSPACE_ID are intentionally NOT set.
# Tests requiring them are excluded via --ignore and -k filters below.
run: |
uv run pytest tests/e2e/ -v --tb=short \
--ignore=tests/e2e/integration \
--ignore=tests/e2e/test_agent_ruintime.py \
--ignore=tests/e2e/test_workspace_id.py \
--ignore=tests/e2e/test_sandbox_browser.py \
--ignore=tests/e2e/test_sandbox_code_interpreter.py \
-k "not (invoke or with_credential or model_proxy or process_get or delete_sandbox or delete_nonexistent_sandbox or connect_nonexistent or connect_sandbox_async or sandbox_lifecycle or connect_with_wrong_template or template_validation_code_interpreter_network)"

- name: E2E Summary
if: always()
run: |
echo "## E2E Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
echo "- **Auth:** OIDC keyless (temporary credentials)" >> $GITHUB_STEP_SUMMARY
57 changes: 57 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Release Notes

## Unreleased

### Breaking Changes

- Sandbox data-plane HTTP JSON APIs now follow standard HTTP error handling:
- `2xx` responses return the business response body.
- `4xx` responses raise `ClientError`.
- `5xx` responses raise `ServerError`.
- Error responses such as `{"code": "...", "requestId": "...", "message": "..."}`
are no longer returned as normal dictionaries for Sandbox HTTP JSON APIs. The
fields are exposed on the raised exception as `error_code`, `request_id`, and
`message`.
- Existing code that checked returned dictionaries for `code` and `requestId`
must migrate to `try` / `except ClientError` / `except ServerError`.
- `HTTPError.__str__()` output format has changed. The old format unconditionally
included `"Request ID: None. Details: {}"` even when those fields were empty.
The new format only includes non-empty fields and uses `". "` as separator.
Code that parses this string representation (e.g. log parsers or test assertions
on `str(error)`) must be updated.

### Migration

Before:

```python
resp = ci.cmd(command="echo hello", cwd="/tmp", timeout=30)
if "code" in resp and "requestId" in resp:
raise RuntimeError(resp["message"])
```

After:

```python
from agentrun.utils.exception import ClientError, ServerError

try:
resp = ci.cmd(command="echo hello", cwd="/tmp", timeout=30)
except ClientError as e:
print(e.status_code, e.error_code, e.request_id, e.message)
raise
except ServerError as e:
print(e.status_code, e.error_code, e.request_id, e.message)
raise
```

Command execution failures are still business-level failures and should be
handled by checking `resp["result"]["exitCode"]` after a successful HTTP
response.

### Scope

This change is intentionally limited to Sandbox data-plane HTTP JSON APIs. It
does not change WebSocket/CDP/VNC URL generation, Playwright connections, file
upload/download helpers, video download helpers, or non-Sandbox data-plane
clients.
2 changes: 1 addition & 1 deletion agentrun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import os
from typing import TYPE_CHECKING

__version__ = "0.0.34"
__version__ = "0.0.35"


# Agent Runtime
Expand Down
54 changes: 52 additions & 2 deletions agentrun/agent_runtime/__client_async_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
)
from typing_extensions import Unpack

from agentrun.agent_runtime._workspace import (
resolve_workspace_id_by_name,
resolve_workspace_id_by_name_async,
resolve_workspace_ids_by_names,
resolve_workspace_ids_by_names_async,
)
from agentrun.agent_runtime.api.data import InvokeArgs
from agentrun.agent_runtime.model import (
AgentRuntimeArtifact,
Expand Down Expand Up @@ -68,11 +74,26 @@ async def create_async(
AgentRuntime: 创建的 Agent Runtime 对象 / Created Agent Runtime object

Raises:
ValueError: 当既未提供代码配置也未提供容器配置时 / When neither code nor container configuration is provided
ValueError: 当既未提供代码配置也未提供容器配置时;或同时传入
workspace_id 与 workspace_name / When neither code nor container
configuration is provided, or when workspace_id and workspace_name
are both set
ResourceAlreadyExistError: 资源已存在 / Resource already exists
ResourceNotExistError: 资源不存在 / Resource does not exist
ResourceNotExistError: 资源不存在;或 workspace_name 在该账号下未找到
/ Resource does not exist, or no workspace matches workspace_name
HTTPError: HTTP 请求错误 / HTTP request error
"""
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 = await resolve_workspace_id_by_name_async(
input.workspace_name, config
)
input.workspace_name = None

if input.network_configuration is None:
input.network_configuration = NetworkConfig()

Expand Down Expand Up @@ -198,12 +219,41 @@ async def list_async(
List[AgentRuntime]: Agent Runtime 对象列表 / List of Agent Runtime objects

Raises:
ValueError: 同时传入 workspace_id 与 workspace_name,或同时传入
workspace_ids 与 workspace_names / When workspace_id and
workspace_name (or workspace_ids and workspace_names) are
both set
ResourceNotExistError: workspace_name(s) 在该账号下未找到
/ No workspace matches the given workspace_name(s)
HTTPError: HTTP 请求错误 / HTTP request error
"""
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

results = await self.__control_api.list_agent_runtimes_async(
ListAgentRuntimesRequest().from_map(input.model_dump()),
config=config,
Expand Down
16 changes: 9 additions & 7 deletions agentrun/agent_runtime/__endpoint_async_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async def create_by_id_async(
ResourceNotExistError: Agent Runtime 不存在 / Agent Runtime does not exist
HTTPError: HTTP 请求错误 / HTTP request error
"""
cli = cls.__get_client()
cli = cls.__get_client(config=config)
return await cli.create_endpoint_async(
agent_runtime_id,
input,
Expand Down Expand Up @@ -95,7 +95,7 @@ async def delete_by_id_async(
ResourceNotExistError: 资源不存在 / Resource does not exist
HTTPError: HTTP 请求错误 / HTTP request error
"""
cli = cls.__get_client()
cli = cls.__get_client(config=config)
return await cli.delete_endpoint_async(
agent_runtime_id,
endpoint_id,
Expand Down Expand Up @@ -125,7 +125,7 @@ async def update_by_id_async(
ResourceNotExistError: 资源不存在 / Resource does not exist
HTTPError: HTTP 请求错误 / HTTP request error
"""
cli = cls.__get_client()
cli = cls.__get_client(config=config)
return await cli.update_endpoint_async(
agent_runtime_id,
endpoint_id,
Expand Down Expand Up @@ -154,7 +154,7 @@ async def get_by_id_async(
ResourceNotExistError: 资源不存在 / Resource does not exist
HTTPError: HTTP 请求错误 / HTTP request error
"""
cli = cls.__get_client()
cli = cls.__get_client(config=config)
return await cli.get_endpoint_async(
agent_runtime_id,
endpoint_id,
Expand Down Expand Up @@ -191,7 +191,7 @@ async def _list_page_async(
"agent_runtime_id is required for listing endpoints"
)

return await cls.__get_client().list_endpoints_async(
return await cls.__get_client(config=config).list_endpoints_async(
agent_runtime_id,
AgentRuntimeEndpointListInput(
page_number=page_input.page_number,
Expand Down Expand Up @@ -219,7 +219,7 @@ async def list_by_id_async(
Raises:
HTTPError: HTTP 请求错误 / HTTP request error
"""
cli = cls.__get_client()
cli = cls.__get_client(config=config)

endpoints: List[AgentRuntimeEndpoint] = []
page = 1
Expand Down Expand Up @@ -339,7 +339,9 @@ async def get_async(self, config: Optional[Config] = None):
)

result = await self.get_by_id_async(
self.agent_runtime_id, self.agent_runtime_endpoint_id
self.agent_runtime_id,
self.agent_runtime_endpoint_id,
config=config,
)
self.update_self(result)
return self
Expand Down
Loading
Loading