simplex 2.0.5__tar.gz → 3.0.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.
Files changed (37) hide show
  1. simplex-3.0.2/PKG-INFO +292 -0
  2. simplex-3.0.2/README.md +259 -0
  3. {simplex-2.0.5 → simplex-3.0.2}/pyproject.toml +9 -1
  4. {simplex-2.0.5 → simplex-3.0.2}/simplex/__init__.py +18 -1
  5. {simplex-2.0.5 → simplex-3.0.2}/simplex/_http_client.py +46 -3
  6. simplex-3.0.2/simplex/cli/__init__.py +0 -0
  7. simplex-3.0.2/simplex/cli/auth.py +201 -0
  8. simplex-3.0.2/simplex/cli/config.py +93 -0
  9. simplex-3.0.2/simplex/cli/connect.py +304 -0
  10. simplex-3.0.2/simplex/cli/editor.py +88 -0
  11. simplex-3.0.2/simplex/cli/main.py +50 -0
  12. simplex-3.0.2/simplex/cli/output.py +45 -0
  13. simplex-3.0.2/simplex/cli/run.py +198 -0
  14. simplex-3.0.2/simplex/cli/send.py +51 -0
  15. simplex-3.0.2/simplex/cli/sessions.py +119 -0
  16. simplex-3.0.2/simplex/cli/variables.py +74 -0
  17. simplex-3.0.2/simplex/cli/workflows.py +343 -0
  18. simplex-3.0.2/simplex/client.py +687 -0
  19. simplex-3.0.2/simplex/types.py +213 -0
  20. simplex-3.0.2/simplex/webhook.py +70 -0
  21. simplex-3.0.2/simplex.egg-info/PKG-INFO +292 -0
  22. simplex-3.0.2/simplex.egg-info/SOURCES.txt +29 -0
  23. simplex-3.0.2/simplex.egg-info/entry_points.txt +2 -0
  24. {simplex-2.0.5 → simplex-3.0.2}/simplex.egg-info/requires.txt +2 -0
  25. simplex-2.0.5/PKG-INFO +0 -224
  26. simplex-2.0.5/README.md +0 -193
  27. simplex-2.0.5/simplex/client.py +0 -293
  28. simplex-2.0.5/simplex/types.py +0 -72
  29. simplex-2.0.5/simplex.egg-info/PKG-INFO +0 -224
  30. simplex-2.0.5/simplex.egg-info/SOURCES.txt +0 -15
  31. {simplex-2.0.5 → simplex-3.0.2}/LICENSE +0 -0
  32. {simplex-2.0.5 → simplex-3.0.2}/MANIFEST.in +0 -0
  33. {simplex-2.0.5 → simplex-3.0.2}/requirements.txt +0 -0
  34. {simplex-2.0.5 → simplex-3.0.2}/setup.cfg +0 -0
  35. {simplex-2.0.5 → simplex-3.0.2}/simplex/errors.py +0 -0
  36. {simplex-2.0.5 → simplex-3.0.2}/simplex.egg-info/dependency_links.txt +0 -0
  37. {simplex-2.0.5 → simplex-3.0.2}/simplex.egg-info/top_level.txt +0 -0
