minimax-code-sdk 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.
- minimax_code/__init__.py +66 -0
- minimax_code/_args.py +90 -0
- minimax_code/_cli.py +205 -0
- minimax_code/_items.py +71 -0
- minimax_code/_protocol.py +56 -0
- minimax_code/client.py +76 -0
- minimax_code/errors.py +167 -0
- minimax_code/py.typed +0 -0
- minimax_code/sync.py +71 -0
- minimax_code/thread.py +480 -0
- minimax_code/types.py +192 -0
- minimax_code_sdk-0.1.0.dist-info/METADATA +174 -0
- minimax_code_sdk-0.1.0.dist-info/RECORD +15 -0
- minimax_code_sdk-0.1.0.dist-info/WHEEL +4 -0
- minimax_code_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
minimax_code/errors.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""SDK errors. Mirrors ``packages/code-sdk/src/errors.ts``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .types import ExecResult, ThreadItem
|
|
9
|
+
|
|
10
|
+
#: Exit codes of ``mcode exec`` (``MINIMAX_CODE_EXEC_EXIT_CODES`` in the CLI).
|
|
11
|
+
MCODE_EXEC_EXIT_CODES: dict[str, int] = {
|
|
12
|
+
"success": 0,
|
|
13
|
+
"invocation": 2,
|
|
14
|
+
"config": 3,
|
|
15
|
+
"runtime": 4,
|
|
16
|
+
"timeout": 6,
|
|
17
|
+
"limit": 7,
|
|
18
|
+
"internal": 70,
|
|
19
|
+
"cancelled": 130,
|
|
20
|
+
"broken_pipe": 141,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
MCODE_INSTALL_HINT = "npm install -g @minimax-ai/code@latest"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MCodeError(Exception):
|
|
27
|
+
"""Base class for every error raised by the SDK."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
*,
|
|
33
|
+
exit_code: int | None = None,
|
|
34
|
+
stderr: str = "",
|
|
35
|
+
session_id: str | None = None,
|
|
36
|
+
result: ExecResult | None = None,
|
|
37
|
+
items: Sequence[ThreadItem] = (),
|
|
38
|
+
) -> None:
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
self.exit_code = exit_code
|
|
41
|
+
#: Tail of the CLI stderr, for diagnostics only.
|
|
42
|
+
self.stderr = stderr
|
|
43
|
+
self.session_id = session_id
|
|
44
|
+
self.result = result
|
|
45
|
+
self.items = list(items)
|
|
46
|
+
|
|
47
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
48
|
+
# The default reduce calls ``cls(*self.args)``, which breaks subclasses whose
|
|
49
|
+
# ``__init__`` takes other arguments. Rebuild from ``args`` and restore the attributes.
|
|
50
|
+
return (_restore_error, (type(self), self.args), self.__dict__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _restore_error(cls: type[MCodeError], args: tuple[Any, ...]) -> MCodeError:
|
|
54
|
+
"""Unpickles an ``MCodeError`` without calling its ``__init__``."""
|
|
55
|
+
return cls.__new__(cls, *args)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MCodeInvocationError(MCodeError):
|
|
59
|
+
"""Invalid SDK options, or the CLI rejected the invocation (exit code 2)."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MCodeRunError(MCodeError):
|
|
63
|
+
"""The Turn ended with ``failed``, ``timeout`` or ``limit_exceeded``."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, message: str, *, status: str, category: str | None = None, **kw: Any):
|
|
66
|
+
super().__init__(message, **kw)
|
|
67
|
+
self.status = status
|
|
68
|
+
self.category = category
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class MCodeAbortError(MCodeError):
|
|
72
|
+
"""The Turn was cancelled."""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class MCodeProtocolError(MCodeError):
|
|
76
|
+
"""The CLI produced output the SDK cannot understand."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class MCodeNotFoundError(MCodeError):
|
|
80
|
+
"""The ``mcode`` CLI could not be found."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MCodeVersionError(MCodeError):
|
|
84
|
+
"""The CLI is too old or speaks an unsupported schema version."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ThreadBusyError(MCodeError):
|
|
88
|
+
"""A Thread already has a Turn in progress."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, thread_id: str | None) -> None:
|
|
91
|
+
super().__init__(f"Thread {thread_id or '(new)'} already has a Turn in progress.")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_STATUS_BY_EXIT_CODE: dict[int, tuple[str, str | None]] = {
|
|
95
|
+
3: ("failed", "config"),
|
|
96
|
+
4: ("failed", "runtime"),
|
|
97
|
+
6: ("timeout", None),
|
|
98
|
+
7: ("limit_exceeded", None),
|
|
99
|
+
70: ("failed", "internal"),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def error_for_result(
|
|
104
|
+
result: ExecResult, items: Sequence[ThreadItem], *, exit_code: int | None, stderr: str
|
|
105
|
+
) -> MCodeError:
|
|
106
|
+
common: dict[str, Any] = {
|
|
107
|
+
"exit_code": exit_code,
|
|
108
|
+
"stderr": stderr,
|
|
109
|
+
"session_id": result["sessionId"],
|
|
110
|
+
"result": result,
|
|
111
|
+
"items": items,
|
|
112
|
+
}
|
|
113
|
+
if result["status"] == "cancelled":
|
|
114
|
+
return MCodeAbortError("The MCode run was cancelled.", **common)
|
|
115
|
+
status = "failed" if result["status"] == "succeeded" else result["status"]
|
|
116
|
+
error: Any = result.get("error")
|
|
117
|
+
if not isinstance(error, dict):
|
|
118
|
+
error = {}
|
|
119
|
+
message = error.get("message")
|
|
120
|
+
reason = message if isinstance(message, str) and message else f"status {result['status']}"
|
|
121
|
+
category = error.get("category")
|
|
122
|
+
if not isinstance(category, str):
|
|
123
|
+
category = None
|
|
124
|
+
return MCodeRunError(
|
|
125
|
+
f"The MCode run ended with {status}: {reason}", status=status, category=category, **common
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def error_for_exit(
|
|
130
|
+
*,
|
|
131
|
+
exit_code: int | None,
|
|
132
|
+
stderr: str,
|
|
133
|
+
items: Sequence[ThreadItem],
|
|
134
|
+
session_id: str | None,
|
|
135
|
+
timed_out: bool = False,
|
|
136
|
+
) -> MCodeError:
|
|
137
|
+
"""Maps a CLI exit without ``exec.completed`` to an error. ``timed_out`` means the SDK's
|
|
138
|
+
``timeout_ms`` backstop killed the CLI, which is a timeout and not a cancellation."""
|
|
139
|
+
lines = [line.strip() for line in stderr.splitlines() if line.strip()]
|
|
140
|
+
reason = lines[-1] if lines else f"exit code {exit_code}"
|
|
141
|
+
common: dict[str, Any] = {
|
|
142
|
+
"exit_code": exit_code,
|
|
143
|
+
"stderr": stderr,
|
|
144
|
+
"items": items,
|
|
145
|
+
"session_id": session_id,
|
|
146
|
+
}
|
|
147
|
+
if timed_out:
|
|
148
|
+
return MCodeRunError(
|
|
149
|
+
"The MCode run exceeded timeout_ms and was force-stopped by the SDK.",
|
|
150
|
+
status="timeout",
|
|
151
|
+
**common,
|
|
152
|
+
)
|
|
153
|
+
if exit_code == MCODE_EXEC_EXIT_CODES["invocation"]:
|
|
154
|
+
return MCodeInvocationError(f"mcode rejected the invocation: {reason}", **common)
|
|
155
|
+
if exit_code == MCODE_EXEC_EXIT_CODES["cancelled"] or (exit_code is not None and exit_code < 0):
|
|
156
|
+
return MCodeAbortError("The MCode run was cancelled.", **common)
|
|
157
|
+
if exit_code == MCODE_EXEC_EXIT_CODES["broken_pipe"]:
|
|
158
|
+
return MCodeProtocolError(f"mcode reported a broken output pipe: {reason}", **common)
|
|
159
|
+
if exit_code == 0:
|
|
160
|
+
return MCodeProtocolError("mcode exited without emitting exec.completed.", **common)
|
|
161
|
+
status, category = _STATUS_BY_EXIT_CODE.get(exit_code or -1, ("failed", "internal"))
|
|
162
|
+
return MCodeRunError(
|
|
163
|
+
f"The MCode run ended without a result: {reason}",
|
|
164
|
+
status=status,
|
|
165
|
+
category=category,
|
|
166
|
+
**common,
|
|
167
|
+
)
|
minimax_code/py.typed
ADDED
|
File without changes
|
minimax_code/sync.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Blocking wrappers around the async API, for code that has no running event loop.
|
|
2
|
+
|
|
3
|
+
They cannot be called where an event loop is already running, such as a Jupyter notebook cell or
|
|
4
|
+
an ``async def`` function (``RuntimeError: Cannot run the event loop while another loop is
|
|
5
|
+
running``); use the async API with ``await`` there.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from .client import MCode
|
|
15
|
+
from .thread import Thread
|
|
16
|
+
from .types import MCodeOptions, RunOptions, ThreadEvent, ThreadOptions, Turn
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SyncThread:
|
|
20
|
+
def __init__(self, loop: asyncio.AbstractEventLoop, thread: Thread) -> None:
|
|
21
|
+
self._loop = loop
|
|
22
|
+
self._thread = thread
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def id(self) -> str | None:
|
|
26
|
+
return self._thread.id
|
|
27
|
+
|
|
28
|
+
def run(self, input: str, options: RunOptions | None = None) -> Turn:
|
|
29
|
+
return self._loop.run_until_complete(self._thread.run(input, options))
|
|
30
|
+
|
|
31
|
+
def run_streamed(self, input: str, options: RunOptions | None = None) -> Iterator[ThreadEvent]:
|
|
32
|
+
"""Yields events; closing the iterator early cancels the Turn."""
|
|
33
|
+
streamed = self._loop.run_until_complete(self._thread.run_streamed(input, options))
|
|
34
|
+
events = streamed.events
|
|
35
|
+
try:
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
yield self._loop.run_until_complete(events.__anext__())
|
|
39
|
+
except StopAsyncIteration:
|
|
40
|
+
return
|
|
41
|
+
finally:
|
|
42
|
+
self._loop.run_until_complete(streamed.aclose())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SyncMCode:
|
|
46
|
+
"""Blocking client. Owns a private event loop; call ``close()`` (or use ``with``) when done."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, options: MCodeOptions | None = None) -> None:
|
|
49
|
+
self._loop = asyncio.new_event_loop()
|
|
50
|
+
self._client = MCode(options)
|
|
51
|
+
|
|
52
|
+
def start_thread(self, options: ThreadOptions | None = None) -> SyncThread:
|
|
53
|
+
return SyncThread(self._loop, self._client.start_thread(options))
|
|
54
|
+
|
|
55
|
+
def resume_thread(self, thread_id: str, options: ThreadOptions | None = None) -> SyncThread:
|
|
56
|
+
"""Continues an existing Session; pass the ``cwd`` it was created with (see
|
|
57
|
+
``MCode.resume_thread``)."""
|
|
58
|
+
return SyncThread(self._loop, self._client.resume_thread(thread_id, options))
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
if not self._loop.is_closed():
|
|
62
|
+
# Let subprocess transports finish closing before the loop goes away.
|
|
63
|
+
self._loop.run_until_complete(asyncio.sleep(0.05))
|
|
64
|
+
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
|
|
65
|
+
self._loop.close()
|
|
66
|
+
|
|
67
|
+
def __enter__(self) -> SyncMCode:
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def __exit__(self, *exc: Any) -> None:
|
|
71
|
+
self.close()
|