625 lines
20 KiB
Python
625 lines
20 KiB
Python
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import (
|
|
Any,
|
|
Callable,
|
|
Dict,
|
|
List,
|
|
Literal,
|
|
Mapping,
|
|
Optional,
|
|
TypedDict,
|
|
Union,
|
|
cast,
|
|
)
|
|
|
|
from typing_extensions import NotRequired, Unpack
|
|
|
|
from e2b.api.client.models import (
|
|
ListedSandbox,
|
|
SandboxDetail,
|
|
SandboxState,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxLifecycle as ClientSandboxLifecycle,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkConfig as ClientSandboxNetworkConfig,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkConfigRules,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkRule as ClientSandboxNetworkRule,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkTransform as ClientSandboxNetworkTransform,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkTransformHeaders as ClientSandboxNetworkTransformHeaders,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkUpdateConfig,
|
|
)
|
|
from e2b.api.client.models import (
|
|
SandboxNetworkUpdateConfigRules,
|
|
)
|
|
from e2b.api.client.types import Unset
|
|
from e2b.connection_config import ApiParams
|
|
from e2b.sandbox.mcp import McpServer as BaseMcpServer
|
|
from e2b.sandbox.network import ALL_TRAFFIC
|
|
from e2b.paginator import PaginatorBase
|
|
|
|
|
|
class GitHubMcpServerConfig(TypedDict):
|
|
"""
|
|
Configuration for a GitHub-based MCP server.
|
|
"""
|
|
|
|
run_cmd: str
|
|
"""
|
|
Command to run the MCP server. Must start a stdio-compatible server.
|
|
"""
|
|
install_cmd: NotRequired[str]
|
|
"""
|
|
Command to install dependencies for the MCP server. Working directory is the root of the github repository.
|
|
"""
|
|
envs: NotRequired[Dict[str, str]]
|
|
"""
|
|
Environment variables to set in the MCP process.
|
|
"""
|
|
|
|
|
|
# Extended MCP server configuration that includes base servers
|
|
# and allows dynamic GitHub-based MCP servers with custom run and install commands.
|
|
# For GitHub servers, use keys in the format "github/owner/repo"
|
|
GitHubMcpServer = Dict[str, Union[GitHubMcpServerConfig, Any]]
|
|
|
|
# Union type that combines base MCP servers with GitHub-based servers
|
|
McpServer = Union[BaseMcpServer, GitHubMcpServer]
|
|
|
|
|
|
class SandboxNetworkTransform(TypedDict):
|
|
"""
|
|
Transform applied to egress requests matching a :class:`SandboxNetworkRule`.
|
|
"""
|
|
|
|
headers: NotRequired[Dict[str, str]]
|
|
"""
|
|
Headers to inject into the outbound request. Values override any headers
|
|
already present on the request.
|
|
"""
|
|
|
|
|
|
class SandboxNetworkRule(TypedDict):
|
|
"""
|
|
Per-domain rule applied to egress requests.
|
|
"""
|
|
|
|
transform: NotRequired[SandboxNetworkTransform]
|
|
"""
|
|
Transform applied to requests matching this rule.
|
|
"""
|
|
|
|
|
|
SandboxNetworkRules = Dict[str, List[SandboxNetworkRule]]
|
|
"""
|
|
Map of host (or CIDR / IP) to ordered list of rules applied to outbound
|
|
requests for that host. Registering a host here does not allow egress on its
|
|
own — the host must also appear in ``SandboxNetworkOpts.allow_out``.
|
|
"""
|
|
|
|
|
|
class SandboxNetworkRuleInfo(TypedDict):
|
|
"""
|
|
Per-domain rule as returned by the sandbox info endpoint. Mirrors
|
|
:class:`SandboxNetworkRule` but with ``transform`` always materialized to
|
|
the static :class:`SandboxNetworkTransform` shape — no callable variant.
|
|
"""
|
|
|
|
transform: NotRequired[SandboxNetworkTransform]
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SandboxNetworkSelectorContext:
|
|
"""
|
|
Context passed to ``allow_out``/``deny_out`` callables.
|
|
"""
|
|
|
|
all_traffic: str
|
|
"""All traffic sentinel — equivalent to ``"0.0.0.0/0"``."""
|
|
|
|
rules: Mapping[str, List[SandboxNetworkRule]]
|
|
"""Rules registered in :attr:`SandboxNetworkOpts.rules`."""
|
|
|
|
|
|
SandboxNetworkSelector = Union[
|
|
List[str],
|
|
Callable[[SandboxNetworkSelectorContext], List[str]],
|
|
]
|
|
"""
|
|
Egress rule list, either a static list of CIDR blocks / IP addresses /
|
|
hostnames, or a callable that receives a :class:`SandboxNetworkSelectorContext`
|
|
and returns the same.
|
|
"""
|
|
|
|
|
|
class SandboxNetworkOpts(TypedDict):
|
|
"""
|
|
Sandbox network configuration options.
|
|
"""
|
|
|
|
allow_out: NotRequired[SandboxNetworkSelector]
|
|
"""
|
|
Allow outbound traffic from the sandbox to the specified addresses.
|
|
If ``allow_out`` is not specified, all outbound traffic is allowed.
|
|
|
|
Accepts either a static list of CIDR blocks / IP addresses / hostnames, or
|
|
a callable that receives a :class:`SandboxNetworkSelectorContext` and
|
|
returns the same. ``ctx.all_traffic`` is ``"0.0.0.0/0"``; ``ctx.rules`` is
|
|
a read-only view of :attr:`rules`.
|
|
|
|
Examples:
|
|
- Static list: ``["1.1.1.1", "8.8.8.0/24"]``
|
|
- Allow only rule-registered hosts:
|
|
``lambda ctx: list(ctx.rules.keys())``
|
|
"""
|
|
|
|
deny_out: NotRequired[SandboxNetworkSelector]
|
|
"""
|
|
Deny outbound traffic from the sandbox to the specified addresses.
|
|
|
|
Accepts the same shapes as ``allow_out``.
|
|
|
|
Examples:
|
|
- Static list: ``["1.1.1.1", "8.8.8.0/24"]``
|
|
- Block all egress: ``lambda ctx: [ctx.all_traffic]``
|
|
"""
|
|
|
|
rules: NotRequired[SandboxNetworkRules]
|
|
"""
|
|
Per-domain transform rules applied to matching egress HTTP/HTTPS
|
|
requests. Keys are domains (e.g. ``"api.example.com"``); values are
|
|
ordered lists of :class:`SandboxNetworkRule`.
|
|
|
|
Registering a host here does not allow egress on its own — the host must
|
|
also appear in ``allow_out``. Hosts registered here are exposed to the
|
|
``allow_out``/``deny_out`` callables via ``ctx.rules``.
|
|
"""
|
|
|
|
allow_public_traffic: NotRequired[bool]
|
|
"""
|
|
Controls whether sandbox URLs should be publicly accessible or require authentication.
|
|
Defaults to True.
|
|
"""
|
|
|
|
mask_request_host: NotRequired[str]
|
|
"""
|
|
Allows specifying a custom host mask for all sandbox requests.
|
|
Supports ${PORT} variable. Defaults to "${PORT}-sandboxid.e2b.app".
|
|
|
|
Examples:
|
|
- Custom subdomain: `"${PORT}-myapp.example.com"`
|
|
"""
|
|
|
|
|
|
class SandboxNetworkUpdate(TypedDict, total=False):
|
|
"""
|
|
Subset of :class:`SandboxNetworkOpts` accepted by ``Sandbox.update_network``.
|
|
The update endpoint replaces all egress rules atomically — fields that are
|
|
omitted are cleared on the server.
|
|
"""
|
|
|
|
allow_out: SandboxNetworkSelector
|
|
"""See :attr:`SandboxNetworkOpts.allow_out`."""
|
|
|
|
deny_out: SandboxNetworkSelector
|
|
"""See :attr:`SandboxNetworkOpts.deny_out`."""
|
|
|
|
rules: SandboxNetworkRules
|
|
"""See :attr:`SandboxNetworkOpts.rules`."""
|
|
|
|
allow_internet_access: bool
|
|
"""
|
|
Allow sandbox to access the internet. When set to ``False``, it behaves the
|
|
same as specifying ``deny_out=["0.0.0.0/0"]`` in the network config.
|
|
"""
|
|
|
|
|
|
class SandboxNetworkInfo(TypedDict, total=False):
|
|
"""
|
|
Network configuration as returned by the sandbox info endpoint.
|
|
Mirrors :class:`SandboxNetworkOpts` but with ``allow_out``/``deny_out``
|
|
always materialized to plain string lists.
|
|
"""
|
|
|
|
allow_out: List[str]
|
|
deny_out: List[str]
|
|
rules: Dict[str, List[SandboxNetworkRuleInfo]]
|
|
allow_public_traffic: bool
|
|
mask_request_host: str
|
|
|
|
|
|
class SandboxOnTimeoutPause(TypedDict):
|
|
"""
|
|
Object form of `on_timeout` that auto-pauses the sandbox when the timeout is
|
|
reached, optionally controlling the pause snapshot kind via `keep_memory`.
|
|
"""
|
|
|
|
action: Literal["pause"]
|
|
"""Auto-pause the sandbox when the timeout is reached."""
|
|
|
|
keep_memory: NotRequired[bool]
|
|
"""
|
|
Whether the timeout auto-pause keeps a full memory snapshot. Defaults to `True`.
|
|
When `False`, the auto-pause drops the in-memory state and persists only the
|
|
filesystem (a filesystem-only snapshot); resuming such a sandbox cold-boots
|
|
(reboots) it from disk, losing running processes and open connections.
|
|
|
|
Cannot be combined with `auto_resume`: auto-resume wakes a paused sandbox on
|
|
inbound traffic by restoring its memory snapshot in place, so the request that
|
|
woke it hits an already-running process. A filesystem-only snapshot has no
|
|
memory to restore — resuming cold-boots it — so it can't be woken transparently
|
|
by traffic and must be resumed explicitly via `connect()`.
|
|
"""
|
|
|
|
|
|
class SandboxOnTimeoutKill(TypedDict):
|
|
"""
|
|
Object form of `on_timeout` that kills the sandbox when the timeout is reached.
|
|
"""
|
|
|
|
action: Literal["kill"]
|
|
"""Kill the sandbox when the timeout is reached."""
|
|
|
|
|
|
SandboxOnTimeout = Union[
|
|
Literal["pause", "kill"], SandboxOnTimeoutPause, SandboxOnTimeoutKill
|
|
]
|
|
"""
|
|
What should happen to the sandbox when the timeout is reached. Either the bare
|
|
action (`"pause"` / `"kill"`) or the object form. The object form is a
|
|
discriminated union on `action`: `keep_memory` is only accepted alongside
|
|
`action: "pause"`. Passing `keep_memory` with `action: "kill"` is a static type
|
|
error.
|
|
"""
|
|
|
|
|
|
class SandboxLifecycle(TypedDict):
|
|
"""
|
|
Sandbox lifecycle configuration; defines post-timeout behavior and auto-resume settings.
|
|
Defaults to `on_timeout="kill"` and `auto_resume=False`.
|
|
"""
|
|
|
|
on_timeout: SandboxOnTimeout
|
|
"""
|
|
What should happen to the sandbox when timeout is reached. `"kill"` terminates
|
|
the sandbox; `"pause"` pauses it for later resume. Accepts either the bare
|
|
action or an object `{"action": "pause", "keep_memory": ...}` /
|
|
`{"action": "kill"}` to also control the pause snapshot kind. Defaults to
|
|
`"kill"`.
|
|
"""
|
|
|
|
auto_resume: NotRequired[bool]
|
|
"""
|
|
Whether activity should cause the sandbox to resume when paused. Defaults to `False`.
|
|
Can be `True` only when `on_timeout` is `pause`. Not supported when
|
|
`keep_memory` is `False` (a filesystem-only snapshot must be resumed
|
|
explicitly via `connect()`).
|
|
"""
|
|
|
|
|
|
class SandboxInfoLifecycle(TypedDict):
|
|
"""
|
|
Sandbox lifecycle configuration returned by sandbox info.
|
|
"""
|
|
|
|
on_timeout: Literal["pause", "kill"]
|
|
"""
|
|
What should happen to the sandbox when timeout is reached.
|
|
"""
|
|
|
|
auto_resume: bool
|
|
"""
|
|
Whether activity should cause the sandbox to resume when paused.
|
|
"""
|
|
|
|
|
|
def _resolve_network_selector(
|
|
selector: Optional[SandboxNetworkSelector],
|
|
rules: Mapping[str, List[SandboxNetworkRule]],
|
|
) -> Optional[List[str]]:
|
|
if selector is None:
|
|
return None
|
|
|
|
if callable(selector):
|
|
ctx = SandboxNetworkSelectorContext(all_traffic=ALL_TRAFFIC, rules=rules)
|
|
return list(selector(ctx))
|
|
|
|
return list(selector)
|
|
|
|
|
|
def _build_client_rules(rules: SandboxNetworkRules) -> SandboxNetworkConfigRules:
|
|
client_rules = SandboxNetworkConfigRules()
|
|
for host, host_rules in rules.items():
|
|
converted: List[ClientSandboxNetworkRule] = []
|
|
for rule in host_rules:
|
|
transform = rule.get("transform")
|
|
if transform is None:
|
|
converted.append(ClientSandboxNetworkRule())
|
|
continue
|
|
|
|
client_transform = ClientSandboxNetworkTransform()
|
|
headers = transform.get("headers")
|
|
if headers:
|
|
client_headers = ClientSandboxNetworkTransformHeaders()
|
|
client_headers.additional_properties = dict(headers)
|
|
client_transform.headers = client_headers
|
|
|
|
converted.append(ClientSandboxNetworkRule(transform=client_transform))
|
|
client_rules.additional_properties[host] = converted
|
|
|
|
return client_rules
|
|
|
|
|
|
def _build_network_egress(
|
|
network: Mapping[str, Any],
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Resolve the shared egress fields (``allow_out`` / ``deny_out`` / per-host
|
|
``rules``) used by both the create and update endpoints. ``rules`` in the
|
|
returned dict is the inner ``Dict[host, List[ClientSandboxNetworkRule]]``
|
|
— callers wrap it in their endpoint-specific rules attrs class.
|
|
"""
|
|
rules = network.get("rules") or {}
|
|
allow_out = _resolve_network_selector(network.get("allow_out"), rules)
|
|
deny_out = _resolve_network_selector(network.get("deny_out"), rules)
|
|
|
|
body: Dict[str, Any] = {}
|
|
if allow_out is not None:
|
|
body["allow_out"] = allow_out
|
|
if deny_out is not None:
|
|
body["deny_out"] = deny_out
|
|
if "rules" in network and network["rules"] is not None:
|
|
body["rules"] = _build_client_rules(network["rules"]).additional_properties
|
|
|
|
return body
|
|
|
|
|
|
def build_network_config(
|
|
network: Optional[SandboxNetworkOpts],
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Resolve a :class:`SandboxNetworkOpts` into the dict the API expects."""
|
|
if network is None:
|
|
return None
|
|
|
|
body = _build_network_egress(network)
|
|
if "rules" in body:
|
|
client_rules = SandboxNetworkConfigRules()
|
|
client_rules.additional_properties = body["rules"]
|
|
body["rules"] = client_rules
|
|
if "allow_public_traffic" in network:
|
|
body["allow_public_traffic"] = network["allow_public_traffic"]
|
|
if "mask_request_host" in network:
|
|
body["mask_request_host"] = network["mask_request_host"]
|
|
|
|
return body
|
|
|
|
|
|
def build_network_update_body(
|
|
network: SandboxNetworkUpdate,
|
|
) -> SandboxNetworkUpdateConfig:
|
|
"""Resolve a :class:`SandboxNetworkUpdate` into the API client body."""
|
|
egress = _build_network_egress(network)
|
|
|
|
body = SandboxNetworkUpdateConfig()
|
|
if "allow_out" in egress:
|
|
body.allow_out = egress["allow_out"]
|
|
if "deny_out" in egress:
|
|
body.deny_out = egress["deny_out"]
|
|
if "rules" in egress:
|
|
rules = SandboxNetworkUpdateConfigRules()
|
|
rules.additional_properties = egress["rules"]
|
|
body.rules = rules
|
|
if "allow_internet_access" in network:
|
|
body.allow_internet_access = network["allow_internet_access"]
|
|
|
|
return body
|
|
|
|
|
|
def from_client_network_config(
|
|
network: Union[Unset, ClientSandboxNetworkConfig],
|
|
) -> Optional[SandboxNetworkInfo]:
|
|
if isinstance(network, Unset):
|
|
return None
|
|
|
|
result: SandboxNetworkInfo = {}
|
|
|
|
if not isinstance(network.allow_out, Unset):
|
|
result["allow_out"] = list(network.allow_out)
|
|
if not isinstance(network.deny_out, Unset):
|
|
result["deny_out"] = list(network.deny_out)
|
|
if not isinstance(network.rules, Unset):
|
|
result["rules"] = cast(
|
|
Dict[str, List[SandboxNetworkRuleInfo]], network.rules.to_dict()
|
|
)
|
|
if not isinstance(network.allow_public_traffic, Unset):
|
|
result["allow_public_traffic"] = network.allow_public_traffic
|
|
if not isinstance(network.mask_request_host, Unset):
|
|
result["mask_request_host"] = network.mask_request_host
|
|
|
|
return result
|
|
|
|
|
|
def from_client_lifecycle(
|
|
lifecycle: Union[Unset, ClientSandboxLifecycle],
|
|
) -> Optional[SandboxInfoLifecycle]:
|
|
if isinstance(lifecycle, Unset):
|
|
return None
|
|
|
|
result: SandboxInfoLifecycle = {
|
|
"on_timeout": cast(Literal["pause", "kill"], lifecycle.on_timeout),
|
|
"auto_resume": lifecycle.auto_resume,
|
|
}
|
|
|
|
return result
|
|
|
|
|
|
@dataclass
|
|
class SandboxInfo:
|
|
"""Information about a sandbox."""
|
|
|
|
sandbox_id: str
|
|
"""Sandbox ID."""
|
|
sandbox_domain: Optional[str]
|
|
"""Domain where the sandbox is hosted."""
|
|
template_id: str
|
|
"""Template ID."""
|
|
name: Optional[str]
|
|
"""Template name."""
|
|
metadata: Dict[str, str]
|
|
"""Saved sandbox metadata."""
|
|
started_at: datetime
|
|
"""Sandbox start time."""
|
|
end_at: datetime
|
|
"""Sandbox expiration date."""
|
|
state: SandboxState
|
|
"""Sandbox state."""
|
|
cpu_count: int
|
|
"""Sandbox CPU count."""
|
|
memory_mb: int
|
|
"""Sandbox Memory size in MiB."""
|
|
envd_version: str
|
|
"""Envd version."""
|
|
allow_internet_access: Optional[bool] = None
|
|
"""Whether internet access was explicitly enabled or disabled for the sandbox."""
|
|
network: Optional[SandboxNetworkInfo] = None
|
|
"""Sandbox network configuration."""
|
|
lifecycle: Optional[SandboxInfoLifecycle] = None
|
|
"""Sandbox lifecycle configuration."""
|
|
volume_mounts: List[Dict[str, str]] = field(default_factory=list)
|
|
"""Volume mounts for the sandbox."""
|
|
|
|
@classmethod
|
|
def _from_sandbox_data(
|
|
cls,
|
|
sandbox: Union[ListedSandbox, SandboxDetail],
|
|
sandbox_domain: Optional[str] = None,
|
|
allow_internet_access: Optional[bool] = None,
|
|
network: Optional[SandboxNetworkInfo] = None,
|
|
lifecycle: Optional[SandboxInfoLifecycle] = None,
|
|
):
|
|
return cls(
|
|
sandbox_domain=sandbox_domain,
|
|
sandbox_id=sandbox.sandbox_id,
|
|
template_id=sandbox.template_id,
|
|
name=(sandbox.alias if isinstance(sandbox.alias, str) else None),
|
|
metadata=cast(
|
|
Dict[str, str],
|
|
sandbox.metadata if isinstance(sandbox.metadata, dict) else {},
|
|
),
|
|
started_at=sandbox.started_at,
|
|
end_at=sandbox.end_at,
|
|
state=sandbox.state,
|
|
cpu_count=sandbox.cpu_count,
|
|
memory_mb=sandbox.memory_mb,
|
|
envd_version=sandbox.envd_version,
|
|
volume_mounts=[
|
|
{"name": vm.name, "path": vm.path} for vm in sandbox.volume_mounts
|
|
]
|
|
if not isinstance(sandbox.volume_mounts, Unset)
|
|
else [],
|
|
allow_internet_access=allow_internet_access,
|
|
network=network,
|
|
lifecycle=lifecycle,
|
|
)
|
|
|
|
@classmethod
|
|
def _from_listed_sandbox(cls, listed_sandbox: ListedSandbox):
|
|
return cls._from_sandbox_data(listed_sandbox)
|
|
|
|
@classmethod
|
|
def _from_sandbox_detail(cls, sandbox_detail: SandboxDetail):
|
|
return cls._from_sandbox_data(
|
|
sandbox_detail,
|
|
sandbox_domain=(
|
|
sandbox_detail.domain
|
|
if isinstance(sandbox_detail.domain, str)
|
|
else None
|
|
),
|
|
allow_internet_access=(
|
|
sandbox_detail.allow_internet_access
|
|
if isinstance(sandbox_detail.allow_internet_access, bool)
|
|
else None
|
|
),
|
|
network=from_client_network_config(sandbox_detail.network),
|
|
lifecycle=from_client_lifecycle(sandbox_detail.lifecycle),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class SandboxQuery:
|
|
"""Query parameters for listing sandboxes."""
|
|
|
|
metadata: Optional[dict[str, str]] = None
|
|
"""Filter sandboxes by metadata."""
|
|
|
|
state: Optional[list[SandboxState]] = None
|
|
"""Filter sandboxes by state."""
|
|
|
|
|
|
@dataclass
|
|
class SandboxMetrics:
|
|
"""Sandbox metrics."""
|
|
|
|
cpu_count: int
|
|
"""Number of CPUs."""
|
|
cpu_used_pct: float
|
|
"""CPU usage percentage."""
|
|
disk_total: int
|
|
"""Total disk space in bytes."""
|
|
disk_used: int
|
|
"""Disk used in bytes."""
|
|
mem_total: int
|
|
"""Total memory in bytes."""
|
|
mem_used: int
|
|
"""Memory used in bytes."""
|
|
mem_cache: int
|
|
"""Cached memory (page cache) in bytes."""
|
|
timestamp: datetime
|
|
"""Timestamp of the metric entry."""
|
|
|
|
|
|
@dataclass
|
|
class SnapshotInfo:
|
|
"""Information about a snapshot."""
|
|
|
|
snapshot_id: str
|
|
"""Snapshot identifier — template ID with tag, or namespaced name with tag (e.g. my-snapshot:latest). Can be used with Sandbox.create() to create a new sandbox from this snapshot."""
|
|
names: List[str] = field(default_factory=list)
|
|
"""Full names of the snapshot template including team namespace and tag (e.g. team-slug/my-snapshot:v2)."""
|
|
|
|
|
|
class SnapshotPaginatorBase(PaginatorBase[SnapshotInfo, ApiParams]):
|
|
def __init__(
|
|
self,
|
|
sandbox_id: Optional[str] = None,
|
|
limit: Optional[int] = None,
|
|
next_token: Optional[str] = None,
|
|
**opts: Unpack[ApiParams],
|
|
):
|
|
super().__init__(limit=limit, next_token=next_token, **opts)
|
|
self.sandbox_id = sandbox_id
|
|
|
|
|
|
class SandboxPaginatorBase(PaginatorBase[SandboxInfo, ApiParams]):
|
|
def __init__(
|
|
self,
|
|
query: Optional[SandboxQuery] = None,
|
|
limit: Optional[int] = None,
|
|
next_token: Optional[str] = None,
|
|
**opts: Unpack[ApiParams],
|
|
):
|
|
super().__init__(limit=limit, next_token=next_token, **opts)
|
|
self.query = query
|