simplex-3.0.2/PKG-INFO ADDED
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: simplex
3
+ Version: 3.0.2
4
+ Summary: Official Python SDK for the Simplex API
5
+ Author-email: Simplex <support@simplex.sh>
6
+ License: MIT
7
+ Project-URL: Homepage, https://simplex.sh
8
+ Project-URL: Documentation, https://docs.simplex.sh
9
+ Project-URL: Repository, https://github.com/simplexlabs/simplex-python
10
+ Keywords: simplex,api,sdk,workflow,automation,browser,scraping
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: requests>=2.25.0
26
+ Requires-Dist: typer>=0.9.0
27
+ Requires-Dist: rich>=13.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
31
+ Requires-Dist: types-requests>=2.25.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # Simplex Python SDK & CLI
35
+
36
+ Official Python SDK and CLI for the [Simplex](https://simplex.sh) browser automation platform.
37
+
38
+ [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install simplex
45
+ ```
46
+
47
+ This installs both the Python SDK (`from simplex import SimplexClient`) and the CLI (`simplex` command).
48
+
49
+ ## Authentication
50
+
51
+ ```bash
52
+ # Option 1: Environment variable
53
+ export SIMPLEX_API_KEY="your-api-key"
54
+
55
+ # Option 2: Login command (saves to ~/.simplex/credentials)
56
+ simplex login
57
+ ```
58
+
59
+ ## Claude Code Plugin
60
+
61
+ Give Claude Code full knowledge of the Simplex CLI and SDK:
62
+
63
+ ```
64
+ /plugin install simplexlabs/simplex-python
65
+ ```
66
+
67
+ Once installed, Claude Code automatically knows how to use `simplex editor`, `simplex connect`, `simplex run`, and the full Python SDK.
68
+
69
+ ## CLI
70
+
71
+ ### `simplex editor` — Create a workflow and start an interactive session
72
+
73
+ ```bash
74
+ simplex editor --name "My Workflow" --url "https://example.com"
75
+ simplex editor -n "My Workflow" -u "https://example.com" --var key=value
76
+ simplex editor -n "Test" -u "https://example.com" --json
77
+ ```
78
+
79
+ Creates a workflow, starts a browser session, and streams live agent events. Prints session info (session_id, workflow_id, vnc_url) then streams SSE events until Ctrl+C.
80
+
81
+ ### `simplex connect` — Stream events from a running session
82
+
83
+ ```bash
84
+ simplex connect <session_id>
85
+ simplex connect "https://host:port/stream" --json
86
+ ```
87
+
88
+ ### `simplex run` — Run an existing workflow
89
+
90
+ ```bash
91
+ simplex run <workflow_id>
92
+ simplex run <workflow_id> --var email=test@test.com --watch
93
+ ```
94
+
95
+ ### `simplex send` — Send a message to a running session
96
+
97
+ ```bash
98
+ simplex send <session_id> "Click the login button"
99
+ ```
100
+
101
+ ### `simplex pause` / `simplex resume`
102
+
103
+ ```bash
104
+ simplex pause <session_id>
105
+ simplex resume <session_id>
106
+ ```
107
+
108
+ ### `simplex workflows list`
109
+
110
+ ```bash
111
+ simplex workflows list --name "search term"
112
+ ```
113
+
114
+ ### `simplex sessions`
115
+
116
+ ```bash
117
+ simplex sessions status <session_id>
118
+ simplex sessions logs <session_id>
119
+ simplex sessions download <session_id> --filename report.pdf --output ./report.pdf
120
+ simplex sessions replay <session_id> --output replay.mp4
121
+ ```
122
+
123
+ ### `simplex login` / `whoami` / `logout`
124
+
125
+ ```bash
126
+ simplex login # Prompts for API key
127
+ simplex whoami # Shows current auth status
128
+ simplex logout # Removes saved credentials
129
+ ```
130
+
131
+ ## Python SDK
132
+
133
+ ### Quick Start
134
+
135
+ ```python
136
+ from simplex import SimplexClient
137
+
138
+ client = SimplexClient(api_key="your-api-key")
139
+
140
+ # Start an interactive editor session
141
+ result = client.start_editor_session(
142
+ name="My Session",
143
+ url="https://example.com",
144
+ test_data={"username": "test"},
145
+ )
146
+ print(f"Session: {result['session_id']}")
147
+ print(f"VNC: {result['vnc_url']}")
148
+
149
+ # Stream live events
150
+ for event in client.stream_session(result["logs_url"]):
151
+ print(event)
152
+
153
+ # Send a message to the agent
154
+ client.send_message(result["message_url"], "Click the login button")
155
+
156
+ # Close the session
157
+ client.close_session(result["session_id"])
158
+ ```
159
+
160
+ ### Client
161
+
162
+ ```python
163
+ client = SimplexClient(
164
+ api_key="your-api-key",
165
+ base_url="https://api.simplex.sh", # Optional
166
+ timeout=30, # Request timeout in seconds
167
+ max_retries=3, # Retry attempts
168
+ retry_delay=1.0, # Delay between retries
169
+ )
170
+ ```
171
+
172
+ ### Workflow Management
173
+
174
+ ```python
175
+ # Create
176
+ result = client.create_workflow(name="My Workflow", url="https://example.com")
177
+ workflow_id = result["workflow"]["id"]
178
+
179
+ # Get
180
+ workflow = client.get_workflow(workflow_id)
181
+
182
+ # Update
183
+ client.update_workflow(workflow_id, name="New Name", url="https://new-url.com")
184
+
185
+ # Search
186
+ results = client.search_workflows(workflow_name="search term")
187
+ ```
188
+
189
+ ### Editor Sessions
190
+
191
+ ```python
192
+ # Start an editor session (creates workflow + browser session)
193
+ # NOTE: Takes 10-15 seconds. Use timeout=120.
194
+ result = client.start_editor_session(
195
+ name="My Session",
196
+ url="https://example.com",
197
+ test_data={"username": "test"},
198
+ )
199
+ # Returns: succeeded, workflow_id, session_id, vnc_url, logs_url, message_url, filesystem_url
200
+
201
+ # Stream live SSE events
202
+ for event in client.stream_session(result["logs_url"]):
203
+ event_type = event.get("event") or event.get("type", "")
204
+ print(f"[{event_type}] {event}")
205
+
206
+ # Send a message to the agent
207
+ client.send_message(result["message_url"], "Click the login button")
208
+ ```
209
+
210
+ ### Run Workflows
211
+
212
+ ```python
213
+ import time
214
+
215
+ result = client.run_workflow("workflow-id", variables={"key": "value"})
216
+
217
+ # Poll for completion
218
+ while True:
219
+ status = client.get_session_status(result["session_id"])
220
+ if not status["in_progress"]:
221
+ break
222
+ time.sleep(2)
223
+
224
+ if status["success"]:
225
+ print(status["scraper_outputs"])
226
+ print(status["structured_output"])
227
+ ```
228
+
229
+ ### Session Management
230
+
231
+ ```python
232
+ client.pause(session_id)
233
+ client.resume(session_id)
234
+ client.close_session(session_id)
235
+ client.get_session_status(session_id)
236
+ client.retrieve_session_logs(session_id)
237
+ client.download_session_files(session_id)
238
+ client.retrieve_session_replay(session_id)
239
+ ```
240
+
241
+ ## SSE Event Format
242
+
243
+ Events from `stream_session()` are dicts. The event type is in the `event` key:
244
+
245
+ | Event | Description |
246
+ |-------|-------------|
247
+ | `RunContent` | Agent text output |
248
+ | `ToolCallStarted` | Tool invocation started |
249
+ | `ToolCallCompleted` | Tool result |
250
+ | `FlowPaused` | Session paused |
251
+ | `FlowResumed` | Session resumed |
252
+ | `RunCompleted` | Agent finished |
253
+ | `RunError` | Error occurred |
254
+
255
+ ## Error Handling
256
+
257
+ ```python
258
+ from simplex import (
259
+ SimplexError,
260
+ AuthenticationError,
261
+ RateLimitError,
262
+ NetworkError,
263
+ ValidationError,
264
+ WorkflowError,
265
+ )
266
+
267
+ try:
268
+ result = client.run_workflow("workflow-id")
269
+ except AuthenticationError:
270
+ print("Invalid API key")
271
+ except RateLimitError as e:
272
+ print(f"Rate limited. Retry after {e.retry_after}s")
273
+ except WorkflowError as e:
274
+ print(f"Workflow error: {e.message}, session: {e.session_id}")
275
+ except SimplexError as e:
276
+ print(f"Error: {e.message}")
277
+ ```
278
+
279
+ ## Requirements
280
+
281
+ - Python 3.9+
282
+ - `requests>=2.25.0`
283
+
284
+ ## License
285
+
286
+ MIT License - see [LICENSE](LICENSE) for details.
287
+
288
+ ## Links
289
+
290
+ - [Simplex](https://simplex.sh)
291
+ - [Documentation](https://docs.simplex.sh)
292
+ - [Support](mailto:support@simplex.sh)
@@ -0,0 +1,259 @@
1
+ # Simplex Python SDK & CLI
2
+
3
+ Official Python SDK and CLI for the [Simplex](https://simplex.sh) browser automation platform.
4
+
5
+ [![Python Version](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install simplex
12
+ ```
13
+
14
+ This installs both the Python SDK (`from simplex import SimplexClient`) and the CLI (`simplex` command).
15
+
16
+ ## Authentication
17
+
18
+ ```bash
19
+ # Option 1: Environment variable
20
+ export SIMPLEX_API_KEY="your-api-key"
21
+
22
+ # Option 2: Login command (saves to ~/.simplex/credentials)
23
+ simplex login
24
+ ```
25
+
26
+ ## Claude Code Plugin
27
+
28
+ Give Claude Code full knowledge of the Simplex CLI and SDK:
29
+
30
+ ```
31
+ /plugin install simplexlabs/simplex-python
32
+ ```
33
+
34
+ Once installed, Claude Code automatically knows how to use `simplex editor`, `simplex connect`, `simplex run`, and the full Python SDK.
35
+
36
+ ## CLI
37
+
38
+ ### `simplex editor` — Create a workflow and start an interactive session
39
+
40
+ ```bash
41
+ simplex editor --name "My Workflow" --url "https://example.com"
42
+ simplex editor -n "My Workflow" -u "https://example.com" --var key=value
43
+ simplex editor -n "Test" -u "https://example.com" --json
44
+ ```
45
+
46
+ Creates a workflow, starts a browser session, and streams live agent events. Prints session info (session_id, workflow_id, vnc_url) then streams SSE events until Ctrl+C.
47
+
48
+ ### `simplex connect` — Stream events from a running session
49
+
50
+ ```bash
51
+ simplex connect <session_id>
52
+ simplex connect "https://host:port/stream" --json
53
+ ```
54
+
55
+ ### `simplex run` — Run an existing workflow
56
+
57
+ ```bash
58
+ simplex run <workflow_id>
59
+ simplex run <workflow_id> --var email=test@test.com --watch
60
+ ```
61
+
62
+ ### `simplex send` — Send a message to a running session
63
+
64
+ ```bash
65
+ simplex send <session_id> "Click the login button"
66
+ ```
67
+
68
+ ### `simplex pause` / `simplex resume`
69
+
70
+ ```bash
71
+ simplex pause <session_id>
72
+ simplex resume <session_id>
73
+ ```
74
+
75
+ ### `simplex workflows list`
76
+
77
+ ```bash
78
+ simplex workflows list --name "search term"
79
+ ```
80
+
81
+ ### `simplex sessions`
82
+
83
+ ```bash
84
+ simplex sessions status <session_id>
85
+ simplex sessions logs <session_id>
86
+ simplex sessions download <session_id> --filename report.pdf --output ./report.pdf
87
+ simplex sessions replay <session_id> --output replay.mp4
88
+ ```
89
+
90
+ ### `simplex login` / `whoami` / `logout`
91
+
92
+ ```bash
93
+ simplex login # Prompts for API key
94
+ simplex whoami # Shows current auth status
95
+ simplex logout # Removes saved credentials
96
+ ```
97
+
98
+ ## Python SDK
99
+
100
+ ### Quick Start
101
+
102
+ ```python
103
+ from simplex import SimplexClient
104
+
105
+ client = SimplexClient(api_key="your-api-key")
106
+
107
+ # Start an interactive editor session
108
+ result = client.start_editor_session(
109
+ name="My Session",
110
+ url="https://example.com",
111
+ test_data={"username": "test"},
112
+ )
113
+ print(f"Session: {result['session_id']}")
114
+ print(f"VNC: {result['vnc_url']}")
115
+
116
+ # Stream live events
117
+ for event in client.stream_session(result["logs_url"]):
118
+ print(event)
119
+
120
+ # Send a message to the agent
121
+ client.send_message(result["message_url"], "Click the login button")
122
+
123
+ # Close the session
124
+ client.close_session(result["session_id"])
125
+ ```
126
+
127
+ ### Client
128
+
129
+ ```python
130
+ client = SimplexClient(
131
+ api_key="your-api-key",
132
+ base_url="https://api.simplex.sh", # Optional
133
+ timeout=30, # Request timeout in seconds
134
+ max_retries=3, # Retry attempts
135
+ retry_delay=1.0, # Delay between retries
136
+ )
137
+ ```
138
+
139
+ ### Workflow Management
140
+
141
+ ```python
142
+ # Create
143
+ result = client.create_workflow(name="My Workflow", url="https://example.com")
144
+ workflow_id = result["workflow"]["id"]
145
+
146
+ # Get
147
+ workflow = client.get_workflow(workflow_id)
148
+
149
+ # Update
150
+ client.update_workflow(workflow_id, name="New Name", url="https://new-url.com")
151
+
152
+ # Search
153
+ results = client.search_workflows(workflow_name="search term")
154
+ ```
155
+
156
+ ### Editor Sessions
157
+
158
+ ```python
159
+ # Start an editor session (creates workflow + browser session)
160
+ # NOTE: Takes 10-15 seconds. Use timeout=120.
161
+ result = client.start_editor_session(
162
+ name="My Session",
163
+ url="https://example.com",
164
+ test_data={"username": "test"},
165
+ )
166
+ # Returns: succeeded, workflow_id, session_id, vnc_url, logs_url, message_url, filesystem_url
167
+
168
+ # Stream live SSE events
169
+ for event in client.stream_session(result["logs_url"]):
170
+ event_type = event.get("event") or event.get("type", "")
171
+ print(f"[{event_type}] {event}")
172
+
173
+ # Send a message to the agent
174
+ client.send_message(result["message_url"], "Click the login button")
175
+ ```
176
+
177
+ ### Run Workflows
178
+
179
+ ```python
180
+ import time
181
+
182
+ result = client.run_workflow("workflow-id", variables={"key": "value"})
183
+
184
+ # Poll for completion
185
+ while True:
186
+ status = client.get_session_status(result["session_id"])
187
+ if not status["in_progress"]:
188
+ break
189
+ time.sleep(2)
190
+
191
+ if status["success"]:
192
+ print(status["scraper_outputs"])
193
+ print(status["structured_output"])
194
+ ```
195
+
196
+ ### Session Management
197
+
198
+ ```python
199
+ client.pause(session_id)
200
+ client.resume(session_id)
201
+ client.close_session(session_id)
202
+ client.get_session_status(session_id)
203
+ client.retrieve_session_logs(session_id)
204
+ client.download_session_files(session_id)
205
+ client.retrieve_session_replay(session_id)
206
+ ```
207
+
208
+ ## SSE Event Format
209
+
210
+ Events from `stream_session()` are dicts. The event type is in the `event` key:
211
+
212
+ | Event | Description |
213
+ |-------|-------------|
214
+ | `RunContent` | Agent text output |
215
+ | `ToolCallStarted` | Tool invocation started |
216
+ | `ToolCallCompleted` | Tool result |
217
+ | `FlowPaused` | Session paused |
218
+ | `FlowResumed` | Session resumed |
219
+ | `RunCompleted` | Agent finished |
220
+ | `RunError` | Error occurred |
221
+
222
+ ## Error Handling
223
+
224
+ ```python
225
+ from simplex import (
226
+ SimplexError,
227
+ AuthenticationError,
228
+ RateLimitError,
229
+ NetworkError,
230
+ ValidationError,
231
+ WorkflowError,
232
+ )
233
+
234
+ try:
235
+ result = client.run_workflow("workflow-id")
236
+ except AuthenticationError:
237
+ print("Invalid API key")
238
+ except RateLimitError as e:
239
+ print(f"Rate limited. Retry after {e.retry_after}s")
240
+ except WorkflowError as e:
241
+ print(f"Workflow error: {e.message}, session: {e.session_id}")
242
+ except SimplexError as e:
243
+ print(f"Error: {e.message}")
244
+ ```
245
+
246
+ ## Requirements
247
+
248
+ - Python 3.9+
249
+ - `requests>=2.25.0`
250
+
251
+ ## License
252
+
253
+ MIT License - see [LICENSE](LICENSE) for details.
254
+
255
+ ## Links
256
+
257
+ - [Simplex](https://simplex.sh)
258
+ - [Documentation](https://docs.simplex.sh)
259
+ - [Support](mailto:support@simplex.sh)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simplex"
7
- version = "2.0.5"
7
+ version = "3.0.2"
8
8
  description = "Official Python SDK for the Simplex API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -28,8 +28,13 @@ classifiers = [
28
28
  ]
29
29
  dependencies = [
30
30
  "requests>=2.25.0",
31
+ "typer>=0.9.0",
32
+ "rich>=13.0.0",
31
33
  ]
32
34
 
35
+ [project.scripts]
36
+ simplex = "simplex.cli.main:app"
37
+
33
38
  [project.optional-dependencies]
34
39
  dev = [
35
40
  "pytest>=7.0.0",
@@ -42,6 +47,9 @@ Homepage = "https://simplex.sh"
42
47
  Documentation = "https://docs.simplex.sh"
43
48
  Repository = "https://github.com/simplexlabs/simplex-python"
44
49
 
50
+ [tool.setuptools.packages.find]
51
+ include = ["simplex*"]
52
+
45
53
  [tool.mypy]
46
54
  python_version = "3.9"
47
55
  warn_return_any = true
@@ -31,11 +31,19 @@ from simplex.errors import (
31
31
  )
32
32
  from simplex.types import (
33
33
  FileMetadata,
34
+ PauseSessionResponse,
35
+ ResumeSessionResponse,
34
36
  RunWorkflowResponse,
37
+ SearchWorkflowItem,
38
+ SearchWorkflowsResponse,
35
39
  SessionStatusResponse,
40
+ StartEditorSessionResponse,
41
+ UpdateWorkflowMetadataResponse,
42
+ WebhookPayload,
36
43
  )
44
+ from simplex.webhook import WebhookVerificationError, verify_simplex_webhook
37
45
 
38
- __version__ = "2.0.5"
46
+ __version__ = "3.0.2"
39
47
  __all__ = [
40
48
  "SimplexClient",
41
49
  "SimplexError",
@@ -47,4 +55,13 @@ __all__ = [
47
55
  "FileMetadata",
48
56
  "SessionStatusResponse",
49
57
  "RunWorkflowResponse",
58
+ "PauseSessionResponse",
59
+ "ResumeSessionResponse",
60
+ "SearchWorkflowsResponse",
61
+ "SearchWorkflowItem",
62
+ "StartEditorSessionResponse",
63
+ "UpdateWorkflowMetadataResponse",
64
+ "WebhookPayload",
65
+ "verify_simplex_webhook",
66
+ "WebhookVerificationError",
50
67
  ]
@@ -21,7 +21,7 @@ from simplex.errors import (
21
21
  ValidationError,
22
22
  )
23
23
 
24
- __version__ = "2.0.5"
24
+ __version__ = "3.0.2"
25
25
 
26
26
 
27
27
  class HttpClient:
@@ -77,6 +77,7 @@ class HttpClient:
77
77
  """Convert HTTP errors to appropriate exception types."""
78
78
  status_code = response.status_code
79
79
 
80
+ data = None
80
81
  try:
81
82
  data = response.json()
82
83
  if isinstance(data, dict):
@@ -87,7 +88,7 @@ class HttpClient:
87
88
  message = response.text or "An error occurred"
88
89
 
89
90
  if status_code == 400:
90
- return ValidationError(message, data=response.json() if response.text else None)
91
+ return ValidationError(message, data=data)
91
92
  elif status_code in [401, 403]:
92
93
  return AuthenticationError(message)
93
94
  elif status_code == 429:
@@ -95,7 +96,7 @@ class HttpClient:
95
96
  retry_after_seconds = int(retry_after) if retry_after and retry_after.isdigit() else None
96
97
  return RateLimitError(message, retry_after=retry_after_seconds)
97
98
  else:
98
- return SimplexError(message, status_code=status_code, data=response.json() if response.text else None)
99
+ return SimplexError(message, status_code=status_code, data=data)
99
100
 
100
101
  def _make_request(
101
102
  self,
@@ -205,6 +206,48 @@ class HttpClient:
205
206
  )
206
207
  return response.json()
207
208
 
209
+ def post_json(self, path: str, data: dict | None = None) -> Any:
210
+ """POST with JSON body."""
211
+ response = self._make_request("POST", path, json=data)
212
+ return response.json()
213
+
214
+ def patch_json(self, path: str, data: dict | None = None) -> Any:
215
+ """PATCH with JSON body."""
216
+ response = self._make_request("PATCH", path, json=data)
217
+ return response.json()
218
+
219
+ def stream_sse(self, url: str) -> Any:
220
+ """Connect to an SSE endpoint and yield parsed events.
221
+
222
+ Uses absolute URL (not base_url) since SSE endpoints are on container tunnels.
223
+ The generator ends cleanly when the connection closes (e.g. session finished).
224
+ """
225
+ import json as json_module
226
+
227
+ response = self.session.get(url, stream=True, timeout=None)
228
+ response.raise_for_status()
229
+ try:
230
+ for line in response.iter_lines(decode_unicode=True):
231
+ if line and line.startswith("data: "):
232
+ try:
233
+ yield json_module.loads(line[6:])
234
+ except json_module.JSONDecodeError:
235
+ continue
236
+ except (requests.exceptions.ChunkedEncodingError, requests.exceptions.ConnectionError):
237
+ return # Connection closed — session ended
238
+
239
+ def post_to_url(self, url: str, json_data: dict) -> Any:
240
+ """POST JSON to an absolute URL (not relative to base_url)."""
241
+ response = self.session.post(url, json=json_data, timeout=self.timeout)
242
+ response.raise_for_status()
243
+ return response.json()
244
+
245
+ def get_from_url(self, url: str, params: dict | None = None) -> Any:
246
+ """GET from an absolute URL (not relative to base_url)."""
247
+ response = self.session.get(url, params=params, timeout=self.timeout)
248
+ response.raise_for_status()
249
+ return response.json()
250
+
208
251
  def download_file(self, path: str, params: dict[str, Any] | None = None) -> bytes:
209
252
  """
210
253
  Download a file from the API.
File without changes