mitos-run 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mitos/__init__.py +52 -0
- mitos/_envelope.py +118 -0
- mitos/_k8s.py +69 -0
- mitos/aio.py +741 -0
- mitos/client.py +382 -0
- mitos/direct.py +639 -0
- mitos/e2b.py +361 -0
- mitos/errors.py +127 -0
- mitos/guest.py +189 -0
- mitos/integrations/__init__.py +43 -0
- mitos/integrations/_mapping.py +178 -0
- mitos/integrations/claude_agent.py +329 -0
- mitos/integrations/langchain.py +226 -0
- mitos/integrations/openai_agents.py +308 -0
- mitos/integrations/vibekit.py +222 -0
- mitos/integrations/zenml.py +244 -0
- mitos/pty.py +173 -0
- mitos/sandbox.py +839 -0
- mitos/template.py +130 -0
- mitos/types.py +225 -0
- mitos/workspace.py +149 -0
- mitos_run-0.1.0.dist-info/METADATA +465 -0
- mitos_run-0.1.0.dist-info/RECORD +24 -0
- mitos_run-0.1.0.dist-info/WHEEL +4 -0
mitos/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from mitos import guest
|
|
2
|
+
from mitos.aio import AsyncAgentRun, AsyncSandbox
|
|
3
|
+
from mitos.client import AgentRun
|
|
4
|
+
from mitos.direct import DirectSandbox, SandboxServer, create
|
|
5
|
+
from mitos.errors import (
|
|
6
|
+
AgentRunError,
|
|
7
|
+
ExecutionDeadlineError,
|
|
8
|
+
IdleTimeoutError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
RateLimitedError,
|
|
11
|
+
RequestCanceledError,
|
|
12
|
+
TimeoutTooLargeError,
|
|
13
|
+
UnauthorizedError,
|
|
14
|
+
)
|
|
15
|
+
from mitos.sandbox import Sandbox
|
|
16
|
+
from mitos.template import Template
|
|
17
|
+
from mitos.types import (
|
|
18
|
+
Execution,
|
|
19
|
+
ExecResult,
|
|
20
|
+
ExecutionError,
|
|
21
|
+
FileInfo,
|
|
22
|
+
ForkPolicy,
|
|
23
|
+
Network,
|
|
24
|
+
Result,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"create",
|
|
29
|
+
"DirectSandbox",
|
|
30
|
+
"SandboxServer",
|
|
31
|
+
"AgentRun",
|
|
32
|
+
"AgentRunError",
|
|
33
|
+
"ExecutionDeadlineError",
|
|
34
|
+
"IdleTimeoutError",
|
|
35
|
+
"NotFoundError",
|
|
36
|
+
"RateLimitedError",
|
|
37
|
+
"RequestCanceledError",
|
|
38
|
+
"TimeoutTooLargeError",
|
|
39
|
+
"UnauthorizedError",
|
|
40
|
+
"AsyncAgentRun",
|
|
41
|
+
"AsyncSandbox",
|
|
42
|
+
"Sandbox",
|
|
43
|
+
"Template",
|
|
44
|
+
"ExecResult",
|
|
45
|
+
"Execution",
|
|
46
|
+
"ExecutionError",
|
|
47
|
+
"Result",
|
|
48
|
+
"FileInfo",
|
|
49
|
+
"ForkPolicy",
|
|
50
|
+
"Network",
|
|
51
|
+
"guest",
|
|
52
|
+
]
|
mitos/_envelope.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from mitos.errors import AgentRunError, error_for_code
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _redact(text: str, token: Optional[str]) -> str:
|
|
11
|
+
"""Replaces every occurrence of a non-empty token with [REDACTED]. Mirrors
|
|
12
|
+
the TypeScript redact helper and internal/mcp redaction."""
|
|
13
|
+
if not token:
|
|
14
|
+
return text
|
|
15
|
+
return text.replace(token, "[REDACTED]")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Default code and remediation per HTTP status, used when the body is not the
|
|
19
|
+
# structured server envelope (an older server, a proxy 502, a transport layer).
|
|
20
|
+
_STATUS_CODE = {
|
|
21
|
+
400: "bad_request",
|
|
22
|
+
401: "unauthorized",
|
|
23
|
+
403: "forbidden",
|
|
24
|
+
404: "not_found",
|
|
25
|
+
409: "conflict",
|
|
26
|
+
413: "request_too_large",
|
|
27
|
+
429: "rate_limited",
|
|
28
|
+
500: "internal_error",
|
|
29
|
+
503: "unavailable",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_STATUS_REMEDIATION = {
|
|
33
|
+
401: "Check the sandbox bearer token is set and authorizes this sandbox.",
|
|
34
|
+
403: "Check the sandbox bearer token is set and authorizes this sandbox.",
|
|
35
|
+
404: "Confirm the sandbox id exists and is Ready before calling.",
|
|
36
|
+
413: "Reduce the request payload size (file content is hex-encoded and bounded by the server).",
|
|
37
|
+
429: "Back off and retry the request after a short delay.",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _status_code(status: int) -> str:
|
|
42
|
+
if status in _STATUS_CODE:
|
|
43
|
+
return _STATUS_CODE[status]
|
|
44
|
+
return "server_error" if status >= 500 else "request_failed"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _status_remediation(status: int) -> str:
|
|
48
|
+
if status in _STATUS_REMEDIATION:
|
|
49
|
+
return _STATUS_REMEDIATION[status]
|
|
50
|
+
if status >= 500:
|
|
51
|
+
return "Retry the request; if it persists, inspect the forkd or sandbox-server logs."
|
|
52
|
+
return "Inspect the request fields against the sandbox API contract."
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def error_from_response(resp: httpx.Response, token: Optional[str] = None) -> AgentRunError:
|
|
56
|
+
"""Builds an AgentRunError from a non-2xx response. Prefers the structured
|
|
57
|
+
server envelope {error:{code,message,cause,remediation}}; falls back to
|
|
58
|
+
status-derived defaults for an older or non-mitos server. Any bearer token
|
|
59
|
+
echoed in the body is redacted before it becomes the cause."""
|
|
60
|
+
status = resp.status_code
|
|
61
|
+
body_text = _redact(resp.text, token)
|
|
62
|
+
|
|
63
|
+
code = _status_code(status)
|
|
64
|
+
message = f"sandbox API request failed: HTTP {status} ({code})"
|
|
65
|
+
cause = body_text.strip() or f"HTTP {status}"
|
|
66
|
+
remediation = _status_remediation(status)
|
|
67
|
+
context: dict = {}
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
parsed = resp.json()
|
|
71
|
+
except Exception: # noqa: BLE001 not JSON; keep the text fallback
|
|
72
|
+
parsed = None
|
|
73
|
+
|
|
74
|
+
if isinstance(parsed, dict):
|
|
75
|
+
err = parsed.get("error")
|
|
76
|
+
if isinstance(err, dict):
|
|
77
|
+
# New structured envelope.
|
|
78
|
+
code = err.get("code") or code
|
|
79
|
+
message = err.get("message") or message
|
|
80
|
+
cause = _redact(err.get("cause", ""), token) or cause
|
|
81
|
+
remediation = err.get("remediation") or remediation
|
|
82
|
+
ctx = err.get("context")
|
|
83
|
+
if isinstance(ctx, dict):
|
|
84
|
+
context = ctx
|
|
85
|
+
elif isinstance(err, str):
|
|
86
|
+
# Legacy bare {"error": "msg"} shape.
|
|
87
|
+
cause = _redact(err, token) or cause
|
|
88
|
+
|
|
89
|
+
# Build the TYPED subclass for the code (issue #216): a caller branches on
|
|
90
|
+
# the exception type, never on the message. An unknown code falls back to the
|
|
91
|
+
# base AgentRunError inside error_for_code.
|
|
92
|
+
return error_for_code(
|
|
93
|
+
code,
|
|
94
|
+
message,
|
|
95
|
+
cause=cause,
|
|
96
|
+
remediation=remediation,
|
|
97
|
+
status=status,
|
|
98
|
+
context=context,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def raise_for_status(resp: httpx.Response, token: Optional[str] = None) -> None:
|
|
103
|
+
"""Raises AgentRunError on a non-2xx response, leaving 2xx untouched. Drop-in
|
|
104
|
+
for httpx Response.raise_for_status() but yields the structured type."""
|
|
105
|
+
if resp.is_success:
|
|
106
|
+
return
|
|
107
|
+
raise error_from_response(resp, token=token)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def raise_for_status_stream(resp: httpx.Response, token: Optional[str] = None) -> None:
|
|
111
|
+
"""Like raise_for_status, for a streaming Response whose body has not been
|
|
112
|
+
read. On a non-2xx status it reads the (small) error body so the structured
|
|
113
|
+
envelope can be parsed; on success it leaves the stream unread so the caller
|
|
114
|
+
iterates it normally."""
|
|
115
|
+
if resp.is_success:
|
|
116
|
+
return
|
|
117
|
+
resp.read()
|
|
118
|
+
raise error_from_response(resp, token=token)
|
mitos/_k8s.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Lazy accessor for the optional ``kubernetes`` client (issue #22).
|
|
2
|
+
|
|
3
|
+
The cluster-mode modules (``client``, ``aio``, ``sandbox``, ``workspace``) need
|
|
4
|
+
the official Kubernetes client, but direct mode (``mitos.direct`` over the
|
|
5
|
+
sandbox-server REST API) and the in-guest SDK (``mitos.guest``) speak only
|
|
6
|
+
httpx and must import with no Kubernetes installed. To keep ``import mitos`` and
|
|
7
|
+
``from mitos.direct import SandboxServer`` light, those modules import the
|
|
8
|
+
``kubernetes`` symbols through :func:`k8s` at the moment a cluster code path
|
|
9
|
+
runs, never at module import time. This mirrors the TypeScript SDK, whose
|
|
10
|
+
``@kubernetes/client-node`` is lazy-loaded so direct mode never pulls it in.
|
|
11
|
+
|
|
12
|
+
A missing ``kubernetes`` raises a clear, actionable AgentRunError naming the
|
|
13
|
+
install command, rather than a bare ModuleNotFoundError from deep in an import.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from mitos.errors import AgentRunError
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
# Annotation-only imports: TYPE_CHECKING is False at runtime, so these never
|
|
24
|
+
# force the kubernetes import for callers that only use direct mode.
|
|
25
|
+
from kubernetes import client as _client_mod
|
|
26
|
+
from kubernetes import config as _config_mod
|
|
27
|
+
from kubernetes.client.rest import ApiException as _ApiException
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _K8s:
|
|
31
|
+
"""The three kubernetes symbols the cluster modules use, bound lazily."""
|
|
32
|
+
|
|
33
|
+
__slots__ = ("client", "config", "ApiException")
|
|
34
|
+
|
|
35
|
+
def __init__(self, client, config, ApiException):
|
|
36
|
+
self.client = client
|
|
37
|
+
self.config = config
|
|
38
|
+
self.ApiException = ApiException
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_cached: "_K8s | None" = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def k8s() -> "_K8s":
|
|
45
|
+
"""Import and return the kubernetes client/config/ApiException, cached.
|
|
46
|
+
|
|
47
|
+
Called only from cluster code paths. If the optional ``kubernetes`` package
|
|
48
|
+
is not installed, raises an AgentRunError that names the fix instead of
|
|
49
|
+
letting a raw ModuleNotFoundError surface from an unexpected place."""
|
|
50
|
+
global _cached
|
|
51
|
+
if _cached is not None:
|
|
52
|
+
return _cached
|
|
53
|
+
try:
|
|
54
|
+
from kubernetes import client as k8s_client
|
|
55
|
+
from kubernetes import config as k8s_config
|
|
56
|
+
from kubernetes.client.rest import ApiException
|
|
57
|
+
except ModuleNotFoundError as exc:
|
|
58
|
+
raise AgentRunError(
|
|
59
|
+
"cluster mode requires the kubernetes client, which is not installed",
|
|
60
|
+
code="kubernetes_not_installed",
|
|
61
|
+
cause=f"importing the kubernetes package failed: {exc}",
|
|
62
|
+
remediation=(
|
|
63
|
+
"Install it with 'pip install kubernetes' or 'pip install mitos-run[k8s]'. "
|
|
64
|
+
"Direct mode (mitos.create / mitos.direct) and the in-guest SDK "
|
|
65
|
+
"(mitos.guest) do not need it."
|
|
66
|
+
),
|
|
67
|
+
) from exc
|
|
68
|
+
_cached = _K8s(k8s_client, k8s_config, ApiException)
|
|
69
|
+
return _cached
|