Metadata-Version: 2.5
Name: gmi-agentbox-sdk
Version: 0.1.0b2
Summary: GMI AgentBox SDK for Python.
Project-URL: Homepage, https://www.gmicloud.ai/
Author: GMI
License: MIT
License-File: LICENSE
Keywords: agentbox,gmi,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Requires-Dist: certifi>=2024.0.0
Requires-Dist: websocket-client>=1.8
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# Usage

GMI AgentBox SDK (`gmi-agentbox-sdk`) is a thin Python client for AgentBox.

## Install

```bash
python -m pip install gmi-agentbox-sdk
```

Requires Python 3.9+. TLS uses [certifi](https://pypi.org/project/certifi/).

## Authenticate

To create a Compute API key, sign in to
[GMI Console](https://console.gmicloud.ai/), select **API keys**, choose
**compute**, then click **Create API key**. Store the key securely; it
authenticates requests for your organization.

```bash
export GMI_AGENTBOX_API_KEY="your-api-key"
```

`GMI_AGENTBOX_API_KEY` is required when constructing `AgentBoxClient()` with no
`api_key=` argument.

Or pass it to the client:

```python
from agentbox_sdk import AgentBoxClient

client = AgentBoxClient(api_key="your-api-key")
```

Default service origin is production (used when `GMI_AGENTBOX_BASE_URL` is unset):

`https://console.gmicloud.ai`

Override with `GMI_AGENTBOX_BASE_URL` or `AgentBoxClient(base_url=...)`.

## Quickstart

```python
import time

from agentbox_sdk import AgentBoxClient, SandboxFailed, SandboxWaitTimeout

client = AgentBoxClient()

# Available values vary by organization. Use the catalogue to select an IDC
# and a SKU for the Sandbox runtime.
idcs = client.idcs.list(runtime="sandbox")
# Choose an `idc_id` returned above. This is the production example value.
idc_id = "us-central-iowa2"
products = client.products.list(idc_name=idc_id, runtime="sandbox")
# Choose an `instance_type` returned above.
instance_type = "gmi.sandbox.x-small"


def wait_for_template(agent, timeout=600, poll_interval=3):
    deadline = time.monotonic() + timeout
    while True:
        agent.refresh()
        status = agent.template_build_status
        if status == "ready" or status is None:
            return
        if status == "error":
            raise RuntimeError(agent.template_build_error or "Sandbox image build failed")
        if time.monotonic() >= deadline:
            raise TimeoutError(f"Sandbox image is still {status!r} after {timeout}s")
        print("...")
        time.sleep(poll_interval)

agent = None
sandbox = None
try:
    agent = client.agents.create(
        title="agentbox-demo",  # choose a unique name
        image_url="docker.io/library/alpine:3.20",
        idc=idc_id,
        instance_type=instance_type,
        runtime="sandbox",
    )
    print("Building Sandbox image; a new image can take several minutes.")
    wait_for_template(agent)
    sandbox = agent.launch(instance_type=instance_type)
    sandbox.wait_until_running()  # default timeout 300s
    print(sandbox.status, sandbox.endpoint_url)
    execution = sandbox.execute("echo hello")
    print(execution.status, execution.exit_code, execution.data.get("stdout"))
except SandboxFailed as exc:
    print("failed", exc.status, exc.message)
except SandboxWaitTimeout:
    print("still not running", sandbox.status, sandbox.last_error)
finally:
    if sandbox is not None:
        sandbox.delete()
    if agent is not None:
        agent.delete()
```

Use `client.idcs.list(runtime="sandbox")` and
`client.products.list(idc_name=..., runtime="sandbox")` to discover available
Sandbox data centers and SKUs.

## Register an Agent

```python
agent = client.agents.create(
    title="agentbox-demo",
    image_url="docker.io/library/alpine:3.20",
    idc=idc_id,
    env=[{"name": "GMI_MODELS", "value": "llama", "secret": False}],
)
print(agent.slug, agent.launchable)
```

- `generated_api_key` is plaintext only on create (and rare patch retries).
- `agent.launchable` is false when the upstream template is missing.

Later:

```python
agent = client.agents.get("agentbox-demo")
page = client.agents.list(page=1, page_size=20)
agent.update(title="renamed")
agent.delete()
```

## Launch a Sandbox

```python
sandbox = agent.launch(instance_type=instance_type)
# or: client.sandboxes.launch(agent.slug, instance_type=..., idc_name=agent.idc)

sandbox.wait_until_running(timeout=300, poll_interval=2.0)
sandbox.refresh()
print(sandbox.status, sandbox.last_error, sandbox.endpoint_url)
```

`wait_until_running` polls until `running`. It raises:

- `SandboxFailed` if status is `error`, `deleted`, `stopped`, or `stopping`
- `SandboxWaitTimeout` if the timeout elapses first

`endpoint_url` is empty until the sandbox is running, and can still be empty
after `running`. Do not assume an HTTP URL is always present.

## Run commands and transfer files

```python
# Finished command
result = sandbox.execute("echo hello")
print(result.status, result.exit_code, result.data.get("stdout"))

# Start a long-running command, then cancel it.
execution = sandbox.execute("sleep 120", wait=False)
if execution.accepted:
    sandbox.cancel_execution(execution.id)

# File round trip
sandbox.upload_file(path="/home/user/input.txt", file=b"hello\n")
download = sandbox.download_file(path="/home/user/input.txt")
print(download.filename, download.content)
```

`wait=False` returns an accepted execution; retrieve its latest state with
`sandbox.get_execution(execution.id)`. File paths must be absolute and cannot
contain `.` or `..` segments.

## Interactive shell

```python
socket = sandbox.shell(timeout=30)
try:
    socket.send("echo hello\n")
    print(socket.recv())
finally:
    socket.close()
```

The returned connection supports `send`, `recv`, and `close`.

## List and filter sandboxes

```python
page = client.sandboxes.list()  # omits stopped and deleted
page = client.sandboxes.list(status=["running", "creating", "error"])
page = client.sandboxes.list(agent_id=agent.id, status="running")
page = agent.sandboxes()  # this Agent's Sandboxes, including API defaults
```

The default Sandbox list **excludes** `stopped` and `deleted`.
Pass `status` when you need those.

## Delete

```python
sandbox.delete()  # one-way; the sandbox cannot be restarted
agent.delete()     # does not stop or delete remaining sandboxes
```

Always delete sandboxes you launched in tests. They bill the Console account.

## Logs

The SDK supports log snapshots and streaming when the Sandbox provides the
corresponding capability. Check `logs` for snapshots and `logs_stream` for
streaming before calling them:

```python
if sandbox.capabilities.get("logs"):
    print(sandbox.logs())

if sandbox.capabilities.get("logs_stream"):
    for event in sandbox.stream():
        print(event.event, event.data)
        if event.event == "status" and isinstance(event.data, dict):
            break
```

When unavailable, these calls raise an unsupported-runtime error. A backend
can enable the capability without requiring an SDK update.

## Eligibility and metrics

```python
entitlement = client.eligibility()
print(entitlement.eligible, entitlement.data_centers)

if sandbox.capabilities.get("metrics"):
    batch = sandbox.metrics(start=start, end=end, kinds=["cpu", "memory"], step=60)
    series = sandbox.metrics_timeseries(kind="cpu", start=start, end=end)
    print(series.empty_reason, series.points)
```

`kinds` omitted means the API default (all 8 charts): `gpu_util`, `gpu_mem`,
`cpu`, `memory`, `disk_read`, `disk_write`, `net_rx`, `net_tx`. `start` /
`end` are unix seconds. Metrics are callable only when the Sandbox provides
the `metrics` capability; otherwise the service reports that the runtime does
not support them.

## Concepts

| Name | Meaning |
|---|---|
| `idc` / `idc_name` | Data center identifier, not a region label. |
| `instance_type` | Billing SKU, not the product object name. |

## Errors

```python
from agentbox_sdk import (
    AuthenticationError,
    SandboxFailed,
    SandboxWaitTimeout,
    NotFoundError,
    UnprocessableError,
)

try:
    client.agents.get("missing")
except NotFoundError as exc:
    print(exc.status_code, exc.message)
```

`401` is `AuthenticationError` (for example, when the API key is invalid).
`422` is `UnprocessableError` (locked field / template mismatch). See
[Reference](https://docs.gmicloud.ai/api-reference/agentbox-sdk/reference).
