splox 0.2.0__tar.gz → 0.2.2__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.
- splox-0.2.2/CHANGELOG.md +101 -0
- splox-0.2.2/PKG-INFO +376 -0
- splox-0.2.2/README.md +343 -0
- {splox-0.2.0 → splox-0.2.2}/pyproject.toml +1 -1
- splox-0.2.2/src/splox/__init__.py +150 -0
- {splox-0.2.0 → splox-0.2.2}/src/splox/_client.py +47 -20
- splox-0.2.2/src/splox/_ids.py +70 -0
- splox-0.2.2/src/splox/_interactions.py +172 -0
- splox-0.2.2/src/splox/_mcp.py +426 -0
- splox-0.2.2/src/splox/_models.py +1537 -0
- splox-0.2.2/src/splox/_resources.py +1474 -0
- splox-0.2.2/src/splox/_runs.py +714 -0
- splox-0.2.2/src/splox/_transport.py +524 -0
- splox-0.2.2/src/splox/exceptions.py +229 -0
- splox-0.2.2/tests/integration_test.py +168 -0
- splox-0.2.2/tests/integration_test_mcp.py +124 -0
- splox-0.2.2/tests/integration_test_memory.py +387 -0
- splox-0.2.2/tests/integration_test_v2.py +315 -0
- {splox-0.2.0 → splox-0.2.2}/tests/test_client.py +148 -181
- splox-0.2.2/tests/test_codemode_contract.py +199 -0
- splox-0.2.2/tests/test_exceptions.py +92 -0
- splox-0.2.2/tests/test_ids.py +65 -0
- splox-0.2.2/tests/test_models.py +363 -0
- splox-0.2.2/tests/test_runs.py +637 -0
- splox-0.2.2/tests/test_sse.py +119 -0
- splox-0.2.0/PKG-INFO +0 -236
- splox-0.2.0/README.md +0 -203
- splox-0.2.0/src/splox/__init__.py +0 -62
- splox-0.2.0/src/splox/_models.py +0 -575
- splox-0.2.0/src/splox/_resources.py +0 -841
- splox-0.2.0/src/splox/_transport.py +0 -220
- splox-0.2.0/src/splox/exceptions.py +0 -70
- splox-0.2.0/tests/integration_test.py +0 -322
- splox-0.2.0/tests/test_exceptions.py +0 -43
- splox-0.2.0/tests/test_models.py +0 -217
- splox-0.2.0/tests/test_sse.py +0 -65
- {splox-0.2.0 → splox-0.2.2}/.github/workflows/publish.yml +0 -0
- {splox-0.2.0 → splox-0.2.2}/.github/workflows/test.yml +0 -0
- {splox-0.2.0 → splox-0.2.2}/.gitignore +0 -0
- {splox-0.2.0 → splox-0.2.2}/LICENSE +0 -0
- {splox-0.2.0 → splox-0.2.2}/src/splox/py.typed +0 -0
- {splox-0.2.0 → splox-0.2.2}/tests/__init__.py +0 -0
splox-0.2.2/CHANGELOG.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1 — v2 API (breaking)
|
|
4
|
+
|
|
5
|
+
The run-execution surface now targets the Splox Core API v2
|
|
6
|
+
(`/v2/runs`, `/v2/interactions`, RFC 9457 problem+json, cursor pagination,
|
|
7
|
+
durable SSE event journals). Workflow CRUD reads, chats, memory, billing,
|
|
8
|
+
webhooks, LLM and the **entire MCP module are unchanged**.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `client.runs` (`Runs` / `AsyncRuns`):
|
|
13
|
+
- `create(workflow_id, input, *, metadata=None, conversation_id=None, workflow_version=None, idempotency_key=None)`
|
|
14
|
+
— returns a bound `Run` handle; `input` accepts a `str` shorthand, a full
|
|
15
|
+
MessageInput dict, or a list of content parts; an `Idempotency-Key`
|
|
16
|
+
(UUIDv4) is generated automatically when omitted; `workflow_id` accepts both
|
|
17
|
+
`wf_...` public ids and raw workflow UUIDs.
|
|
18
|
+
- `get`, `list` (cursor pagination: `{data, page:{has_more, next_cursor}}`,
|
|
19
|
+
filters: `status`, `workflow_id`, `created_after`, `created_before`),
|
|
20
|
+
`cancel` (idempotent), `wait(timeout=, poll_interval=)`.
|
|
21
|
+
- `list_events` (JSON pages) and `stream_events` (SSE with automatic
|
|
22
|
+
`Last-Event-ID` reconnect, at-least-once dedupe by event id, terminates
|
|
23
|
+
after the terminal `run.status_changed` event).
|
|
24
|
+
- `messages`, `outputs`, `tree`, `usage`, `pending_interactions`.
|
|
25
|
+
- `Run` handle methods: `wait`, `cancel`, `events(stream=True|False)`,
|
|
26
|
+
`messages`, `outputs`, `tree`, `usage`, `pending_interactions`, `refresh`.
|
|
27
|
+
- `client.interactions` (`Interactions` / `AsyncInteractions`):
|
|
28
|
+
`list(status=, run_id=, ...)`, `get`, and
|
|
29
|
+
`respond(id, type=..., **fields)` / `respond(id, response={...})` with an
|
|
30
|
+
auto-generated idempotency key.
|
|
31
|
+
- ID helpers: `splox.encode_id(prefix, uuid)` / `splox.decode_id(public_id)`
|
|
32
|
+
(Crockford base32 / ULID alphabet).
|
|
33
|
+
- v2 models: `Run`, `RunFailure`, `RunEvent`, `Message`, `ContentPart`,
|
|
34
|
+
`Output`, `RunTree`, `RunTreeNode`, `RunUsage`, `Interaction`,
|
|
35
|
+
`InteractionResponse`, `PageInfo` and page wrappers (`RunPage`,
|
|
36
|
+
`RunEventPage`, `MessagePage`, `OutputPage`, `InteractionPage`), plus
|
|
37
|
+
`TERMINAL_RUN_STATUSES`.
|
|
38
|
+
- Exceptions: `SploxBadRequestError` (400), `SploxConflictError` (409),
|
|
39
|
+
`SploxValidationError` (422, carries `errors[]` with JSON Pointer names),
|
|
40
|
+
`SploxServerError` (5xx). All API errors now expose `code`, `trace_id` and
|
|
41
|
+
`problem` parsed from RFC 9457 bodies. `SploxRateLimitError.retry_after`
|
|
42
|
+
is populated from `Retry-After`.
|
|
43
|
+
- Transport retries: GETs are retried up to 3 times with exponential backoff
|
|
44
|
+
on connection errors / 429 / 5xx; POSTs are retried only when they carry an
|
|
45
|
+
`Idempotency-Key` header (same key on every attempt).
|
|
46
|
+
- v2 URL resolution: `/v2/...` paths resolve against the server origin
|
|
47
|
+
(base URL with a trailing `/api/v1` or `/v1` stripped), so the default
|
|
48
|
+
base URL keeps working for both API generations.
|
|
49
|
+
|
|
50
|
+
### Changed (BREAKING)
|
|
51
|
+
|
|
52
|
+
- **"Agent" was renamed to "workflow" across the v2 wire contract** and the
|
|
53
|
+
SDK follows: `runs.create(workflow_id=..., workflow_version=...)`,
|
|
54
|
+
`Run.workflow_id`, `runs.list(workflow_id=...)`, public id prefix `wf_`
|
|
55
|
+
(was `agt_`), error pointers `/workflow_id` / `/workflow_version`, and
|
|
56
|
+
codes `workflow_not_runnable` / `workflow_mismatch`.
|
|
57
|
+
- **`client.workflows` lost the run-execution surface.** Removed:
|
|
58
|
+
`workflows.run`, `workflows.listen`, `workflows.get_execution_tree`,
|
|
59
|
+
`workflows.get_history`, `workflows.stop`, `workflows.run_and_wait`
|
|
60
|
+
(sync and async). Replacement: `client.runs.create(...)`,
|
|
61
|
+
`run.events()` / `client.runs.stream_events(...)`, `client.runs.tree(...)`,
|
|
62
|
+
`client.runs.list(...)`, `client.runs.cancel(...)`,
|
|
63
|
+
`client.runs.create(...).wait(...)` respectively. Workflow CRUD reads
|
|
64
|
+
(`list`, `get`, `get_latest_version`, `list_versions`, `get_entry_nodes`)
|
|
65
|
+
and secrets management remain on v1 and are unchanged.
|
|
66
|
+
- **Removed models** (v1 run-execution): `RunResponse`,
|
|
67
|
+
`ExecutionTreeResponse`, `HistoryResponse`, `ExecutionTree`,
|
|
68
|
+
`ExecutionNode`, `ChildExecution`. The v2 equivalents are `Run`, `RunTree`,
|
|
69
|
+
`RunTreeNode` and `RunPage`. (`WorkflowRequest`, `NodeExecution` and
|
|
70
|
+
`SSEEvent` remain for the kept v1 chat streams.)
|
|
71
|
+
- **Removed: the `agents.*` surface entirely** (`client.agents`,
|
|
72
|
+
`Agents`/`AsyncAgents`, `AgentRun`/`AsyncAgentRun`, `AgentResult`/
|
|
73
|
+
`AgentGatherResult`, `AgentError`, `spawn`/`gather`/`stop`) — use
|
|
74
|
+
`client.runs.create(...)` + `run.wait()`/`runs.cancel()` instead; in-run
|
|
75
|
+
subagent spawning is a runtime concern (executor tool), not an SDK API.
|
|
76
|
+
`spawn`'s `SPLOX_RUN_ID`/`parent_run_id` mechanics are gone with it.
|
|
77
|
+
- **Run state names are the v2 state machine**: terminal is
|
|
78
|
+
`succeeded|failed|cancelled` (previously `completed|failed|stopped`);
|
|
79
|
+
cancellation passes through `cancelling`.
|
|
80
|
+
- **Timeouts**: `run.wait()` raises `SploxTimeoutError` (subclass of both
|
|
81
|
+
`SploxError` and `TimeoutError`), so existing `except TimeoutError` code
|
|
82
|
+
keeps working.
|
|
83
|
+
- **500-level responses** now raise `SploxServerError` (a `SploxAPIError`
|
|
84
|
+
subclass) instead of the generic `SploxAPIError`; 400 responses raise
|
|
85
|
+
`SploxBadRequestError`, 409 `SploxConflictError`, 422
|
|
86
|
+
`SploxValidationError`. Code that caught `SploxAPIError` keeps working.
|
|
87
|
+
- GET requests are now retried automatically (previously no retries); disable
|
|
88
|
+
by catching errors yourself — retry semantics never re-send an unkeyed POST.
|
|
89
|
+
|
|
90
|
+
### Unchanged (explicitly)
|
|
91
|
+
|
|
92
|
+
- `client.mcp` — catalog, connections, `execute_tool(mcp_server_id=, tool_slug=, args=)`,
|
|
93
|
+
server tools, OAuth/connection links, `generate_connection_token/link`.
|
|
94
|
+
- `client.chats`, `client.memory`, `client.billing`, `client.llm`,
|
|
95
|
+
`client.events` (webhooks), `SploxClient()`/`AsyncSploxClient()`
|
|
96
|
+
construction and the `SPLOX_API_KEY` / `SPLOX_BASE_URL` env fallbacks.
|
|
97
|
+
- Workflow provisioning reads and workflow secrets management.
|
|
98
|
+
|
|
99
|
+
## 0.0.18
|
|
100
|
+
|
|
101
|
+
- Last release of the v1 run-execution surface.
|
splox-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splox
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Official Python SDK for the Splox API — run workflows, manage chats, and monitor execution
|
|
5
|
+
Project-URL: Homepage, https://splox.io
|
|
6
|
+
Project-URL: Documentation, https://docs.splox.io
|
|
7
|
+
Project-URL: Repository, https://github.com/splox-ai/python-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/splox-ai/python-sdk/issues
|
|
9
|
+
Author-email: Splox <support@splox.io>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agent,ai,sdk,splox,workflow
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: httpx>=0.25.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# Splox Python SDK
|
|
35
|
+
|
|
36
|
+
Official Python SDK for the [Splox API](https://docs.splox.io) — create and observe workflow **runs**, resolve human-in-the-loop **interactions**, browse the MCP catalog, and manage workflows programmatically.
|
|
37
|
+
|
|
38
|
+
> **v0.1.0 is a breaking release.** The run-execution surface moved to the v2 API
|
|
39
|
+
> (`client.runs` / `client.interactions`). Workflow CRUD reads, chats, memory,
|
|
40
|
+
> billing and the **MCP module are unchanged**. See [CHANGELOG.md](CHANGELOG.md)
|
|
41
|
+
> for the full breaking-change list.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install splox
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from splox import SploxClient
|
|
53
|
+
|
|
54
|
+
client = SploxClient(api_key="your-api-key")
|
|
55
|
+
|
|
56
|
+
# Create a run: workflow id ("wf_...") or a raw workflow UUID both work.
|
|
57
|
+
run = client.runs.create(
|
|
58
|
+
"wf_01JAZ6Y5M3Q8F7N2R4T6V9W0XC",
|
|
59
|
+
"Summarize the latest sales report",
|
|
60
|
+
)
|
|
61
|
+
print(run.id, run.status) # run_01... queued
|
|
62
|
+
|
|
63
|
+
# Block until the run finishes (raises SploxTimeoutError on timeout).
|
|
64
|
+
run = run.wait(timeout=300)
|
|
65
|
+
print(run.status) # succeeded | failed | cancelled
|
|
66
|
+
|
|
67
|
+
# Read the transcript and outputs.
|
|
68
|
+
for message in run.messages().data:
|
|
69
|
+
print(f"[{message.role}] {message.text}")
|
|
70
|
+
for output in run.outputs().data:
|
|
71
|
+
print(output.type, output.value)
|
|
72
|
+
|
|
73
|
+
# Usage (includes descendant runs; amount is a decimal string).
|
|
74
|
+
usage = run.usage()
|
|
75
|
+
print(usage.input_tokens, usage.output_tokens, usage.amount, usage.currency)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Creating runs
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
run = client.runs.create(
|
|
82
|
+
workflow_id, # "wf_..." or raw UUID
|
|
83
|
+
input, # str shorthand, or a full MessageInput dict,
|
|
84
|
+
# or a list of content parts
|
|
85
|
+
metadata={"ticket": "T-123"}, # optional client metadata
|
|
86
|
+
conversation_id="conv_...", # optional: continue a conversation (multi-turn)
|
|
87
|
+
workflow_version=3, # optional: pin a workflow version
|
|
88
|
+
idempotency_key="my-key", # optional: auto-generated UUIDv4 when omitted
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`input` accepts:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
"What is 2+2?" # text shorthand
|
|
96
|
+
{"role": "user", "content": [{"type": "text", "text": "hi"}]} # full MessageInput
|
|
97
|
+
[{"type": "text", "text": "hi"}, {"type": "json", "value": {"x": 1}}] # parts
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Every `POST /v2/runs` carries an `Idempotency-Key` (auto UUIDv4). Retrying with
|
|
101
|
+
the same key and body returns the same run; the SDK only ever retries POSTs
|
|
102
|
+
that carry an idempotency key.
|
|
103
|
+
|
|
104
|
+
### Streaming events (SSE)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# Live event stream: auto-reconnects with Last-Event-ID, dedupes by event id,
|
|
108
|
+
# and ends after the terminal run.status_changed event.
|
|
109
|
+
for event in run.events(): # stream=True is the default
|
|
110
|
+
print(event.sequence, event.type, event.data)
|
|
111
|
+
|
|
112
|
+
# Durable JSON pages instead of a stream:
|
|
113
|
+
page = run.events(stream=False, limit=100)
|
|
114
|
+
for event in page.data:
|
|
115
|
+
print(event.sequence, event.type)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Listing and cancelling
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
page = client.runs.list(status=["running", "waiting"], limit=20)
|
|
122
|
+
for run in page.data:
|
|
123
|
+
print(run.id, run.status)
|
|
124
|
+
if page.page.has_more:
|
|
125
|
+
page = client.runs.list(status=["running", "waiting"], cursor=page.page.next_cursor)
|
|
126
|
+
|
|
127
|
+
run = client.runs.cancel(run_id) # idempotent; terminal runs are returned unchanged
|
|
128
|
+
tree = client.runs.tree(run_id) # run + descendants snapshot
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Human-in-the-loop interactions
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# Pending questions raised by a run:
|
|
135
|
+
for interaction in run.pending_interactions():
|
|
136
|
+
print(interaction.type, interaction.prompt, interaction.payload)
|
|
137
|
+
|
|
138
|
+
# Respond (variant must match the interaction type):
|
|
139
|
+
client.interactions.respond(interaction.id, type="approval", approved=True)
|
|
140
|
+
client.interactions.respond(interaction.id, type="text", text="blue")
|
|
141
|
+
client.interactions.respond(interaction.id, type="choice", option_ids=["opt_a"])
|
|
142
|
+
client.interactions.respond(interaction.id, type="confirmation", confirmed=True)
|
|
143
|
+
# ...or pass a prebuilt body:
|
|
144
|
+
client.interactions.respond(interaction.id, response={"type": "approval", "approved": False})
|
|
145
|
+
|
|
146
|
+
# Inbox-style listing:
|
|
147
|
+
page = client.interactions.list(status="pending", limit=50)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Public IDs
|
|
151
|
+
|
|
152
|
+
v2 IDs are `<prefix>_<26-char Crockford base32>` (`run_`, `wf_`, `int_`, ...).
|
|
153
|
+
The SDK accepts raw UUIDs for workflow ids and encodes them client-side:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from splox import encode_id, decode_id
|
|
157
|
+
|
|
158
|
+
encode_id("wf", "019f455e-a84c-7d4c-87b0-c951d38bc224") # -> "wf_01KX2NXA2C..."
|
|
159
|
+
decode_id("wf_01KX2NXA2CFN68FC69A79RQGH4") # -> UUID(...)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Async Support
|
|
163
|
+
|
|
164
|
+
Every resource has an async twin with the same shape:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import asyncio
|
|
168
|
+
from splox import AsyncSploxClient
|
|
169
|
+
|
|
170
|
+
async def main():
|
|
171
|
+
async with AsyncSploxClient(api_key="your-api-key") as client:
|
|
172
|
+
run = await client.runs.create("wf_01JAZ6Y5M3Q8F7N2R4T6V9W0XC", "Hello!")
|
|
173
|
+
|
|
174
|
+
async for event in run.events(): # SSE with auto-reconnect
|
|
175
|
+
print(event.type, event.data)
|
|
176
|
+
|
|
177
|
+
run = await run.wait(timeout=300)
|
|
178
|
+
page = await run.messages()
|
|
179
|
+
print([m.text for m in page.data])
|
|
180
|
+
|
|
181
|
+
asyncio.run(main())
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Error Handling
|
|
185
|
+
|
|
186
|
+
All non-2xx v2 responses are RFC 9457 `problem+json` and map onto a typed
|
|
187
|
+
hierarchy; `code` carries the stable machine-readable error code and
|
|
188
|
+
`trace_id` correlates with server logs.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from splox.exceptions import (
|
|
192
|
+
SploxAPIError, # base for HTTP errors (.status_code/.code/.trace_id/.problem)
|
|
193
|
+
SploxBadRequestError, # 400
|
|
194
|
+
SploxAuthError, # 401
|
|
195
|
+
SploxForbiddenError, # 403
|
|
196
|
+
SploxNotFoundError, # 404
|
|
197
|
+
SploxConflictError, # 409 (idempotency_key_conflict, interaction_not_pending, ...)
|
|
198
|
+
SploxGoneError, # 410 (event_cursor_expired, ...)
|
|
199
|
+
SploxValidationError, # 422 (.errors = [{name, reason, ...}] with JSON Pointers)
|
|
200
|
+
SploxRateLimitError, # 429 (.retry_after)
|
|
201
|
+
SploxServerError, # 5xx
|
|
202
|
+
SploxTimeoutError, # run.wait()/result() timeouts (also a TimeoutError)
|
|
203
|
+
SploxConnectionError, # network failures
|
|
204
|
+
SploxStreamError, # SSE stream gave up reconnecting
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
run = client.runs.create(workflow_id, "hi")
|
|
209
|
+
except SploxValidationError as e:
|
|
210
|
+
for item in e.errors:
|
|
211
|
+
print(item["name"], item["reason"])
|
|
212
|
+
except SploxRateLimitError as e:
|
|
213
|
+
print(f"Rate limited. Retry after: {e.retry_after}")
|
|
214
|
+
except SploxAPIError as e:
|
|
215
|
+
print(f"API error {e.status_code} ({e.code}): {e.message}")
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Retries:** GET requests are retried up to 3 times with exponential backoff on
|
|
219
|
+
connection errors, 429 and 5xx. POSTs are retried only when they carry an
|
|
220
|
+
`Idempotency-Key` (always true for `runs.create` and `interactions.respond`),
|
|
221
|
+
reusing the same key so replays are safe.
|
|
222
|
+
|
|
223
|
+
## Workflow provisioning (v1, unchanged)
|
|
224
|
+
|
|
225
|
+
Workflow CRUD reads remain available for provisioning:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
workflows = client.workflows.list(search="Test")
|
|
229
|
+
full = client.workflows.get(workflow_id)
|
|
230
|
+
version = client.workflows.get_latest_version(workflow_id)
|
|
231
|
+
entry_nodes = client.workflows.get_entry_nodes(version.id)
|
|
232
|
+
versions = client.workflows.list_versions(workflow_id)
|
|
233
|
+
# plus workflow secrets management: list_secrets / set_env_secret / ...
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Chats (`client.chats`), memory (`client.memory`), billing (`client.billing`)
|
|
237
|
+
and webhooks (`client.events`) are also unchanged.
|
|
238
|
+
|
|
239
|
+
## MCP (Model Context Protocol) — unchanged
|
|
240
|
+
|
|
241
|
+
The MCP module is fully supported and **unchanged in this release**: catalog,
|
|
242
|
+
connections, tool execution, OAuth and connection links work exactly as in 0.0.x.
|
|
243
|
+
|
|
244
|
+
### Catalog
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
# Search the MCP catalog
|
|
248
|
+
catalog = client.mcp.list_catalog(search="github", per_page=10)
|
|
249
|
+
for server in catalog.mcp_servers:
|
|
250
|
+
print(f"{server.name} — {server.url}")
|
|
251
|
+
|
|
252
|
+
# Get featured servers
|
|
253
|
+
featured = client.mcp.list_catalog(featured=True)
|
|
254
|
+
|
|
255
|
+
# Get a single catalog item
|
|
256
|
+
item = client.mcp.get_catalog_item("mcp-server-id")
|
|
257
|
+
print(item.name, item.auth_type)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Connections & tools
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
conns = client.mcp.list_connections()
|
|
264
|
+
owner_servers = client.mcp.list_connections(scope="owner_user")
|
|
265
|
+
|
|
266
|
+
tools = client.mcp.get_server_tools("mcp-server-id")
|
|
267
|
+
|
|
268
|
+
result = client.mcp.execute_tool(
|
|
269
|
+
mcp_server_id="mcp-server-id",
|
|
270
|
+
tool_slug="list_servers",
|
|
271
|
+
args={"query": "x"},
|
|
272
|
+
)
|
|
273
|
+
print(result.result.content, result.result.structured_content, result.result.is_error)
|
|
274
|
+
|
|
275
|
+
client.mcp.delete_connection("connection-id")
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Connection Token & Link
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from splox import generate_connection_token, generate_connection_link
|
|
282
|
+
|
|
283
|
+
token = generate_connection_token(
|
|
284
|
+
mcp_server_id="mcp-server-id",
|
|
285
|
+
owner_user_id="owner-user-id",
|
|
286
|
+
end_user_id="end-user-id",
|
|
287
|
+
credentials_encryption_key="your-credentials-encryption-key",
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
link = generate_connection_link(
|
|
291
|
+
base_url="https://app.splox.io",
|
|
292
|
+
mcp_server_id="mcp-server-id",
|
|
293
|
+
owner_user_id="owner-user-id",
|
|
294
|
+
end_user_id="end-user-id",
|
|
295
|
+
credentials_encryption_key="your-credentials-encryption-key",
|
|
296
|
+
)
|
|
297
|
+
# → https://app.splox.io/tools/connect?token=eyJhbG...
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Webhooks
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from splox import SploxClient
|
|
304
|
+
|
|
305
|
+
client = SploxClient() # No API key needed for webhooks
|
|
306
|
+
|
|
307
|
+
result = client.events.send(
|
|
308
|
+
webhook_id="your-webhook-id",
|
|
309
|
+
payload={"order_id": "12345", "status": "paid"},
|
|
310
|
+
)
|
|
311
|
+
print(result.event_id)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Custom Base URL
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
client = SploxClient(
|
|
318
|
+
api_key="your-api-key",
|
|
319
|
+
base_url="https://your-self-hosted-instance.com/api/v1",
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
v1 endpoints use the base URL as-is; v2 endpoints (`/v2/...`) are resolved
|
|
324
|
+
against the server origin (the base URL with its `/api/v1` suffix stripped).
|
|
325
|
+
|
|
326
|
+
## API Reference
|
|
327
|
+
|
|
328
|
+
### `SploxClient` / `AsyncSploxClient`
|
|
329
|
+
|
|
330
|
+
| Parameter | Type | Default | Description |
|
|
331
|
+
|-----------|------|---------|-------------|
|
|
332
|
+
| `api_key` | `str \| None` | `SPLOX_API_KEY` env | API authentication token |
|
|
333
|
+
| `base_url` | `str` | `SPLOX_BASE_URL` env, then `https://splox.io/api/v1` | API base URL |
|
|
334
|
+
| `timeout` | `float` | `300.0` | Request timeout in seconds |
|
|
335
|
+
|
|
336
|
+
### `client.runs` (v2)
|
|
337
|
+
|
|
338
|
+
| Method | Description |
|
|
339
|
+
|--------|-------------|
|
|
340
|
+
| `create(workflow_id, input, *, metadata=, conversation_id=, workflow_version=, idempotency_key=)` | Create a run; returns a bound `Run` handle |
|
|
341
|
+
| `get(run_id)` | Get a run |
|
|
342
|
+
| `list(*, status=, workflow_id=, created_after=, created_before=, cursor=, limit=)` | Cursor-paged listing (newest first) |
|
|
343
|
+
| `cancel(run_id)` | Idempotent cancellation |
|
|
344
|
+
| `wait(run_id, *, timeout=, poll_interval=)` | Poll until terminal |
|
|
345
|
+
| `stream_events(run_id, *, cursor=)` | SSE stream with auto-reconnect + dedupe |
|
|
346
|
+
| `list_events(run_id, *, cursor=, limit=)` | Durable JSON event pages |
|
|
347
|
+
| `messages / outputs / tree / usage / pending_interactions` | Run reads |
|
|
348
|
+
|
|
349
|
+
`Run` handles expose the same operations instance-bound: `run.wait()`,
|
|
350
|
+
`run.cancel()`, `run.events()`, `run.messages()`, `run.outputs()`,
|
|
351
|
+
`run.tree()`, `run.usage()`, `run.pending_interactions()`, `run.refresh()`.
|
|
352
|
+
|
|
353
|
+
### `client.interactions` (v2)
|
|
354
|
+
|
|
355
|
+
| Method | Description |
|
|
356
|
+
|--------|-------------|
|
|
357
|
+
| `list(*, status=, run_id=, cursor=, limit=)` | Cursor-paged listing (newest first) |
|
|
358
|
+
| `get(interaction_id)` | Get an interaction |
|
|
359
|
+
| `respond(interaction_id, *, type=, ..., response=, idempotency_key=)` | Resolve a pending interaction |
|
|
360
|
+
|
|
361
|
+
### `client.workflows` (v1 provisioning reads)
|
|
362
|
+
|
|
363
|
+
| Method | Description |
|
|
364
|
+
|--------|-------------|
|
|
365
|
+
| `list(...)` / `get(id)` | List/get workflows |
|
|
366
|
+
| `get_latest_version(id)` / `list_versions(id)` | Version reads |
|
|
367
|
+
| `get_entry_nodes(version_id)` | Entry nodes for a version |
|
|
368
|
+
| `list_secrets / set_env_secret / set_file_secret / delete_secret / ...` | Secrets management |
|
|
369
|
+
|
|
370
|
+
### `client.chats`, `client.memory`, `client.billing`, `client.mcp`
|
|
371
|
+
|
|
372
|
+
Unchanged from 0.0.x — see the sections above.
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
MIT
|