Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,4 @@ jobs:
env:
TENKI_API_KEY: ${{ secrets.TENKI_API_KEY }}
TENKI_API_ENDPOINT: ${{ secrets.TENKI_API_ENDPOINT }}
TENKI_PROJECT_ID: ${{ secrets.TENKI_PROJECT_ID }}
run: pytest tests/integration -q
3 changes: 1 addition & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ if they drift (`git diff --exit-code open-webui/`).
```bash
export TENKI_API_KEY=sk-...
# optional: export TENKI_API_ENDPOINT=https://api.tenki.cloud
# optional: export TENKI_PROJECT_ID=proj_...
pytest tests/integration # provisions real microVMs; costs quota
python scripts/try_live.py # ad-hoc single run; images saved to ./live_out/
```
Expand Down Expand Up @@ -76,7 +75,7 @@ publishing.
trust. (Ours: secrets only in Valves, never logged; fully `async` handlers;
documented security posture — all in line with Open WebUI's sharing guidance.)
- **`requirements:` installs at load time.** Open WebUI pip-installs
`tenki-sandbox[async]` on import. This requires that version to be published on
`tenki[async]` on import. This requires that version to be published on
PyPI. Note for operators: in multi-replica deployments, runtime installs can
race across workers — they can set
`ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=False` and bake the dependency into
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ between runs**, is **non-blocking** (async), enforces a **timeout**, and is
`open-webui/tools/tenki_code_execution.py`.
- **Action** (optional) — Admin Panel → Functions → import _Tenki Run Code_,
or paste the contents of `open-webui/functions/tenki_run_code.py`.
- Open WebUI installs the declared `tenki-sandbox[async]` dependency
- Open WebUI installs the declared `tenki[async]` dependency
automatically on import — no manual `pip install`, no server restart.
_(Multi-replica deployments may prefer to bake the dependency into their
image and set `ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=False` to avoid
Expand Down Expand Up @@ -74,7 +74,6 @@ between runs**, is **non-blocking** (async), enforces a **timeout**, and is
| `tenki_api_key` | _(empty)_ | **Required** (unless every user sets their own). Instance-wide Tenki key. |
| `tenki_api_endpoint` | `https://api.tenki.cloud` | Override for self-hosted / staging. |
| `tenki_workspace_id` | _(empty)_ | Optional workspace scope; uses the account default when empty. |
| `tenki_project_id` | _(empty)_ | Optional project scope; uses the account default when empty. |
| `default_language` | `python` | `python` or `shell`, used when the caller doesn't specify. |
| `cpu_cores` / `memory_mb` / `disk_size_gb` | `1` / `512` / `5` | Sandbox resource size (disk 5–100 GiB). |
| `timeout_seconds` | `300` | Max execution time per run (1–600). |
Expand All @@ -83,6 +82,11 @@ between runs**, is **non-blocking** (async), enforces a **timeout**, and is
| `max_concurrent_executions` | `5` | Cap on simultaneous sandboxes per instance. |
| `max_output_bytes` | `100000` | Truncation cap for captured output. |

> **Upgrade note — `tenki_project_id` was removed.** Tenki dropped project
> scoping from the API, so the SDK no longer accepts a project ID at all. If you
> had set this valve, no action is needed: the stored value is ignored and
> nothing breaks. Use `tenki_workspace_id` to scope sandboxes instead.

### Per-user keys (UserValves)

Each user may set their **own** Tenki key under their personal plugin settings.
Expand All @@ -101,7 +105,7 @@ crashes or silently does nothing.
Everything your code runs against — the Python version, the shell, and the
available libraries — comes from the **Tenki sandbox image**, not from this
plugin. The `requirements:` frontmatter only installs the plugin's own dependency
(`tenki-sandbox[async]`) on the Open WebUI host; it does **not** add packages to
(`tenki[async]`) on the Open WebUI host; it does **not** add packages to
the sandbox.

The default image ships a common set of libraries. To use a different set, point
Expand Down
3 changes: 1 addition & 2 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ green without secrets.
export TENKI_API_KEY=sk-...
# optional, for a self-hosted or non-default deployment:
# export TENKI_API_ENDPOINT=https://api.tenki.cloud
# export TENKI_PROJECT_ID=proj_...

pytest tests/integration -v # full gated suite
python scripts/try_live.py # ad-hoc single run; images saved to ./live_out/
Expand Down Expand Up @@ -84,7 +83,7 @@ docker run -d -p 3000:8080 \

Open <http://localhost:3000> and create the first (admin) account.

> Open WebUI installs the plugin's declared `tenki-sandbox[async]` dependency
> Open WebUI installs the plugin's declared `tenki[async]` dependency
> automatically on import (from the `requirements:` frontmatter) — no manual
> install, no restart.

Expand Down
9 changes: 2 additions & 7 deletions open-webui/functions/tenki_run_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
description: A "Run code" button that executes a message's Python or shell code block in an isolated, ephemeral Tenki microVM sandbox — a secure code interpreter / code execution sandbox for Open WebUI.
version: 0.1.0
license: MIT
requirements: tenki-sandbox[async]>=0.3.5
requirements: tenki[async]>=0.5.1
"""

# ---------------------------------------------------------------------------
Expand All @@ -19,7 +19,7 @@
from dataclasses import dataclass, field
from os.path import splitext
from pydantic import BaseModel, Field, field_validator
from tenki_sandbox import AsyncSandbox
from tenki import AsyncSandbox
from typing import Literal
import asyncio
import base64
Expand Down Expand Up @@ -71,10 +71,6 @@ class Valves(BaseModel):
default="",
description="Optional Tenki workspace ID. Uses the account default when empty.",
)
tenki_project_id: str = Field(
default="",
description="Optional Tenki project ID. Uses the account default when empty.",
)
default_language: Literal["python", "shell"] = Field(
default="python",
description="Language used when the caller does not specify one.",
Expand Down Expand Up @@ -414,7 +410,6 @@ async def _provision_and_run(request: CodeExecutionRequest) -> ExecutionResult:
auth_token=request.api_key,
base_url=valves.tenki_api_endpoint, # data goes only to the configured endpoint
workspace_id=valves.tenki_workspace_id or None,
project_id=valves.tenki_project_id or None,
cpu_cores=valves.cpu_cores,
memory_mb=valves.memory_mb,
disk_size_gb=valves.disk_size_gb,
Expand Down
9 changes: 2 additions & 7 deletions open-webui/tools/tenki_code_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
description: Run Python and shell code in isolated, ephemeral Tenki microVM sandboxes. A secure code interpreter / code execution sandbox for Open WebUI (isolated, ephemeral, microVM).
version: 0.1.0
license: MIT
requirements: tenki-sandbox[async]>=0.3.5
requirements: tenki[async]>=0.5.1
"""

# ---------------------------------------------------------------------------
Expand All @@ -19,7 +19,7 @@
from dataclasses import dataclass, field
from os.path import splitext
from pydantic import BaseModel, Field, field_validator
from tenki_sandbox import AsyncSandbox
from tenki import AsyncSandbox
from typing import Literal
import asyncio
import base64
Expand Down Expand Up @@ -70,10 +70,6 @@ class Valves(BaseModel):
default="",
description="Optional Tenki workspace ID. Uses the account default when empty.",
)
tenki_project_id: str = Field(
default="",
description="Optional Tenki project ID. Uses the account default when empty.",
)
default_language: Literal["python", "shell"] = Field(
default="python",
description="Language used when the caller does not specify one.",
Expand Down Expand Up @@ -413,7 +409,6 @@ async def _provision_and_run(request: CodeExecutionRequest) -> ExecutionResult:
auth_token=request.api_key,
base_url=valves.tenki_api_endpoint, # data goes only to the configured endpoint
workspace_id=valves.tenki_workspace_id or None,
project_id=valves.tenki_project_id or None,
cpu_cores=valves.cpu_cores,
memory_mb=valves.memory_mb,
disk_size_gb=valves.disk_size_gb,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ keywords = [
"tenki",
]
dependencies = [
"tenki-sandbox[async]>=0.3.5",
"tenki[async]>=0.5.1",
"pydantic>=2",
]

Expand Down
4 changes: 2 additions & 2 deletions scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
description: Run Python and shell code in isolated, ephemeral Tenki microVM sandboxes. A secure code interpreter / code execution sandbox for Open WebUI (isolated, ephemeral, microVM).
version: 0.1.0
license: MIT
requirements: tenki-sandbox[async]>=0.3.5
requirements: tenki[async]>=0.5.1
"""'''

ACTION_FRONTMATTER = '''\
Expand All @@ -41,7 +41,7 @@
description: A "Run code" button that executes a message's Python or shell code block in an isolated, ephemeral Tenki microVM sandbox — a secure code interpreter / code execution sandbox for Open WebUI.
version: 0.1.0
license: MIT
requirements: tenki-sandbox[async]>=0.3.5
requirements: tenki[async]>=0.5.1
"""'''

# (entry_module, frontmatter, output_path)
Expand Down
2 changes: 0 additions & 2 deletions scripts/try_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
Usage:
export TENKI_API_KEY=sk-...
# optional: export TENKI_API_ENDPOINT=https://api.tenki.cloud
# optional: export TENKI_PROJECT_ID=proj_...

python scripts/try_live.py # default demo snippet
python scripts/try_live.py -l shell -c 'uname -a'
Expand Down Expand Up @@ -61,7 +60,6 @@ async def main() -> int:
valves = Valves(
tenki_api_key=key,
tenki_api_endpoint=os.getenv("TENKI_API_ENDPOINT", "https://api.tenki.cloud"),
tenki_project_id=os.getenv("TENKI_PROJECT_ID", ""),
timeout_seconds=args.timeout,
network_egress=args.egress,
)
Expand Down
4 changes: 0 additions & 4 deletions src/open_webui_tenki/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ class Valves(BaseModel):
default="",
description="Optional Tenki workspace ID. Uses the account default when empty.",
)
tenki_project_id: str = Field(
default="",
description="Optional Tenki project ID. Uses the account default when empty.",
)
default_language: Literal["python", "shell"] = Field(
default="python",
description="Language used when the caller does not specify one.",
Expand Down
3 changes: 1 addition & 2 deletions src/open_webui_tenki/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from dataclasses import dataclass
from os.path import splitext

from tenki_sandbox import AsyncSandbox
from tenki import AsyncSandbox

from .config import Valves
from .constants import (
Expand Down Expand Up @@ -131,7 +131,6 @@ async def _provision_and_run(request: CodeExecutionRequest) -> ExecutionResult:
auth_token=request.api_key,
base_url=valves.tenki_api_endpoint, # data goes only to the configured endpoint
workspace_id=valves.tenki_workspace_id or None,
project_id=valves.tenki_project_id or None,
cpu_cores=valves.cpu_cores,
memory_mb=valves.memory_mb,
disk_size_gb=valves.disk_size_gb,
Expand Down
6 changes: 3 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@


class FakeEntry:
"""Mimics tenki_sandbox FileInfo for fs.list()."""
"""Mimics tenki FileInfo for fs.list()."""

def __init__(self, name: str, is_dir: bool = False, size: int = 0):
self.path = name
Expand All @@ -19,7 +19,7 @@ def __init__(self, name: str, is_dir: bool = False, size: int = 0):


class FakeCommandResult:
"""Mimics tenki_sandbox CommandResult (stdout/stderr are bytes)."""
"""Mimics tenki CommandResult (stdout/stderr are bytes)."""

def __init__(
self,
Expand Down Expand Up @@ -99,7 +99,7 @@ async def close(self):


class FakeSDK:
"""Stand-in for tenki_sandbox.AsyncSandbox; patched into core."""
"""Stand-in for tenki.AsyncSandbox; patched into core."""

def __init__(
self,
Expand Down
7 changes: 2 additions & 5 deletions tests/integration/test_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
Env:
- ``TENKI_API_KEY`` (required to run)
- ``TENKI_API_ENDPOINT`` (optional; defaults to https://api.tenki.cloud)
- ``TENKI_PROJECT_ID`` (optional)
"""

from __future__ import annotations
Expand All @@ -34,7 +33,6 @@ def _valves(**overrides) -> Valves:
return Valves(
tenki_api_key=API_KEY,
tenki_api_endpoint=os.getenv("TENKI_API_ENDPOINT", "https://api.tenki.cloud"),
tenki_project_id=os.getenv("TENKI_PROJECT_ID", ""),
**overrides,
)

Expand All @@ -47,13 +45,12 @@ def _req(code: str, language: str = "python", **overrides) -> CodeExecutionReque

async def _raw_sandbox():
"""Provision a raw AsyncSandbox with the same knobs the core uses."""
from tenki_sandbox import AsyncSandbox
from tenki import AsyncSandbox

v = _valves()
return await AsyncSandbox.create(
auth_token=API_KEY,
base_url=v.tenki_api_endpoint,
project_id=v.tenki_project_id or None,
cpu_cores=v.cpu_cores,
memory_mb=v.memory_mb,
disk_size_gb=v.disk_size_gb,
Expand Down Expand Up @@ -170,7 +167,7 @@ async def test_fs_mkdir_recursive_is_writable_by_exec_user():

async def test_no_sandbox_leak():
# Snapshot RUNNING sessions before/after; the run's sandbox must be torn down.
from tenki_sandbox import AsyncClient
from tenki import AsyncClient

endpoint = os.getenv("TENKI_API_ENDPOINT", "https://api.tenki.cloud")

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_no_local_package_imports(path, _class, _attr):
def test_has_frontmatter(path, _class, _attr):
src = path.read_text()
assert src.lstrip().startswith('"""')
assert "requirements: tenki-sandbox[async]" in src
assert "requirements: tenki[async]" in src
assert "title:" in src and "author: LuxorLabs" in src


Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def test_only_configured_endpoint_no_other_host(patch_sdk):
assert kwargs["auth_token"] == "k"
# No stray host/url params leak into the create call.
for key, value in kwargs.items():
if key in ("auth_token", "project_id", "image"):
if key in ("auth_token", "image"):
continue
if isinstance(value, str):
assert "http" not in value or value == kwargs["base_url"]
Expand Down
Loading