esoul 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.
esoul-0.1.0/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ # Build artifacts
2
+ dist/
3
+ build/
4
+ *.egg-info/
5
+ *.egg
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+ .ruff_cache/
14
+ .coverage
15
+ htmlcov/
16
+
17
+ # Virtual envs
18
+ .venv/
19
+ venv/
20
+ env/
21
+
22
+ # IDE
23
+ .idea/
24
+ .vscode/
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
5
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-05-17
10
+
11
+ ### Added
12
+ - **`client.agents`** — SDK-driven agent_builder invocation. Primary
13
+ surface `agents.invoke(workspace_id, agent, input=..., images=...)`
14
+ where `agent` is a nodeId (uuid) or instanceName (case-insensitive).
15
+ Convenience layer `agents.invoke_pin(pinned_agent_id, ...)` for
16
+ cross-workspace pins on the caller's own account. Polling handle
17
+ with `wait(timeout, on_question, on_approval)` (exponential backoff
18
+ 2s → 10s, callbacks dedup'd by questionId / summary). `agents.get`,
19
+ `agents.list`, `handle.cancel`.
20
+ - **`client.questions`** — workspace HIL queue. Programs can ask
21
+ questions via `questions.ask(workspace_id, "...", default_on_timeout=)`
22
+ which blocks until a human answers via the workspace's header drawer.
23
+ Also `ask_async`, `wait_for_answer`, `list`, `get`, `answer`, `cancel`.
24
+ - Async parity for both resources: `AsyncAgentsResource`,
25
+ `AsyncInvocationHandle`, `AsyncQuestionsResource`.
26
+ - New typed dataclasses: `InvocationStatus`, `InvocationResult`,
27
+ `Question`, `Answer`.
28
+ - New typed exceptions: `InvocationTimeout`, `InvocationError`,
29
+ `QuestionTimeout`, `QuestionAlreadyResolved`. Mapped from server
30
+ error codes `agent_not_found`, `agent_ambiguous`, `agent_invalid_type`,
31
+ `invocation_*`, `pinned_agent_*`, `question_*`.
32
+ - Initial sync `Esoul` and async `AsyncEsoul` clients.
33
+ - Credential auto-detection: explicit kwarg → `ESOUL_TOKEN` env →
34
+ `/var/run/esoul/token` (sandbox) → `~/.config/esoul/credentials` (PAT).
35
+ - Low-level dispatch surface: `dispatch_event`, `dispatch_batch`,
36
+ `read_state`, `describe`, `refresh_token`.
37
+ - Drive proxy resource: `drive.list_folder`, `drive.read_file`,
38
+ `drive.upload_file`.
39
+ - Typed exception hierarchy mapped from server `error.code`:
40
+ `AuthError`, `WorkspaceAccessDenied`, `AppNotFoundError`,
41
+ `EventNotRegistered`, `IdempotencyConflict`, `RateLimitError`,
42
+ `DriveNotConnected`, `APIError`.
43
+ - Auto-generated `Idempotency-Key` (UUID v4 per call), reused across
44
+ retries; caller can override via `idempotency_key=` kwarg.
45
+ - Retry-on-5xx + network-error policy with exponential backoff + jitter.
46
+ 4xx errors don't retry. 429 respects `Retry-After`.
47
+ - Background token-refresh for sandbox JWTs (60s before expiry). PAT
48
+ tokens don't refresh.
49
+ - `py.typed` marker for PEP 561 / mypy compatibility.
50
+
51
+ ### Not yet shipped
52
+ - Per-app typed resources (`spreadsheet`, `notes`, etc.) — landing as
53
+ codegen pipeline ships, one app at a time.
54
+ - Batch context manager (`with client.batch():`) for client-side id
55
+ pre-generation in multi-event flows.
56
+ - Per-event return shapes (depends on server adding `returnShape` to
57
+ event definitions).
esoul-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ExternalSoul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
esoul-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: esoul
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the ExternalSoul platform — programmatic workspace mutation, event-sourced, audit-logged.
5
+ Project-URL: Homepage, https://github.com/vyomkeshj/esoul-python
6
+ Project-URL: Documentation, https://externalsoul.com/sdk-docs/llm-reference.md
7
+ Project-URL: Repository, https://github.com/vyomkeshj/esoul-python
8
+ Project-URL: Changelog, https://github.com/vyomkeshj/esoul-python/blob/main/CHANGELOG.md
9
+ Author-email: ExternalSoul <hey@vyomkeshj.com>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 ExternalSoul
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: automation,esoul,event-sourcing,externalsoul,kinetic,workspace
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Programming Language :: Python :: 3.13
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.9
46
+ Requires-Dist: httpx<1.0,>=0.27
47
+ Requires-Dist: pydantic<3.0,>=2.0
48
+ Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
49
+ Provides-Extra: dev
50
+ Requires-Dist: build>=1.0; extra == 'dev'
51
+ Requires-Dist: mypy>=1.10; extra == 'dev'
52
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
53
+ Requires-Dist: pytest>=8.0; extra == 'dev'
54
+ Requires-Dist: respx>=0.21; extra == 'dev'
55
+ Requires-Dist: ruff>=0.6; extra == 'dev'
56
+ Requires-Dist: twine>=5.0; extra == 'dev'
57
+ Description-Content-Type: text/markdown
58
+
59
+ # esoul — Python SDK for ExternalSoul
60
+
61
+ `esoul` is the official Python SDK for the **ExternalSoul** platform. It lets
62
+ scripts mutate workspace state from anywhere Python runs — inside the
63
+ platform's E2B sandboxes, on a data scientist's laptop, in a Colab notebook,
64
+ in CI, in a scheduled cron job.
65
+
66
+ ```bash
67
+ pip install esoul
68
+ ```
69
+
70
+ ```python
71
+ import esoul
72
+
73
+ client = esoul.Esoul() # auto-detects credentials
74
+ state = client.describe() # what workspaces + apps + events are available
75
+ print(state.session.workspace_ids)
76
+
77
+ # Low-level dispatch
78
+ result = client.dispatch_event(
79
+ app_id="app_abc123",
80
+ event_name="spreadsheet_add_row",
81
+ event_data={"cells": {"name": "Alice", "email": "a@example.com"}},
82
+ )
83
+ print(result.event_id, result.sequence_num)
84
+ ```
85
+
86
+ ## Why an SDK?
87
+
88
+ ExternalSoul workspaces are **event-sourced** — every state mutation is a
89
+ typed event on a per-workspace timeline, scrubbable and forkable. The UI,
90
+ agents, and now scripts all dispatch through the **same** reducers; events
91
+ are the source of truth, audit logging is automatic.
92
+
93
+ The SDK is the platform's universal binding for **scripted workspace
94
+ operations**. Common use cases:
95
+
96
+ - Bulk-import 1000 rows into a spreadsheet (one event, not 1000 LLM tool calls)
97
+ - Run an OpenCV pipeline over Drive images and write masks back
98
+ - Programmatic kanban / contacts load from a CSV
99
+ - Notebook-as-workspace-builder
100
+ - Any agent or human writing Python to mutate app state
101
+
102
+ ## Authentication
103
+
104
+ `esoul.Esoul()` auto-detects credentials in this order:
105
+
106
+ 1. Explicit `token=` kwarg
107
+ 2. `ESOUL_TOKEN` environment variable
108
+ 3. `/var/run/esoul/token` (the file mode 0600 token written by every E2B
109
+ sandbox at boot — `Esoul()` inside a sandbox Just Works)
110
+ 4. `~/.config/esoul/credentials` (TOML file with a `[default]` section
111
+ containing `token = "esoul_pat_..."`)
112
+
113
+ For off-platform use (laptop, CI), create a **Personal Access Token** in
114
+ workspace settings → "Access Tokens", pick the workspaces it can mutate,
115
+ copy the token (shown once), and either:
116
+
117
+ ```bash
118
+ # Export inline:
119
+ export ESOUL_TOKEN="esoul_pat_..."
120
+
121
+ # Or write to ~/.config/esoul/credentials:
122
+ mkdir -p ~/.config/esoul
123
+ cat > ~/.config/esoul/credentials <<EOF
124
+ [default]
125
+ token = "esoul_pat_..."
126
+ EOF
127
+ chmod 600 ~/.config/esoul/credentials
128
+ ```
129
+
130
+ ## Workspace isolation
131
+
132
+ A given credential is scoped to one or more specific workspaces. Cross-
133
+ workspace dispatch is **structurally impossible** — not a permission check
134
+ in your script, an architectural invariant of the server.
135
+
136
+ ## Idempotency
137
+
138
+ Every write is automatically idempotent. The SDK generates a UUID per call
139
+ and reuses it across retries; the server caches the response under
140
+ `(session, key)` for 24h. Same key + same body → identical cached response,
141
+ not a duplicate event. You can override the key for application-level retry
142
+ control:
143
+
144
+ ```python
145
+ client.dispatch_event(
146
+ app_id=...,
147
+ event_name=...,
148
+ event_data=...,
149
+ idempotency_key="my-task-2026-05-15", # caller-controlled
150
+ )
151
+ ```
152
+
153
+ ## Async client
154
+
155
+ The async client mirrors the sync API exactly:
156
+
157
+ ```python
158
+ import asyncio
159
+ import esoul
160
+
161
+ async def main():
162
+ async with esoul.AsyncEsoul() as client:
163
+ result = await client.dispatch_event(
164
+ app_id=..., event_name=..., event_data=...,
165
+ )
166
+
167
+ asyncio.run(main())
168
+ ```
169
+
170
+ ## Typed errors
171
+
172
+ Failures surface as Python exception classes mapped from the server's
173
+ `error.code`. You can catch the kind you care about, and `EsoulError`
174
+ catches everything:
175
+
176
+ ```python
177
+ import esoul
178
+
179
+ try:
180
+ client.dispatch_event(...)
181
+ except esoul.WorkspaceAccessDenied as e:
182
+ print(f"This token cannot reach workspace {e.details['workspaceId']}")
183
+ except esoul.IdempotencyConflict as e:
184
+ print("Same key was used with a different request body")
185
+ except esoul.RateLimitError as e:
186
+ time.sleep(e.retry_after_seconds or 1)
187
+ except esoul.AuthError:
188
+ print("Token expired or revoked — get a new one")
189
+ except esoul.EsoulError as e:
190
+ print(f"{e.code}: {e.message}")
191
+ ```
192
+
193
+ ## Status
194
+
195
+ **v0.1 — alpha.** Wire surface is stable; per-app typed resources are
196
+ codegen'd and shipping incrementally. The low-level `dispatch_event` /
197
+ `dispatch_batch` / `read_state` paths are production-ready.
198
+
199
+ See [CHANGELOG.md](./CHANGELOG.md) for release notes.
200
+
201
+ ## License
202
+
203
+ MIT.
esoul-0.1.0/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # esoul — Python SDK for ExternalSoul
2
+
3
+ `esoul` is the official Python SDK for the **ExternalSoul** platform. It lets
4
+ scripts mutate workspace state from anywhere Python runs — inside the
5
+ platform's E2B sandboxes, on a data scientist's laptop, in a Colab notebook,
6
+ in CI, in a scheduled cron job.
7
+
8
+ ```bash
9
+ pip install esoul
10
+ ```
11
+
12
+ ```python
13
+ import esoul
14
+
15
+ client = esoul.Esoul() # auto-detects credentials
16
+ state = client.describe() # what workspaces + apps + events are available
17
+ print(state.session.workspace_ids)
18
+
19
+ # Low-level dispatch
20
+ result = client.dispatch_event(
21
+ app_id="app_abc123",
22
+ event_name="spreadsheet_add_row",
23
+ event_data={"cells": {"name": "Alice", "email": "a@example.com"}},
24
+ )
25
+ print(result.event_id, result.sequence_num)
26
+ ```
27
+
28
+ ## Why an SDK?
29
+
30
+ ExternalSoul workspaces are **event-sourced** — every state mutation is a
31
+ typed event on a per-workspace timeline, scrubbable and forkable. The UI,
32
+ agents, and now scripts all dispatch through the **same** reducers; events
33
+ are the source of truth, audit logging is automatic.
34
+
35
+ The SDK is the platform's universal binding for **scripted workspace
36
+ operations**. Common use cases:
37
+
38
+ - Bulk-import 1000 rows into a spreadsheet (one event, not 1000 LLM tool calls)
39
+ - Run an OpenCV pipeline over Drive images and write masks back
40
+ - Programmatic kanban / contacts load from a CSV
41
+ - Notebook-as-workspace-builder
42
+ - Any agent or human writing Python to mutate app state
43
+
44
+ ## Authentication
45
+
46
+ `esoul.Esoul()` auto-detects credentials in this order:
47
+
48
+ 1. Explicit `token=` kwarg
49
+ 2. `ESOUL_TOKEN` environment variable
50
+ 3. `/var/run/esoul/token` (the file mode 0600 token written by every E2B
51
+ sandbox at boot — `Esoul()` inside a sandbox Just Works)
52
+ 4. `~/.config/esoul/credentials` (TOML file with a `[default]` section
53
+ containing `token = "esoul_pat_..."`)
54
+
55
+ For off-platform use (laptop, CI), create a **Personal Access Token** in
56
+ workspace settings → "Access Tokens", pick the workspaces it can mutate,
57
+ copy the token (shown once), and either:
58
+
59
+ ```bash
60
+ # Export inline:
61
+ export ESOUL_TOKEN="esoul_pat_..."
62
+
63
+ # Or write to ~/.config/esoul/credentials:
64
+ mkdir -p ~/.config/esoul
65
+ cat > ~/.config/esoul/credentials <<EOF
66
+ [default]
67
+ token = "esoul_pat_..."
68
+ EOF
69
+ chmod 600 ~/.config/esoul/credentials
70
+ ```
71
+
72
+ ## Workspace isolation
73
+
74
+ A given credential is scoped to one or more specific workspaces. Cross-
75
+ workspace dispatch is **structurally impossible** — not a permission check
76
+ in your script, an architectural invariant of the server.
77
+
78
+ ## Idempotency
79
+
80
+ Every write is automatically idempotent. The SDK generates a UUID per call
81
+ and reuses it across retries; the server caches the response under
82
+ `(session, key)` for 24h. Same key + same body → identical cached response,
83
+ not a duplicate event. You can override the key for application-level retry
84
+ control:
85
+
86
+ ```python
87
+ client.dispatch_event(
88
+ app_id=...,
89
+ event_name=...,
90
+ event_data=...,
91
+ idempotency_key="my-task-2026-05-15", # caller-controlled
92
+ )
93
+ ```
94
+
95
+ ## Async client
96
+
97
+ The async client mirrors the sync API exactly:
98
+
99
+ ```python
100
+ import asyncio
101
+ import esoul
102
+
103
+ async def main():
104
+ async with esoul.AsyncEsoul() as client:
105
+ result = await client.dispatch_event(
106
+ app_id=..., event_name=..., event_data=...,
107
+ )
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ## Typed errors
113
+
114
+ Failures surface as Python exception classes mapped from the server's
115
+ `error.code`. You can catch the kind you care about, and `EsoulError`
116
+ catches everything:
117
+
118
+ ```python
119
+ import esoul
120
+
121
+ try:
122
+ client.dispatch_event(...)
123
+ except esoul.WorkspaceAccessDenied as e:
124
+ print(f"This token cannot reach workspace {e.details['workspaceId']}")
125
+ except esoul.IdempotencyConflict as e:
126
+ print("Same key was used with a different request body")
127
+ except esoul.RateLimitError as e:
128
+ time.sleep(e.retry_after_seconds or 1)
129
+ except esoul.AuthError:
130
+ print("Token expired or revoked — get a new one")
131
+ except esoul.EsoulError as e:
132
+ print(f"{e.code}: {e.message}")
133
+ ```
134
+
135
+ ## Status
136
+
137
+ **v0.1 — alpha.** Wire surface is stable; per-app typed resources are
138
+ codegen'd and shipping incrementally. The low-level `dispatch_event` /
139
+ `dispatch_batch` / `read_state` paths are production-ready.
140
+
141
+ See [CHANGELOG.md](./CHANGELOG.md) for release notes.
142
+
143
+ ## License
144
+
145
+ MIT.
@@ -0,0 +1,117 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.21"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "esoul"
7
+ dynamic = ["version"]
8
+ description = "Python SDK for the ExternalSoul platform — programmatic workspace mutation, event-sourced, audit-logged."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { file = "LICENSE" }
12
+ authors = [
13
+ { name = "ExternalSoul", email = "hey@vyomkeshj.com" },
14
+ ]
15
+ keywords = ["externalsoul", "esoul", "kinetic", "workspace", "automation", "event-sourcing"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ "Typing :: Typed",
29
+ ]
30
+ dependencies = [
31
+ # httpx 0.27 added Timeout + connection-pool improvements we rely on;
32
+ # earlier versions work but we don't test against them.
33
+ "httpx>=0.27,<1.0",
34
+ # pydantic v2 for typed response models. v1 had a different validation
35
+ # API; the codegen pipeline targets v2 exclusively.
36
+ "pydantic>=2.0,<3.0",
37
+ # typing_extensions for TypeAlias / Self on Python 3.9-3.10.
38
+ "typing_extensions>=4.7; python_version<'3.11'",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/vyomkeshj/esoul-python"
43
+ Documentation = "https://externalsoul.com/sdk-docs/llm-reference.md"
44
+ Repository = "https://github.com/vyomkeshj/esoul-python"
45
+ Changelog = "https://github.com/vyomkeshj/esoul-python/blob/main/CHANGELOG.md"
46
+
47
+ [project.optional-dependencies]
48
+ dev = [
49
+ "mypy>=1.10",
50
+ "ruff>=0.6",
51
+ "pytest>=8.0",
52
+ "pytest-asyncio>=0.23",
53
+ "respx>=0.21", # httpx mocking
54
+ "build>=1.0",
55
+ "twine>=5.0",
56
+ ]
57
+
58
+ [tool.hatch.version]
59
+ path = "src/esoul/_version.py"
60
+
61
+ [tool.hatch.build.targets.wheel]
62
+ packages = ["src/esoul"]
63
+
64
+ [tool.hatch.build.targets.sdist]
65
+ include = [
66
+ "src/esoul",
67
+ "README.md",
68
+ "LICENSE",
69
+ "CHANGELOG.md",
70
+ "pyproject.toml",
71
+ ]
72
+
73
+ # ─── mypy ────────────────────────────────────────────────────────────────
74
+
75
+ [tool.mypy]
76
+ python_version = "3.9"
77
+ strict = true
78
+ warn_return_any = true
79
+ warn_unused_configs = true
80
+ # The generated resources/types modules will land here after codegen.
81
+ # Their typing is stricter than mypy's defaults expect.
82
+ files = ["src/esoul"]
83
+
84
+ [[tool.mypy.overrides]]
85
+ module = "esoul.resources.*"
86
+ # Codegen-emitted modules carry a `# mypy: file generated` marker; we keep
87
+ # strict mode but allow ignore-comments to pile up around generated bits.
88
+ strict = true
89
+
90
+ # ─── ruff ────────────────────────────────────────────────────────────────
91
+
92
+ [tool.ruff]
93
+ target-version = "py39"
94
+ line-length = 100
95
+ src = ["src"]
96
+
97
+ [tool.ruff.lint]
98
+ select = [
99
+ "E", # pycodestyle errors
100
+ "F", # pyflakes
101
+ "I", # isort
102
+ "B", # flake8-bugbear
103
+ "UP", # pyupgrade (modernise type hints etc.)
104
+ "RUF", # ruff-specific
105
+ ]
106
+ ignore = [
107
+ "E501", # line length — formatter handles it
108
+ ]
109
+
110
+ [tool.ruff.lint.per-file-ignores]
111
+ "tests/*" = ["B011"] # asserts in tests are fine
112
+
113
+ # ─── pytest ──────────────────────────────────────────────────────────────
114
+
115
+ [tool.pytest.ini_options]
116
+ testpaths = ["tests"]
117
+ asyncio_mode = "auto"
@@ -0,0 +1,79 @@
1
+ """esoul — Python SDK for the ExternalSoul platform.
2
+
3
+ See README.md for usage. The top-level surface:
4
+
5
+ esoul.Esoul — sync client
6
+ esoul.AsyncEsoul — async client
7
+ esoul.Credentials — credential dataclass (typically accessed via client.credentials)
8
+ esoul.<Exception> — typed errors; see exceptions module docstring for the full hierarchy
9
+
10
+ Per-app typed resources (spreadsheet, slideshow, notes, …) ship via the
11
+ codegen pipeline and attach to the client as new attributes; they're
12
+ non-breaking additions.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from ._async_client import AsyncEsoul
18
+ from ._auth import Credentials
19
+ from ._client import Esoul
20
+ from ._version import __version__
21
+ from .exceptions import (
22
+ APIError,
23
+ AuthError,
24
+ DriveError,
25
+ DriveNotConnected,
26
+ EsoulError,
27
+ IdempotencyConflict,
28
+ InvalidRequest,
29
+ MissingCredentialsError,
30
+ NotFoundError,
31
+ RateLimitError,
32
+ TransportError,
33
+ WorkspaceAccessDenied,
34
+ )
35
+ from .resources.agents import (
36
+ InvocationError,
37
+ InvocationHandle,
38
+ InvocationResult,
39
+ InvocationStatus,
40
+ InvocationTimeout,
41
+ )
42
+ from .resources.questions import (
43
+ Answer,
44
+ Question,
45
+ QuestionAlreadyResolved,
46
+ QuestionTimeout,
47
+ )
48
+
49
+ __all__ = [
50
+ # version
51
+ "__version__",
52
+ # clients
53
+ "Esoul",
54
+ "AsyncEsoul",
55
+ "Credentials",
56
+ # exceptions (re-exported at top-level for ergonomic `except esoul.AuthError`)
57
+ "EsoulError",
58
+ "MissingCredentialsError",
59
+ "TransportError",
60
+ "APIError",
61
+ "AuthError",
62
+ "WorkspaceAccessDenied",
63
+ "NotFoundError",
64
+ "InvalidRequest",
65
+ "IdempotencyConflict",
66
+ "RateLimitError",
67
+ "DriveNotConnected",
68
+ "DriveError",
69
+ # Stage 12 — agent invocation + workspace HIL queue
70
+ "InvocationStatus",
71
+ "InvocationResult",
72
+ "InvocationHandle",
73
+ "InvocationTimeout",
74
+ "InvocationError",
75
+ "Question",
76
+ "Answer",
77
+ "QuestionTimeout",
78
+ "QuestionAlreadyResolved",
79
+ ]