mirasoth-agentspace 0.1.0__tar.gz

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.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: mirasoth-agentspace
3
+ Version: 0.1.0
4
+ Summary: Python SDK and CLI for the Mirasoth AgentSpace API - isolated cloud workspaces for agent execution
5
+ Author: Mirasoth Inc.
6
+ License: Dual-licensed: AGPL-3.0-or-later or Mirasoth Commercial License
7
+ Project-URL: Homepage, https://github.com/mirasoth/agentspace
8
+ Keywords: agentspace,mirasoth,agent,workspace,sdk,cli
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+
24
+ # mirasoth-agentspace (Python)
25
+
26
+ Python SDK and CLI for the [Mirasoth AgentSpace](../README.md) API —
27
+ isolated cloud workspaces for agent execution.
28
+
29
+ - **Package**: `mirasoth-agentspace` (pip name) · imported as `agentspace`
30
+ - **CLI**: installs an `agentspace` console script, command-compatible with
31
+ the Go CLI (`cmd/agentspace`)
32
+ - **Zero runtime dependencies** — pure stdlib (`urllib`), Python 3.9+
33
+
34
+ ## Install
35
+
36
+ ```sh
37
+ pip install -e python/ # from the repo
38
+
39
+ # or, once published
40
+ pip install mirasoth-agentspace
41
+ ```
42
+
43
+ ## SDK quickstart
44
+
45
+ ```python
46
+ from agentspace import AgentSpace, ImageAttachment, TurnHints
47
+
48
+ api = AgentSpace("http://127.0.0.1:8620", "asp_...")
49
+
50
+ # workspace + files
51
+ ws = api.create_workspace("demo")
52
+ api.upload_file(ws.id, "docs/brief.md", b"# project brief")
53
+ entries = api.list_files(ws.id, recursive=True).entries
54
+
55
+ # streaming agent turn (SSE)
56
+ ses = api.create_session(ws.id, "main")
57
+ reply = api.run_turn(
58
+ ws.id, ses.id,
59
+ "Read docs/brief.md and write output/summary.md with a summary.",
60
+ on_event=lambda ev: print(ev.data, end="") if ev.type == "delta" else None,
61
+ )
62
+
63
+ # one-shot background task (fresh agent session)
64
+ task = api.run_task(ws.id, "Append an 'Appendix' section to output/summary.md.")
65
+ print(task.status, task.result)
66
+
67
+ # scoped handles: api.workspace(ws.id).list_files(...)
68
+ ws_handle = api.workspace(ws.id)
69
+ result = ws_handle.search_memory("project goals")
70
+ ```
71
+
72
+ ### Error handling
73
+
74
+ ```python
75
+ from agentspace import (
76
+ AgentSpaceError, APIError, NotFoundError, ConflictError,
77
+ RateLimitError, TurnError, TransportError,
78
+ )
79
+
80
+ try:
81
+ api.run_turn(ws.id, ses.id, "...")
82
+ except TurnError as e: # terminal SSE `error` event
83
+ print(e.user_message, e.error_code)
84
+ except ConflictError: # a turn is already in flight (409)
85
+ ...
86
+ except RateLimitError: # service-wide concurrent turn limit (429)
87
+ ...
88
+ ```
89
+
90
+ HTTP errors map to typed subclasses (`BadRequestError`, `NotFoundError`,
91
+ `ConflictError`, `RateLimitError`, `ServerError`, ...); every
92
+ `APIError` carries `.status`, `.message` and the decoded `.payload`
93
+ (`{code, message}`). `TransportError` covers connection failures.
94
+
95
+ ### Streaming events
96
+
97
+ `iter_session_events` / `iter_task_events` yield `StreamEvent(type, data)`
98
+ tuples — `status`, `delta`, `thinking_step`, then a terminal
99
+ `complete` / `error` (plus raw agent protocol `event` frames on task
100
+ streams). The server replays the last completed reply to the first late
101
+ subscriber, so prompt-then-attach never misses the answer.
102
+
103
+ ### Custom transport / testing
104
+
105
+ ```python
106
+ from agentspace import AgentSpace, UrllibTransport, HTTPResponse
107
+
108
+ class MyTransport(UrllibTransport):
109
+ def request(self, method, url, headers, body, timeout):
110
+ ... # proxy, logging, retry policy...
111
+ return HTTPResponse(200, {}, b"{}")
112
+
113
+ api = AgentSpace(base_url, api_key, transport=MyTransport())
114
+ ```
115
+
116
+ ## CLI
117
+
118
+ Command-compatible with the Go CLI — same output formats, same config
119
+ file (`~/.agentspace/config.json`), same env vars (`AGENTSPACE_URL`,
120
+ `AGENTSPACE_API_KEY`). Credentials resolve as: **flags > env > config**.
121
+
122
+ ```sh
123
+ agentspace config set http://127.0.0.1:8620 asp_...
124
+ agentspace whoami
125
+
126
+ agentspace workspaces create demo # alias: ws
127
+ agentspace ws ls --json # machine-readable output
128
+ agentspace ws rename <id> new-name
129
+ agentspace ws rm <id> # asks for confirmation (--yes to skip)
130
+
131
+ agentspace files ls -w <ws> [path] # alias: f
132
+ agentspace files find -w <ws> [path] # recursive
133
+ agentspace files upload local.txt docs/notes.txt -w <ws>
134
+ agentspace files download docs/notes.txt ./out -w <ws>
135
+ agentspace files mv docs/a.txt docs/b.txt -w <ws>
136
+ agentspace files mkdir newdir -w <ws>
137
+ agentspace files rm -r olddir -w <ws> # -r asks for confirmation
138
+
139
+ agentspace sessions create main -w <ws> # alias: ses
140
+ agentspace sessions ls -w <ws>
141
+ agentspace sessions log <ses> -w <ws> # persisted transcript
142
+ agentspace sessions reset <ses> -w <ws> # fresh agent context, files kept
143
+ agentspace sessions cancel <ses> -w <ws>
144
+
145
+ agentspace send "summarize docs/notes.txt" -w <ws> -s <ses> # prompt + stream
146
+ agentspace attach -w <ws> -s <ses> # live stream only
147
+
148
+ agentspace tasks create "clean up temp files" -w <ws> # alias: task/t
149
+ agentspace tasks get <task> -w <ws> # status + result
150
+ agentspace tasks attach <task> -w <ws> # live task events
151
+
152
+ agentspace memory search "deploy notes" -w <ws> -n 5 # pgvector semantic search
153
+ agentspace apikeys create ci # alias: keys
154
+ agentspace apikeys ls
155
+ agentspace apikeys rm <key-id>
156
+ ```
157
+
158
+ Global flags (usable anywhere): `--url`, `--api-key`, `--timeout`,
159
+ `--json`, `-v/--verbose` (status + raw agent events on stderr),
160
+ `-y/--yes` (skip destructive confirmations). In non-interactive shells,
161
+ destructive commands (`ws rm`, `files rm -r`) refuse to run unless
162
+ `--yes` is passed.
163
+
164
+ Exit codes: `0` success · `1` API/stream error · `2` usage / missing
165
+ credentials / declined action.
166
+
167
+ ## Layout & development
168
+
169
+ ```
170
+ python/
171
+ agentspace/ # the SDK package (import name: agentspace)
172
+ client.py # AgentSpace client: REST + SSE + handles
173
+ models.py # typed dataclass wire models
174
+ errors.py # exception hierarchy
175
+ sse.py # SSE parsing (server Go-quotes data fields)
176
+ cli.py # the `agentspace` CLI
177
+ _config.py # CLI credentials (shared with the Go CLI)
178
+ tests/ # pytest suite; no network needed (scripted fake transport)
179
+ ```
180
+
181
+ ```sh
182
+ cd python
183
+ python -m pytest # 76 tests
184
+ pip install -e .[dev] # editable install with dev extras
185
+ ```
186
+
187
+ End-to-end example against a live stack: `examples/python/walkthrough.py`
188
+ in the repo root (drives this SDK; also doubles as a smoke test).
@@ -0,0 +1,165 @@
1
+ # mirasoth-agentspace (Python)
2
+
3
+ Python SDK and CLI for the [Mirasoth AgentSpace](../README.md) API —
4
+ isolated cloud workspaces for agent execution.
5
+
6
+ - **Package**: `mirasoth-agentspace` (pip name) · imported as `agentspace`
7
+ - **CLI**: installs an `agentspace` console script, command-compatible with
8
+ the Go CLI (`cmd/agentspace`)
9
+ - **Zero runtime dependencies** — pure stdlib (`urllib`), Python 3.9+
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ pip install -e python/ # from the repo
15
+
16
+ # or, once published
17
+ pip install mirasoth-agentspace
18
+ ```
19
+
20
+ ## SDK quickstart
21
+
22
+ ```python
23
+ from agentspace import AgentSpace, ImageAttachment, TurnHints
24
+
25
+ api = AgentSpace("http://127.0.0.1:8620", "asp_...")
26
+
27
+ # workspace + files
28
+ ws = api.create_workspace("demo")
29
+ api.upload_file(ws.id, "docs/brief.md", b"# project brief")
30
+ entries = api.list_files(ws.id, recursive=True).entries
31
+
32
+ # streaming agent turn (SSE)
33
+ ses = api.create_session(ws.id, "main")
34
+ reply = api.run_turn(
35
+ ws.id, ses.id,
36
+ "Read docs/brief.md and write output/summary.md with a summary.",
37
+ on_event=lambda ev: print(ev.data, end="") if ev.type == "delta" else None,
38
+ )
39
+
40
+ # one-shot background task (fresh agent session)
41
+ task = api.run_task(ws.id, "Append an 'Appendix' section to output/summary.md.")
42
+ print(task.status, task.result)
43
+
44
+ # scoped handles: api.workspace(ws.id).list_files(...)
45
+ ws_handle = api.workspace(ws.id)
46
+ result = ws_handle.search_memory("project goals")
47
+ ```
48
+
49
+ ### Error handling
50
+
51
+ ```python
52
+ from agentspace import (
53
+ AgentSpaceError, APIError, NotFoundError, ConflictError,
54
+ RateLimitError, TurnError, TransportError,
55
+ )
56
+
57
+ try:
58
+ api.run_turn(ws.id, ses.id, "...")
59
+ except TurnError as e: # terminal SSE `error` event
60
+ print(e.user_message, e.error_code)
61
+ except ConflictError: # a turn is already in flight (409)
62
+ ...
63
+ except RateLimitError: # service-wide concurrent turn limit (429)
64
+ ...
65
+ ```
66
+
67
+ HTTP errors map to typed subclasses (`BadRequestError`, `NotFoundError`,
68
+ `ConflictError`, `RateLimitError`, `ServerError`, ...); every
69
+ `APIError` carries `.status`, `.message` and the decoded `.payload`
70
+ (`{code, message}`). `TransportError` covers connection failures.
71
+
72
+ ### Streaming events
73
+
74
+ `iter_session_events` / `iter_task_events` yield `StreamEvent(type, data)`
75
+ tuples — `status`, `delta`, `thinking_step`, then a terminal
76
+ `complete` / `error` (plus raw agent protocol `event` frames on task
77
+ streams). The server replays the last completed reply to the first late
78
+ subscriber, so prompt-then-attach never misses the answer.
79
+
80
+ ### Custom transport / testing
81
+
82
+ ```python
83
+ from agentspace import AgentSpace, UrllibTransport, HTTPResponse
84
+
85
+ class MyTransport(UrllibTransport):
86
+ def request(self, method, url, headers, body, timeout):
87
+ ... # proxy, logging, retry policy...
88
+ return HTTPResponse(200, {}, b"{}")
89
+
90
+ api = AgentSpace(base_url, api_key, transport=MyTransport())
91
+ ```
92
+
93
+ ## CLI
94
+
95
+ Command-compatible with the Go CLI — same output formats, same config
96
+ file (`~/.agentspace/config.json`), same env vars (`AGENTSPACE_URL`,
97
+ `AGENTSPACE_API_KEY`). Credentials resolve as: **flags > env > config**.
98
+
99
+ ```sh
100
+ agentspace config set http://127.0.0.1:8620 asp_...
101
+ agentspace whoami
102
+
103
+ agentspace workspaces create demo # alias: ws
104
+ agentspace ws ls --json # machine-readable output
105
+ agentspace ws rename <id> new-name
106
+ agentspace ws rm <id> # asks for confirmation (--yes to skip)
107
+
108
+ agentspace files ls -w <ws> [path] # alias: f
109
+ agentspace files find -w <ws> [path] # recursive
110
+ agentspace files upload local.txt docs/notes.txt -w <ws>
111
+ agentspace files download docs/notes.txt ./out -w <ws>
112
+ agentspace files mv docs/a.txt docs/b.txt -w <ws>
113
+ agentspace files mkdir newdir -w <ws>
114
+ agentspace files rm -r olddir -w <ws> # -r asks for confirmation
115
+
116
+ agentspace sessions create main -w <ws> # alias: ses
117
+ agentspace sessions ls -w <ws>
118
+ agentspace sessions log <ses> -w <ws> # persisted transcript
119
+ agentspace sessions reset <ses> -w <ws> # fresh agent context, files kept
120
+ agentspace sessions cancel <ses> -w <ws>
121
+
122
+ agentspace send "summarize docs/notes.txt" -w <ws> -s <ses> # prompt + stream
123
+ agentspace attach -w <ws> -s <ses> # live stream only
124
+
125
+ agentspace tasks create "clean up temp files" -w <ws> # alias: task/t
126
+ agentspace tasks get <task> -w <ws> # status + result
127
+ agentspace tasks attach <task> -w <ws> # live task events
128
+
129
+ agentspace memory search "deploy notes" -w <ws> -n 5 # pgvector semantic search
130
+ agentspace apikeys create ci # alias: keys
131
+ agentspace apikeys ls
132
+ agentspace apikeys rm <key-id>
133
+ ```
134
+
135
+ Global flags (usable anywhere): `--url`, `--api-key`, `--timeout`,
136
+ `--json`, `-v/--verbose` (status + raw agent events on stderr),
137
+ `-y/--yes` (skip destructive confirmations). In non-interactive shells,
138
+ destructive commands (`ws rm`, `files rm -r`) refuse to run unless
139
+ `--yes` is passed.
140
+
141
+ Exit codes: `0` success · `1` API/stream error · `2` usage / missing
142
+ credentials / declined action.
143
+
144
+ ## Layout & development
145
+
146
+ ```
147
+ python/
148
+ agentspace/ # the SDK package (import name: agentspace)
149
+ client.py # AgentSpace client: REST + SSE + handles
150
+ models.py # typed dataclass wire models
151
+ errors.py # exception hierarchy
152
+ sse.py # SSE parsing (server Go-quotes data fields)
153
+ cli.py # the `agentspace` CLI
154
+ _config.py # CLI credentials (shared with the Go CLI)
155
+ tests/ # pytest suite; no network needed (scripted fake transport)
156
+ ```
157
+
158
+ ```sh
159
+ cd python
160
+ python -m pytest # 76 tests
161
+ pip install -e .[dev] # editable install with dev extras
162
+ ```
163
+
164
+ End-to-end example against a live stack: `examples/python/walkthrough.py`
165
+ in the repo root (drives this SDK; also doubles as a smoke test).
@@ -0,0 +1,118 @@
1
+ """agentspace — Python SDK and CLI for the Mirasoth AgentSpace API.
2
+
3
+ Distributed as ``mirasoth-agentspace`` on PyPI; imported as ``agentspace``.
4
+ Standalone cloud service providing isolated workspaces for agent
5
+ execution: workspace/file CRUD, streaming agent sessions (SSE), one-shot
6
+ tasks, semantic memory search — pure stdlib, zero runtime dependencies.
7
+
8
+ Quickstart::
9
+
10
+ from agentspace import AgentSpace
11
+
12
+ api = AgentSpace("http://127.0.0.1:8620", "asp_...")
13
+ ws = api.create_workspace("demo")
14
+ api.upload_file(ws.id, "docs/brief.md", b"# brief")
15
+
16
+ ses = api.create_session(ws.id, "main")
17
+ reply = api.run_turn(ws.id, ses.id, "Read docs/brief.md and summarize it.")
18
+
19
+ The package also installs an ``agentspace`` console script (see
20
+ :mod:`agentspace.cli`).
21
+ """
22
+
23
+ from .client import (
24
+ DEFAULT_TIMEOUT,
25
+ AgentSpace,
26
+ AgentSpaceClient,
27
+ HTTPResponse,
28
+ SessionHandle,
29
+ TaskHandle,
30
+ Transport,
31
+ UrllibTransport,
32
+ WorkspaceHandle,
33
+ )
34
+ from .errors import (
35
+ AgentSpaceError,
36
+ APIError,
37
+ BadRequestError,
38
+ ConflictError,
39
+ ForbiddenError,
40
+ NotFoundError,
41
+ RateLimitError,
42
+ ServerError,
43
+ TransportError,
44
+ TurnError,
45
+ UnauthorizedError,
46
+ )
47
+ from .models import (
48
+ TASK_TERMINAL_STATUSES,
49
+ ApiKey,
50
+ ApiKeyCreated,
51
+ ApiKeyList,
52
+ FileEntry,
53
+ FileEntryList,
54
+ Identity,
55
+ ImageAttachment,
56
+ Session,
57
+ SessionList,
58
+ SessionMessage,
59
+ SessionMessageList,
60
+ Task,
61
+ TaskList,
62
+ TurnAccepted,
63
+ TurnHints,
64
+ Workspace,
65
+ WorkspaceList,
66
+ )
67
+ from .sse import StreamEvent, parse_error_payload, parse_sse_stream
68
+
69
+ __version__ = "0.1.0"
70
+
71
+ __all__ = [
72
+ "__version__",
73
+ # client
74
+ "AgentSpace",
75
+ "AgentSpaceClient",
76
+ "WorkspaceHandle",
77
+ "SessionHandle",
78
+ "TaskHandle",
79
+ "Transport",
80
+ "UrllibTransport",
81
+ "HTTPResponse",
82
+ "DEFAULT_TIMEOUT",
83
+ # errors
84
+ "AgentSpaceError",
85
+ "APIError",
86
+ "BadRequestError",
87
+ "UnauthorizedError",
88
+ "ForbiddenError",
89
+ "NotFoundError",
90
+ "ConflictError",
91
+ "RateLimitError",
92
+ "ServerError",
93
+ "TransportError",
94
+ "TurnError",
95
+ # models
96
+ "Identity",
97
+ "Workspace",
98
+ "WorkspaceList",
99
+ "FileEntry",
100
+ "FileEntryList",
101
+ "Session",
102
+ "SessionList",
103
+ "SessionMessage",
104
+ "SessionMessageList",
105
+ "TurnAccepted",
106
+ "TurnHints",
107
+ "Task",
108
+ "TaskList",
109
+ "TASK_TERMINAL_STATUSES",
110
+ "ApiKey",
111
+ "ApiKeyCreated",
112
+ "ApiKeyList",
113
+ "ImageAttachment",
114
+ # sse
115
+ "StreamEvent",
116
+ "parse_sse_stream",
117
+ "parse_error_payload",
118
+ ]
@@ -0,0 +1,8 @@
1
+ """``python -m agentspace`` — delegates to the ``agentspace`` CLI."""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1,57 @@
1
+ """CLI credential storage, file-compatible with the Go CLI.
2
+
3
+ The Go CLI (``cmd/agentspace``) and this Python CLI share the same config
4
+ file (``~/.agentspace/config.json`` with ``{"url": ..., "apiKey": ...}``)
5
+ and the same environment variables (``AGENTSPACE_URL`` /
6
+ ``AGENTSPACE_API_KEY``), so either CLI can pick up where the other left
7
+ off. Environment variables always win over the file.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ from pathlib import Path
15
+ from typing import Tuple
16
+
17
+ ENV_URL = "AGENTSPACE_URL"
18
+ ENV_API_KEY = "AGENTSPACE_API_KEY"
19
+ ENV_CONFIG = "AGENTSPACE_CONFIG"
20
+
21
+
22
+ def config_file() -> Path:
23
+ """Location of the shared CLI config file."""
24
+ override = os.environ.get(ENV_CONFIG)
25
+ if override:
26
+ return Path(override)
27
+ return Path.home() / ".agentspace" / "config.json"
28
+
29
+
30
+ def load_config() -> Tuple[str, str]:
31
+ """Resolve credentials: env vars win over the config file. May be empty."""
32
+ stored: dict = {}
33
+ path = config_file()
34
+ try:
35
+ stored = json.loads(path.read_text(encoding="utf-8"))
36
+ if not isinstance(stored, dict):
37
+ stored = {}
38
+ except (OSError, ValueError):
39
+ stored = {}
40
+ url = os.environ.get(ENV_URL, "") or str(stored.get("url") or "")
41
+ api_key = os.environ.get(ENV_API_KEY, "") or str(stored.get("apiKey") or "")
42
+ return url, api_key
43
+
44
+
45
+ def save_config(url: str, api_key: str) -> Path:
46
+ """Persist credentials to the shared config file (created 0600)."""
47
+ path = config_file()
48
+ path.parent.mkdir(parents=True, exist_ok=True)
49
+ path.write_text(
50
+ json.dumps({"url": url, "apiKey": api_key}, indent=2) + "\n",
51
+ encoding="utf-8",
52
+ )
53
+ try:
54
+ path.chmod(0o600)
55
+ except OSError:
56
+ pass # e.g. some filesystems ignore POSIX modes
57
+ return path