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/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()