agi-python 0.0.1__tar.gz → 0.5.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.
- agi_python-0.5.0/CHANGELOG.md +33 -0
- {agi_python-0.0.1/agi_python.egg-info → agi_python-0.5.0}/PKG-INFO +80 -2
- {agi_python-0.0.1 → agi_python-0.5.0}/README.md +79 -1
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/__init__.py +48 -2
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/_http.py +56 -0
- agi_python-0.5.0/agi/driver/__init__.py +85 -0
- agi_python-0.5.0/agi/driver/_binary.py +102 -0
- agi_python-0.5.0/agi/driver/_driver.py +568 -0
- agi_python-0.5.0/agi/driver/_protocol.py +417 -0
- agi_python-0.5.0/agi/loop.py +291 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/resources/sessions.py +117 -3
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/types/__init__.py +17 -1
- agi_python-0.5.0/agi/types/desktop.py +68 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/types/sessions.py +13 -1
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/types/shared.py +3 -0
- {agi_python-0.0.1 → agi_python-0.5.0/agi_python.egg-info}/PKG-INFO +80 -2
- {agi_python-0.0.1 → agi_python-0.5.0}/agi_python.egg-info/SOURCES.txt +8 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/pyproject.toml +1 -1
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/conftest.py +2 -1
- agi_python-0.5.0/tests/integration/test_driver.py +115 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/LICENSE +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/MANIFEST.in +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/_session_context.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/_sse.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/client.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/exceptions.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/py.typed +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/resources/__init__.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi/types/results.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi_python.egg-info/dependency_links.txt +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi_python.egg-info/requires.txt +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/agi_python.egg-info/top_level.txt +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/setup.cfg +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/__init__.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/__init__.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_comprehensive.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_edge_cases.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_examples.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_sessions.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_smoke.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/integration/test_snapshots.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/unit/__init__.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/unit/test_client.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/unit/test_error_handling.py +0 -0
- {agi_python-0.0.1 → agi_python-0.5.0}/tests/unit/test_session_context.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.4.1](https://github.com/agi-inc/agi-python/compare/v0.4.0...v0.4.1) (2026-02-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add agi python sdk ([1440466](https://github.com/agi-inc/agi-python/commit/14404667032849e3835e3d40db7ca346a03c8474))
|
|
9
|
+
* add client-driven session support with AgentLoop ([0165ae7](https://github.com/agi-inc/agi-python/commit/0165ae73adceaefd7008f8bb47e01780a58982eb))
|
|
10
|
+
* Add client-driven session support with AgentLoop ([f3d024e](https://github.com/agi-inc/agi-python/commit/f3d024e1a0cfbc49a761f9ebc8745bdcb5af67a0))
|
|
11
|
+
* add Task response models ([1021036](https://github.com/agi-inc/agi-python/commit/10210367ba4df2022131dadb40901fa923c9d357))
|
|
12
|
+
* agi-python sdk ([0fe2e32](https://github.com/agi-inc/agi-python/commit/0fe2e3227d6a254aeb302e9b5262aa5013320d35))
|
|
13
|
+
* **ci:** add release-please workflow for automated releases ([f25ca3f](https://github.com/agi-inc/agi-python/commit/f25ca3fe8601873dc8f96408900a3a0bdd7b3c59))
|
|
14
|
+
* **ci:** add release-please workflow for automated releases ([9da9c57](https://github.com/agi-inc/agi-python/commit/9da9c57ccbe4b51fa0ac3b72438587eb362c2d06))
|
|
15
|
+
* **driver:** Add driver module for binary execution ([#5](https://github.com/agi-inc/agi-python/issues/5)) ([33588e6](https://github.com/agi-inc/agi-python/commit/33588e65d84e0eebca611ac52ed4529a7345e964))
|
|
16
|
+
* update step method in SessionsResource to include session_id ([560e268](https://github.com/agi-inc/agi-python/commit/560e26892101b272db93011da58de8dc55d5abae))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **ci:** Fix security check false positive and add workflow permissions ([442e993](https://github.com/agi-inc/agi-python/commit/442e993ef7317f5458b7b6721d207ccb2e55ecd4))
|
|
22
|
+
* lint and test issues ([502d104](https://github.com/agi-inc/agi-python/commit/502d104bb75e9903b87766b071d8f47a10eb885f))
|
|
23
|
+
* lint issues ([fd15035](https://github.com/agi-inc/agi-python/commit/fd15035c06d125cb65d5a92299788d8cf1750844))
|
|
24
|
+
* **loop:** Add session_id parameter to AgentLoop ([de1ac36](https://github.com/agi-inc/agi-python/commit/de1ac36224bda410415713aab46ae081ac5e3d44))
|
|
25
|
+
* update session context to handle question event ([d062367](https://github.com/agi-inc/agi-python/commit/d0623672a4a0c6dd51100792e8fe71842624b4c2))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
* add CHANGELOG.md ([f9316c7](https://github.com/agi-inc/agi-python/commit/f9316c762f170750642974074c327cdc1e2b63bc))
|
|
31
|
+
* add enterprise notice for desktop mode ([237eb5e](https://github.com/agi-inc/agi-python/commit/237eb5ea12b3c0015af86c24e9df92687a3454cc))
|
|
32
|
+
* Add enterprise notice for desktop mode ([a2df41e](https://github.com/agi-inc/agi-python/commit/a2df41ed0523131f2ac53f39b76defc3cbb83e31))
|
|
33
|
+
* update readme ([cf05e24](https://github.com/agi-inc/agi-python/commit/cf05e248e0e06164e8475c76dc8bf4fc3d33beb8))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agi-python
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Official Python SDK for AGI.tech API
|
|
5
5
|
Author-email: AGI Inc <kaushik@theagi.company>
|
|
6
6
|
Maintainer-email: AGI Inc <kaushik@theagi.company>
|
|
@@ -51,7 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**Universal Computer-Use AI**
|
|
55
55
|
|
|
56
56
|
<br />
|
|
57
57
|
|
|
@@ -305,6 +305,84 @@ session = client.sessions.create(
|
|
|
305
305
|
|
|
306
306
|
</details>
|
|
307
307
|
|
|
308
|
+
<details>
|
|
309
|
+
<summary><b>Client-Driven Sessions</b> – Run agents on desktop, mobile, or custom environments</summary>
|
|
310
|
+
|
|
311
|
+
<br />
|
|
312
|
+
|
|
313
|
+
> **Note:** Desktop mode is currently feature-gated. For enterprise access, contact [`partner@theagi.company`](mailto:partner@theagi.company).
|
|
314
|
+
|
|
315
|
+
For scenarios where you control the execution environment (desktop automation, mobile apps, custom browsers), use client-driven sessions with `AgentLoop`:
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
import asyncio
|
|
319
|
+
from agi import AGIClient, AgentLoop
|
|
320
|
+
|
|
321
|
+
async def main():
|
|
322
|
+
client = AGIClient()
|
|
323
|
+
|
|
324
|
+
# Create a client-driven session
|
|
325
|
+
session = client.sessions.create(
|
|
326
|
+
agent_name="agi-2-claude",
|
|
327
|
+
agent_session_type="desktop",
|
|
328
|
+
goal="Open calculator and compute 2+2"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Define your callbacks
|
|
332
|
+
async def capture_screenshot() -> str:
|
|
333
|
+
# Return base64-encoded screenshot from your environment
|
|
334
|
+
return "..."
|
|
335
|
+
|
|
336
|
+
async def execute_actions(actions):
|
|
337
|
+
for action in actions:
|
|
338
|
+
print(f"Executing: {action['type']}")
|
|
339
|
+
# Execute action in your environment
|
|
340
|
+
|
|
341
|
+
# Create and run the loop
|
|
342
|
+
loop = AgentLoop(
|
|
343
|
+
client=client,
|
|
344
|
+
agent_url=session.agent_url,
|
|
345
|
+
capture_screenshot=capture_screenshot,
|
|
346
|
+
execute_actions=execute_actions,
|
|
347
|
+
on_thinking=lambda t: print(f"💭 {t}"),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
result = await loop.start()
|
|
351
|
+
print(f"Finished: {result.finished}")
|
|
352
|
+
|
|
353
|
+
asyncio.run(main())
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Loop Control:**
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Start in background
|
|
360
|
+
task = asyncio.create_task(loop.start())
|
|
361
|
+
|
|
362
|
+
# Pause/resume/stop
|
|
363
|
+
loop.pause() # Pause after current step
|
|
364
|
+
loop.resume() # Continue execution
|
|
365
|
+
loop.stop() # Stop the loop
|
|
366
|
+
|
|
367
|
+
# Check state
|
|
368
|
+
loop.state # LoopState.RUNNING, PAUSED, STOPPED, FINISHED
|
|
369
|
+
loop.current_step # Current step number
|
|
370
|
+
loop.last_result # Last StepDesktopResponse
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Manual Step Control:**
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
# Or manage the loop yourself
|
|
377
|
+
while not finished:
|
|
378
|
+
screenshot = capture_screenshot()
|
|
379
|
+
result = client.sessions.step(session.agent_url, screenshot)
|
|
380
|
+
execute_actions(result.actions)
|
|
381
|
+
finished = result.finished
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
</details>
|
|
385
|
+
|
|
308
386
|
---
|
|
309
387
|
|
|
310
388
|
## Error Handling
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
**
|
|
16
|
+
**Universal Computer-Use AI**
|
|
17
17
|
|
|
18
18
|
<br />
|
|
19
19
|
|
|
@@ -267,6 +267,84 @@ session = client.sessions.create(
|
|
|
267
267
|
|
|
268
268
|
</details>
|
|
269
269
|
|
|
270
|
+
<details>
|
|
271
|
+
<summary><b>Client-Driven Sessions</b> – Run agents on desktop, mobile, or custom environments</summary>
|
|
272
|
+
|
|
273
|
+
<br />
|
|
274
|
+
|
|
275
|
+
> **Note:** Desktop mode is currently feature-gated. For enterprise access, contact [`partner@theagi.company`](mailto:partner@theagi.company).
|
|
276
|
+
|
|
277
|
+
For scenarios where you control the execution environment (desktop automation, mobile apps, custom browsers), use client-driven sessions with `AgentLoop`:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
import asyncio
|
|
281
|
+
from agi import AGIClient, AgentLoop
|
|
282
|
+
|
|
283
|
+
async def main():
|
|
284
|
+
client = AGIClient()
|
|
285
|
+
|
|
286
|
+
# Create a client-driven session
|
|
287
|
+
session = client.sessions.create(
|
|
288
|
+
agent_name="agi-2-claude",
|
|
289
|
+
agent_session_type="desktop",
|
|
290
|
+
goal="Open calculator and compute 2+2"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Define your callbacks
|
|
294
|
+
async def capture_screenshot() -> str:
|
|
295
|
+
# Return base64-encoded screenshot from your environment
|
|
296
|
+
return "..."
|
|
297
|
+
|
|
298
|
+
async def execute_actions(actions):
|
|
299
|
+
for action in actions:
|
|
300
|
+
print(f"Executing: {action['type']}")
|
|
301
|
+
# Execute action in your environment
|
|
302
|
+
|
|
303
|
+
# Create and run the loop
|
|
304
|
+
loop = AgentLoop(
|
|
305
|
+
client=client,
|
|
306
|
+
agent_url=session.agent_url,
|
|
307
|
+
capture_screenshot=capture_screenshot,
|
|
308
|
+
execute_actions=execute_actions,
|
|
309
|
+
on_thinking=lambda t: print(f"💭 {t}"),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
result = await loop.start()
|
|
313
|
+
print(f"Finished: {result.finished}")
|
|
314
|
+
|
|
315
|
+
asyncio.run(main())
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Loop Control:**
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
# Start in background
|
|
322
|
+
task = asyncio.create_task(loop.start())
|
|
323
|
+
|
|
324
|
+
# Pause/resume/stop
|
|
325
|
+
loop.pause() # Pause after current step
|
|
326
|
+
loop.resume() # Continue execution
|
|
327
|
+
loop.stop() # Stop the loop
|
|
328
|
+
|
|
329
|
+
# Check state
|
|
330
|
+
loop.state # LoopState.RUNNING, PAUSED, STOPPED, FINISHED
|
|
331
|
+
loop.current_step # Current step number
|
|
332
|
+
loop.last_result # Last StepDesktopResponse
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Manual Step Control:**
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
# Or manage the loop yourself
|
|
339
|
+
while not finished:
|
|
340
|
+
screenshot = capture_screenshot()
|
|
341
|
+
result = client.sessions.step(session.agent_url, screenshot)
|
|
342
|
+
execute_actions(result.actions)
|
|
343
|
+
finished = result.finished
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
</details>
|
|
347
|
+
|
|
270
348
|
---
|
|
271
349
|
|
|
272
350
|
## Error Handling
|
|
@@ -4,7 +4,7 @@ The agi package provides a complete Python SDK for the AGI.tech API,
|
|
|
4
4
|
enabling developers to create and manage AI agent sessions that can perform
|
|
5
5
|
complex web tasks.
|
|
6
6
|
|
|
7
|
-
Example:
|
|
7
|
+
Example (server-driven session):
|
|
8
8
|
>>> from agi import AGIClient
|
|
9
9
|
>>>
|
|
10
10
|
>>> client = AGIClient(api_key="your_api_key")
|
|
@@ -14,9 +14,25 @@ Example:
|
|
|
14
14
|
... "Find three nonstop SFO→JFK flights next month under $450"
|
|
15
15
|
... )
|
|
16
16
|
... print(result)
|
|
17
|
+
|
|
18
|
+
Example (local desktop session using driver):
|
|
19
|
+
>>> from agi import AgentDriver, DriverOptions
|
|
20
|
+
>>>
|
|
21
|
+
>>> driver = AgentDriver(DriverOptions(mode="local"))
|
|
22
|
+
>>> result = await driver.start(goal="Open calculator and compute 2+2")
|
|
23
|
+
>>> print(result.summary)
|
|
17
24
|
"""
|
|
18
25
|
|
|
19
26
|
from agi.client import AGIClient
|
|
27
|
+
from agi.driver import (
|
|
28
|
+
AgentDriver,
|
|
29
|
+
DriverAction,
|
|
30
|
+
DriverOptions,
|
|
31
|
+
DriverResult,
|
|
32
|
+
DriverState,
|
|
33
|
+
find_binary_path,
|
|
34
|
+
is_binary_available,
|
|
35
|
+
)
|
|
20
36
|
from agi.exceptions import (
|
|
21
37
|
AgentExecutionError,
|
|
22
38
|
AGIError,
|
|
@@ -26,6 +42,14 @@ from agi.exceptions import (
|
|
|
26
42
|
PermissionError,
|
|
27
43
|
RateLimitError,
|
|
28
44
|
)
|
|
45
|
+
from agi.loop import AgentLoop, LoopState
|
|
46
|
+
from agi.types.desktop import (
|
|
47
|
+
DesktopAction,
|
|
48
|
+
DesktopActionType,
|
|
49
|
+
ModelsResponse,
|
|
50
|
+
StepDesktopRequest,
|
|
51
|
+
StepDesktopResponse,
|
|
52
|
+
)
|
|
29
53
|
from agi.types.results import Screenshot, TaskMetadata, TaskResult
|
|
30
54
|
from agi.types.sessions import (
|
|
31
55
|
DeleteResponse,
|
|
@@ -38,12 +62,17 @@ from agi.types.sessions import (
|
|
|
38
62
|
SSEEvent,
|
|
39
63
|
SuccessResponse,
|
|
40
64
|
)
|
|
41
|
-
from agi.types.shared import EventType, MessageType, SessionStatus, SnapshotMode
|
|
65
|
+
from agi.types.shared import AgentSessionType, EventType, MessageType, SessionStatus, SnapshotMode
|
|
42
66
|
|
|
43
67
|
__version__ = "0.0.1"
|
|
44
68
|
|
|
45
69
|
__all__ = [
|
|
70
|
+
# Client
|
|
46
71
|
"AGIClient",
|
|
72
|
+
# Loop
|
|
73
|
+
"AgentLoop",
|
|
74
|
+
"LoopState",
|
|
75
|
+
# Exceptions
|
|
47
76
|
"AGIError",
|
|
48
77
|
"APIError",
|
|
49
78
|
"AgentExecutionError",
|
|
@@ -51,6 +80,7 @@ __all__ = [
|
|
|
51
80
|
"NotFoundError",
|
|
52
81
|
"PermissionError",
|
|
53
82
|
"RateLimitError",
|
|
83
|
+
# Session types
|
|
54
84
|
"SessionResponse",
|
|
55
85
|
"SSEEvent",
|
|
56
86
|
"MessageResponse",
|
|
@@ -63,8 +93,24 @@ __all__ = [
|
|
|
63
93
|
"SuccessResponse",
|
|
64
94
|
"TaskResult",
|
|
65
95
|
"TaskMetadata",
|
|
96
|
+
# Desktop types
|
|
97
|
+
"DesktopAction",
|
|
98
|
+
"DesktopActionType",
|
|
99
|
+
"ModelsResponse",
|
|
100
|
+
"StepDesktopRequest",
|
|
101
|
+
"StepDesktopResponse",
|
|
102
|
+
# Shared types
|
|
103
|
+
"AgentSessionType",
|
|
66
104
|
"EventType",
|
|
67
105
|
"MessageType",
|
|
68
106
|
"SessionStatus",
|
|
69
107
|
"SnapshotMode",
|
|
108
|
+
# Driver
|
|
109
|
+
"AgentDriver",
|
|
110
|
+
"DriverOptions",
|
|
111
|
+
"DriverResult",
|
|
112
|
+
"DriverAction",
|
|
113
|
+
"DriverState",
|
|
114
|
+
"find_binary_path",
|
|
115
|
+
"is_binary_available",
|
|
70
116
|
]
|
|
@@ -94,6 +94,62 @@ class HTTPClient:
|
|
|
94
94
|
|
|
95
95
|
raise APIError("Request failed")
|
|
96
96
|
|
|
97
|
+
def request_url(
|
|
98
|
+
self,
|
|
99
|
+
method: str,
|
|
100
|
+
url: str,
|
|
101
|
+
**kwargs: Any,
|
|
102
|
+
) -> httpx.Response:
|
|
103
|
+
"""Make HTTP request to an absolute URL with retry logic.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
method: HTTP method (GET, POST, etc.)
|
|
107
|
+
url: Absolute URL to request
|
|
108
|
+
**kwargs: Additional arguments for httpx.request()
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
HTTP response
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
AGIError: On API errors
|
|
115
|
+
"""
|
|
116
|
+
last_exception: httpx.HTTPStatusError | None = None
|
|
117
|
+
|
|
118
|
+
for attempt in range(self._max_retries):
|
|
119
|
+
try:
|
|
120
|
+
# Use httpx directly for absolute URLs
|
|
121
|
+
response = httpx.request(
|
|
122
|
+
method,
|
|
123
|
+
url,
|
|
124
|
+
headers=self._client.headers,
|
|
125
|
+
timeout=self._client.timeout,
|
|
126
|
+
**kwargs,
|
|
127
|
+
)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
return response
|
|
130
|
+
|
|
131
|
+
except httpx.HTTPStatusError as e:
|
|
132
|
+
if e.response.status_code >= 500 and attempt < self._max_retries - 1:
|
|
133
|
+
wait_time = 2**attempt
|
|
134
|
+
time.sleep(wait_time)
|
|
135
|
+
last_exception = e
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
self._handle_error(e.response)
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
except (httpx.RequestError, httpx.TimeoutException) as e:
|
|
142
|
+
if attempt < self._max_retries - 1:
|
|
143
|
+
wait_time = 2**attempt
|
|
144
|
+
time.sleep(wait_time)
|
|
145
|
+
continue
|
|
146
|
+
raise APIError(f"Request failed: {str(e)}") from e
|
|
147
|
+
|
|
148
|
+
if last_exception:
|
|
149
|
+
raise APIError(f"Max retries exceeded: {str(last_exception)}") from last_exception
|
|
150
|
+
|
|
151
|
+
raise APIError("Request failed")
|
|
152
|
+
|
|
97
153
|
def _handle_error(self, response: httpx.Response) -> None:
|
|
98
154
|
"""Map HTTP errors to SDK exceptions.
|
|
99
155
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Driver module for spawning and managing the agi-driver binary.
|
|
2
|
+
|
|
3
|
+
The driver communicates via JSON lines over stdin/stdout and provides
|
|
4
|
+
an event-based interface for agent control.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agi.driver._binary import (
|
|
8
|
+
PlatformId,
|
|
9
|
+
find_binary_path,
|
|
10
|
+
get_platform_id,
|
|
11
|
+
is_binary_available,
|
|
12
|
+
)
|
|
13
|
+
from agi.driver._driver import AgentDriver, DriverOptions, DriverResult
|
|
14
|
+
from agi.driver._protocol import (
|
|
15
|
+
ActionEvent,
|
|
16
|
+
AnswerCommand,
|
|
17
|
+
AskQuestionEvent,
|
|
18
|
+
AudioTranscriptEvent,
|
|
19
|
+
CommandType,
|
|
20
|
+
ConfirmEvent,
|
|
21
|
+
ConfirmResponseCommand,
|
|
22
|
+
DriverAction,
|
|
23
|
+
DriverCommand,
|
|
24
|
+
DriverEvent,
|
|
25
|
+
DriverState,
|
|
26
|
+
ErrorEvent,
|
|
27
|
+
EventType,
|
|
28
|
+
FinishedEvent,
|
|
29
|
+
GetAudioTranscriptCommand,
|
|
30
|
+
GetVideoFrameCommand,
|
|
31
|
+
PauseCommand,
|
|
32
|
+
ReadyEvent,
|
|
33
|
+
ResumeCommand,
|
|
34
|
+
ScreenshotCapturedEvent,
|
|
35
|
+
ScreenshotCommand,
|
|
36
|
+
SessionCreatedEvent,
|
|
37
|
+
SpeechFinishedEvent,
|
|
38
|
+
SpeechStartedEvent,
|
|
39
|
+
StartCommand,
|
|
40
|
+
StateChangeEvent,
|
|
41
|
+
StopCommand,
|
|
42
|
+
ThinkingEvent,
|
|
43
|
+
TurnDetectedEvent,
|
|
44
|
+
VideoFrameEvent,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AgentDriver",
|
|
49
|
+
"DriverOptions",
|
|
50
|
+
"DriverResult",
|
|
51
|
+
"DriverEvent",
|
|
52
|
+
"DriverAction",
|
|
53
|
+
"DriverState",
|
|
54
|
+
"DriverCommand",
|
|
55
|
+
"EventType",
|
|
56
|
+
"CommandType",
|
|
57
|
+
"ReadyEvent",
|
|
58
|
+
"StateChangeEvent",
|
|
59
|
+
"ThinkingEvent",
|
|
60
|
+
"ActionEvent",
|
|
61
|
+
"ConfirmEvent",
|
|
62
|
+
"AskQuestionEvent",
|
|
63
|
+
"FinishedEvent",
|
|
64
|
+
"ErrorEvent",
|
|
65
|
+
"ScreenshotCapturedEvent",
|
|
66
|
+
"SessionCreatedEvent",
|
|
67
|
+
"AudioTranscriptEvent",
|
|
68
|
+
"VideoFrameEvent",
|
|
69
|
+
"SpeechStartedEvent",
|
|
70
|
+
"SpeechFinishedEvent",
|
|
71
|
+
"TurnDetectedEvent",
|
|
72
|
+
"GetAudioTranscriptCommand",
|
|
73
|
+
"GetVideoFrameCommand",
|
|
74
|
+
"StartCommand",
|
|
75
|
+
"ScreenshotCommand",
|
|
76
|
+
"PauseCommand",
|
|
77
|
+
"ResumeCommand",
|
|
78
|
+
"StopCommand",
|
|
79
|
+
"ConfirmResponseCommand",
|
|
80
|
+
"AnswerCommand",
|
|
81
|
+
"find_binary_path",
|
|
82
|
+
"is_binary_available",
|
|
83
|
+
"get_platform_id",
|
|
84
|
+
"PlatformId",
|
|
85
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Binary locator for the agi-driver.
|
|
2
|
+
|
|
3
|
+
Finds the platform-specific binary bundled with the package
|
|
4
|
+
or a global installation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
PlatformId = Literal["darwin-arm64", "darwin-x64", "linux-x64", "windows-x64"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_platform_id() -> PlatformId:
|
|
19
|
+
"""Get the current platform identifier."""
|
|
20
|
+
os_name = platform.system().lower()
|
|
21
|
+
arch = platform.machine().lower()
|
|
22
|
+
|
|
23
|
+
if os_name == "darwin":
|
|
24
|
+
if arch == "arm64":
|
|
25
|
+
return "darwin-arm64"
|
|
26
|
+
elif arch in ("x86_64", "amd64"):
|
|
27
|
+
return "darwin-x64"
|
|
28
|
+
else:
|
|
29
|
+
raise RuntimeError(f"Unsupported architecture for macOS: {arch}")
|
|
30
|
+
elif os_name == "linux":
|
|
31
|
+
if arch not in ("x86_64", "amd64"):
|
|
32
|
+
raise RuntimeError(f"Unsupported architecture for Linux: {arch}")
|
|
33
|
+
return "linux-x64"
|
|
34
|
+
elif os_name == "windows":
|
|
35
|
+
if arch not in ("x86_64", "amd64"):
|
|
36
|
+
raise RuntimeError(f"Unsupported architecture for Windows: {arch}")
|
|
37
|
+
return "windows-x64"
|
|
38
|
+
else:
|
|
39
|
+
raise RuntimeError(f"Unsupported platform: {os_name}-{arch}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_binary_filename(platform_id: PlatformId | None = None) -> str:
|
|
43
|
+
"""Get the binary filename for the given platform."""
|
|
44
|
+
pid = platform_id or get_platform_id()
|
|
45
|
+
if pid == "windows-x64":
|
|
46
|
+
return "agi-driver.exe"
|
|
47
|
+
return "agi-driver"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _get_search_paths(platform_id: PlatformId) -> list[Path]:
|
|
51
|
+
"""Get the list of paths to search for the binary."""
|
|
52
|
+
filename = get_binary_filename(platform_id)
|
|
53
|
+
paths: list[Path] = []
|
|
54
|
+
|
|
55
|
+
# 1. Bundled in package's bin directory
|
|
56
|
+
package_dir = Path(__file__).parent.parent
|
|
57
|
+
paths.append(package_dir / "bin" / filename)
|
|
58
|
+
|
|
59
|
+
# 2. In the package root's bin directory
|
|
60
|
+
paths.append(package_dir.parent / "bin" / filename)
|
|
61
|
+
|
|
62
|
+
# 3. In site-packages bin directory
|
|
63
|
+
for site_pkg in sys.path:
|
|
64
|
+
if "site-packages" in site_pkg:
|
|
65
|
+
paths.append(Path(site_pkg).parent / "bin" / filename)
|
|
66
|
+
|
|
67
|
+
# 4. Global installation (in PATH)
|
|
68
|
+
env_path = os.environ.get("PATH", "")
|
|
69
|
+
for dir_path in env_path.split(os.pathsep):
|
|
70
|
+
if dir_path:
|
|
71
|
+
paths.append(Path(dir_path) / filename)
|
|
72
|
+
|
|
73
|
+
return paths
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def find_binary_path() -> str:
|
|
77
|
+
"""Find the agi-driver binary path.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
RuntimeError: If the binary cannot be found
|
|
81
|
+
"""
|
|
82
|
+
platform_id = get_platform_id()
|
|
83
|
+
search_paths = _get_search_paths(platform_id)
|
|
84
|
+
|
|
85
|
+
for path in search_paths:
|
|
86
|
+
if path.exists() and path.is_file():
|
|
87
|
+
return str(path)
|
|
88
|
+
|
|
89
|
+
raise RuntimeError(
|
|
90
|
+
f"Could not find agi-driver binary for {platform_id}. "
|
|
91
|
+
f"Searched: {', '.join(str(p) for p in search_paths[:5])}... "
|
|
92
|
+
f"Ensure agi-driver is installed or in PATH."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def is_binary_available() -> bool:
|
|
97
|
+
"""Check if the binary is available."""
|
|
98
|
+
try:
|
|
99
|
+
find_binary_path()
|
|
100
|
+
return True
|
|
101
|
+
except RuntimeError:
|
|
102
|
+
return False
|