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.
- mirasoth_agentspace-0.1.0/PKG-INFO +188 -0
- mirasoth_agentspace-0.1.0/README.md +165 -0
- mirasoth_agentspace-0.1.0/agentspace/__init__.py +118 -0
- mirasoth_agentspace-0.1.0/agentspace/__main__.py +8 -0
- mirasoth_agentspace-0.1.0/agentspace/_config.py +57 -0
- mirasoth_agentspace-0.1.0/agentspace/cli.py +838 -0
- mirasoth_agentspace-0.1.0/agentspace/client.py +941 -0
- mirasoth_agentspace-0.1.0/agentspace/errors.py +129 -0
- mirasoth_agentspace-0.1.0/agentspace/models.py +388 -0
- mirasoth_agentspace-0.1.0/agentspace/py.typed +0 -0
- mirasoth_agentspace-0.1.0/agentspace/sse.py +111 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/PKG-INFO +188 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/SOURCES.txt +21 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/dependency_links.txt +1 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/entry_points.txt +2 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/requires.txt +3 -0
- mirasoth_agentspace-0.1.0/mirasoth_agentspace.egg-info/top_level.txt +1 -0
- mirasoth_agentspace-0.1.0/pyproject.toml +45 -0
- mirasoth_agentspace-0.1.0/setup.cfg +4 -0
- mirasoth_agentspace-0.1.0/tests/test_cli.py +408 -0
- mirasoth_agentspace-0.1.0/tests/test_client.py +563 -0
- mirasoth_agentspace-0.1.0/tests/test_models.py +135 -0
- mirasoth_agentspace-0.1.0/tests/test_sse.py +79 -0
|
@@ -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,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
|