simplex 2.0.4__tar.gz → 3.0.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplex
3
- Version: 2.0.4
3
+ Version: 3.0.1
4
4
  Summary: Official Python SDK for the Simplex API
5
5
  Author-email: Simplex <support@simplex.sh>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simplex"
7
- version = "2.0.4"
7
+ version = "3.0.1"
8
8
  description = "Official Python SDK for the Simplex API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -31,11 +31,18 @@ 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
+ UpdateWorkflowMetadataResponse,
41
+ WebhookPayload,
36
42
  )
43
+ from simplex.webhook import WebhookVerificationError, verify_simplex_webhook
37
44
 
38
- __version__ = "2.0.4"
45
+ __version__ = "3.0.1"
39
46
  __all__ = [
40
47
  "SimplexClient",
41
48
  "SimplexError",
@@ -47,4 +54,12 @@ __all__ = [
47
54
  "FileMetadata",
48
55
  "SessionStatusResponse",
49
56
  "RunWorkflowResponse",
57
+ "PauseSessionResponse",
58
+ "ResumeSessionResponse",
59
+ "SearchWorkflowsResponse",
60
+ "SearchWorkflowItem",
61
+ "UpdateWorkflowMetadataResponse",
62
+ "WebhookPayload",
63
+ "verify_simplex_webhook",
64
+ "WebhookVerificationError",
50
65
  ]
@@ -21,7 +21,7 @@ from simplex.errors import (
21
21
  ValidationError,
22
22
  )
23
23
 
24
- __version__ = "2.0.4"
24
+ __version__ = "3.0.1"
25
25
 
26
26
 
27
27
  class HttpClient:
@@ -12,7 +12,14 @@ from typing import Any
12
12
 
13
13
  from simplex._http_client import HttpClient
14
14
  from simplex.errors import WorkflowError
15
- from simplex.types import RunWorkflowResponse, SessionStatusResponse
15
+ from simplex.types import (
16
+ PauseSessionResponse,
17
+ ResumeSessionResponse,
18
+ RunWorkflowResponse,
19
+ SearchWorkflowsResponse,
20
+ SessionStatusResponse,
21
+ UpdateWorkflowMetadataResponse,
22
+ )
16
23
 
17
24
 
18
25
  class SimplexClient:
@@ -291,3 +298,165 @@ class SimplexClient:
291
298
  f"Failed to retrieve session logs: {e}",
292
299
  session_id=session_id,
293
300
  )
