From ad6fdfd7b4c72d3d9df7ae8c3da1e0e60dfb4b0b Mon Sep 17 00:00:00 2001 From: Carl Calaquian <36902555+camcalaquian@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:37:21 +0800 Subject: [PATCH] fix(sandbox): update Tenki SDKs and drop removed project_id (#18117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Summary Brings both halves of the Tenki sandbox provider onto current SDKs and removes `project_id`, which Tenki deleted from its API. **Go:** `github.com/LuxorLabs/tenki-sdk-go/sandbox` `v0.5.2` → `v0.7.0` (current latest). **Python:** the provider's SDK was renamed on PyPI — `tenki-sandbox` is frozen at 0.4.0 and everything from 0.5 ships as [`tenki`](https://pypi.org/project/tenki/). The docs told operators to `pip install tenki-sandbox`, which installs a stale SDK that no longer matches this provider's expectations. **`project_id` is gone.** Tenki removed project scoping from the sandbox API in 0.5.x: `Client.create()` no longer accepts `project_id`, so the current code path would raise `TypeError` against a current SDK. It was also marked `required: True` in the config schema, so the Admin > Sandbox Settings form asked for a value that no longer exists. --- agent/sandbox/providers/tenki.py | 18 ++---------------- .../agent_quickstarts/sandbox_quickstart.md | 5 ++--- go.mod | 2 +- go.sum | 4 ++-- .../agent/sandbox/test_tenki_provider.py | 12 +++++------- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/agent/sandbox/providers/tenki.py b/agent/sandbox/providers/tenki.py index 7f3b7d2d51..d33b5c4c79 100644 --- a/agent/sandbox/providers/tenki.py +++ b/agent/sandbox/providers/tenki.py @@ -71,7 +71,6 @@ class TenkiProvider(SandboxProvider): def __init__(self): self.api_key = "" - self.project_id = "" self.base_url = "" self.image = "" self.allow_outbound = False @@ -89,7 +88,6 @@ class TenkiProvider(SandboxProvider): def initialize(self, config: Dict[str, Any]) -> bool: self.api_key = str(config.get("api_key", "") or "").strip() - self.project_id = str(config.get("project_id", "") or "").strip() self.base_url = str(config.get("base_url", "") or "").strip() self.image = str(config.get("image", "") or "").strip() self.allow_outbound = bool(config.get("allow_outbound", False)) @@ -105,7 +103,6 @@ class TenkiProvider(SandboxProvider): is_valid, error_message = self.validate_config( { "api_key": self.api_key, - "project_id": self.project_id, "timeout": self.timeout, "max_lifetime": self.max_lifetime, "max_output_bytes": self.max_output_bytes, @@ -131,7 +128,6 @@ class TenkiProvider(SandboxProvider): errors = self._tenki_errors() create_kwargs: dict[str, Any] = { - "project_id": self.project_id, "allow_outbound": self.allow_outbound, "max_duration": self.max_lifetime, "metadata": {"source": "ragflow"}, @@ -285,13 +281,6 @@ class TenkiProvider(SandboxProvider): "placeholder": "tk_...", "description": "Tenki API key. Create one at https://app.tenki.cloud under API Keys.", }, - "project_id": { - "type": "string", - "required": True, - "label": "Project ID", - "placeholder": "Tenki project UUID", - "description": "Tenki project that sandboxes are created under.", - }, "base_url": { "type": "string", "required": False, @@ -388,12 +377,9 @@ class TenkiProvider(SandboxProvider): def validate_config(self, config: Dict[str, Any]) -> tuple[bool, Optional[str]]: api_key = str(config.get("api_key", "") or "").strip() - project_id = str(config.get("project_id", "") or "").strip() if not api_key: return False, "Tenki API key is required" - if not project_id: - return False, "Tenki project_id is required" for key in ("timeout", "max_lifetime", "max_output_bytes", "max_artifacts", "max_artifact_bytes"): try: @@ -528,8 +514,8 @@ def _get_tenki_module(): try: import tenki_sandbox except ImportError as exc: - # tenki-sandbox is an optional dependency: it requires protobuf>=6.31, + # tenki is an optional dependency: it requires protobuf>=6.31, # which conflicts with RAGFlow's pinned gRPC stack, so it is not a core # dependency. Install it into the runtime to enable this provider. - raise SandboxProviderConfigError("tenki-sandbox is required for the Tenki sandbox provider. Install it with `pip install tenki-sandbox` (or `uv pip install tenki-sandbox`).") from exc + raise SandboxProviderConfigError("tenki is required for the Tenki sandbox provider. Install it with `pip install tenki` (or `uv pip install tenki`).") from exc return tenki_sandbox diff --git a/docs/guides/agent/agent_quickstarts/sandbox_quickstart.md b/docs/guides/agent/agent_quickstarts/sandbox_quickstart.md index 96bf8ba8a4..d286cd0b61 100644 --- a/docs/guides/agent/agent_quickstarts/sandbox_quickstart.md +++ b/docs/guides/agent/agent_quickstarts/sandbox_quickstart.md @@ -45,16 +45,15 @@ Admin > Sandbox Settings after the services are up. `tenki` runs each code execution in a fresh Tenki microVM and destroys it afterwards. It is cloud-hosted, so it needs no local sandbox services, gVisor, or Docker base images — only outbound network access and an API key. -The `tenki-sandbox` SDK is an optional dependency (it requires `protobuf>=6.31`, which differs from RAGFlow's default gRPC stack), so it is not installed by default. Install it into the RAGFlow runtime before selecting this provider: +The `tenki` SDK is an optional dependency (it requires `protobuf>=6.31`, which differs from RAGFlow's default gRPC stack), so it is not installed by default. Install it into the RAGFlow runtime before selecting this provider: ```bash -pip install tenki-sandbox +pip install tenki ``` Configure it in **Admin > Sandbox Settings**: - `api_key` (required): Tenki API key. Create one at [app.tenki.cloud](https://app.tenki.cloud) under **API Keys**. -- `project_id` (required): the Tenki project that sandboxes are created under. - `base_url` (optional): override the Tenki API endpoint. - `image` (optional): sandbox base image. Leave empty to use the Tenki default image, which includes `python3` and `node`. - `allow_outbound` (optional, security-relevant): whether the sandbox may make outbound network connections. Defaults to `false` so sandboxed code has no network access; set it to `true` when code needs the network (for example, to install packages). diff --git a/go.mod b/go.mod index 6bdaaf2206..4836665ba1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.4 require ( cloud.google.com/go/storage v1.63.0 github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2 + github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0 github.com/alibabacloud-go/agentrun-20250910/v5 v5.8.4 github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.1 github.com/alicebob/miniredis/v2 v2.38.0 diff --git a/go.sum b/go.sum index 10f65eb35c..15646aea4d 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk= -github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2 h1:HN2UwfNhHYCT7qmJJ5KKf+erRoH5RTfGb7N8dEbE9FQ= -github.com/LuxorLabs/tenki-sdk-go/sandbox v0.5.2/go.mod h1:rBypesed6hSrj+OgpJwyMVBF76PaQ1iOqc2RGUL4RmI= +github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0 h1:PBQ+ad8fZiLH/1ij1hoynR9aAPVmI3EzT7DiLNmUdBM= +github.com/LuxorLabs/tenki-sdk-go/sandbox v0.7.0/go.mod h1:rBypesed6hSrj+OgpJwyMVBF76PaQ1iOqc2RGUL4RmI= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= github.com/alibabacloud-go/agentrun-20250910/v5 v5.8.4 h1:hiAsm9pz6aICOPLI1FC54vga10xwd/XxfNj06ow5jVM= diff --git a/test/unit_test/agent/sandbox/test_tenki_provider.py b/test/unit_test/agent/sandbox/test_tenki_provider.py index 0b3e00c566..8801609c8d 100644 --- a/test/unit_test/agent/sandbox/test_tenki_provider.py +++ b/test/unit_test/agent/sandbox/test_tenki_provider.py @@ -125,7 +125,6 @@ class _FakeClient: def _build_provider(sandbox: _FakeSandbox, monkeypatch) -> tuple[TenkiProvider, _FakeClient]: provider = TenkiProvider() provider.api_key = "tk_test" - provider.project_id = "proj-1" provider.timeout = 30 provider.max_output_bytes = 1024 * 1024 provider.max_artifacts = 20 @@ -227,7 +226,7 @@ def test_tenki_provider_create_passes_project_and_outbound(monkeypatch): provider.create_instance("python") - assert client.create_kwargs["project_id"] == "proj-1" + assert "project_id" not in client.create_kwargs assert client.create_kwargs["allow_outbound"] is True assert client.create_kwargs["image"] == "my-image" assert client.create_kwargs["cpu_cores"] == 4 @@ -237,12 +236,12 @@ def test_tenki_provider_config_schema_and_validation(): schema = TenkiProvider.get_config_schema() assert schema["api_key"]["required"] is True assert schema["api_key"]["secret"] is True - assert schema["project_id"]["required"] is True + assert "project_id" not in schema provider = TenkiProvider() - ok, _ = provider.validate_config({"api_key": "tk", "project_id": "p", "timeout": 30, "max_lifetime": 3600, "max_output_bytes": 1024, "max_artifacts": 5, "max_artifact_bytes": 1024}) + ok, _ = provider.validate_config({"api_key": "tk", "timeout": 30, "max_lifetime": 3600, "max_output_bytes": 1024, "max_artifacts": 5, "max_artifact_bytes": 1024}) assert ok is True - bad, msg = provider.validate_config({"api_key": "", "project_id": "p"}) + bad, msg = provider.validate_config({"api_key": ""}) assert bad is False assert "API key" in msg @@ -262,7 +261,7 @@ def test_tenki_provider_initialize_maps_auth_error(monkeypatch): monkeypatch.setattr(provider, "_create_client", lambda: _UnauthorizedClient()) with pytest.raises(SandboxProviderConfigError, match="authentication failed"): - provider.initialize({"api_key": "tk_bad", "project_id": "proj-1"}) + provider.initialize({"api_key": "tk_bad"}) def test_tenki_provider_instances_are_independent(monkeypatch): @@ -277,7 +276,6 @@ def test_tenki_provider_instances_are_independent(monkeypatch): provider = TenkiProvider() provider.api_key = "tk_test" - provider.project_id = "proj-1" provider.timeout = 30 provider._initialized = True provider._client = _MultiClient(None)