fix(sandbox): update Tenki SDKs and drop removed project_id (#18117)

### 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.
This commit is contained in:
Carl Calaquian
2026-08-11 20:37:21 +08:00
committed by GitHub
parent ec63ab37b1
commit ad6fdfd7b4
5 changed files with 12 additions and 29 deletions

View File

@@ -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

View File

@@ -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).

2
go.mod
View File

@@ -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

4
go.sum
View File

@@ -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=

View File

@@ -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)