301
+
302
+ def pause(self, session_id: str) -> PauseSessionResponse:
303
+ """
304
+ Pause a running session.
305
+
306
+ Args:
307
+ session_id: The session ID to pause
308
+
309
+ Returns:
310
+ PauseSessionResponse with pause details
311
+
312
+ Raises:
313
+ WorkflowError: If pausing the session fails
314
+
315
+ Example:
316
+ >>> result = client.pause("session-123")
317
+ >>> print(f"Paused with key: {result['pause_key']}")
318
+ """
319
+ try:
320
+ response: PauseSessionResponse = self._http_client.post(
321
+ "/pause",
322
+ data={"session_id": session_id},
323
+ )
324
+ if not response.get("succeeded"):
325
+ raise WorkflowError(
326
+ response.get("error", "Failed to pause session"),
327
+ session_id=session_id,
328
+ )
329
+ return response
330
+ except Exception as e:
331
+ if isinstance(e, WorkflowError):
332
+ raise
333
+ raise WorkflowError(
334
+ f"Failed to pause session: {e}",
335
+ session_id=session_id,
336
+ )
337
+
338
+ def resume(self, session_id: str) -> ResumeSessionResponse:
339
+ """
340
+ Resume a paused session.
341
+
342
+ Args:
343
+ session_id: The session ID to resume
344
+
345
+ Returns:
346
+ ResumeSessionResponse with resume details
347
+
348
+ Raises:
349
+ WorkflowError: If resuming the session fails
350
+
351
+ Example:
352
+ >>> result = client.resume("session-123")
353
+ >>> print(f"Resumed, pause type: {result['pause_type']}")
354
+ """
355
+ try:
356
+ response: ResumeSessionResponse = self._http_client.post(
357
+ "/resume_session",
358
+ data={"session_id": session_id},
359
+ )
360
+ if not response.get("succeeded"):
361
+ raise WorkflowError(
362
+ response.get("error", "Failed to resume session"),
363
+ session_id=session_id,
364
+ )
365
+ return response
366
+ except Exception as e:
367
+ if isinstance(e, WorkflowError):
368
+ raise
369
+ raise WorkflowError(
370
+ f"Failed to resume session: {e}",
371
+ session_id=session_id,
372
+ )
373
+
374
+ def search_workflows(
375
+ self,
376
+ workflow_name: str | None = None,
377
+ metadata: str | None = None,
378
+ ) -> SearchWorkflowsResponse:
379
+ """
380
+ Search workflows by name and/or metadata.
381
+
382
+ At least one of workflow_name or metadata must be provided.
383
+
384
+ Args:
385
+ workflow_name: Name of the workflow to search for
386
+ metadata: Metadata string to search for
387
+
388
+ Returns:
389
+ SearchWorkflowsResponse with matching workflows
390
+
391
+ Raises:
392
+ ValueError: If neither workflow_name nor metadata is provided
393
+ WorkflowError: If the search fails
394
+
395
+ Example:
396
+ >>> results = client.search_workflows(workflow_name="my-workflow")
397
+ >>> for wf in results["workflows"]:
398
+ ... print(f"{wf['workflow_name']} ({wf['workflow_id']})")
399
+ """
400
+ if workflow_name is None and metadata is None:
401
+ raise ValueError("At least one of workflow_name or metadata must be provided")
402
+
403
+ params: dict[str, str] = {}
404
+ if workflow_name is not None:
405
+ params["workflow_name"] = workflow_name
406
+ if metadata is not None:
407
+ params["metadata"] = metadata
408
+
409
+ try:
410
+ response: SearchWorkflowsResponse = self._http_client.get(
411
+ "/search_workflows",
412
+ params=params,
413
+ )
414
+ return response
415
+ except Exception as e:
416
+ if isinstance(e, WorkflowError):
417
+ raise
418
+ raise WorkflowError(f"Failed to search workflows: {e}")
419
+
420
+ def update_workflow_metadata(
421
+ self,
422
+ workflow_id: str,
423
+ metadata: str,
424
+ ) -> UpdateWorkflowMetadataResponse:
425
+ """
426
+ Update the metadata of a workflow.
427
+
428
+ Args:
429
+ workflow_id: The ID of the workflow to update
430
+ metadata: The new metadata string
431
+
432
+ Returns:
433
+ UpdateWorkflowMetadataResponse with update confirmation
434
+
435
+ Raises:
436
+ WorkflowError: If updating the metadata fails
437
+
438
+ Example:
439
+ >>> result = client.update_workflow_metadata(
440
+ ... "workflow-123",
441
+ ... "new-metadata-value"
442
+ ... )
443
+ >>> print(f"Updated: {result['message']}")
444
+ """
445
+ try:
446
+ response: UpdateWorkflowMetadataResponse = self._http_client.post(
447
+ "/update_workflow_metadata",
448
+ data={"workflow_id": workflow_id, "metadata": metadata},
449
+ )
450
+ if not response.get("succeeded"):
451
+ raise WorkflowError(
452
+ "Failed to update workflow metadata",
453
+ workflow_id=workflow_id,
454
+ )
455
+ return response
456
+ except Exception as e:
457
+ if isinstance(e, WorkflowError):
458
+ raise
459
+ raise WorkflowError(
460
+ f"Failed to update workflow metadata: {e}",
461
+ workflow_id=workflow_id,
462
+ )
@@ -0,0 +1,188 @@
1
+ """
2
+ Type definitions for the Simplex SDK.
3
+
4
+ This module contains TypedDict classes used for type hinting throughout the SDK.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, TypedDict
10
+
11
+
12
+ class FileMetadata(TypedDict):
13
+ """
14
+ Metadata for a file downloaded or created during a session.
15
+
16
+ Attributes:
17
+ filename: The filename
18
+ download_url: The URL the file was downloaded from
19
+ file_size: File size in bytes
20
+ download_timestamp: ISO timestamp when the file was downloaded/created
21
+ """
22
+
23
+ filename: str
24
+ download_url: str
25
+ file_size: int
26
+ download_timestamp: str
27
+
28
+
29
+ class SessionStatusResponse(TypedDict, total=False):
30
+ """
31
+ Response from polling session status.
32
+
33
+ Attributes:
34
+ in_progress: Whether the session is still running
35
+ success: Whether the session completed successfully (None while in progress)
36
+ metadata: Custom metadata provided when the session was started
37
+ workflow_metadata: Metadata from the workflow definition
38
+ file_metadata: Metadata for files downloaded during the session
39
+ scraper_outputs: Scraper outputs collected during the session, keyed by output name
40
+ structured_output: Structured output fields from workflow execution (None while in progress)
41
+ paused: Whether the session is currently paused
42
+ paused_key: The pause key if the session is paused
43
+ """
44
+
45
+ in_progress: bool
46
+ success: bool | None
47
+ metadata: dict[str, Any]
48
+ workflow_metadata: dict[str, Any]
49
+ file_metadata: list[FileMetadata]
50
+ scraper_outputs: dict[str, Any]
51
+ structured_output: dict[str, Any] | None
52
+ paused: bool
53
+ paused_key: str
54
+
55
+
56
+ class RunWorkflowResponse(TypedDict):
57
+ """
58
+ Response from running a workflow.
59
+
60
+ Attributes:
61
+ succeeded: Whether the workflow started successfully
62
+ message: Human-readable status message
63
+ session_id: Unique identifier for this workflow session
64
+ vnc_url: URL for VNC access to the workflow session
65
+ logs_url: URL for viewing session logs
66
+ """
67
+
68
+ succeeded: bool
69
+ message: str
70
+ session_id: str
71
+ vnc_url: str
72
+ logs_url: str
73
+
74
+
75
+ class PauseSessionResponse(TypedDict, total=False):
76
+ """
77
+ Response from pausing a session.
78
+
79
+ Attributes:
80
+ succeeded: Whether the pause operation succeeded
81
+ action: The action that was performed
82
+ pause_key: The key associated with the pause
83
+ error: Error message if the operation failed
84
+ """
85
+
86
+ succeeded: bool
87
+ action: str
88
+ pause_key: str
89
+ error: str
90
+
91
+
92
+ class ResumeSessionResponse(TypedDict, total=False):
93
+ """
94
+ Response from resuming a paused session.
95
+
96
+ Attributes:
97
+ succeeded: Whether the resume operation succeeded
98
+ action: The action that was performed
99
+ pause_type: The type of pause ('external' or 'internal')
100
+ key: The key associated with the pause
101
+ error: Error message if the operation failed
102
+ """
103
+
104
+ succeeded: bool
105
+ action: str
106
+ pause_type: str
107
+ key: str
108
+ error: str
109
+
110
+
111
+ class SearchWorkflowItem(TypedDict, total=False):
112
+ """
113
+ A single workflow item returned from a search.
114
+
115
+ Attributes:
116
+ workflow_id: The workflow's unique identifier
117
+ workflow_name: The workflow's name
118
+ variables: Variables defined in the workflow
119
+ metadata: Optional metadata string
120
+ """
121
+
122
+ workflow_id: str
123
+ workflow_name: str
124
+ variables: dict[str, Any]
125
+ metadata: str
126
+
127
+
128
+ class SearchWorkflowsResponse(TypedDict):
129
+ """
130
+ Response from searching workflows.
131
+
132
+ Attributes:
133
+ succeeded: Whether the search succeeded
134
+ workflows: List of matching workflows
135
+ count: Total number of matching workflows
136
+ """
137
+
138
+ succeeded: bool
139
+ workflows: list[SearchWorkflowItem]
140
+ count: int
141
+
142
+
143
+ class UpdateWorkflowMetadataResponse(TypedDict):
144
+ """
145
+ Response from updating workflow metadata.
146
+
147
+ Attributes:
148
+ succeeded: Whether the update succeeded
149
+ message: Human-readable status message
150
+ workflow_id: The workflow that was updated
151
+ metadata: The updated metadata string
152
+ """
153
+
154
+ succeeded: bool
155
+ message: str
156
+ workflow_id: str
157
+ metadata: str
158
+
159
+
160
+ class WebhookPayload(TypedDict, total=False):
161
+ """
162
+ Payload received from a Simplex webhook.
163
+
164
+ Attributes:
165
+ success: Whether the session completed successfully
166
+ agent_response: The agent's response text
167
+ session_id: The session identifier
168
+ file_metadata: Metadata for files created during the session
169
+ scraper_outputs: Scraper outputs collected during the session
170
+ session_metadata: Custom metadata attached to the session
171
+ workflow_id: The workflow identifier
172
+ workflow_metadata: Metadata from the workflow definition
173
+ workflow_result: The workflow execution result
174
+ structured_output: Structured output from the workflow
175
+ screenshot_url: URL to a screenshot taken during the session
176
+ """
177
+
178
+ success: bool
179
+ agent_response: str
180
+ session_id: str
181
+ file_metadata: list[FileMetadata]
182
+ scraper_outputs: dict[str, Any]
183
+ session_metadata: dict[str, Any]
184
+ workflow_id: str
185
+ workflow_metadata: dict[str, Any]
186
+ workflow_result: dict[str, Any]
187
+ structured_output: dict[str, Any]
188
+ screenshot_url: str
@@ -0,0 +1,70 @@
1
+ """
2
+ Webhook verification for the Simplex SDK.
3
+
4
+ This module provides utilities to verify incoming Simplex webhook requests
5
+ using HMAC-SHA256 signature verification.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import hmac
12
+
13
+
14
+ class WebhookVerificationError(Exception):
15
+ """Raised when webhook signature verification fails."""
16
+
17
+
18
+ def verify_simplex_webhook(
19
+ body: str,
20
+ headers: dict[str, str],
21
+ webhook_secret: str,
22
+ ) -> None:
23
+ """
24
+ Verify a Simplex webhook request using HMAC-SHA256 signature verification.
25
+
26
+ This function ensures that webhook requests are authentic and haven't been
27
+ tampered with in transit.
28
+
29
+ Args:
30
+ body: Raw request body as a string (must be the original unparsed body)
31
+ headers: Request headers dict containing the X-Simplex-Signature header
32
+ webhook_secret: Your webhook secret from the Simplex dashboard
33
+
34
+ Raises:
35
+ WebhookVerificationError: If the signature is missing, invalid,
36
+ or verification fails
37
+
38
+ Example:
39
+ >>> # Flask example
40
+ >>> from flask import Flask, request
41
+ >>> from simplex import verify_simplex_webhook, WebhookPayload
42
+ >>>
43
+ >>> @app.route("/webhook", methods=["POST"])
44
+ >>> def webhook():
45
+ ... body = request.get_data(as_text=True)
46
+ ... verify_simplex_webhook(body, dict(request.headers), WEBHOOK_SECRET)
47
+ ... payload: WebhookPayload = request.get_json()
48
+ ... print(f"Session: {payload['session_id']}")
49
+ ... return {"success": True}
50
+ """
51
+ # Normalize header lookup (case-insensitive)
52
+ signature = None
53
+ for key, value in headers.items():
54
+ if key.lower() == "x-simplex-signature":
55
+ signature = value
56
+ break
57
+
58
+ if not signature:
59
+ raise WebhookVerificationError("Missing X-Simplex-Signature header")
60
+
61
+ # Compute expected signature
62
+ expected_signature = hmac.new(
63
+ webhook_secret.encode("utf-8"),
64
+ body.encode("utf-8"),
65
+ hashlib.sha256,
66
+ ).hexdigest()
67
+
68
+ # Constant-time comparison to prevent timing attacks
69
+ if not hmac.compare_digest(signature, expected_signature):
70
+ raise WebhookVerificationError("Invalid webhook signature")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplex
3
- Version: 2.0.4
3
+ Version: 3.0.1
4
4
  Summary: Official Python SDK for the Simplex API
