predev-api 0.12.0__tar.gz → 1.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.
- {predev_api-0.12.0/predev_api.egg-info → predev_api-1.1.0}/PKG-INFO +66 -18
- {predev_api-0.12.0 → predev_api-1.1.0}/README.md +65 -17
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api/__init__.py +16 -2
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api/client.py +123 -27
- predev_api-1.1.0/predev_api/exceptions.py +86 -0
- {predev_api-0.12.0 → predev_api-1.1.0/predev_api.egg-info}/PKG-INFO +66 -18
- {predev_api-0.12.0 → predev_api-1.1.0}/setup.py +1 -1
- predev_api-0.12.0/predev_api/exceptions.py +0 -18
- {predev_api-0.12.0 → predev_api-1.1.0}/LICENSE +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/MANIFEST.in +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api.egg-info/SOURCES.txt +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api.egg-info/dependency_links.txt +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api.egg-info/requires.txt +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/predev_api.egg-info/top_level.txt +0 -0
- {predev_api-0.12.0 → predev_api-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: predev-api
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Python client for the Pre.dev Architect API - Generate comprehensive software specifications
|
|
5
5
|
Home-page: https://github.com/predotdev/predev-api
|
|
6
6
|
Author: Pre.dev
|
|
@@ -502,7 +502,7 @@ For issues, questions, or contributions, please visit the [GitHub repository](ht
|
|
|
502
502
|
|
|
503
503
|
## Browser Tasks
|
|
504
504
|
|
|
505
|
-
Run browser automation —
|
|
505
|
+
Run browser automation — navigate, interact, and/or extract data from any web page. Each task navigates a URL, performs actions, and optionally returns typed JSON.
|
|
506
506
|
|
|
507
507
|
### Quick start
|
|
508
508
|
|
|
@@ -511,7 +511,7 @@ from predev_api import PredevAPI
|
|
|
511
511
|
|
|
512
512
|
client = PredevAPI(api_key="your_api_key")
|
|
513
513
|
|
|
514
|
-
result = client.
|
|
514
|
+
result = client.browser_agent([
|
|
515
515
|
{
|
|
516
516
|
"url": "https://news.ycombinator.com",
|
|
517
517
|
"instruction": "Extract the top 5 stories",
|
|
@@ -542,7 +542,7 @@ for story in result["results"][0]["data"]["stories"]:
|
|
|
542
542
|
#### 1. Sync (default) — wait for completion
|
|
543
543
|
|
|
544
544
|
```python
|
|
545
|
-
result = client.
|
|
545
|
+
result = client.browser_agent([
|
|
546
546
|
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
547
547
|
])
|
|
548
548
|
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
@@ -554,7 +554,7 @@ print(result["totalCreditsUsed"]) # 0.1
|
|
|
554
554
|
Yields events as the agent runs. Good for showing progress in a UI.
|
|
555
555
|
|
|
556
556
|
```python
|
|
557
|
-
for msg in client.
|
|
557
|
+
for msg in client.browser_agent(tasks, stream=True):
|
|
558
558
|
e, d = msg["event"], msg["data"]
|
|
559
559
|
if e == "task_event":
|
|
560
560
|
# navigation | screenshot | plan | action | validation | done
|
|
@@ -574,11 +574,11 @@ Returns the batch ID immediately. Use for long-running batches or background job
|
|
|
574
574
|
```python
|
|
575
575
|
import time
|
|
576
576
|
|
|
577
|
-
r = client.
|
|
577
|
+
r = client.browser_agent(tasks, run_async=True)
|
|
578
578
|
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
579
579
|
|
|
580
580
|
while True:
|
|
581
|
-
state = client.
|
|
581
|
+
state = client.get_browser_agent(r["id"])
|
|
582
582
|
print(f"{state['completed']}/{state['total']}")
|
|
583
583
|
for done in state["results"]:
|
|
584
584
|
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
@@ -600,27 +600,27 @@ Each task's behavior is determined by which fields are set:
|
|
|
600
600
|
|
|
601
601
|
### Retrieving a batch with the full timeline
|
|
602
602
|
|
|
603
|
-
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging
|
|
603
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging. Screenshots are uploaded to a CDN during execution — retrieved events contain `data["url"]`, not base64.
|
|
604
604
|
|
|
605
605
|
```python
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
606
|
+
details = client.get_browser_agent(batch_id, include_events=True)
|
|
609
607
|
for result in details["results"]:
|
|
610
608
|
for ev in result.get("events", []):
|
|
611
609
|
if ev["type"] == "screenshot":
|
|
612
|
-
|
|
613
|
-
|
|
610
|
+
# ev["data"]["url"] is a permanent CDN URL. Use directly in an <img>.
|
|
611
|
+
print(f"Iter {ev.get('iteration')} screenshot:", ev["data"]["url"])
|
|
614
612
|
elif ev["type"] == "plan":
|
|
615
613
|
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
616
614
|
```
|
|
617
615
|
|
|
616
|
+
> **Note:** The live SSE stream (`stream=True`) still sends screenshots inline as base64 (`ev["data"]["base64"]`) so live UIs render instantly. The retrieval path (`get_browser_agent`) always returns CDN URLs.
|
|
617
|
+
|
|
618
618
|
### Parallel batch example
|
|
619
619
|
|
|
620
620
|
```python
|
|
621
621
|
# Scrape 100 URLs with 20 browsers in parallel
|
|
622
622
|
urls = [...100 urls...]
|
|
623
|
-
result = client.
|
|
623
|
+
result = client.browser_agent(
|
|
624
624
|
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
625
625
|
concurrency=20
|
|
626
626
|
)
|
|
@@ -631,10 +631,10 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
631
631
|
|
|
632
632
|
| Method | Returns | Use when |
|
|
633
633
|
|---|---|---|
|
|
634
|
-
| `
|
|
635
|
-
| `
|
|
636
|
-
| `
|
|
637
|
-
| `
|
|
634
|
+
| `browser_agent(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
635
|
+
| `browser_agent(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
636
|
+
| `browser_agent(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
637
|
+
| `get_browser_agent(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
638
638
|
|
|
639
639
|
### Task result statuses
|
|
640
640
|
|
|
@@ -653,3 +653,51 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
653
653
|
- Minimum: **0.1 credits per task** ($0.01)
|
|
654
654
|
- 10x margin on underlying LLM + sandbox compute
|
|
655
655
|
- 1 credit = $0.10
|
|
656
|
+
|
|
657
|
+
### Error handling
|
|
658
|
+
|
|
659
|
+
The browser-task endpoints raise typed exceptions for the most common
|
|
660
|
+
gating cases. Every billing-gate exception carries an `action_url` — a
|
|
661
|
+
deep link back to pre.dev that auto-opens the right modal (subscribe /
|
|
662
|
+
buy credits) when the user lands there:
|
|
663
|
+
|
|
664
|
+
```python
|
|
665
|
+
from predev_api import (
|
|
666
|
+
PredevAPI,
|
|
667
|
+
InsufficientCreditsError,
|
|
668
|
+
SubscriptionRequiredError,
|
|
669
|
+
RateLimitError,
|
|
670
|
+
QueueFullError,
|
|
671
|
+
)
|
|
672
|
+
import webbrowser
|
|
673
|
+
|
|
674
|
+
client = PredevAPI(api_key="your_pre.dev_api_key")
|
|
675
|
+
|
|
676
|
+
try:
|
|
677
|
+
client.browser_agent([{"url": "https://example.com", "instruction": "extract h1"}])
|
|
678
|
+
except InsufficientCreditsError as e:
|
|
679
|
+
# Send the user to the credits modal — `action_url` is the canonical link.
|
|
680
|
+
if e.action_url:
|
|
681
|
+
webbrowser.open(e.action_url)
|
|
682
|
+
except SubscriptionRequiredError as e:
|
|
683
|
+
if e.action_url:
|
|
684
|
+
webbrowser.open(e.action_url)
|
|
685
|
+
except RateLimitError:
|
|
686
|
+
pass # back off and retry
|
|
687
|
+
except QueueFullError:
|
|
688
|
+
pass # wait for in-flight tasks to drain
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Mid-stream errors on the SSE stream raise the same typed exceptions, so a
|
|
692
|
+
`for msg in client.browser_agent(..., stream=True):` loop wrapped in
|
|
693
|
+
`try/except` is enough — no special event handling required.
|
|
694
|
+
|
|
695
|
+
| Exception | HTTP | `code` |
|
|
696
|
+
|---|---|---|
|
|
697
|
+
| `SubscriptionRequiredError` | 402 | `SUBSCRIPTION_REQUIRED` |
|
|
698
|
+
| `InsufficientCreditsError` | 402 | `INSUFFICIENT_CREDITS` |
|
|
699
|
+
| `RateLimitError` | 429 | `RATE_LIMITED` |
|
|
700
|
+
| `QueueFullError` | 429 | `QUEUE_FULL` |
|
|
701
|
+
| `BatchTooLargeError` | 400 | `BATCH_TOO_LARGE` |
|
|
702
|
+
| `AuthenticationError` | 401 | — |
|
|
703
|
+
| `PredevAPIError` | other | — |
|
|
@@ -462,7 +462,7 @@ For issues, questions, or contributions, please visit the [GitHub repository](ht
|
|
|
462
462
|
|
|
463
463
|
## Browser Tasks
|
|
464
464
|
|
|
465
|
-
Run browser automation —
|
|
465
|
+
Run browser automation — navigate, interact, and/or extract data from any web page. Each task navigates a URL, performs actions, and optionally returns typed JSON.
|
|
466
466
|
|
|
467
467
|
### Quick start
|
|
468
468
|
|
|
@@ -471,7 +471,7 @@ from predev_api import PredevAPI
|
|
|
471
471
|
|
|
472
472
|
client = PredevAPI(api_key="your_api_key")
|
|
473
473
|
|
|
474
|
-
result = client.
|
|
474
|
+
result = client.browser_agent([
|
|
475
475
|
{
|
|
476
476
|
"url": "https://news.ycombinator.com",
|
|
477
477
|
"instruction": "Extract the top 5 stories",
|
|
@@ -502,7 +502,7 @@ for story in result["results"][0]["data"]["stories"]:
|
|
|
502
502
|
#### 1. Sync (default) — wait for completion
|
|
503
503
|
|
|
504
504
|
```python
|
|
505
|
-
result = client.
|
|
505
|
+
result = client.browser_agent([
|
|
506
506
|
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
507
507
|
])
|
|
508
508
|
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
@@ -514,7 +514,7 @@ print(result["totalCreditsUsed"]) # 0.1
|
|
|
514
514
|
Yields events as the agent runs. Good for showing progress in a UI.
|
|
515
515
|
|
|
516
516
|
```python
|
|
517
|
-
for msg in client.
|
|
517
|
+
for msg in client.browser_agent(tasks, stream=True):
|
|
518
518
|
e, d = msg["event"], msg["data"]
|
|
519
519
|
if e == "task_event":
|
|
520
520
|
# navigation | screenshot | plan | action | validation | done
|
|
@@ -534,11 +534,11 @@ Returns the batch ID immediately. Use for long-running batches or background job
|
|
|
534
534
|
```python
|
|
535
535
|
import time
|
|
536
536
|
|
|
537
|
-
r = client.
|
|
537
|
+
r = client.browser_agent(tasks, run_async=True)
|
|
538
538
|
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
539
539
|
|
|
540
540
|
while True:
|
|
541
|
-
state = client.
|
|
541
|
+
state = client.get_browser_agent(r["id"])
|
|
542
542
|
print(f"{state['completed']}/{state['total']}")
|
|
543
543
|
for done in state["results"]:
|
|
544
544
|
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
@@ -560,27 +560,27 @@ Each task's behavior is determined by which fields are set:
|
|
|
560
560
|
|
|
561
561
|
### Retrieving a batch with the full timeline
|
|
562
562
|
|
|
563
|
-
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging
|
|
563
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging. Screenshots are uploaded to a CDN during execution — retrieved events contain `data["url"]`, not base64.
|
|
564
564
|
|
|
565
565
|
```python
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
566
|
+
details = client.get_browser_agent(batch_id, include_events=True)
|
|
569
567
|
for result in details["results"]:
|
|
570
568
|
for ev in result.get("events", []):
|
|
571
569
|
if ev["type"] == "screenshot":
|
|
572
|
-
|
|
573
|
-
|
|
570
|
+
# ev["data"]["url"] is a permanent CDN URL. Use directly in an <img>.
|
|
571
|
+
print(f"Iter {ev.get('iteration')} screenshot:", ev["data"]["url"])
|
|
574
572
|
elif ev["type"] == "plan":
|
|
575
573
|
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
576
574
|
```
|
|
577
575
|
|
|
576
|
+
> **Note:** The live SSE stream (`stream=True`) still sends screenshots inline as base64 (`ev["data"]["base64"]`) so live UIs render instantly. The retrieval path (`get_browser_agent`) always returns CDN URLs.
|
|
577
|
+
|
|
578
578
|
### Parallel batch example
|
|
579
579
|
|
|
580
580
|
```python
|
|
581
581
|
# Scrape 100 URLs with 20 browsers in parallel
|
|
582
582
|
urls = [...100 urls...]
|
|
583
|
-
result = client.
|
|
583
|
+
result = client.browser_agent(
|
|
584
584
|
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
585
585
|
concurrency=20
|
|
586
586
|
)
|
|
@@ -591,10 +591,10 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
591
591
|
|
|
592
592
|
| Method | Returns | Use when |
|
|
593
593
|
|---|---|---|
|
|
594
|
-
| `
|
|
595
|
-
| `
|
|
596
|
-
| `
|
|
597
|
-
| `
|
|
594
|
+
| `browser_agent(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
595
|
+
| `browser_agent(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
596
|
+
| `browser_agent(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
597
|
+
| `get_browser_agent(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
598
598
|
|
|
599
599
|
### Task result statuses
|
|
600
600
|
|
|
@@ -613,3 +613,51 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
613
613
|
- Minimum: **0.1 credits per task** ($0.01)
|
|
614
614
|
- 10x margin on underlying LLM + sandbox compute
|
|
615
615
|
- 1 credit = $0.10
|
|
616
|
+
|
|
617
|
+
### Error handling
|
|
618
|
+
|
|
619
|
+
The browser-task endpoints raise typed exceptions for the most common
|
|
620
|
+
gating cases. Every billing-gate exception carries an `action_url` — a
|
|
621
|
+
deep link back to pre.dev that auto-opens the right modal (subscribe /
|
|
622
|
+
buy credits) when the user lands there:
|
|
623
|
+
|
|
624
|
+
```python
|
|
625
|
+
from predev_api import (
|
|
626
|
+
PredevAPI,
|
|
627
|
+
InsufficientCreditsError,
|
|
628
|
+
SubscriptionRequiredError,
|
|
629
|
+
RateLimitError,
|
|
630
|
+
QueueFullError,
|
|
631
|
+
)
|
|
632
|
+
import webbrowser
|
|
633
|
+
|
|
634
|
+
client = PredevAPI(api_key="your_pre.dev_api_key")
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
client.browser_agent([{"url": "https://example.com", "instruction": "extract h1"}])
|
|
638
|
+
except InsufficientCreditsError as e:
|
|
639
|
+
# Send the user to the credits modal — `action_url` is the canonical link.
|
|
640
|
+
if e.action_url:
|
|
641
|
+
webbrowser.open(e.action_url)
|
|
642
|
+
except SubscriptionRequiredError as e:
|
|
643
|
+
if e.action_url:
|
|
644
|
+
webbrowser.open(e.action_url)
|
|
645
|
+
except RateLimitError:
|
|
646
|
+
pass # back off and retry
|
|
647
|
+
except QueueFullError:
|
|
648
|
+
pass # wait for in-flight tasks to drain
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Mid-stream errors on the SSE stream raise the same typed exceptions, so a
|
|
652
|
+
`for msg in client.browser_agent(..., stream=True):` loop wrapped in
|
|
653
|
+
`try/except` is enough — no special event handling required.
|
|
654
|
+
|
|
655
|
+
| Exception | HTTP | `code` |
|
|
656
|
+
|---|---|---|
|
|
657
|
+
| `SubscriptionRequiredError` | 402 | `SUBSCRIPTION_REQUIRED` |
|
|
658
|
+
| `InsufficientCreditsError` | 402 | `INSUFFICIENT_CREDITS` |
|
|
659
|
+
| `RateLimitError` | 429 | `RATE_LIMITED` |
|
|
660
|
+
| `QueueFullError` | 429 | `QUEUE_FULL` |
|
|
661
|
+
| `BatchTooLargeError` | 400 | `BATCH_TOO_LARGE` |
|
|
662
|
+
| `AuthenticationError` | 401 | — |
|
|
663
|
+
| `PredevAPIError` | other | — |
|
|
@@ -27,14 +27,28 @@ from .client import (
|
|
|
27
27
|
AlternativeTechStackItemApi,
|
|
28
28
|
SpecEnrichedTechStackItem,
|
|
29
29
|
)
|
|
30
|
-
from .exceptions import
|
|
30
|
+
from .exceptions import (
|
|
31
|
+
PredevAPIError,
|
|
32
|
+
AuthenticationError,
|
|
33
|
+
RateLimitError,
|
|
34
|
+
SubscriptionRequiredError,
|
|
35
|
+
InsufficientCreditsError,
|
|
36
|
+
QueueFullError,
|
|
37
|
+
BatchTooLargeError,
|
|
38
|
+
exception_for_code,
|
|
39
|
+
)
|
|
31
40
|
|
|
32
|
-
__version__ = "
|
|
41
|
+
__version__ = "1.1.0"
|
|
33
42
|
__all__ = [
|
|
34
43
|
"PredevAPI",
|
|
35
44
|
"PredevAPIError",
|
|
36
45
|
"AuthenticationError",
|
|
37
46
|
"RateLimitError",
|
|
47
|
+
"SubscriptionRequiredError",
|
|
48
|
+
"InsufficientCreditsError",
|
|
49
|
+
"QueueFullError",
|
|
50
|
+
"BatchTooLargeError",
|
|
51
|
+
"exception_for_code",
|
|
38
52
|
"ZippedDocsUrl",
|
|
39
53
|
"SpecCoreFunctionality",
|
|
40
54
|
"SpecTechStackItem",
|
|
@@ -6,7 +6,16 @@ from typing import Optional, Dict, Any, Literal, List, Union, BinaryIO, Iterator
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
import json
|
|
8
8
|
import requests
|
|
9
|
-
from .exceptions import
|
|
9
|
+
from .exceptions import (
|
|
10
|
+
PredevAPIError,
|
|
11
|
+
AuthenticationError,
|
|
12
|
+
RateLimitError,
|
|
13
|
+
SubscriptionRequiredError,
|
|
14
|
+
InsufficientCreditsError,
|
|
15
|
+
QueueFullError,
|
|
16
|
+
BatchTooLargeError,
|
|
17
|
+
exception_for_code,
|
|
18
|
+
)
|
|
10
19
|
|
|
11
20
|
|
|
12
21
|
@dataclass
|
|
@@ -132,7 +141,9 @@ class SpecGraphNode:
|
|
|
132
141
|
label: str = ""
|
|
133
142
|
type: Optional[str] = None
|
|
134
143
|
description: Optional[str] = None
|
|
135
|
-
|
|
144
|
+
# userFlowGraph: BFS depth from roles (roles = 0, flows >= 1, None when
|
|
145
|
+
# unreachable). architectureGraph: C4 diagram level ("C1" | "C2").
|
|
146
|
+
level: Optional[Union[int, str]] = None
|
|
136
147
|
hours: Optional[float] = None
|
|
137
148
|
|
|
138
149
|
|
|
@@ -608,27 +619,27 @@ class PredevAPI:
|
|
|
608
619
|
except requests.RequestException as e:
|
|
609
620
|
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
610
621
|
|
|
611
|
-
def
|
|
622
|
+
def get_browser_agent(
|
|
612
623
|
self,
|
|
613
624
|
batch_id: str,
|
|
614
625
|
include_events: bool = False
|
|
615
626
|
) -> Dict[str, Any]:
|
|
616
627
|
"""
|
|
617
|
-
Get the status + results of a browser
|
|
628
|
+
Get the status + results of a browser-agent batch by ID.
|
|
618
629
|
|
|
619
|
-
Works for both in-progress and completed batches - use with
|
|
630
|
+
Works for both in-progress and completed batches - use with run_async=True
|
|
620
631
|
submissions to poll for progress. Set include_events=True to get the
|
|
621
632
|
full timeline (screenshots, plans, actions, validations) for each task.
|
|
622
633
|
|
|
623
634
|
Args:
|
|
624
|
-
batch_id: The batch ID returned from
|
|
635
|
+
batch_id: The batch ID returned from browser_agent()
|
|
625
636
|
include_events: Include full event timeline (can be large due to screenshots)
|
|
626
637
|
|
|
627
638
|
Returns:
|
|
628
|
-
Dict with id, total, completed, results,
|
|
639
|
+
Dict with id, total, completed, results, totalCreditsUsed, status
|
|
629
640
|
"""
|
|
630
641
|
qs = "?includeEvents=true" if include_events else ""
|
|
631
|
-
url = f"{self.base_url}/
|
|
642
|
+
url = f"{self.base_url}/browser-agent/{batch_id}{qs}"
|
|
632
643
|
try:
|
|
633
644
|
response = requests.get(url, headers=self.headers, timeout=60)
|
|
634
645
|
self._handle_response(response)
|
|
@@ -636,7 +647,55 @@ class PredevAPI:
|
|
|
636
647
|
except requests.RequestException as e:
|
|
637
648
|
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
638
649
|
|
|
639
|
-
def
|
|
650
|
+
def list_browser_agents(
|
|
651
|
+
self,
|
|
652
|
+
limit: Optional[int] = None,
|
|
653
|
+
skip: Optional[int] = None,
|
|
654
|
+
status: Optional[str] = None
|
|
655
|
+
) -> Dict[str, Any]:
|
|
656
|
+
"""
|
|
657
|
+
List browser-agent runs for the authenticated caller. Sorted by most recent.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
limit: Page size (1-100, default 20)
|
|
661
|
+
skip: Offset for pagination
|
|
662
|
+
status: Optional filter ('processing' | 'completed')
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
Dict with keys: batches, total, hasMore
|
|
666
|
+
"""
|
|
667
|
+
params: Dict[str, Any] = {}
|
|
668
|
+
if limit is not None:
|
|
669
|
+
params["limit"] = limit
|
|
670
|
+
if skip is not None:
|
|
671
|
+
params["skip"] = skip
|
|
672
|
+
if status is not None:
|
|
673
|
+
params["status"] = status
|
|
674
|
+
url = f"{self.base_url}/list-browser-agents"
|
|
675
|
+
try:
|
|
676
|
+
response = requests.get(url, headers=self.headers, params=params, timeout=60)
|
|
677
|
+
self._handle_response(response)
|
|
678
|
+
return response.json()
|
|
679
|
+
except requests.RequestException as e:
|
|
680
|
+
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
681
|
+
|
|
682
|
+
def browser_agent_status(self) -> Dict[str, Any]:
|
|
683
|
+
"""
|
|
684
|
+
Caller's live queue snapshot. Useful for watching in-flight work
|
|
685
|
+
against the per-user queue cap (1000 live tasks).
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
Dict with keys: userId, running, claimed, pending, total, cap
|
|
689
|
+
"""
|
|
690
|
+
url = f"{self.base_url}/browser-agent-status"
|
|
691
|
+
try:
|
|
692
|
+
response = requests.get(url, headers=self.headers, timeout=30)
|
|
693
|
+
self._handle_response(response)
|
|
694
|
+
return response.json()
|
|
695
|
+
except requests.RequestException as e:
|
|
696
|
+
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
697
|
+
|
|
698
|
+
def browser_agent(
|
|
640
699
|
self,
|
|
641
700
|
tasks: List[Dict[str, Any]],
|
|
642
701
|
concurrency: Optional[int] = None,
|
|
@@ -644,7 +703,7 @@ class PredevAPI:
|
|
|
644
703
|
run_async: bool = False
|
|
645
704
|
) -> Union[Dict[str, Any], Iterator[Dict[str, Any]]]:
|
|
646
705
|
"""
|
|
647
|
-
Run browser automation tasks - scrape data, fill forms, navigate pages.
|
|
706
|
+
Run browser-agent automation tasks - scrape data, fill forms, navigate pages.
|
|
648
707
|
|
|
649
708
|
Always pass an array of tasks (even for a single task). Each task has a
|
|
650
709
|
url and optional instruction, input, and output schema.
|
|
@@ -658,9 +717,11 @@ class PredevAPI:
|
|
|
658
717
|
concurrency: Parallel browsers (default 5, max 20)
|
|
659
718
|
stream: If True, returns an iterator of SSE events instead of
|
|
660
719
|
waiting for all tasks to complete
|
|
720
|
+
run_async: If True, returns immediately with the batch id; poll
|
|
721
|
+
get_browser_agent(id) for progress.
|
|
661
722
|
|
|
662
723
|
Returns:
|
|
663
|
-
Without stream: Dict with id, total, completed, results,
|
|
724
|
+
Without stream: Dict with id, total, completed, results, totalCreditsUsed
|
|
664
725
|
With stream: Iterator yielding dicts with 'event' and 'data' keys:
|
|
665
726
|
- event='task_event': real-time events (navigation, screenshot, plan, action, etc.)
|
|
666
727
|
- event='task_result': a single task completed
|
|
@@ -669,20 +730,20 @@ class PredevAPI:
|
|
|
669
730
|
|
|
670
731
|
Example:
|
|
671
732
|
>>> # Standard mode
|
|
672
|
-
>>> result = client.
|
|
733
|
+
>>> result = client.browser_agent([
|
|
673
734
|
... {"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
674
735
|
... ])
|
|
675
736
|
>>> print(result["results"][0]["data"])
|
|
676
737
|
>>>
|
|
677
738
|
>>> # Stream mode
|
|
678
|
-
>>> for msg in client.
|
|
739
|
+
>>> for msg in client.browser_agent([...], stream=True):
|
|
679
740
|
... if msg["event"] == "done":
|
|
680
741
|
... print("Done:", msg["data"]["totalCreditsUsed"], "credits")
|
|
681
742
|
"""
|
|
682
743
|
if stream:
|
|
683
|
-
return self.
|
|
744
|
+
return self._browser_agent_stream(tasks, concurrency)
|
|
684
745
|
|
|
685
|
-
url = f"{self.base_url}/
|
|
746
|
+
url = f"{self.base_url}/browser-agent"
|
|
686
747
|
payload: Dict[str, Any] = {"tasks": tasks}
|
|
687
748
|
if concurrency is not None:
|
|
688
749
|
payload["concurrency"] = concurrency
|
|
@@ -701,13 +762,13 @@ class PredevAPI:
|
|
|
701
762
|
except requests.RequestException as e:
|
|
702
763
|
raise PredevAPIError(f"Request failed: {str(e)}") from e
|
|
703
764
|
|
|
704
|
-
def
|
|
765
|
+
def _browser_agent_stream(
|
|
705
766
|
self,
|
|
706
767
|
tasks: List[Dict[str, Any]],
|
|
707
768
|
concurrency: Optional[int] = None
|
|
708
769
|
) -> Iterator[Dict[str, Any]]:
|
|
709
|
-
"""SSE streaming implementation for
|
|
710
|
-
url = f"{self.base_url}/
|
|
770
|
+
"""SSE streaming implementation for browser_agent."""
|
|
771
|
+
url = f"{self.base_url}/browser-agent"
|
|
711
772
|
payload: Dict[str, Any] = {"tasks": tasks, "stream": True}
|
|
712
773
|
if concurrency is not None:
|
|
713
774
|
payload["concurrency"] = concurrency
|
|
@@ -732,6 +793,18 @@ class PredevAPI:
|
|
|
732
793
|
current_event = line[7:].strip()
|
|
733
794
|
elif line.startswith("data: ") and current_event:
|
|
734
795
|
data = json.loads(line[6:])
|
|
796
|
+
# Mid-stream `error` events carry the same
|
|
797
|
+
# `{ error, code, actionUrl? }` shape the REST endpoints
|
|
798
|
+
# do — surface them as typed exceptions so callers can
|
|
799
|
+
# `except InsufficientCreditsError` cleanly.
|
|
800
|
+
if current_event == "error" and isinstance(data, dict):
|
|
801
|
+
raise exception_for_code(
|
|
802
|
+
data.get("code"),
|
|
803
|
+
data.get("error")
|
|
804
|
+
or data.get("message")
|
|
805
|
+
or "Browser agent error",
|
|
806
|
+
data.get("actionUrl") or data.get("action_url"),
|
|
807
|
+
)
|
|
735
808
|
yield {"event": current_event, "data": data}
|
|
736
809
|
current_event = ""
|
|
737
810
|
except requests.RequestException as e:
|
|
@@ -896,22 +969,45 @@ class PredevAPI:
|
|
|
896
969
|
return {"file": (filename, file)}
|
|
897
970
|
|
|
898
971
|
def _handle_response(self, response: requests.Response) -> None:
|
|
899
|
-
"""Handle API response and raise appropriate exceptions.
|
|
972
|
+
"""Handle API response and raise appropriate exceptions.
|
|
973
|
+
|
|
974
|
+
Browser-task gating errors come back as a structured body
|
|
975
|
+
``{"error": ..., "code": ..., "actionUrl": ...}`` (HTTP 402 / 429 /
|
|
976
|
+
400). We parse the body before short-circuiting on status code so
|
|
977
|
+
``code: SUBSCRIPTION_REQUIRED`` becomes :class:`SubscriptionRequiredError`
|
|
978
|
+
with its ``action_url`` populated, instead of a generic
|
|
979
|
+
:class:`PredevAPIError`.
|
|
980
|
+
"""
|
|
900
981
|
if response.status_code == 200:
|
|
901
982
|
return
|
|
902
983
|
|
|
984
|
+
# Try to parse the structured error body up front.
|
|
985
|
+
error_body = None
|
|
986
|
+
error_message = "Unknown error"
|
|
987
|
+
try:
|
|
988
|
+
error_body = response.json()
|
|
989
|
+
error_message = (
|
|
990
|
+
error_body.get("error")
|
|
991
|
+
or error_body.get("message")
|
|
992
|
+
or str(error_body)
|
|
993
|
+
)
|
|
994
|
+
except Exception:
|
|
995
|
+
error_message = response.text or f"HTTP {response.status_code}"
|
|
996
|
+
|
|
997
|
+
# Structured `code` wins — covers SUBSCRIPTION_REQUIRED /
|
|
998
|
+
# INSUFFICIENT_CREDITS / QUEUE_FULL / BATCH_TOO_LARGE / RATE_LIMITED.
|
|
999
|
+
if isinstance(error_body, dict) and error_body.get("code"):
|
|
1000
|
+
raise exception_for_code(
|
|
1001
|
+
error_body["code"],
|
|
1002
|
+
error_message,
|
|
1003
|
+
error_body.get("actionUrl") or error_body.get("action_url"),
|
|
1004
|
+
)
|
|
1005
|
+
|
|
903
1006
|
if response.status_code == 401:
|
|
904
1007
|
raise AuthenticationError("Invalid API key")
|
|
905
1008
|
|
|
906
1009
|
if response.status_code == 429:
|
|
907
|
-
raise RateLimitError(
|
|
908
|
-
|
|
909
|
-
try:
|
|
910
|
-
error_data = response.json()
|
|
911
|
-
error_message = error_data.get("error") or error_data.get(
|
|
912
|
-
"message") or str(error_data)
|
|
913
|
-
except Exception:
|
|
914
|
-
error_message = response.text or "Unknown error"
|
|
1010
|
+
raise RateLimitError(error_message)
|
|
915
1011
|
|
|
916
1012
|
raise PredevAPIError(
|
|
917
1013
|
f"API request failed with status {response.status_code}: {error_message}"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the Pre.dev API client.
|
|
3
|
+
|
|
4
|
+
Browser-task gating errors carry the same shape as the Node SDK:
|
|
5
|
+
``code`` selects a typed exception, and ``action_url`` is a deep link
|
|
6
|
+
back to pre.dev that auto-opens the right billing modal (subscribe /
|
|
7
|
+
buy credits) when the user lands there. Older endpoints just return
|
|
8
|
+
``{"error": "..."}`` and surface as ``PredevAPIError``.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PredevAPIError(Exception):
|
|
15
|
+
"""Base exception for Pre.dev API errors."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AuthenticationError(PredevAPIError):
|
|
20
|
+
"""Raised when authentication fails (HTTP 401)."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RateLimitError(PredevAPIError):
|
|
25
|
+
"""Raised when a request is rate-limited (HTTP 429, ``code: RATE_LIMITED``)."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SubscriptionRequiredError(PredevAPIError):
|
|
30
|
+
"""HTTP 402, ``code: SUBSCRIPTION_REQUIRED``.
|
|
31
|
+
|
|
32
|
+
Trial limit reached on this API key — the user needs an active
|
|
33
|
+
subscription to keep running browser-agent tasks.
|
|
34
|
+
|
|
35
|
+
``action_url``, when present, is a deep link back to pre.dev that
|
|
36
|
+
auto-opens the subscribe modal.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, message: str, action_url: Optional[str] = None):
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
self.action_url = action_url
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InsufficientCreditsError(PredevAPIError):
|
|
45
|
+
"""HTTP 402, ``code: INSUFFICIENT_CREDITS``.
|
|
46
|
+
|
|
47
|
+
Subscription is fine but the credit balance is too low to cover this
|
|
48
|
+
request. ``action_url`` deep-links to the credit-purchase modal.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, message: str, action_url: Optional[str] = None):
|
|
52
|
+
super().__init__(message)
|
|
53
|
+
self.action_url = action_url
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class QueueFullError(PredevAPIError):
|
|
57
|
+
"""HTTP 429, ``code: QUEUE_FULL`` — too many in-flight tasks for this user."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BatchTooLargeError(PredevAPIError):
|
|
62
|
+
"""HTTP 400, ``code: BATCH_TOO_LARGE`` — request had more tasks than the per-batch maximum."""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def exception_for_code(
|
|
67
|
+
code: Optional[str],
|
|
68
|
+
message: str,
|
|
69
|
+
action_url: Optional[str] = None,
|
|
70
|
+
) -> PredevAPIError:
|
|
71
|
+
"""Map a structured error ``code`` to the right typed exception.
|
|
72
|
+
|
|
73
|
+
Falls back to :class:`PredevAPIError` for unknown codes so callers
|
|
74
|
+
that just want ``str(e)`` keep working.
|
|
75
|
+
"""
|
|
76
|
+
if code == "SUBSCRIPTION_REQUIRED":
|
|
77
|
+
return SubscriptionRequiredError(message, action_url)
|
|
78
|
+
if code == "INSUFFICIENT_CREDITS":
|
|
79
|
+
return InsufficientCreditsError(message, action_url)
|
|
80
|
+
if code == "QUEUE_FULL":
|
|
81
|
+
return QueueFullError(message)
|
|
82
|
+
if code == "BATCH_TOO_LARGE":
|
|
83
|
+
return BatchTooLargeError(message)
|
|
84
|
+
if code == "RATE_LIMITED":
|
|
85
|
+
return RateLimitError(message)
|
|
86
|
+
return PredevAPIError(message)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: predev-api
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Python client for the Pre.dev Architect API - Generate comprehensive software specifications
|
|
5
5
|
Home-page: https://github.com/predotdev/predev-api
|
|
6
6
|
Author: Pre.dev
|
|
@@ -502,7 +502,7 @@ For issues, questions, or contributions, please visit the [GitHub repository](ht
|
|
|
502
502
|
|
|
503
503
|
## Browser Tasks
|
|
504
504
|
|
|
505
|
-
Run browser automation —
|
|
505
|
+
Run browser automation — navigate, interact, and/or extract data from any web page. Each task navigates a URL, performs actions, and optionally returns typed JSON.
|
|
506
506
|
|
|
507
507
|
### Quick start
|
|
508
508
|
|
|
@@ -511,7 +511,7 @@ from predev_api import PredevAPI
|
|
|
511
511
|
|
|
512
512
|
client = PredevAPI(api_key="your_api_key")
|
|
513
513
|
|
|
514
|
-
result = client.
|
|
514
|
+
result = client.browser_agent([
|
|
515
515
|
{
|
|
516
516
|
"url": "https://news.ycombinator.com",
|
|
517
517
|
"instruction": "Extract the top 5 stories",
|
|
@@ -542,7 +542,7 @@ for story in result["results"][0]["data"]["stories"]:
|
|
|
542
542
|
#### 1. Sync (default) — wait for completion
|
|
543
543
|
|
|
544
544
|
```python
|
|
545
|
-
result = client.
|
|
545
|
+
result = client.browser_agent([
|
|
546
546
|
{"url": "https://example.com", "output": {"type": "object", "properties": {"heading": {"type": "string"}}}}
|
|
547
547
|
])
|
|
548
548
|
print(result["results"][0]["data"]) # {'heading': 'Example Domain'}
|
|
@@ -554,7 +554,7 @@ print(result["totalCreditsUsed"]) # 0.1
|
|
|
554
554
|
Yields events as the agent runs. Good for showing progress in a UI.
|
|
555
555
|
|
|
556
556
|
```python
|
|
557
|
-
for msg in client.
|
|
557
|
+
for msg in client.browser_agent(tasks, stream=True):
|
|
558
558
|
e, d = msg["event"], msg["data"]
|
|
559
559
|
if e == "task_event":
|
|
560
560
|
# navigation | screenshot | plan | action | validation | done
|
|
@@ -574,11 +574,11 @@ Returns the batch ID immediately. Use for long-running batches or background job
|
|
|
574
574
|
```python
|
|
575
575
|
import time
|
|
576
576
|
|
|
577
|
-
r = client.
|
|
577
|
+
r = client.browser_agent(tasks, run_async=True)
|
|
578
578
|
# {"id": "batch_abc", "status": "processing", "completed": 0, "total": 3}
|
|
579
579
|
|
|
580
580
|
while True:
|
|
581
|
-
state = client.
|
|
581
|
+
state = client.get_browser_agent(r["id"])
|
|
582
582
|
print(f"{state['completed']}/{state['total']}")
|
|
583
583
|
for done in state["results"]:
|
|
584
584
|
print(f" ✓ {done['url']} -> {done.get('data')}")
|
|
@@ -600,27 +600,27 @@ Each task's behavior is determined by which fields are set:
|
|
|
600
600
|
|
|
601
601
|
### Retrieving a batch with the full timeline
|
|
602
602
|
|
|
603
|
-
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging
|
|
603
|
+
Every task records navigation, screenshots, LLM plans, actions, validations. Retrieve for audit, replay, or debugging. Screenshots are uploaded to a CDN during execution — retrieved events contain `data["url"]`, not base64.
|
|
604
604
|
|
|
605
605
|
```python
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
details = client.get_browser_tasks(batch_id, include_events=True)
|
|
606
|
+
details = client.get_browser_agent(batch_id, include_events=True)
|
|
609
607
|
for result in details["results"]:
|
|
610
608
|
for ev in result.get("events", []):
|
|
611
609
|
if ev["type"] == "screenshot":
|
|
612
|
-
|
|
613
|
-
|
|
610
|
+
# ev["data"]["url"] is a permanent CDN URL. Use directly in an <img>.
|
|
611
|
+
print(f"Iter {ev.get('iteration')} screenshot:", ev["data"]["url"])
|
|
614
612
|
elif ev["type"] == "plan":
|
|
615
613
|
print(f"Iter {ev.get('iteration')} plan: {ev['data'].get('notes')}")
|
|
616
614
|
```
|
|
617
615
|
|
|
616
|
+
> **Note:** The live SSE stream (`stream=True`) still sends screenshots inline as base64 (`ev["data"]["base64"]`) so live UIs render instantly. The retrieval path (`get_browser_agent`) always returns CDN URLs.
|
|
617
|
+
|
|
618
618
|
### Parallel batch example
|
|
619
619
|
|
|
620
620
|
```python
|
|
621
621
|
# Scrape 100 URLs with 20 browsers in parallel
|
|
622
622
|
urls = [...100 urls...]
|
|
623
|
-
result = client.
|
|
623
|
+
result = client.browser_agent(
|
|
624
624
|
[{"url": u, "output": {"type": "object", "properties": {"title": {"type": "string"}}}} for u in urls],
|
|
625
625
|
concurrency=20
|
|
626
626
|
)
|
|
@@ -631,10 +631,10 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
631
631
|
|
|
632
632
|
| Method | Returns | Use when |
|
|
633
633
|
|---|---|---|
|
|
634
|
-
| `
|
|
635
|
-
| `
|
|
636
|
-
| `
|
|
637
|
-
| `
|
|
634
|
+
| `browser_agent(tasks, concurrency=N)` | dict | Default — wait for completion |
|
|
635
|
+
| `browser_agent(tasks, stream=True)` | iterator of SSE dicts | Live UI showing execution timeline |
|
|
636
|
+
| `browser_agent(tasks, run_async=True)` | dict (empty results, returned immediately) | Long batches, background jobs |
|
|
637
|
+
| `get_browser_agent(batch_id, include_events=False)` | dict | Poll progress or retrieve a completed batch |
|
|
638
638
|
|
|
639
639
|
### Task result statuses
|
|
640
640
|
|
|
@@ -653,3 +653,51 @@ print(f"{result['completed']}/{result['total']} done in {result['totalCreditsUse
|
|
|
653
653
|
- Minimum: **0.1 credits per task** ($0.01)
|
|
654
654
|
- 10x margin on underlying LLM + sandbox compute
|
|
655
655
|
- 1 credit = $0.10
|
|
656
|
+
|
|
657
|
+
### Error handling
|
|
658
|
+
|
|
659
|
+
The browser-task endpoints raise typed exceptions for the most common
|
|
660
|
+
gating cases. Every billing-gate exception carries an `action_url` — a
|
|
661
|
+
deep link back to pre.dev that auto-opens the right modal (subscribe /
|
|
662
|
+
buy credits) when the user lands there:
|
|
663
|
+
|
|
664
|
+
```python
|
|
665
|
+
from predev_api import (
|
|
666
|
+
PredevAPI,
|
|
667
|
+
InsufficientCreditsError,
|
|
668
|
+
SubscriptionRequiredError,
|
|
669
|
+
RateLimitError,
|
|
670
|
+
QueueFullError,
|
|
671
|
+
)
|
|
672
|
+
import webbrowser
|
|
673
|
+
|
|
674
|
+
client = PredevAPI(api_key="your_pre.dev_api_key")
|
|
675
|
+
|
|
676
|
+
try:
|
|
677
|
+
client.browser_agent([{"url": "https://example.com", "instruction": "extract h1"}])
|
|
678
|
+
except InsufficientCreditsError as e:
|
|
679
|
+
# Send the user to the credits modal — `action_url` is the canonical link.
|
|
680
|
+
if e.action_url:
|
|
681
|
+
webbrowser.open(e.action_url)
|
|
682
|
+
except SubscriptionRequiredError as e:
|
|
683
|
+
if e.action_url:
|
|
684
|
+
webbrowser.open(e.action_url)
|
|
685
|
+
except RateLimitError:
|
|
686
|
+
pass # back off and retry
|
|
687
|
+
except QueueFullError:
|
|
688
|
+
pass # wait for in-flight tasks to drain
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Mid-stream errors on the SSE stream raise the same typed exceptions, so a
|
|
692
|
+
`for msg in client.browser_agent(..., stream=True):` loop wrapped in
|
|
693
|
+
`try/except` is enough — no special event handling required.
|
|
694
|
+
|
|
695
|
+
| Exception | HTTP | `code` |
|
|
696
|
+
|---|---|---|
|
|
697
|
+
| `SubscriptionRequiredError` | 402 | `SUBSCRIPTION_REQUIRED` |
|
|
698
|
+
| `InsufficientCreditsError` | 402 | `INSUFFICIENT_CREDITS` |
|
|
699
|
+
| `RateLimitError` | 429 | `RATE_LIMITED` |
|
|
700
|
+
| `QueueFullError` | 429 | `QUEUE_FULL` |
|
|
701
|
+
| `BatchTooLargeError` | 400 | `BATCH_TOO_LARGE` |
|
|
702
|
+
| `AuthenticationError` | 401 | — |
|
|
703
|
+
| `PredevAPIError` | other | — |
|
|
@@ -9,7 +9,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="predev-api",
|
|
12
|
-
version="
|
|
12
|
+
version="1.1.0",
|
|
13
13
|
author="Pre.dev",
|
|
14
14
|
author_email="support@pre.dev",
|
|
15
15
|
description="Python client for the Pre.dev Architect API - Generate comprehensive software specifications",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Custom exceptions for the Pre.dev API client
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class PredevAPIError(Exception):
|
|
7
|
-
"""Base exception for Pre.dev API errors."""
|
|
8
|
-
pass
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class AuthenticationError(PredevAPIError):
|
|
12
|
-
"""Raised when authentication fails."""
|
|
13
|
-
pass
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class RateLimitError(PredevAPIError):
|
|
17
|
-
"""Raised when rate limit is exceeded."""
|
|
18
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|