agentgen 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.
- agentgen-0.1.0/PKG-INFO +11 -0
- agentgen-0.1.0/README.md +357 -0
- agentgen-0.1.0/agentgen/__init__.py +52 -0
- agentgen-0.1.0/agentgen/client.py +244 -0
- agentgen-0.1.0/agentgen/errors.py +38 -0
- agentgen-0.1.0/agentgen/types.py +83 -0
- agentgen-0.1.0/pyproject.toml +20 -0
agentgen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentgen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for the AgentGen API — HTML to PDF and Image
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: agentgen,html-to-image,html-to-pdf,pdf,screenshot
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Requires-Dist: httpx>=0.25.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
agentgen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# agentgen · Python SDK
|
|
2
|
+
|
|
3
|
+
Python client for the [AgentGen API](https://www.agent-gen.com) — HTML → PDF and HTML → Image generation.
|
|
4
|
+
|
|
5
|
+
- **Sync + async** — `AgentGenClient` for regular code, `AsyncAgentGenClient` for `asyncio` / `async`/`await`
|
|
6
|
+
- **Typed** — dataclass-based request/response types with full type annotations
|
|
7
|
+
- **Lightweight** — single dependency: [`httpx`](https://www.python-httpx.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install agentgen
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires **Python 3.9+** and `httpx >= 0.25`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from agentgen import AgentGenClient, GenerateImageOptions, PdfPage
|
|
25
|
+
|
|
26
|
+
client = AgentGenClient(api_key="agk_...")
|
|
27
|
+
|
|
28
|
+
# Render HTML to a PNG image
|
|
29
|
+
image = client.generate_image(GenerateImageOptions(
|
|
30
|
+
html="<h1 style='font-family:sans-serif'>Hello, world!</h1>",
|
|
31
|
+
width=1200,
|
|
32
|
+
height=630,
|
|
33
|
+
))
|
|
34
|
+
print(image.url) # https://…/output.png
|
|
35
|
+
|
|
36
|
+
# Render HTML to a PDF
|
|
37
|
+
pdf = client.generate_pdf(PdfPage(
|
|
38
|
+
html="<h1>Invoice #42</h1><p>Amount due: $99.00</p>",
|
|
39
|
+
format="A4",
|
|
40
|
+
))
|
|
41
|
+
print(pdf.url) # https://…/output.pdf
|
|
42
|
+
|
|
43
|
+
# Check token balance
|
|
44
|
+
balance = client.get_balance()
|
|
45
|
+
print(f"Remaining tokens: {balance.tokens}")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Client classes
|
|
51
|
+
|
|
52
|
+
### `AgentGenClient` (synchronous)
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from agentgen import AgentGenClient
|
|
56
|
+
|
|
57
|
+
client = AgentGenClient(
|
|
58
|
+
api_key="agk_...", # required
|
|
59
|
+
base_url="https://www.agent-gen.com/api", # optional — default shown
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `AsyncAgentGenClient` (async / await)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from agentgen import AsyncAgentGenClient
|
|
67
|
+
|
|
68
|
+
client = AsyncAgentGenClient(api_key="agk_...")
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
image = await client.generate_image(...)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Both classes expose exactly the same methods with the same signatures. The only difference is that `AsyncAgentGenClient` returns coroutines that must be awaited.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Methods
|
|
79
|
+
|
|
80
|
+
### `generate_image(options)` → `GenerateImageResult`
|
|
81
|
+
|
|
82
|
+
Renders an HTML string to a screenshot image. **Costs 1 token.**
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from agentgen import GenerateImageOptions
|
|
86
|
+
|
|
87
|
+
result = client.generate_image(GenerateImageOptions(
|
|
88
|
+
html="<div style='background:#6366f1;color:#fff;padding:40px'>Hello</div>",
|
|
89
|
+
width=1200, # px, default 1200
|
|
90
|
+
height=630, # px, default 630
|
|
91
|
+
format="png", # "png" | "jpeg" | "webp", default "png"
|
|
92
|
+
device_scale_factor=2, # 1–3, default 2 (retina-quality)
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
print(result.url) # public URL, valid for 24 h
|
|
96
|
+
print(result.width) # actual rendered width
|
|
97
|
+
print(result.height) # actual rendered height
|
|
98
|
+
print(result.format) # "png" | "jpeg" | "webp"
|
|
99
|
+
print(result.tokens_used) # always 1
|
|
100
|
+
print(result.request_id) # unique request identifier
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Only `html` is required — all other fields default to `None` (server applies defaults).
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `generate_pdf(options)` → `GeneratePdfResult`
|
|
108
|
+
|
|
109
|
+
Renders HTML to a PDF document. **Costs 2 tokens per page.**
|
|
110
|
+
|
|
111
|
+
Pass a single `PdfPage` object for a one-page PDF, or a **list** of `PdfPage` objects for a multi-page PDF.
|
|
112
|
+
|
|
113
|
+
#### Single-page PDF
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from agentgen import PdfPage, PdfMargin
|
|
117
|
+
|
|
118
|
+
result = client.generate_pdf(PdfPage(
|
|
119
|
+
html="""
|
|
120
|
+
<html>
|
|
121
|
+
<body style="font-family:sans-serif;padding:40px">
|
|
122
|
+
<h1>Invoice #42</h1>
|
|
123
|
+
<p>Amount due: $99.00</p>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
126
|
+
""",
|
|
127
|
+
format="A4", # "A4" | "Letter" | "A3" | "Legal"
|
|
128
|
+
landscape=False, # default False
|
|
129
|
+
print_background=True, # default True
|
|
130
|
+
margin=PdfMargin(
|
|
131
|
+
top="20mm",
|
|
132
|
+
bottom="20mm",
|
|
133
|
+
left="15mm",
|
|
134
|
+
right="15mm",
|
|
135
|
+
),
|
|
136
|
+
))
|
|
137
|
+
|
|
138
|
+
print(result.url) # public URL pointing to the PDF
|
|
139
|
+
print(result.pages) # number of pages generated
|
|
140
|
+
print(result.tokens_used) # pages × 2
|
|
141
|
+
print(result.request_id)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Multi-page PDF
|
|
145
|
+
|
|
146
|
+
Pass a list of `PdfPage` objects — each entry is an independent page with its own HTML and settings.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
result = client.generate_pdf([
|
|
150
|
+
PdfPage(
|
|
151
|
+
html="<h1 style='padding:40px'>Page 1 — Cover</h1>",
|
|
152
|
+
format="A4",
|
|
153
|
+
),
|
|
154
|
+
PdfPage(
|
|
155
|
+
html="<h1 style='padding:40px'>Page 2 — Content</h1>",
|
|
156
|
+
format="A4",
|
|
157
|
+
landscape=True,
|
|
158
|
+
),
|
|
159
|
+
PdfPage(
|
|
160
|
+
html="<h1 style='padding:40px'>Page 3 — Appendix</h1>",
|
|
161
|
+
format="A4",
|
|
162
|
+
margin=PdfMargin(top="10mm", bottom="10mm", left="10mm", right="10mm"),
|
|
163
|
+
),
|
|
164
|
+
])
|
|
165
|
+
|
|
166
|
+
print(f"Generated {result.pages} pages, used {result.tokens_used} tokens")
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Up to 100 pages per request.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### `upload_temp(file, filename=None)` → `UploadTempResult`
|
|
174
|
+
|
|
175
|
+
Uploads a file to temporary storage so you can embed it by URL inside your HTML before calling `generate_image` or `generate_pdf`. **Free — no tokens consumed.** Files are auto-deleted after **24 hours**.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
# Upload from a file path (str or pathlib.Path)
|
|
179
|
+
upload = client.upload_temp("./logo.png")
|
|
180
|
+
|
|
181
|
+
print(upload.url) # public URL, valid for 24 h
|
|
182
|
+
print(upload.key) # storage key
|
|
183
|
+
print(upload.size) # file size in bytes
|
|
184
|
+
print(upload.expires_at) # ISO 8601 expiry timestamp
|
|
185
|
+
|
|
186
|
+
# Now embed the URL in your HTML
|
|
187
|
+
result = client.generate_image(GenerateImageOptions(
|
|
188
|
+
html=f'<img src="{upload.url}" style="width:200px" />',
|
|
189
|
+
width=400,
|
|
190
|
+
height=200,
|
|
191
|
+
))
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
# Upload from an open file object
|
|
196
|
+
with open("./chart.svg", "rb") as f:
|
|
197
|
+
upload = client.upload_temp(f, filename="chart.svg")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Accepted types:** `image/png`, `image/jpeg`, `image/webp`, `image/gif`, `image/svg+xml`. Max size: **10 MB**.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
### `get_balance()` → `BalanceResult`
|
|
205
|
+
|
|
206
|
+
Returns the current token balance.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
balance = client.get_balance()
|
|
210
|
+
print(f"You have {balance.tokens} tokens remaining.")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Async usage
|
|
216
|
+
|
|
217
|
+
Every method above works identically with `AsyncAgentGenClient`. Just `await` each call:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import asyncio
|
|
221
|
+
from agentgen import AsyncAgentGenClient, GenerateImageOptions, PdfPage
|
|
222
|
+
|
|
223
|
+
async def main():
|
|
224
|
+
client = AsyncAgentGenClient(api_key="agk_...")
|
|
225
|
+
|
|
226
|
+
# Run both requests concurrently
|
|
227
|
+
image_coro = client.generate_image(GenerateImageOptions(
|
|
228
|
+
html="<h1>OG Image</h1>",
|
|
229
|
+
width=1200,
|
|
230
|
+
height=630,
|
|
231
|
+
))
|
|
232
|
+
pdf_coro = client.generate_pdf(PdfPage(
|
|
233
|
+
html="<h1>Report</h1>",
|
|
234
|
+
format="A4",
|
|
235
|
+
))
|
|
236
|
+
|
|
237
|
+
image, pdf = await asyncio.gather(image_coro, pdf_coro)
|
|
238
|
+
print(image.url, pdf.url)
|
|
239
|
+
|
|
240
|
+
asyncio.run(main())
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Error handling
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from agentgen import AgentGenClient, AgentGenError, InsufficientTokensError, GenerateImageOptions
|
|
249
|
+
|
|
250
|
+
client = AgentGenClient(api_key="agk_...")
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
result = client.generate_image(GenerateImageOptions(html="<h1>Hello</h1>"))
|
|
254
|
+
print(result.url)
|
|
255
|
+
|
|
256
|
+
except InsufficientTokensError as e:
|
|
257
|
+
print(f"Not enough tokens. Have {e.balance}, need {e.required}.")
|
|
258
|
+
print(f"Buy more at: {e.buy_more_url}")
|
|
259
|
+
|
|
260
|
+
except AgentGenError as e:
|
|
261
|
+
print(f"API error {e.status}: {e}")
|
|
262
|
+
if e.detail:
|
|
263
|
+
print(f"Detail: {e.detail}")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Exception classes
|
|
267
|
+
|
|
268
|
+
#### `AgentGenError(Exception)`
|
|
269
|
+
|
|
270
|
+
Raised for all API errors (4xx / 5xx) except HTTP 402.
|
|
271
|
+
|
|
272
|
+
| Attribute | Type | Description |
|
|
273
|
+
|-----------|------|-------------|
|
|
274
|
+
| `args[0]` / `str(e)` | `str` | Human-readable error message |
|
|
275
|
+
| `status` | `int` | HTTP status code |
|
|
276
|
+
| `detail` | `str \| None` | Optional additional detail from the API |
|
|
277
|
+
| `details` | `dict \| None` | Validation field errors (present on HTTP 422) |
|
|
278
|
+
|
|
279
|
+
#### `InsufficientTokensError(AgentGenError)`
|
|
280
|
+
|
|
281
|
+
Raised when the account has too few tokens (HTTP 402).
|
|
282
|
+
|
|
283
|
+
| Attribute | Type | Description |
|
|
284
|
+
|-----------|------|-------------|
|
|
285
|
+
| `balance` | `int` | Current token balance |
|
|
286
|
+
| `required` | `int` | Tokens needed for this request |
|
|
287
|
+
| `buy_more_url` | `str` | Direct link to purchase more tokens |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Type reference
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from agentgen import (
|
|
295
|
+
# Clients
|
|
296
|
+
AgentGenClient,
|
|
297
|
+
AsyncAgentGenClient,
|
|
298
|
+
|
|
299
|
+
# Request types
|
|
300
|
+
GenerateImageOptions,
|
|
301
|
+
PdfPage,
|
|
302
|
+
PdfMargin,
|
|
303
|
+
|
|
304
|
+
# Response types
|
|
305
|
+
GenerateImageResult,
|
|
306
|
+
GeneratePdfResult,
|
|
307
|
+
UploadTempResult,
|
|
308
|
+
BalanceResult,
|
|
309
|
+
|
|
310
|
+
# Literal type aliases
|
|
311
|
+
ImageFormat, # Literal["png", "jpeg", "webp"]
|
|
312
|
+
PdfFormat, # Literal["A4", "Letter", "A3", "Legal"]
|
|
313
|
+
|
|
314
|
+
# Exceptions
|
|
315
|
+
AgentGenError,
|
|
316
|
+
InsufficientTokensError,
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### `GenerateImageOptions` fields
|
|
321
|
+
|
|
322
|
+
| Field | Type | Default | Description |
|
|
323
|
+
|-------|------|---------|-------------|
|
|
324
|
+
| `html` | `str` | **required** | HTML to render (max 500 KB) |
|
|
325
|
+
| `width` | `int \| None` | 1200 | Viewport width in px (1–5000) |
|
|
326
|
+
| `height` | `int \| None` | 630 | Viewport height in px (1–5000) |
|
|
327
|
+
| `format` | `ImageFormat \| None` | `"png"` | Output format |
|
|
328
|
+
| `device_scale_factor` | `float \| None` | 2.0 | Device pixel ratio (1–3) |
|
|
329
|
+
|
|
330
|
+
### `PdfPage` fields
|
|
331
|
+
|
|
332
|
+
| Field | Type | Default | Description |
|
|
333
|
+
|-------|------|---------|-------------|
|
|
334
|
+
| `html` | `str` | **required** | HTML to render (max 500 KB) |
|
|
335
|
+
| `format` | `PdfFormat \| None` | `"A4"` | Paper size |
|
|
336
|
+
| `width` | `str \| None` | — | Custom width, e.g. `"8.5in"` |
|
|
337
|
+
| `height` | `str \| None` | — | Custom height, e.g. `"11in"` |
|
|
338
|
+
| `landscape` | `bool \| None` | `False` | Landscape orientation |
|
|
339
|
+
| `margin` | `PdfMargin \| None` | — | Page margins |
|
|
340
|
+
| `print_background` | `bool \| None` | `True` | Render CSS backgrounds |
|
|
341
|
+
|
|
342
|
+
### `PdfMargin` fields
|
|
343
|
+
|
|
344
|
+
All fields are `str | None` and accept any CSS length value (e.g. `"20mm"`, `"1in"`, `"72pt"`).
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Token pricing
|
|
349
|
+
|
|
350
|
+
| Operation | Cost |
|
|
351
|
+
|-----------|------|
|
|
352
|
+
| Generate image | 1 token |
|
|
353
|
+
| Generate PDF (per page) | 2 tokens |
|
|
354
|
+
| Upload temp file | Free |
|
|
355
|
+
| Check balance | Free |
|
|
356
|
+
|
|
357
|
+
Purchase tokens at [agent-gen.com](https://www.agent-gen.com).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""AgentGen Python SDK — HTML to PDF and Image.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
from agentgen import AgentGenClient, GenerateImageOptions
|
|
6
|
+
|
|
7
|
+
client = AgentGenClient(api_key="agk_...")
|
|
8
|
+
|
|
9
|
+
# Generate an image
|
|
10
|
+
img = client.generate_image(GenerateImageOptions(html="<h1>Hello</h1>"))
|
|
11
|
+
print(img.url)
|
|
12
|
+
|
|
13
|
+
# Generate a PDF
|
|
14
|
+
from agentgen import PdfPage
|
|
15
|
+
pdf = client.generate_pdf(PdfPage(html="<h1>Invoice</h1>", format="A4"))
|
|
16
|
+
print(pdf.url)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .client import AgentGenClient, AsyncAgentGenClient
|
|
20
|
+
from .errors import AgentGenError, InsufficientTokensError
|
|
21
|
+
from .types import (
|
|
22
|
+
BalanceResult,
|
|
23
|
+
GenerateImageOptions,
|
|
24
|
+
GenerateImageResult,
|
|
25
|
+
GeneratePdfResult,
|
|
26
|
+
ImageFormat,
|
|
27
|
+
PdfFormat,
|
|
28
|
+
PdfMargin,
|
|
29
|
+
PdfPage,
|
|
30
|
+
UploadTempResult,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
# Clients
|
|
35
|
+
"AgentGenClient",
|
|
36
|
+
"AsyncAgentGenClient",
|
|
37
|
+
# Request types
|
|
38
|
+
"GenerateImageOptions",
|
|
39
|
+
"PdfMargin",
|
|
40
|
+
"PdfPage",
|
|
41
|
+
# Response types
|
|
42
|
+
"GenerateImageResult",
|
|
43
|
+
"GeneratePdfResult",
|
|
44
|
+
"UploadTempResult",
|
|
45
|
+
"BalanceResult",
|
|
46
|
+
# Literals
|
|
47
|
+
"ImageFormat",
|
|
48
|
+
"PdfFormat",
|
|
49
|
+
# Errors
|
|
50
|
+
"AgentGenError",
|
|
51
|
+
"InsufficientTokensError",
|
|
52
|
+
]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import dataclasses
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, BinaryIO, Optional, Union
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .errors import AgentGenError, InsufficientTokensError
|
|
11
|
+
from .types import (
|
|
12
|
+
BalanceResult,
|
|
13
|
+
GenerateImageOptions,
|
|
14
|
+
GenerateImageResult,
|
|
15
|
+
GeneratePdfResult,
|
|
16
|
+
PdfPage,
|
|
17
|
+
UploadTempResult,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
DEFAULT_BASE_URL = "https://www.agent-gen.com/api"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _to_dict(obj: Any) -> Any:
|
|
24
|
+
"""Recursively convert a dataclass to a dict, dropping None values."""
|
|
25
|
+
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
26
|
+
return {
|
|
27
|
+
f.name: _to_dict(v)
|
|
28
|
+
for f in dataclasses.fields(obj) # type: ignore[arg-type]
|
|
29
|
+
if (v := getattr(obj, f.name)) is not None
|
|
30
|
+
}
|
|
31
|
+
return obj
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _raise_for_error(response: httpx.Response) -> None:
|
|
35
|
+
data: dict[str, Any] = response.json()
|
|
36
|
+
if response.status_code == 402:
|
|
37
|
+
raise InsufficientTokensError(
|
|
38
|
+
data["error"],
|
|
39
|
+
data["balance"],
|
|
40
|
+
data["required"],
|
|
41
|
+
data["buy_more_url"],
|
|
42
|
+
)
|
|
43
|
+
raise AgentGenError(
|
|
44
|
+
data["error"],
|
|
45
|
+
response.status_code,
|
|
46
|
+
data.get("detail"),
|
|
47
|
+
data.get("details"),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AgentGenClient:
|
|
52
|
+
"""Synchronous AgentGen API client.
|
|
53
|
+
|
|
54
|
+
Example::
|
|
55
|
+
|
|
56
|
+
from agentgen import AgentGenClient, GenerateImageOptions
|
|
57
|
+
|
|
58
|
+
client = AgentGenClient(api_key="agk_...")
|
|
59
|
+
result = client.generate_image(GenerateImageOptions(html="<h1>Hello</h1>"))
|
|
60
|
+
print(result.url)
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL) -> None:
|
|
64
|
+
self._base_url = base_url.rstrip("/")
|
|
65
|
+
self._headers = {"X-API-Key": api_key}
|
|
66
|
+
|
|
67
|
+
def generate_image(self, options: GenerateImageOptions) -> GenerateImageResult:
|
|
68
|
+
"""Render HTML to an image (PNG/JPEG/WebP). Costs **1 token**."""
|
|
69
|
+
with httpx.Client() as client:
|
|
70
|
+
r = client.post(
|
|
71
|
+
f"{self._base_url}/v1/generate/image",
|
|
72
|
+
json=_to_dict(options),
|
|
73
|
+
headers=self._headers,
|
|
74
|
+
)
|
|
75
|
+
if not r.is_success:
|
|
76
|
+
_raise_for_error(r)
|
|
77
|
+
return GenerateImageResult(**r.json())
|
|
78
|
+
|
|
79
|
+
def generate_pdf(
|
|
80
|
+
self, options: Union[PdfPage, list[PdfPage]]
|
|
81
|
+
) -> GeneratePdfResult:
|
|
82
|
+
"""Render HTML to a PDF. Costs **2 tokens per page**.
|
|
83
|
+
|
|
84
|
+
Pass a single :class:`PdfPage` for a one-page document, or a list of
|
|
85
|
+
:class:`PdfPage` objects for a multi-page document.
|
|
86
|
+
"""
|
|
87
|
+
body = (
|
|
88
|
+
{"pages": [_to_dict(p) for p in options]}
|
|
89
|
+
if isinstance(options, list)
|
|
90
|
+
else _to_dict(options)
|
|
91
|
+
)
|
|
92
|
+
with httpx.Client() as client:
|
|
93
|
+
r = client.post(
|
|
94
|
+
f"{self._base_url}/v1/generate/pdf",
|
|
95
|
+
json=body,
|
|
96
|
+
headers=self._headers,
|
|
97
|
+
)
|
|
98
|
+
if not r.is_success:
|
|
99
|
+
_raise_for_error(r)
|
|
100
|
+
return GeneratePdfResult(**r.json())
|
|
101
|
+
|
|
102
|
+
def upload_temp(
|
|
103
|
+
self,
|
|
104
|
+
file: Union[str, Path, BinaryIO],
|
|
105
|
+
filename: Optional[str] = None,
|
|
106
|
+
) -> UploadTempResult:
|
|
107
|
+
"""Upload a file for use inside HTML templates.
|
|
108
|
+
|
|
109
|
+
**Free** (no tokens). Auto-deleted after **24 hours**.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
file: A file path (str/Path) or an open binary file object.
|
|
113
|
+
filename: Optional filename hint for the upload.
|
|
114
|
+
"""
|
|
115
|
+
if isinstance(file, (str, Path)):
|
|
116
|
+
path = Path(file)
|
|
117
|
+
filename = filename or path.name
|
|
118
|
+
file_obj: BinaryIO = open(path, "rb")
|
|
119
|
+
should_close = True
|
|
120
|
+
else:
|
|
121
|
+
file_obj = file
|
|
122
|
+
should_close = False
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
with httpx.Client() as client:
|
|
126
|
+
r = client.post(
|
|
127
|
+
f"{self._base_url}/v1/upload/temp",
|
|
128
|
+
files={"file": (filename or "upload", file_obj)},
|
|
129
|
+
headers=self._headers,
|
|
130
|
+
)
|
|
131
|
+
finally:
|
|
132
|
+
if should_close:
|
|
133
|
+
file_obj.close() # type: ignore[union-attr]
|
|
134
|
+
|
|
135
|
+
if not r.is_success:
|
|
136
|
+
_raise_for_error(r)
|
|
137
|
+
return UploadTempResult(**r.json())
|
|
138
|
+
|
|
139
|
+
def get_balance(self) -> BalanceResult:
|
|
140
|
+
"""Return the current token balance for the API key owner."""
|
|
141
|
+
with httpx.Client() as client:
|
|
142
|
+
r = client.get(
|
|
143
|
+
f"{self._base_url}/v1/balance",
|
|
144
|
+
headers=self._headers,
|
|
145
|
+
)
|
|
146
|
+
if not r.is_success:
|
|
147
|
+
_raise_for_error(r)
|
|
148
|
+
return BalanceResult(**r.json())
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AsyncAgentGenClient:
|
|
152
|
+
"""Async AgentGen API client (use with ``async``/``await``).
|
|
153
|
+
|
|
154
|
+
Example::
|
|
155
|
+
|
|
156
|
+
from agentgen import AsyncAgentGenClient, GenerateImageOptions
|
|
157
|
+
|
|
158
|
+
async def main():
|
|
159
|
+
client = AsyncAgentGenClient(api_key="agk_...")
|
|
160
|
+
result = await client.generate_image(GenerateImageOptions(html="<h1>Hello</h1>"))
|
|
161
|
+
print(result.url)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL) -> None:
|
|
165
|
+
self._base_url = base_url.rstrip("/")
|
|
166
|
+
self._headers = {"X-API-Key": api_key}
|
|
167
|
+
|
|
168
|
+
async def generate_image(
|
|
169
|
+
self, options: GenerateImageOptions
|
|
170
|
+
) -> GenerateImageResult:
|
|
171
|
+
"""Render HTML to an image (PNG/JPEG/WebP). Costs **1 token**."""
|
|
172
|
+
async with httpx.AsyncClient() as client:
|
|
173
|
+
r = await client.post(
|
|
174
|
+
f"{self._base_url}/v1/generate/image",
|
|
175
|
+
json=_to_dict(options),
|
|
176
|
+
headers=self._headers,
|
|
177
|
+
)
|
|
178
|
+
if not r.is_success:
|
|
179
|
+
_raise_for_error(r)
|
|
180
|
+
return GenerateImageResult(**r.json())
|
|
181
|
+
|
|
182
|
+
async def generate_pdf(
|
|
183
|
+
self, options: Union[PdfPage, list[PdfPage]]
|
|
184
|
+
) -> GeneratePdfResult:
|
|
185
|
+
"""Render HTML to a PDF. Costs **2 tokens per page**.
|
|
186
|
+
|
|
187
|
+
Pass a single :class:`PdfPage` or a list for multi-page output.
|
|
188
|
+
"""
|
|
189
|
+
body = (
|
|
190
|
+
{"pages": [_to_dict(p) for p in options]}
|
|
191
|
+
if isinstance(options, list)
|
|
192
|
+
else _to_dict(options)
|
|
193
|
+
)
|
|
194
|
+
async with httpx.AsyncClient() as client:
|
|
195
|
+
r = await client.post(
|
|
196
|
+
f"{self._base_url}/v1/generate/pdf",
|
|
197
|
+
json=body,
|
|
198
|
+
headers=self._headers,
|
|
199
|
+
)
|
|
200
|
+
if not r.is_success:
|
|
201
|
+
_raise_for_error(r)
|
|
202
|
+
return GeneratePdfResult(**r.json())
|
|
203
|
+
|
|
204
|
+
async def upload_temp(
|
|
205
|
+
self,
|
|
206
|
+
file: Union[str, Path, bytes],
|
|
207
|
+
filename: Optional[str] = None,
|
|
208
|
+
) -> UploadTempResult:
|
|
209
|
+
"""Upload a file for use inside HTML templates.
|
|
210
|
+
|
|
211
|
+
**Free** (no tokens). Auto-deleted after **24 hours**.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
file: A file path (str/Path) or raw bytes.
|
|
215
|
+
filename: Optional filename hint for the upload.
|
|
216
|
+
"""
|
|
217
|
+
if isinstance(file, (str, Path)):
|
|
218
|
+
path = Path(file)
|
|
219
|
+
filename = filename or path.name
|
|
220
|
+
loop = asyncio.get_event_loop()
|
|
221
|
+
content: bytes = await loop.run_in_executor(None, path.read_bytes)
|
|
222
|
+
else:
|
|
223
|
+
content = file
|
|
224
|
+
|
|
225
|
+
async with httpx.AsyncClient() as client:
|
|
226
|
+
r = await client.post(
|
|
227
|
+
f"{self._base_url}/v1/upload/temp",
|
|
228
|
+
files={"file": (filename or "upload", content)},
|
|
229
|
+
headers=self._headers,
|
|
230
|
+
)
|
|
231
|
+
if not r.is_success:
|
|
232
|
+
_raise_for_error(r)
|
|
233
|
+
return UploadTempResult(**r.json())
|
|
234
|
+
|
|
235
|
+
async def get_balance(self) -> BalanceResult:
|
|
236
|
+
"""Return the current token balance for the API key owner."""
|
|
237
|
+
async with httpx.AsyncClient() as client:
|
|
238
|
+
r = await client.get(
|
|
239
|
+
f"{self._base_url}/v1/balance",
|
|
240
|
+
headers=self._headers,
|
|
241
|
+
)
|
|
242
|
+
if not r.is_success:
|
|
243
|
+
_raise_for_error(r)
|
|
244
|
+
return BalanceResult(**r.json())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AgentGenError(Exception):
|
|
7
|
+
"""Raised when the AgentGen API returns an error response."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
status: int,
|
|
13
|
+
detail: Optional[str] = None,
|
|
14
|
+
details: Optional[dict[str, Any]] = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(message)
|
|
17
|
+
self.status = status
|
|
18
|
+
self.detail = detail
|
|
19
|
+
self.details = details
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return f"{self.__class__.__name__}(status={self.status}, message={str(self)!r})"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InsufficientTokensError(AgentGenError):
|
|
26
|
+
"""Raised when the account has too few tokens for the requested operation."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
message: str,
|
|
31
|
+
balance: int,
|
|
32
|
+
required: int,
|
|
33
|
+
buy_more_url: str,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(message, 402)
|
|
36
|
+
self.balance = balance
|
|
37
|
+
self.required = required
|
|
38
|
+
self.buy_more_url = buy_more_url
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import dataclasses
|
|
4
|
+
from typing import Literal, Optional
|
|
5
|
+
|
|
6
|
+
ImageFormat = Literal["png", "jpeg", "webp"]
|
|
7
|
+
PdfFormat = Literal["A4", "Letter", "A3", "Legal"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass
|
|
11
|
+
class GenerateImageOptions:
|
|
12
|
+
"""Options for HTML → image generation."""
|
|
13
|
+
|
|
14
|
+
html: str
|
|
15
|
+
"""HTML content to render (max 500 KB)."""
|
|
16
|
+
width: Optional[int] = None
|
|
17
|
+
"""Viewport width in px (1–5000, default 1200)."""
|
|
18
|
+
height: Optional[int] = None
|
|
19
|
+
"""Viewport height in px (1–5000, default 630)."""
|
|
20
|
+
format: Optional[ImageFormat] = None
|
|
21
|
+
"""Output format: "png" | "jpeg" | "webp" (default "png")."""
|
|
22
|
+
device_scale_factor: Optional[float] = None
|
|
23
|
+
"""Device pixel ratio (1–3, default 2)."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass
|
|
27
|
+
class GenerateImageResult:
|
|
28
|
+
url: str
|
|
29
|
+
width: int
|
|
30
|
+
height: int
|
|
31
|
+
format: ImageFormat
|
|
32
|
+
tokens_used: int
|
|
33
|
+
request_id: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclasses.dataclass
|
|
37
|
+
class PdfMargin:
|
|
38
|
+
top: Optional[str] = None
|
|
39
|
+
bottom: Optional[str] = None
|
|
40
|
+
left: Optional[str] = None
|
|
41
|
+
right: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclasses.dataclass
|
|
45
|
+
class PdfPage:
|
|
46
|
+
"""A single PDF page."""
|
|
47
|
+
|
|
48
|
+
html: str
|
|
49
|
+
"""HTML content to render (max 500 KB)."""
|
|
50
|
+
format: Optional[PdfFormat] = None
|
|
51
|
+
"""Paper size: "A4" | "Letter" | "A3" | "Legal" (default "A4")."""
|
|
52
|
+
width: Optional[str] = None
|
|
53
|
+
"""Custom page width, e.g. "8.5in" (overrides format)."""
|
|
54
|
+
height: Optional[str] = None
|
|
55
|
+
"""Custom page height, e.g. "11in"."""
|
|
56
|
+
landscape: Optional[bool] = None
|
|
57
|
+
"""Landscape orientation (default False)."""
|
|
58
|
+
margin: Optional[PdfMargin] = None
|
|
59
|
+
"""Page margins."""
|
|
60
|
+
print_background: Optional[bool] = None
|
|
61
|
+
"""Print CSS backgrounds (default True)."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass
|
|
65
|
+
class GeneratePdfResult:
|
|
66
|
+
url: str
|
|
67
|
+
pages: int
|
|
68
|
+
tokens_used: int
|
|
69
|
+
request_id: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclasses.dataclass
|
|
73
|
+
class UploadTempResult:
|
|
74
|
+
url: str
|
|
75
|
+
key: str
|
|
76
|
+
size: int
|
|
77
|
+
expires_in: int
|
|
78
|
+
expires_at: str
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclasses.dataclass
|
|
82
|
+
class BalanceResult:
|
|
83
|
+
tokens: int
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentgen"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python client for the AgentGen API — HTML to PDF and Image"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
keywords = ["agentgen", "html-to-pdf", "html-to-image", "pdf", "screenshot"]
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.25.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.24"]
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["agentgen"]
|