5
5
  Author-email: Simplex <support@simplex.sh>
6
6
  License: MIT
@@ -8,6 +8,7 @@ simplex/_http_client.py
8
8
  simplex/client.py
9
9
  simplex/errors.py
10
10
  simplex/types.py
11
+ simplex/webhook.py
11
12
  simplex.egg-info/PKG-INFO
12
13
  simplex.egg-info/SOURCES.txt
13
14
  simplex.egg-info/dependency_links.txt
@@ -1,70 +0,0 @@
1
- """
2
- Type definitions for the Simplex SDK.
3
-
4
- This module contains TypedDict classes used for type hinting throughout the SDK.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Any, TypedDict
10
-
11
-
12
- class FileMetadata(TypedDict):
13
- """
14
- Metadata for a file downloaded or created during a session.
15
-
16
- Attributes:
17
- filename: The filename
18
- download_url: The URL the file was downloaded from
19
- file_size: File size in bytes
20
- download_timestamp: ISO timestamp when the file was downloaded/created
21
- """
22
-
23
- filename: str
24
- download_url: str
25
- file_size: int
26
- download_timestamp: str
27
-
28
-
29
- class SessionStatusResponse(TypedDict, total=False):
30
- """
31
- Response from polling session status.
32
-
33
- Attributes:
34
- in_progress: Whether the session is still running
35
- success: Whether the session completed successfully (None while in progress)
36
- metadata: Custom metadata provided when the session was started
37
- workflow_metadata: Metadata from the workflow definition
38
- file_metadata: Metadata for files downloaded during the session
39
- scraper_outputs: Scraper outputs collected during the session, keyed by output name
40
- paused: Whether the session is currently paused
41
- paused_key: The pause key if the session is paused
42
- """
43
-
44
- in_progress: bool
45
- success: bool | None
46
- metadata: dict[str, Any]
47
- workflow_metadata: dict[str, Any]
48
- file_metadata: list[FileMetadata]
49
- scraper_outputs: dict[str, Any]
50
- paused: bool
51
- paused_key: str
52
-
53
-
54
- class RunWorkflowResponse(TypedDict):
55
- """
56
- Response from running a workflow.
57
-
58
- Attributes:
59
- succeeded: Whether the workflow started successfully
60
- message: Human-readable status message
61
- session_id: Unique identifier for this workflow session
62
- vnc_url: URL for VNC access to the workflow session
63
- logs_url: URL for viewing session logs
64
- """
65
-
66
- succeeded: bool
67
- message: str
68
- session_id: str
69
- vnc_url: str
70
- logs_url: str
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes