agentdraft 0.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.
- agentdraft-0.1.0/LICENSE +21 -0
- agentdraft-0.1.0/PKG-INFO +147 -0
- agentdraft-0.1.0/README.md +120 -0
- agentdraft-0.1.0/agentdraft/__init__.py +42 -0
- agentdraft-0.1.0/agentdraft/client.py +312 -0
- agentdraft-0.1.0/agentdraft/errors.py +58 -0
- agentdraft-0.1.0/agentdraft/models.py +64 -0
- agentdraft-0.1.0/agentdraft/py.typed +0 -0
- agentdraft-0.1.0/agentdraft.egg-info/PKG-INFO +147 -0
- agentdraft-0.1.0/agentdraft.egg-info/SOURCES.txt +13 -0
- agentdraft-0.1.0/agentdraft.egg-info/dependency_links.txt +1 -0
- agentdraft-0.1.0/agentdraft.egg-info/requires.txt +1 -0
- agentdraft-0.1.0/agentdraft.egg-info/top_level.txt +1 -0
- agentdraft-0.1.0/pyproject.toml +35 -0
- agentdraft-0.1.0/setup.cfg +4 -0
agentdraft-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentDraft Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentdraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for AgentDraft — the scheduling source of truth for AI agents
|
|
5
|
+
Author-email: AgentDraft Labs <hello@agentdraft.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://agentdraft.io
|
|
8
|
+
Project-URL: Documentation, https://agentdraft.io/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/GipsyChef/agentdraft
|
|
10
|
+
Project-URL: Issues, https://github.com/GipsyChef/agentdraft/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/GipsyChef/agentdraft/blob/main/sdks/python/CHANGELOG.md
|
|
12
|
+
Keywords: calendar,scheduling,ai-agents,agentdraft
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business :: Scheduling
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# agentdraft — Python SDK
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/agentdraft/)
|
|
31
|
+
[](https://pypi.org/project/agentdraft/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
|
|
34
|
+
The official Python SDK for [AgentDraft](https://agentdraft.io) — the
|
|
35
|
+
scheduling source of truth for AI agents.
|
|
36
|
+
|
|
37
|
+
AgentDraft is the coordination layer that prevents AI scheduling agents
|
|
38
|
+
from colliding on the same calendar. Every agent calls one API before
|
|
39
|
+
booking; every commit goes through one deterministic engine; every
|
|
40
|
+
action is recorded in one tamper-evident audit log.
|
|
41
|
+
|
|
42
|
+
This SDK gives Python agents a typed, one-line surface to participate.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install agentdraft
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.9+.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from datetime import datetime, timedelta, timezone
|
|
56
|
+
from agentdraft import Client, Conflict
|
|
57
|
+
|
|
58
|
+
client = Client(api_key="avs_live_...") # or set AGENTDRAFT_API_KEY
|
|
59
|
+
|
|
60
|
+
start = datetime.now(timezone.utc) + timedelta(hours=4)
|
|
61
|
+
end = start + timedelta(minutes=30)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
booking = client.bookings.commit(
|
|
65
|
+
start=start, end=end,
|
|
66
|
+
idempotency_key="ik_call_42",
|
|
67
|
+
metadata={"title": "Discovery call"},
|
|
68
|
+
)
|
|
69
|
+
print("booked:", booking.booking_id)
|
|
70
|
+
except Conflict as e:
|
|
71
|
+
print(f"outranked by {e.winning_agent_id} (rank {e.winning_agent_priority})")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A losing agent gets a typed `Conflict` exception, not a timeout — it
|
|
75
|
+
knows who won, by what priority, and where the audit row lives, so
|
|
76
|
+
fallback behavior (propose an alternate, escalate, defer) is a clean
|
|
77
|
+
`except` clause away.
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
API keys are issued from the AgentDraft dashboard and start with
|
|
82
|
+
`avs_live_`. Pass it explicitly or let the client read it from the
|
|
83
|
+
environment:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
Client(api_key="avs_live_...")
|
|
87
|
+
# or
|
|
88
|
+
import os; os.environ["AGENTDRAFT_API_KEY"] = "avs_live_..."
|
|
89
|
+
Client()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For local development against a dev backend, point at it via
|
|
93
|
+
`AGENTDRAFT_BASE_URL` or the `base_url=` kwarg.
|
|
94
|
+
|
|
95
|
+
## Surface
|
|
96
|
+
|
|
97
|
+
| Attribute | Purpose |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `client.availability` | Read merged availability across all agents writing to the calendar |
|
|
100
|
+
| `client.bookings` | `hold`, `release`, `commit`, `cancel` — the four state transitions |
|
|
101
|
+
| `client.agents` | `me()` — confirm key + current priority + scopes |
|
|
102
|
+
| `client.mailbox` | Inbound/outbound mail surface for agents that book via email |
|
|
103
|
+
|
|
104
|
+
All blocking I/O. An async client is on the roadmap; for now wrap with
|
|
105
|
+
`asyncio.to_thread` if you need concurrency.
|
|
106
|
+
|
|
107
|
+
## Error types
|
|
108
|
+
|
|
109
|
+
Every failure is a typed exception so callers can branch precisely:
|
|
110
|
+
|
|
111
|
+
- `Conflict` — your write was outranked. Carries `winning_agent_id`,
|
|
112
|
+
`winning_agent_priority`, `winning_booking_id`, `audit_event_id`.
|
|
113
|
+
- `AuthError` — bad/missing/expired API key.
|
|
114
|
+
- `RateLimited` — token bucket exhausted. Has `retry_after_seconds`.
|
|
115
|
+
- `RuleViolation` — request was syntactically valid but violated a
|
|
116
|
+
rule (focus block, daily cap, business hours).
|
|
117
|
+
- `AgentDraftError` — base class; catch this if you only need a
|
|
118
|
+
catch-all.
|
|
119
|
+
|
|
120
|
+
## Idempotency
|
|
121
|
+
|
|
122
|
+
Pass `idempotency_key=` to `bookings.commit(...)`. The server caches the
|
|
123
|
+
result by `(agent_id, key)` for 24 hours, so a retry over a flaky network
|
|
124
|
+
returns the original booking, not a duplicate.
|
|
125
|
+
|
|
126
|
+
## Links
|
|
127
|
+
|
|
128
|
+
- Protocol spec: <https://agentdraft.io/spec>
|
|
129
|
+
- API docs: <https://agentdraft.io/docs>
|
|
130
|
+
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
131
|
+
- Issues / source: <https://github.com/GipsyChef/agentdraft>
|
|
132
|
+
- TypeScript SDK: [`@agentdraft/sdk`](https://www.npmjs.com/package/@agentdraft/sdk)
|
|
133
|
+
|
|
134
|
+
## Security
|
|
135
|
+
|
|
136
|
+
Found a vulnerability? Please follow [SECURITY.md](SECURITY.md) — **do
|
|
137
|
+
not** open a public issue for a security report.
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). This SDK lives inside the
|
|
142
|
+
[`GipsyChef/agentdraft`](https://github.com/GipsyChef/agentdraft)
|
|
143
|
+
monorepo under `sdks/python/`.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# agentdraft — Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/agentdraft/)
|
|
4
|
+
[](https://pypi.org/project/agentdraft/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
The official Python SDK for [AgentDraft](https://agentdraft.io) — the
|
|
8
|
+
scheduling source of truth for AI agents.
|
|
9
|
+
|
|
10
|
+
AgentDraft is the coordination layer that prevents AI scheduling agents
|
|
11
|
+
from colliding on the same calendar. Every agent calls one API before
|
|
12
|
+
booking; every commit goes through one deterministic engine; every
|
|
13
|
+
action is recorded in one tamper-evident audit log.
|
|
14
|
+
|
|
15
|
+
This SDK gives Python agents a typed, one-line surface to participate.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install agentdraft
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires Python 3.9+.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from datetime import datetime, timedelta, timezone
|
|
29
|
+
from agentdraft import Client, Conflict
|
|
30
|
+
|
|
31
|
+
client = Client(api_key="avs_live_...") # or set AGENTDRAFT_API_KEY
|
|
32
|
+
|
|
33
|
+
start = datetime.now(timezone.utc) + timedelta(hours=4)
|
|
34
|
+
end = start + timedelta(minutes=30)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
booking = client.bookings.commit(
|
|
38
|
+
start=start, end=end,
|
|
39
|
+
idempotency_key="ik_call_42",
|
|
40
|
+
metadata={"title": "Discovery call"},
|
|
41
|
+
)
|
|
42
|
+
print("booked:", booking.booking_id)
|
|
43
|
+
except Conflict as e:
|
|
44
|
+
print(f"outranked by {e.winning_agent_id} (rank {e.winning_agent_priority})")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A losing agent gets a typed `Conflict` exception, not a timeout — it
|
|
48
|
+
knows who won, by what priority, and where the audit row lives, so
|
|
49
|
+
fallback behavior (propose an alternate, escalate, defer) is a clean
|
|
50
|
+
`except` clause away.
|
|
51
|
+
|
|
52
|
+
## Authentication
|
|
53
|
+
|
|
54
|
+
API keys are issued from the AgentDraft dashboard and start with
|
|
55
|
+
`avs_live_`. Pass it explicitly or let the client read it from the
|
|
56
|
+
environment:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
Client(api_key="avs_live_...")
|
|
60
|
+
# or
|
|
61
|
+
import os; os.environ["AGENTDRAFT_API_KEY"] = "avs_live_..."
|
|
62
|
+
Client()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For local development against a dev backend, point at it via
|
|
66
|
+
`AGENTDRAFT_BASE_URL` or the `base_url=` kwarg.
|
|
67
|
+
|
|
68
|
+
## Surface
|
|
69
|
+
|
|
70
|
+
| Attribute | Purpose |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `client.availability` | Read merged availability across all agents writing to the calendar |
|
|
73
|
+
| `client.bookings` | `hold`, `release`, `commit`, `cancel` — the four state transitions |
|
|
74
|
+
| `client.agents` | `me()` — confirm key + current priority + scopes |
|
|
75
|
+
| `client.mailbox` | Inbound/outbound mail surface for agents that book via email |
|
|
76
|
+
|
|
77
|
+
All blocking I/O. An async client is on the roadmap; for now wrap with
|
|
78
|
+
`asyncio.to_thread` if you need concurrency.
|
|
79
|
+
|
|
80
|
+
## Error types
|
|
81
|
+
|
|
82
|
+
Every failure is a typed exception so callers can branch precisely:
|
|
83
|
+
|
|
84
|
+
- `Conflict` — your write was outranked. Carries `winning_agent_id`,
|
|
85
|
+
`winning_agent_priority`, `winning_booking_id`, `audit_event_id`.
|
|
86
|
+
- `AuthError` — bad/missing/expired API key.
|
|
87
|
+
- `RateLimited` — token bucket exhausted. Has `retry_after_seconds`.
|
|
88
|
+
- `RuleViolation` — request was syntactically valid but violated a
|
|
89
|
+
rule (focus block, daily cap, business hours).
|
|
90
|
+
- `AgentDraftError` — base class; catch this if you only need a
|
|
91
|
+
catch-all.
|
|
92
|
+
|
|
93
|
+
## Idempotency
|
|
94
|
+
|
|
95
|
+
Pass `idempotency_key=` to `bookings.commit(...)`. The server caches the
|
|
96
|
+
result by `(agent_id, key)` for 24 hours, so a retry over a flaky network
|
|
97
|
+
returns the original booking, not a duplicate.
|
|
98
|
+
|
|
99
|
+
## Links
|
|
100
|
+
|
|
101
|
+
- Protocol spec: <https://agentdraft.io/spec>
|
|
102
|
+
- API docs: <https://agentdraft.io/docs>
|
|
103
|
+
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
104
|
+
- Issues / source: <https://github.com/GipsyChef/agentdraft>
|
|
105
|
+
- TypeScript SDK: [`@agentdraft/sdk`](https://www.npmjs.com/package/@agentdraft/sdk)
|
|
106
|
+
|
|
107
|
+
## Security
|
|
108
|
+
|
|
109
|
+
Found a vulnerability? Please follow [SECURITY.md](SECURITY.md) — **do
|
|
110
|
+
not** open a public issue for a security report.
|
|
111
|
+
|
|
112
|
+
## Contributing
|
|
113
|
+
|
|
114
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). This SDK lives inside the
|
|
115
|
+
[`GipsyChef/agentdraft`](https://github.com/GipsyChef/agentdraft)
|
|
116
|
+
monorepo under `sdks/python/`.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""AgentDraft — Python SDK.
|
|
2
|
+
|
|
3
|
+
The scheduling source of truth for AI agents. See https://agentdraft.io/docs.
|
|
4
|
+
|
|
5
|
+
Quickstart::
|
|
6
|
+
|
|
7
|
+
from agentdraft import Client, Conflict
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
|
+
|
|
10
|
+
client = Client(api_key="avs_live_...")
|
|
11
|
+
|
|
12
|
+
start = datetime.now(timezone.utc) + timedelta(hours=4)
|
|
13
|
+
end = start + timedelta(minutes=30)
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
booking = client.bookings.commit(
|
|
17
|
+
start=start, end=end,
|
|
18
|
+
idempotency_key="ik_call_42",
|
|
19
|
+
metadata={"title": "Discovery call"},
|
|
20
|
+
)
|
|
21
|
+
print("booked:", booking.booking_id)
|
|
22
|
+
except Conflict as e:
|
|
23
|
+
print(f"outranked by {e.winning_agent_id} (rank {e.winning_agent_priority})")
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from .client import Client
|
|
27
|
+
from .errors import AgentDraftError, AuthError, Conflict, RateLimited, RuleViolation
|
|
28
|
+
from .models import Booking, Hold, Slot
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"Client",
|
|
32
|
+
"AgentDraftError",
|
|
33
|
+
"AuthError",
|
|
34
|
+
"Conflict",
|
|
35
|
+
"RateLimited",
|
|
36
|
+
"RuleViolation",
|
|
37
|
+
"Booking",
|
|
38
|
+
"Hold",
|
|
39
|
+
"Slot",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""The Client class — the SDK's only entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from .errors import AgentDraftError, AuthError, Conflict, RateLimited, RuleViolation
|
|
12
|
+
from .models import AgentIdentity, Booking, Slot
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://api.agentdraft.io"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Client:
|
|
18
|
+
"""Top-level SDK client.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
api_key:
|
|
23
|
+
An ``avs_live_…`` key issued from your AgentDraft dashboard. If omitted,
|
|
24
|
+
reads ``AGENTDRAFT_API_KEY`` from the environment.
|
|
25
|
+
base_url:
|
|
26
|
+
Override the API base. Defaults to ``https://api.agentdraft.io``; for
|
|
27
|
+
local development point at ``http://localhost:8080``.
|
|
28
|
+
timeout:
|
|
29
|
+
Per-request timeout in seconds (default 10).
|
|
30
|
+
user_agent:
|
|
31
|
+
Override the User-Agent string sent on every request.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
*,
|
|
38
|
+
base_url: Optional[str] = None,
|
|
39
|
+
timeout: float = 10.0,
|
|
40
|
+
user_agent: str = "agentdraft-python/0.1.0",
|
|
41
|
+
):
|
|
42
|
+
api_key = api_key or os.environ.get("AGENTDRAFT_API_KEY")
|
|
43
|
+
if not api_key:
|
|
44
|
+
raise ValueError("api_key is required (pass it or set AGENTDRAFT_API_KEY)")
|
|
45
|
+
if not api_key.startswith("avs_live_"):
|
|
46
|
+
raise ValueError("api_key must start with 'avs_live_'")
|
|
47
|
+
self._api_key = api_key
|
|
48
|
+
self._base = (base_url or os.environ.get("AGENTDRAFT_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
49
|
+
self._http = httpx.Client(
|
|
50
|
+
timeout=timeout,
|
|
51
|
+
headers={"Authorization": f"Bearer {api_key}", "User-Agent": user_agent},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.availability = _Availability(self)
|
|
55
|
+
self.bookings = _Bookings(self)
|
|
56
|
+
self.agents = _Agents(self)
|
|
57
|
+
self.mailbox = _Mailbox(self)
|
|
58
|
+
|
|
59
|
+
def close(self) -> None:
|
|
60
|
+
self._http.close()
|
|
61
|
+
|
|
62
|
+
def __enter__(self) -> "Client":
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, *_exc) -> None:
|
|
66
|
+
self.close()
|
|
67
|
+
|
|
68
|
+
# -- low-level ---------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def _request(
|
|
71
|
+
self,
|
|
72
|
+
method: str,
|
|
73
|
+
path: str,
|
|
74
|
+
*,
|
|
75
|
+
json: Any = None,
|
|
76
|
+
params: dict | None = None,
|
|
77
|
+
headers: dict | None = None,
|
|
78
|
+
) -> tuple[int, dict, dict]:
|
|
79
|
+
resp = self._http.request(method, f"{self._base}{path}", json=json, params=params, headers=headers)
|
|
80
|
+
try:
|
|
81
|
+
body = resp.json() if resp.content else {}
|
|
82
|
+
except Exception:
|
|
83
|
+
body = {"raw": resp.text}
|
|
84
|
+
|
|
85
|
+
if resp.status_code >= 400:
|
|
86
|
+
self._raise_for(resp.status_code, body, resp.headers)
|
|
87
|
+
|
|
88
|
+
return resp.status_code, body, dict(resp.headers)
|
|
89
|
+
|
|
90
|
+
def _raise_for(self, status: int, body: dict, headers: dict) -> None:
|
|
91
|
+
if status == 401 or status == 403:
|
|
92
|
+
raise AuthError(body.get("detail") or str(body), status=status, body=body)
|
|
93
|
+
if status == 409:
|
|
94
|
+
raise Conflict(
|
|
95
|
+
body.get("error", "outranked"),
|
|
96
|
+
body=body,
|
|
97
|
+
winning_booking_id=body.get("winning_booking_id"),
|
|
98
|
+
winning_agent_id=body.get("winning_agent_id"),
|
|
99
|
+
winning_agent_priority=body.get("winning_agent_priority"),
|
|
100
|
+
your_priority=body.get("your_priority"),
|
|
101
|
+
reason=body.get("error", "outranked"),
|
|
102
|
+
)
|
|
103
|
+
if status == 422:
|
|
104
|
+
raise RuleViolation(body.get("detail") or "rule violation", status=status, body=body)
|
|
105
|
+
if status == 429:
|
|
106
|
+
retry = headers.get("retry-after") or headers.get("Retry-After")
|
|
107
|
+
raise RateLimited(
|
|
108
|
+
"rate limited",
|
|
109
|
+
body=body,
|
|
110
|
+
retry_after=int(retry) if retry else None,
|
|
111
|
+
)
|
|
112
|
+
raise AgentDraftError(f"HTTP {status}: {body}", status=status, body=body)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# -- resources -----------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class _Resource:
|
|
119
|
+
def __init__(self, client: Client):
|
|
120
|
+
self._c = client
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _Availability(_Resource):
|
|
124
|
+
def list(
|
|
125
|
+
self,
|
|
126
|
+
*,
|
|
127
|
+
start: datetime,
|
|
128
|
+
end: datetime,
|
|
129
|
+
duration_minutes: int = 30,
|
|
130
|
+
granularity_minutes: int = 15,
|
|
131
|
+
include_holds: bool = True,
|
|
132
|
+
user_id: Optional[str] = None,
|
|
133
|
+
) -> list[Slot]:
|
|
134
|
+
params: dict[str, Any] = {
|
|
135
|
+
"range_start": start.isoformat(),
|
|
136
|
+
"range_end": end.isoformat(),
|
|
137
|
+
"duration_minutes": duration_minutes,
|
|
138
|
+
"granularity_minutes": granularity_minutes,
|
|
139
|
+
"include_holds": str(include_holds).lower(),
|
|
140
|
+
}
|
|
141
|
+
if user_id:
|
|
142
|
+
params["user_id"] = user_id
|
|
143
|
+
else:
|
|
144
|
+
# The endpoint requires user_id today; resolve it via /v1/agents/me
|
|
145
|
+
me = self._c.agents.me()
|
|
146
|
+
params["user_id"] = me.user_id
|
|
147
|
+
|
|
148
|
+
_, body, _ = self._c._request("GET", "/v1/availability", params=params)
|
|
149
|
+
return [Slot.from_json(s) for s in body.get("slots", [])]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class _Bookings(_Resource):
|
|
153
|
+
def commit(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
start: datetime,
|
|
157
|
+
end: datetime,
|
|
158
|
+
idempotency_key: Optional[str] = None,
|
|
159
|
+
buffer_before_min: int = 0,
|
|
160
|
+
buffer_after_min: int = 0,
|
|
161
|
+
bump_window_seconds: Optional[int] = None,
|
|
162
|
+
metadata: Optional[dict] = None,
|
|
163
|
+
) -> Booking:
|
|
164
|
+
return self._post(
|
|
165
|
+
start=start,
|
|
166
|
+
end=end,
|
|
167
|
+
mode="commit",
|
|
168
|
+
idempotency_key=idempotency_key,
|
|
169
|
+
buffer_before_min=buffer_before_min,
|
|
170
|
+
buffer_after_min=buffer_after_min,
|
|
171
|
+
bump_window_seconds=bump_window_seconds,
|
|
172
|
+
metadata=metadata,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def hold(
|
|
176
|
+
self,
|
|
177
|
+
*,
|
|
178
|
+
start: datetime,
|
|
179
|
+
end: datetime,
|
|
180
|
+
idempotency_key: Optional[str] = None,
|
|
181
|
+
buffer_before_min: int = 0,
|
|
182
|
+
buffer_after_min: int = 0,
|
|
183
|
+
metadata: Optional[dict] = None,
|
|
184
|
+
) -> Booking:
|
|
185
|
+
return self._post(
|
|
186
|
+
start=start,
|
|
187
|
+
end=end,
|
|
188
|
+
mode="hold",
|
|
189
|
+
idempotency_key=idempotency_key,
|
|
190
|
+
buffer_before_min=buffer_before_min,
|
|
191
|
+
buffer_after_min=buffer_after_min,
|
|
192
|
+
metadata=metadata,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def release(self, hold_id: str) -> None:
|
|
196
|
+
self._c._request("POST", f"/v1/holds/{hold_id}/release")
|
|
197
|
+
|
|
198
|
+
def cancel(self, booking_id: str) -> None:
|
|
199
|
+
"""Cancel a previously-committed booking owned by this agent."""
|
|
200
|
+
self._c._request("DELETE", f"/v1/bookings/{booking_id}")
|
|
201
|
+
|
|
202
|
+
def _post(self, **kwargs: Any) -> Booking:
|
|
203
|
+
idempotency_key = kwargs.pop("idempotency_key", None)
|
|
204
|
+
body = {
|
|
205
|
+
"start": kwargs["start"].isoformat(),
|
|
206
|
+
"end": kwargs["end"].isoformat(),
|
|
207
|
+
"mode": kwargs["mode"],
|
|
208
|
+
"buffer_before_min": kwargs.get("buffer_before_min", 0),
|
|
209
|
+
"buffer_after_min": kwargs.get("buffer_after_min", 0),
|
|
210
|
+
}
|
|
211
|
+
if kwargs.get("bump_window_seconds") is not None:
|
|
212
|
+
body["bump_window_seconds"] = kwargs["bump_window_seconds"]
|
|
213
|
+
if kwargs.get("metadata"):
|
|
214
|
+
body["metadata"] = kwargs["metadata"]
|
|
215
|
+
|
|
216
|
+
headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
|
|
217
|
+
_, j, _ = self._c._request("POST", "/v1/bookings", json=body, headers=headers)
|
|
218
|
+
return Booking.from_json(j)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class _Agents(_Resource):
|
|
222
|
+
def me(self) -> AgentIdentity:
|
|
223
|
+
_, j, _ = self._c._request("GET", "/v1/agents/me")
|
|
224
|
+
return AgentIdentity.from_json(j)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class _Mailbox(_Resource):
|
|
228
|
+
"""Inbound + outbound mail for the calling agent.
|
|
229
|
+
|
|
230
|
+
Threading + recipient on ``reply`` are derived server-side from the
|
|
231
|
+
original message — the SDK only needs the body. The address returned
|
|
232
|
+
by ``info()`` is the agent's inbox, suitable for surfacing to humans.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
def info(self) -> dict:
|
|
236
|
+
_, j, _ = self._c._request("GET", "/v1/mailbox/me")
|
|
237
|
+
return j
|
|
238
|
+
|
|
239
|
+
def quota(self) -> dict:
|
|
240
|
+
"""Convenience: just the quota sub-object of ``info()``."""
|
|
241
|
+
return self.info().get("quota", {})
|
|
242
|
+
|
|
243
|
+
def list_messages(
|
|
244
|
+
self,
|
|
245
|
+
*,
|
|
246
|
+
limit: int = 25,
|
|
247
|
+
cursor: Optional[str] = None,
|
|
248
|
+
direction: Optional[str] = None,
|
|
249
|
+
booking_id: Optional[str] = None,
|
|
250
|
+
since: Optional[datetime] = None,
|
|
251
|
+
) -> dict:
|
|
252
|
+
params: dict[str, Any] = {"limit": limit}
|
|
253
|
+
if cursor:
|
|
254
|
+
params["cursor"] = cursor
|
|
255
|
+
if direction:
|
|
256
|
+
params["direction"] = direction
|
|
257
|
+
if booking_id:
|
|
258
|
+
params["booking_id"] = booking_id
|
|
259
|
+
if since:
|
|
260
|
+
params["since"] = since.isoformat()
|
|
261
|
+
_, j, _ = self._c._request("GET", "/v1/mailbox/messages", params=params)
|
|
262
|
+
return j
|
|
263
|
+
|
|
264
|
+
def get_message(self, message_id: str) -> dict:
|
|
265
|
+
_, j, _ = self._c._request("GET", f"/v1/mailbox/messages/{message_id}")
|
|
266
|
+
return j
|
|
267
|
+
|
|
268
|
+
def send(
|
|
269
|
+
self,
|
|
270
|
+
*,
|
|
271
|
+
to: str,
|
|
272
|
+
subject: str,
|
|
273
|
+
body_text: str,
|
|
274
|
+
booking_id: Optional[str] = None,
|
|
275
|
+
in_reply_to: Optional[str] = None,
|
|
276
|
+
reply_to: Optional[str] = None,
|
|
277
|
+
) -> dict:
|
|
278
|
+
body: dict[str, Any] = {"to": to, "subject": subject, "body_text": body_text}
|
|
279
|
+
if booking_id is not None:
|
|
280
|
+
body["booking_id"] = booking_id
|
|
281
|
+
if in_reply_to is not None:
|
|
282
|
+
body["in_reply_to"] = in_reply_to
|
|
283
|
+
if reply_to is not None:
|
|
284
|
+
body["reply_to"] = reply_to
|
|
285
|
+
_, j, _ = self._c._request("POST", "/v1/mailbox/send", json=body)
|
|
286
|
+
return j
|
|
287
|
+
|
|
288
|
+
def reply(
|
|
289
|
+
self,
|
|
290
|
+
message_id: str,
|
|
291
|
+
*,
|
|
292
|
+
body_text: str,
|
|
293
|
+
subject: Optional[str] = None,
|
|
294
|
+
reply_to: Optional[str] = None,
|
|
295
|
+
) -> dict:
|
|
296
|
+
body: dict[str, Any] = {"body_text": body_text}
|
|
297
|
+
if subject is not None:
|
|
298
|
+
body["subject"] = subject
|
|
299
|
+
if reply_to is not None:
|
|
300
|
+
body["reply_to"] = reply_to
|
|
301
|
+
_, j, _ = self._c._request(
|
|
302
|
+
"POST", f"/v1/mailbox/messages/{message_id}/reply", json=body
|
|
303
|
+
)
|
|
304
|
+
return j
|
|
305
|
+
|
|
306
|
+
def check_suppression(self, address: str) -> dict:
|
|
307
|
+
from urllib.parse import quote
|
|
308
|
+
|
|
309
|
+
_, j, _ = self._c._request(
|
|
310
|
+
"GET", f"/v1/mailbox/suppressions/{quote(address, safe='@+.')}"
|
|
311
|
+
)
|
|
312
|
+
return j
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""SDK exception hierarchy.
|
|
2
|
+
|
|
3
|
+
All AgentDraft errors inherit from ``AgentDraftError``. ``Conflict`` is the
|
|
4
|
+
domain-meaningful one — your booking lost to a higher-priority agent — and
|
|
5
|
+
carries the winner's identity for graceful fallback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentDraftError(Exception):
|
|
14
|
+
"""Base for all SDK errors."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str, *, status: int | None = None, body: dict | None = None):
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
self.status = status
|
|
19
|
+
self.body = body or {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthError(AgentDraftError):
|
|
23
|
+
"""401 / 403 — your API key is missing, invalid, or lacks the required scope."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Conflict(AgentDraftError):
|
|
27
|
+
"""409 — your booking lost. Inspect ``winning_*`` to decide what to do next."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
message: str,
|
|
32
|
+
*,
|
|
33
|
+
status: int = 409,
|
|
34
|
+
body: dict,
|
|
35
|
+
winning_booking_id: Optional[str] = None,
|
|
36
|
+
winning_agent_id: Optional[str] = None,
|
|
37
|
+
winning_agent_priority: Optional[int] = None,
|
|
38
|
+
your_priority: Optional[int] = None,
|
|
39
|
+
reason: str = "outranked",
|
|
40
|
+
):
|
|
41
|
+
super().__init__(message, status=status, body=body)
|
|
42
|
+
self.winning_booking_id = winning_booking_id
|
|
43
|
+
self.winning_agent_id = winning_agent_id
|
|
44
|
+
self.winning_agent_priority = winning_agent_priority
|
|
45
|
+
self.your_priority = your_priority
|
|
46
|
+
self.reason = reason
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RuleViolation(AgentDraftError):
|
|
50
|
+
"""422 — the proposed booking violates a rule (working hours, focus block, etc.)."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RateLimited(AgentDraftError):
|
|
54
|
+
"""429 — you've exceeded your rate limit. ``retry_after`` is in seconds."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, message: str, *, status: int = 429, body: dict, retry_after: int | None = None):
|
|
57
|
+
super().__init__(message, status=status, body=body)
|
|
58
|
+
self.retry_after = retry_after
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Lightweight value objects. No Pydantic dep — keep the SDK small."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _parse_dt(s: Optional[str]) -> Optional[datetime]:
|
|
11
|
+
if not s:
|
|
12
|
+
return None
|
|
13
|
+
return datetime.fromisoformat(s.replace("Z", "+00:00"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Booking:
|
|
18
|
+
booking_id: str
|
|
19
|
+
status: str # "COMMITTED" | "HOLD"
|
|
20
|
+
expires_at: Optional[datetime] = None
|
|
21
|
+
audit_event_id: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def from_json(cls, j: dict) -> "Booking":
|
|
25
|
+
return cls(
|
|
26
|
+
booking_id=j["booking_id"],
|
|
27
|
+
status=j["status"],
|
|
28
|
+
expires_at=_parse_dt(j.get("expires_at")),
|
|
29
|
+
audit_event_id=j.get("audit_event_id"),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Hold(Booking):
|
|
35
|
+
"""A Booking with status=HOLD. Same shape; semantic alias for clarity."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class Slot:
|
|
42
|
+
start: datetime
|
|
43
|
+
end: datetime
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def from_json(cls, j: dict) -> "Slot":
|
|
47
|
+
return cls(start=_parse_dt(j["start"]), end=_parse_dt(j["end"]))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AgentIdentity:
|
|
52
|
+
agent_id: str
|
|
53
|
+
user_id: str
|
|
54
|
+
priority: int
|
|
55
|
+
scopes: list[str]
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_json(cls, j: dict) -> "AgentIdentity":
|
|
59
|
+
return cls(
|
|
60
|
+
agent_id=j["agent_id"],
|
|
61
|
+
user_id=j["user_id"],
|
|
62
|
+
priority=j["priority"],
|
|
63
|
+
scopes=j.get("scopes", []),
|
|
64
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentdraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for AgentDraft — the scheduling source of truth for AI agents
|
|
5
|
+
Author-email: AgentDraft Labs <hello@agentdraft.io>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://agentdraft.io
|
|
8
|
+
Project-URL: Documentation, https://agentdraft.io/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/GipsyChef/agentdraft
|
|
10
|
+
Project-URL: Issues, https://github.com/GipsyChef/agentdraft/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/GipsyChef/agentdraft/blob/main/sdks/python/CHANGELOG.md
|
|
12
|
+
Keywords: calendar,scheduling,ai-agents,agentdraft
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business :: Scheduling
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# agentdraft — Python SDK
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/agentdraft/)
|
|
31
|
+
[](https://pypi.org/project/agentdraft/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
|
|
34
|
+
The official Python SDK for [AgentDraft](https://agentdraft.io) — the
|
|
35
|
+
scheduling source of truth for AI agents.
|
|
36
|
+
|
|
37
|
+
AgentDraft is the coordination layer that prevents AI scheduling agents
|
|
38
|
+
from colliding on the same calendar. Every agent calls one API before
|
|
39
|
+
booking; every commit goes through one deterministic engine; every
|
|
40
|
+
action is recorded in one tamper-evident audit log.
|
|
41
|
+
|
|
42
|
+
This SDK gives Python agents a typed, one-line surface to participate.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install agentdraft
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python 3.9+.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from datetime import datetime, timedelta, timezone
|
|
56
|
+
from agentdraft import Client, Conflict
|
|
57
|
+
|
|
58
|
+
client = Client(api_key="avs_live_...") # or set AGENTDRAFT_API_KEY
|
|
59
|
+
|
|
60
|
+
start = datetime.now(timezone.utc) + timedelta(hours=4)
|
|
61
|
+
end = start + timedelta(minutes=30)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
booking = client.bookings.commit(
|
|
65
|
+
start=start, end=end,
|
|
66
|
+
idempotency_key="ik_call_42",
|
|
67
|
+
metadata={"title": "Discovery call"},
|
|
68
|
+
)
|
|
69
|
+
print("booked:", booking.booking_id)
|
|
70
|
+
except Conflict as e:
|
|
71
|
+
print(f"outranked by {e.winning_agent_id} (rank {e.winning_agent_priority})")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A losing agent gets a typed `Conflict` exception, not a timeout — it
|
|
75
|
+
knows who won, by what priority, and where the audit row lives, so
|
|
76
|
+
fallback behavior (propose an alternate, escalate, defer) is a clean
|
|
77
|
+
`except` clause away.
|
|
78
|
+
|
|
79
|
+
## Authentication
|
|
80
|
+
|
|
81
|
+
API keys are issued from the AgentDraft dashboard and start with
|
|
82
|
+
`avs_live_`. Pass it explicitly or let the client read it from the
|
|
83
|
+
environment:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
Client(api_key="avs_live_...")
|
|
87
|
+
# or
|
|
88
|
+
import os; os.environ["AGENTDRAFT_API_KEY"] = "avs_live_..."
|
|
89
|
+
Client()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For local development against a dev backend, point at it via
|
|
93
|
+
`AGENTDRAFT_BASE_URL` or the `base_url=` kwarg.
|
|
94
|
+
|
|
95
|
+
## Surface
|
|
96
|
+
|
|
97
|
+
| Attribute | Purpose |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `client.availability` | Read merged availability across all agents writing to the calendar |
|
|
100
|
+
| `client.bookings` | `hold`, `release`, `commit`, `cancel` — the four state transitions |
|
|
101
|
+
| `client.agents` | `me()` — confirm key + current priority + scopes |
|
|
102
|
+
| `client.mailbox` | Inbound/outbound mail surface for agents that book via email |
|
|
103
|
+
|
|
104
|
+
All blocking I/O. An async client is on the roadmap; for now wrap with
|
|
105
|
+
`asyncio.to_thread` if you need concurrency.
|
|
106
|
+
|
|
107
|
+
## Error types
|
|
108
|
+
|
|
109
|
+
Every failure is a typed exception so callers can branch precisely:
|
|
110
|
+
|
|
111
|
+
- `Conflict` — your write was outranked. Carries `winning_agent_id`,
|
|
112
|
+
`winning_agent_priority`, `winning_booking_id`, `audit_event_id`.
|
|
113
|
+
- `AuthError` — bad/missing/expired API key.
|
|
114
|
+
- `RateLimited` — token bucket exhausted. Has `retry_after_seconds`.
|
|
115
|
+
- `RuleViolation` — request was syntactically valid but violated a
|
|
116
|
+
rule (focus block, daily cap, business hours).
|
|
117
|
+
- `AgentDraftError` — base class; catch this if you only need a
|
|
118
|
+
catch-all.
|
|
119
|
+
|
|
120
|
+
## Idempotency
|
|
121
|
+
|
|
122
|
+
Pass `idempotency_key=` to `bookings.commit(...)`. The server caches the
|
|
123
|
+
result by `(agent_id, key)` for 24 hours, so a retry over a flaky network
|
|
124
|
+
returns the original booking, not a duplicate.
|
|
125
|
+
|
|
126
|
+
## Links
|
|
127
|
+
|
|
128
|
+
- Protocol spec: <https://agentdraft.io/spec>
|
|
129
|
+
- API docs: <https://agentdraft.io/docs>
|
|
130
|
+
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
131
|
+
- Issues / source: <https://github.com/GipsyChef/agentdraft>
|
|
132
|
+
- TypeScript SDK: [`@agentdraft/sdk`](https://www.npmjs.com/package/@agentdraft/sdk)
|
|
133
|
+
|
|
134
|
+
## Security
|
|
135
|
+
|
|
136
|
+
Found a vulnerability? Please follow [SECURITY.md](SECURITY.md) — **do
|
|
137
|
+
not** open a public issue for a security report.
|
|
138
|
+
|
|
139
|
+
## Contributing
|
|
140
|
+
|
|
141
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). This SDK lives inside the
|
|
142
|
+
[`GipsyChef/agentdraft`](https://github.com/GipsyChef/agentdraft)
|
|
143
|
+
monorepo under `sdks/python/`.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
agentdraft/__init__.py
|
|
5
|
+
agentdraft/client.py
|
|
6
|
+
agentdraft/errors.py
|
|
7
|
+
agentdraft/models.py
|
|
8
|
+
agentdraft/py.typed
|
|
9
|
+
agentdraft.egg-info/PKG-INFO
|
|
10
|
+
agentdraft.egg-info/SOURCES.txt
|
|
11
|
+
agentdraft.egg-info/dependency_links.txt
|
|
12
|
+
agentdraft.egg-info/requires.txt
|
|
13
|
+
agentdraft.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
httpx>=0.27.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agentdraft
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agentdraft"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python SDK for AgentDraft — the scheduling source of truth for AI agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "AgentDraft Labs", email = "hello@agentdraft.io" }]
|
|
9
|
+
keywords = ["calendar", "scheduling", "ai-agents", "agentdraft"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.9",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Office/Business :: Scheduling",
|
|
20
|
+
]
|
|
21
|
+
dependencies = ["httpx>=0.27.0"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://agentdraft.io"
|
|
25
|
+
Documentation = "https://agentdraft.io/docs"
|
|
26
|
+
Repository = "https://github.com/GipsyChef/agentdraft"
|
|
27
|
+
Issues = "https://github.com/GipsyChef/agentdraft/issues"
|
|
28
|
+
Changelog = "https://github.com/GipsyChef/agentdraft/blob/main/sdks/python/CHANGELOG.md"
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["setuptools>=68"]
|
|
32
|
+
build-backend = "setuptools.build_meta"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
include = ["agentdraft*"]
|