codeforge-dev 1.4.0
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.
- package/.devcontainer/.env +22 -0
- package/.devcontainer/CHANGELOG.md +197 -0
- package/.devcontainer/CLAUDE.md +117 -0
- package/.devcontainer/README.md +222 -0
- package/.devcontainer/config/main-system-prompt.md +502 -0
- package/.devcontainer/config/settings.json +47 -0
- package/.devcontainer/devcontainer.json +94 -0
- package/.devcontainer/features/README.md +113 -0
- package/.devcontainer/features/agent-browser/README.md +65 -0
- package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
- package/.devcontainer/features/agent-browser/install.sh +79 -0
- package/.devcontainer/features/ast-grep/README.md +24 -0
- package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
- package/.devcontainer/features/ast-grep/install.sh +51 -0
- package/.devcontainer/features/ccstatusline/README.md +296 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
- package/.devcontainer/features/ccstatusline/install.sh +290 -0
- package/.devcontainer/features/ccusage/README.md +205 -0
- package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccusage/install.sh +132 -0
- package/.devcontainer/features/claude-code/README.md +498 -0
- package/.devcontainer/features/claude-code/config/settings.json +36 -0
- package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
- package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
- package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
- package/.devcontainer/features/claude-code/install.sh +466 -0
- package/.devcontainer/features/claude-monitor/README.md +74 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
- package/.devcontainer/features/claude-monitor/install.sh +99 -0
- package/.devcontainer/features/lsp-servers/README.md +85 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
- package/.devcontainer/features/lsp-servers/install.sh +116 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
- package/.devcontainer/features/mcp-qdrant/README.md +474 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
- package/.devcontainer/features/mcp-reasoner/README.md +177 -0
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
- package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
- package/.devcontainer/features/notify-hook/README.md +86 -0
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
- package/.devcontainer/features/notify-hook/install.sh +38 -0
- package/.devcontainer/features/splitrail/README.md +140 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
- package/.devcontainer/features/splitrail/install.sh +129 -0
- package/.devcontainer/features/tree-sitter/README.md +138 -0
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
- package/.devcontainer/features/tree-sitter/install.sh +173 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
- package/.devcontainer/scripts/setup-aliases.sh +80 -0
- package/.devcontainer/scripts/setup-config.sh +28 -0
- package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
- package/.devcontainer/scripts/setup-plugins.sh +80 -0
- package/.devcontainer/scripts/setup.sh +58 -0
- package/LICENSE.txt +674 -0
- package/README.md +267 -0
- package/package.json +44 -0
- package/setup.js +83 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Server-Sent Events and Streaming -- Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. SSE Protocol Fundamentals
|
|
4
|
+
|
|
5
|
+
Server-Sent Events use a persistent HTTP connection with `Content-Type: text/event-stream`. The server writes UTF-8 text frames separated by double newlines. Each frame contains one or more fields:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
event: message
|
|
9
|
+
id: 42
|
|
10
|
+
retry: 5000
|
|
11
|
+
data: {"text": "hello"}
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Field | Purpose | Default |
|
|
16
|
+
|-------|---------|---------|
|
|
17
|
+
| `data` | Event payload (required) | -- |
|
|
18
|
+
| `event` | Event type name | `"message"` |
|
|
19
|
+
| `id` | Last-event ID for reconnection | none |
|
|
20
|
+
| `retry` | Client reconnection interval (ms) | browser default (~3s) |
|
|
21
|
+
|
|
22
|
+
Multiple `data` lines in one frame are concatenated with newlines. A line starting with `:` is a comment -- browsers ignore it, but it serves as a keep-alive heartbeat.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. sse-starlette API
|
|
27
|
+
|
|
28
|
+
`sse-starlette` wraps an async iterable into a compliant `EventSourceResponse`. Install with `pip install sse-starlette`.
|
|
29
|
+
|
|
30
|
+
### Basic Configuration
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from sse_starlette.sse import EventSourceResponse
|
|
34
|
+
|
|
35
|
+
@app.get("/events")
|
|
36
|
+
async def stream():
|
|
37
|
+
return EventSourceResponse(
|
|
38
|
+
content=event_generator(),
|
|
39
|
+
media_type="text/event-stream",
|
|
40
|
+
ping=30, # heartbeat interval in seconds
|
|
41
|
+
ping_message_factory=lambda: {"comment": "keep-alive"},
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Yielding Events
|
|
46
|
+
|
|
47
|
+
The generator can yield several formats:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
async def event_generator():
|
|
51
|
+
# Dict with SSE fields
|
|
52
|
+
yield {"event": "status", "data": "connected", "id": "1"}
|
|
53
|
+
|
|
54
|
+
# Plain string -- becomes a data-only event
|
|
55
|
+
yield "simple message"
|
|
56
|
+
|
|
57
|
+
# Dict with just data
|
|
58
|
+
yield {"data": '{"count": 42}'}
|
|
59
|
+
|
|
60
|
+
# ServerSentEvent object for full control
|
|
61
|
+
from sse_starlette.sse import ServerSentEvent
|
|
62
|
+
yield ServerSentEvent(data="precise", event="custom", id="2", retry=10000)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Reconnection with Last-Event-ID
|
|
68
|
+
|
|
69
|
+
Browsers automatically reconnect when an SSE connection drops. They send the last received `id` in the `Last-Event-ID` header. Use this to resume the stream without replaying events the client already received:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from fastapi import Request
|
|
73
|
+
|
|
74
|
+
async def resumable_stream(request: Request):
|
|
75
|
+
last_id = request.headers.get("last-event-id")
|
|
76
|
+
cursor = int(last_id) + 1 if last_id else 0
|
|
77
|
+
|
|
78
|
+
async for event in fetch_events_from(cursor):
|
|
79
|
+
yield {
|
|
80
|
+
"event": "update",
|
|
81
|
+
"data": event.to_json(),
|
|
82
|
+
"id": str(event.sequence_id),
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Designing for Resumability
|
|
87
|
+
|
|
88
|
+
- Assign monotonically increasing IDs (database sequence, timestamp, or counter).
|
|
89
|
+
- Store events in a bounded buffer or persistent log so the server can replay from any recent ID.
|
|
90
|
+
- Set `retry` to control how quickly clients reconnect (default is ~3 seconds).
|
|
91
|
+
- Send an initial "snapshot" event on reconnection if the gap is too large to replay individual events.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 4. Backpressure with Bounded Queues
|
|
96
|
+
|
|
97
|
+
When an event source produces faster than clients consume, unbounded buffering causes memory growth. Use `asyncio.Queue` with a maximum size to apply backpressure:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import asyncio
|
|
101
|
+
|
|
102
|
+
class EventBus:
|
|
103
|
+
def __init__(self):
|
|
104
|
+
self.subscribers: list[asyncio.Queue] = []
|
|
105
|
+
|
|
106
|
+
def subscribe(self, maxsize: int = 100) -> asyncio.Queue:
|
|
107
|
+
queue = asyncio.Queue(maxsize=maxsize)
|
|
108
|
+
self.subscribers.append(queue)
|
|
109
|
+
return queue
|
|
110
|
+
|
|
111
|
+
def unsubscribe(self, queue: asyncio.Queue):
|
|
112
|
+
self.subscribers.remove(queue)
|
|
113
|
+
|
|
114
|
+
async def publish(self, event: dict):
|
|
115
|
+
for queue in self.subscribers:
|
|
116
|
+
try:
|
|
117
|
+
queue.put_nowait(event)
|
|
118
|
+
except asyncio.QueueFull:
|
|
119
|
+
# Drop oldest event to make room
|
|
120
|
+
try:
|
|
121
|
+
queue.get_nowait()
|
|
122
|
+
except asyncio.QueueEmpty:
|
|
123
|
+
pass
|
|
124
|
+
queue.put_nowait(event)
|
|
125
|
+
|
|
126
|
+
bus = EventBus()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
async def subscriber_generator(request: Request):
|
|
131
|
+
queue = bus.subscribe(maxsize=100)
|
|
132
|
+
try:
|
|
133
|
+
while True:
|
|
134
|
+
if await request.is_disconnected():
|
|
135
|
+
break
|
|
136
|
+
try:
|
|
137
|
+
event = await asyncio.wait_for(queue.get(), timeout=30)
|
|
138
|
+
yield {"data": json.dumps(event)}
|
|
139
|
+
except asyncio.TimeoutError:
|
|
140
|
+
yield {"comment": "keep-alive"}
|
|
141
|
+
finally:
|
|
142
|
+
bus.unsubscribe(queue)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The drop-oldest strategy keeps the client close to real-time. Alternative strategies: block the producer (`await queue.put()`), drop the newest event, or disconnect slow clients.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 5. Heartbeats and Keep-Alive
|
|
150
|
+
|
|
151
|
+
Proxies, load balancers, and CDNs may close idle connections. Send periodic heartbeat comments to keep the connection alive:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
async def heartbeat_generator():
|
|
155
|
+
event_iter = aiter(fetch_events())
|
|
156
|
+
while True:
|
|
157
|
+
try:
|
|
158
|
+
event = await asyncio.wait_for(anext(event_iter), timeout=15)
|
|
159
|
+
yield {"data": event.json(), "id": str(event.id)}
|
|
160
|
+
except asyncio.TimeoutError:
|
|
161
|
+
yield {"comment": "heartbeat"}
|
|
162
|
+
except StopAsyncIteration:
|
|
163
|
+
break
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`sse-starlette` provides built-in ping support via the `ping` parameter (interval in seconds). Use the built-in ping for simple cases; implement custom heartbeats when the heartbeat needs to carry data (e.g., server timestamp, queue depth).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 6. Disconnect Detection
|
|
171
|
+
|
|
172
|
+
Detect client disconnection to stop generating events and release resources:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from fastapi import Request
|
|
176
|
+
|
|
177
|
+
async def safe_generator(request: Request):
|
|
178
|
+
try:
|
|
179
|
+
async for chunk in data_source():
|
|
180
|
+
if await request.is_disconnected():
|
|
181
|
+
break
|
|
182
|
+
yield {"data": chunk}
|
|
183
|
+
finally:
|
|
184
|
+
await cleanup_resources()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`request.is_disconnected()` is an async check -- call it between yields, not inside tight loops. For generators driven by a queue, the `finally` block handles cleanup when the client disconnects and `EventSourceResponse` cancels the generator.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 7. LLM Streaming with Tool Calls
|
|
192
|
+
|
|
193
|
+
Stream LLM responses that may include interleaved text and tool-use events. Structure events by type so the client can render text incrementally while handling tool calls separately:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
import json
|
|
197
|
+
|
|
198
|
+
async def stream_with_tools(messages: list[dict]):
|
|
199
|
+
response = await client.messages.create(
|
|
200
|
+
model="claude-sonnet-4-20250514",
|
|
201
|
+
messages=messages,
|
|
202
|
+
tools=tool_definitions,
|
|
203
|
+
stream=True,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
async for event in response:
|
|
207
|
+
if event.type == "content_block_start":
|
|
208
|
+
if event.content_block.type == "text":
|
|
209
|
+
yield {"event": "text_start", "data": ""}
|
|
210
|
+
elif event.content_block.type == "tool_use":
|
|
211
|
+
yield {
|
|
212
|
+
"event": "tool_start",
|
|
213
|
+
"data": json.dumps({
|
|
214
|
+
"id": event.content_block.id,
|
|
215
|
+
"name": event.content_block.name,
|
|
216
|
+
}),
|
|
217
|
+
}
|
|
218
|
+
elif event.type == "content_block_delta":
|
|
219
|
+
if event.delta.type == "text_delta":
|
|
220
|
+
yield {"event": "text_delta", "data": event.delta.text}
|
|
221
|
+
elif event.delta.type == "input_json_delta":
|
|
222
|
+
yield {"event": "tool_delta", "data": event.delta.partial_json}
|
|
223
|
+
elif event.type == "message_stop":
|
|
224
|
+
yield {"event": "done", "data": ""}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Client-Side Handling (JavaScript)
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
const source = new EventSource("/chat");
|
|
231
|
+
|
|
232
|
+
source.addEventListener("text_delta", (e) => {
|
|
233
|
+
appendToOutput(e.data);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
source.addEventListener("tool_start", (e) => {
|
|
237
|
+
const { id, name } = JSON.parse(e.data);
|
|
238
|
+
startToolIndicator(id, name);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
source.addEventListener("done", () => {
|
|
242
|
+
source.close();
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 8. Testing SSE Endpoints
|
|
249
|
+
|
|
250
|
+
### Unit Testing with httpx
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
import pytest
|
|
254
|
+
from httpx import AsyncClient, ASGITransport
|
|
255
|
+
|
|
256
|
+
@pytest.mark.anyio
|
|
257
|
+
async def test_sse_stream():
|
|
258
|
+
transport = ASGITransport(app=app)
|
|
259
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
260
|
+
async with client.stream("GET", "/events") as response:
|
|
261
|
+
assert response.headers["content-type"] == "text/event-stream"
|
|
262
|
+
events = []
|
|
263
|
+
async for line in response.aiter_lines():
|
|
264
|
+
if line.startswith("data:"):
|
|
265
|
+
events.append(line[5:].strip())
|
|
266
|
+
if len(events) >= 3:
|
|
267
|
+
break
|
|
268
|
+
assert len(events) == 3
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Parsing SSE Lines
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
def parse_sse_events(raw_lines: list[str]) -> list[dict]:
|
|
275
|
+
events = []
|
|
276
|
+
current = {}
|
|
277
|
+
for line in raw_lines:
|
|
278
|
+
if line == "":
|
|
279
|
+
if current:
|
|
280
|
+
events.append(current)
|
|
281
|
+
current = {}
|
|
282
|
+
elif line.startswith("event:"):
|
|
283
|
+
current["event"] = line[6:].strip()
|
|
284
|
+
elif line.startswith("data:"):
|
|
285
|
+
data = line[5:].strip()
|
|
286
|
+
current["data"] = current.get("data", "") + data
|
|
287
|
+
elif line.startswith("id:"):
|
|
288
|
+
current["id"] = line[3:].strip()
|
|
289
|
+
if current:
|
|
290
|
+
events.append(current)
|
|
291
|
+
return events
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Testing Reconnection
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
@pytest.mark.anyio
|
|
298
|
+
async def test_sse_reconnection():
|
|
299
|
+
transport = ASGITransport(app=app)
|
|
300
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
301
|
+
# First connection -- collect some events
|
|
302
|
+
headers = {}
|
|
303
|
+
async with client.stream("GET", "/events", headers=headers) as resp:
|
|
304
|
+
last_id = None
|
|
305
|
+
async for line in resp.aiter_lines():
|
|
306
|
+
if line.startswith("id:"):
|
|
307
|
+
last_id = line[3:].strip()
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
# Reconnect with Last-Event-ID
|
|
311
|
+
headers = {"Last-Event-ID": last_id}
|
|
312
|
+
async with client.stream("GET", "/events", headers=headers) as resp:
|
|
313
|
+
async for line in resp.aiter_lines():
|
|
314
|
+
if line.startswith("data:"):
|
|
315
|
+
event = json.loads(line[5:].strip())
|
|
316
|
+
assert event["sequence"] > int(last_id)
|
|
317
|
+
break
|
|
318
|
+
```
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pydantic-ai
|
|
3
|
+
description: >-
|
|
4
|
+
This skill should be used when the user asks to "build a PydanticAI agent",
|
|
5
|
+
"create an AI agent with PydanticAI", "add tools to a PydanticAI agent",
|
|
6
|
+
"stream responses with PydanticAI", "test a PydanticAI agent",
|
|
7
|
+
"connect PydanticAI to a Svelte frontend", "use RunContext for dependency injection",
|
|
8
|
+
"configure model fallbacks in PydanticAI", or discusses PydanticAI agents,
|
|
9
|
+
tool decorators, structured output, agent testing, or VercelAIAdapter.
|
|
10
|
+
version: 0.1.0
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# PydanticAI Agent Development
|
|
14
|
+
|
|
15
|
+
## Mental Model
|
|
16
|
+
|
|
17
|
+
PydanticAI is a Python agent framework where **the `Agent` class is the single orchestration primitive**. An agent binds a model, a set of tools, and a typed output contract into one object. Tools are plain Python functions decorated onto the agent; the framework generates JSON schemas from their type annotations and docstrings, sends them to the model, and validates the model's tool-call arguments with Pydantic before invoking the function. This means type annotations are the contract -- there is no separate schema definition layer.
|
|
18
|
+
|
|
19
|
+
Dependency injection flows through `RunContext[DepsT]`. The caller passes a `deps` object at run time; every tool and validator receives it via `RunContext.deps`. This keeps tools pure (no global state, no module-level singletons) and makes testing straightforward -- swap the deps object, swap the behavior.
|
|
20
|
+
|
|
21
|
+
Streaming and structured output are first-class. An agent can stream raw text deltas, partially-validated Pydantic models, or both. The `VercelAIAdapter` bridges PydanticAI's streaming protocol to the Vercel AI Data Stream Protocol, enabling direct consumption by `@ai-sdk/svelte` on the frontend without a custom SSE layer.
|
|
22
|
+
|
|
23
|
+
Assume `pydantic-ai>=0.1` with Pydantic v2 for all new code.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Agent Definition
|
|
28
|
+
|
|
29
|
+
The `Agent` class is generic over dependencies and output type: `Agent[DepsT, OutputT]`. The constructor wires together model, tools, output contract, and defaults:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from pydantic_ai import Agent
|
|
33
|
+
from pydantic import BaseModel
|
|
34
|
+
|
|
35
|
+
class CityInfo(BaseModel):
|
|
36
|
+
name: str
|
|
37
|
+
country: str
|
|
38
|
+
population: int
|
|
39
|
+
|
|
40
|
+
agent = Agent(
|
|
41
|
+
"anthropic:claude-sonnet-4-20250514",
|
|
42
|
+
output_type=CityInfo,
|
|
43
|
+
instructions="Extract structured city information from user queries.",
|
|
44
|
+
deps_type=type(None),
|
|
45
|
+
retries=2,
|
|
46
|
+
end_strategy="early",
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Key constructor parameters:
|
|
51
|
+
|
|
52
|
+
- **`model`**: String identifier like `"openai:gpt-5"` or `"anthropic:claude-sonnet-4-20250514"`, or a `Model` instance. Overridable per-run.
|
|
53
|
+
- **`output_type`**: Defaults to `str`. Accepts a Pydantic `BaseModel`, dataclass, `TypedDict`, list of types (union), `ToolOutput`, `NativeOutput`, or `PromptedOutput`.
|
|
54
|
+
- **`instructions`**: Static string or dynamic callable returning a string. Re-evaluated each run. Not included when `message_history` is provided.
|
|
55
|
+
- **`system_prompt`**: Static string(s) included in every request, preserved in conversation history.
|
|
56
|
+
- **`deps_type`**: The type of the dependency object passed at run time.
|
|
57
|
+
- **`retries`**: Default retry count for tools when the model sends invalid arguments (default 1).
|
|
58
|
+
- **`end_strategy`**: `"early"` stops on first valid output; `"exhaustive"` runs all pending tool calls first.
|
|
59
|
+
|
|
60
|
+
### Running an Agent
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Async (preferred)
|
|
64
|
+
result = await agent.run("Tell me about Paris", deps=my_deps)
|
|
65
|
+
print(result.output) # CityInfo(name='Paris', country='France', population=2161000)
|
|
66
|
+
print(result.usage()) # RunUsage(requests=1, input_tokens=..., output_tokens=...)
|
|
67
|
+
|
|
68
|
+
# Sync convenience
|
|
69
|
+
result = agent.run_sync("Tell me about Paris")
|
|
70
|
+
|
|
71
|
+
# With overrides
|
|
72
|
+
result = await agent.run(
|
|
73
|
+
"Tell me about Paris",
|
|
74
|
+
model="openai:gpt-5",
|
|
75
|
+
model_settings={"temperature": 0.0},
|
|
76
|
+
usage_limits=UsageLimits(request_limit=5),
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The `AgentRunResult` exposes `.output` (typed), `.usage()`, `.all_messages()`, and `.new_messages()` for conversation continuation.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Tools
|
|
85
|
+
|
|
86
|
+
Tools give the agent the ability to take actions and retrieve information. There are two decorator forms based on whether the tool needs access to dependencies:
|
|
87
|
+
|
|
88
|
+
### Context-Aware Tools
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from pydantic_ai import Agent, RunContext
|
|
92
|
+
|
|
93
|
+
agent = Agent("openai:gpt-5", deps_type=DatabasePool)
|
|
94
|
+
|
|
95
|
+
@agent.tool(retries=3)
|
|
96
|
+
async def lookup_user(ctx: RunContext[DatabasePool], user_id: int) -> str:
|
|
97
|
+
"""Find a user by their ID. Returns the user's name and email."""
|
|
98
|
+
row = await ctx.deps.fetchrow("SELECT name, email FROM users WHERE id = $1", user_id)
|
|
99
|
+
if not row:
|
|
100
|
+
raise ModelRetry("No user found with that ID, try a different one")
|
|
101
|
+
return f"{row['name']} ({row['email']})"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The first parameter **must** be `RunContext[DepsT]`. All subsequent parameters become the tool's JSON schema. The docstring becomes the tool description sent to the model.
|
|
105
|
+
|
|
106
|
+
### Plain Tools
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
@agent.tool_plain
|
|
110
|
+
def roll_dice(sides: int = 6) -> str:
|
|
111
|
+
"""Roll a die with the specified number of sides."""
|
|
112
|
+
return str(random.randint(1, sides))
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
No `RunContext` parameter. Use for stateless utilities that need no dependencies.
|
|
116
|
+
|
|
117
|
+
### Tool Decorator Options
|
|
118
|
+
|
|
119
|
+
Both `@agent.tool` and `@agent.tool_plain` accept:
|
|
120
|
+
|
|
121
|
+
- **`retries`**: Override the agent-level retry count for this tool.
|
|
122
|
+
- **`prepare`**: A `ToolsPrepareFunc` to dynamically include/exclude the tool or modify its schema per run.
|
|
123
|
+
- **`name`** / **`description`**: Override the function name or docstring.
|
|
124
|
+
|
|
125
|
+
### Error Handling with ModelRetry
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from pydantic_ai import ModelRetry
|
|
129
|
+
|
|
130
|
+
@agent.tool
|
|
131
|
+
async def search(ctx: RunContext[SearchClient], query: str) -> str:
|
|
132
|
+
"""Search the knowledge base."""
|
|
133
|
+
results = await ctx.deps.search(query)
|
|
134
|
+
if not results:
|
|
135
|
+
raise ModelRetry("No results found. Try broader or different search terms.")
|
|
136
|
+
return "\n".join(r.summary for r in results[:5])
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Raising `ModelRetry(message)` sends the error back to the model as a tool-call error, letting it self-correct its arguments.
|
|
140
|
+
|
|
141
|
+
> **Deep dive:** See `references/agents-and-tools.md` for tool registration via constructor, `prepare` functions for dynamic tool filtering, result types and validators, and advanced output type patterns (unions, `ToolOutput`, `NativeOutput`).
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Dependency Injection with RunContext
|
|
146
|
+
|
|
147
|
+
The dependency system is explicit: define a deps type, pass it at run time, access it in tools and validators through `RunContext.deps`:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from dataclasses import dataclass
|
|
151
|
+
from pydantic_ai import Agent, RunContext
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class AppDeps:
|
|
155
|
+
db: DatabasePool
|
|
156
|
+
cache: RedisClient
|
|
157
|
+
api_key: str
|
|
158
|
+
|
|
159
|
+
agent = Agent("anthropic:claude-sonnet-4-20250514", deps_type=AppDeps)
|
|
160
|
+
|
|
161
|
+
@agent.tool
|
|
162
|
+
async def fetch_order(ctx: RunContext[AppDeps], order_id: str) -> str:
|
|
163
|
+
"""Retrieve order details by ID."""
|
|
164
|
+
cached = await ctx.deps.cache.get(f"order:{order_id}")
|
|
165
|
+
if cached:
|
|
166
|
+
return cached
|
|
167
|
+
row = await ctx.deps.db.fetchrow("SELECT * FROM orders WHERE id = $1", order_id)
|
|
168
|
+
return json.dumps(dict(row))
|
|
169
|
+
|
|
170
|
+
# At call site
|
|
171
|
+
deps = AppDeps(db=pool, cache=redis, api_key=os.environ["API_KEY"])
|
|
172
|
+
result = await agent.run("What's the status of order ORD-123?", deps=deps)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Dependencies can be any object -- a dataclass, a named tuple, a plain class, or even a single value. The `deps_type` annotation enables type checking throughout the tool chain.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Model Configuration
|
|
180
|
+
|
|
181
|
+
### Model Selection
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# By string identifier (most common)
|
|
185
|
+
agent = Agent("anthropic:claude-sonnet-4-20250514")
|
|
186
|
+
agent = Agent("openai:gpt-5")
|
|
187
|
+
agent = Agent("google-gla:gemini-2.0-flash")
|
|
188
|
+
|
|
189
|
+
# Override at run time
|
|
190
|
+
result = await agent.run("prompt", model="openai:gpt-5")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### FallbackModel
|
|
194
|
+
|
|
195
|
+
Sequence multiple models; the framework switches to the next on API errors:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from pydantic_ai.models.fallback import FallbackModel
|
|
199
|
+
|
|
200
|
+
fallback = FallbackModel("anthropic:claude-sonnet-4-20250514", "openai:gpt-5")
|
|
201
|
+
agent = Agent(fallback)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
On failure of all models, raises `FallbackExceptionGroup`. By default only `ModelHTTPError` triggers fallback; validation errors use the retry mechanism instead.
|
|
205
|
+
|
|
206
|
+
### ModelSettings
|
|
207
|
+
|
|
208
|
+
Control generation parameters per-agent or per-run:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from pydantic_ai import ModelSettings
|
|
212
|
+
|
|
213
|
+
agent = Agent(
|
|
214
|
+
"openai:gpt-5",
|
|
215
|
+
model_settings=ModelSettings(temperature=0.0, max_tokens=1000),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Override per-run
|
|
219
|
+
result = await agent.run("prompt", model_settings={"temperature": 0.7})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Available settings: `temperature`, `max_tokens`, `top_p`, `timeout`, `seed`, `parallel_tool_calls`, `presence_penalty`, `frequency_penalty`, `stop_sequences`.
|
|
223
|
+
|
|
224
|
+
> **Deep dive:** See `references/models-and-streaming.md` for streaming patterns (text, structured, deltas), `VercelAIAdapter` for Svelte integration, `TestModel` and `FunctionModel` for testing, and usage tracking with `UsageLimits`.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Streaming
|
|
229
|
+
|
|
230
|
+
### Text Streaming
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
async with agent.run_stream("Tell me a story") as result:
|
|
234
|
+
async for delta in result.stream_text(delta=True):
|
|
235
|
+
print(delta, end="", flush=True)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`stream_text()` yields cumulative text by default. Pass `delta=True` for incremental chunks. The `debounce_by` parameter (default `0.1s`) batches rapid chunks to reduce overhead.
|
|
239
|
+
|
|
240
|
+
### Structured Output Streaming
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
agent = Agent("openai:gpt-5", output_type=Story)
|
|
244
|
+
|
|
245
|
+
async with agent.run_stream("Write a story") as result:
|
|
246
|
+
async for partial in result.stream_output(debounce_by=0.1):
|
|
247
|
+
print(partial) # Partially-validated Story objects
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Each yielded object is a `Story` instance with whatever fields have been parsed so far. Validators with side effects should check `ctx.partial_output` to avoid running during intermediate validations.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Testing
|
|
255
|
+
|
|
256
|
+
PydanticAI provides two test models that avoid real API calls:
|
|
257
|
+
|
|
258
|
+
### TestModel
|
|
259
|
+
|
|
260
|
+
Calls all registered tools and generates data matching output schemas procedurally:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from pydantic_ai.models.test import TestModel
|
|
264
|
+
|
|
265
|
+
with agent.override(model=TestModel()):
|
|
266
|
+
result = agent.run_sync("test prompt")
|
|
267
|
+
assert isinstance(result.output, CityInfo)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### FunctionModel
|
|
271
|
+
|
|
272
|
+
Full control over model responses via a callback:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from pydantic_ai.models.function import FunctionModel, AgentInfo
|
|
276
|
+
from pydantic_ai.messages import ModelResponse, TextPart
|
|
277
|
+
|
|
278
|
+
def mock_model(messages, info: AgentInfo) -> ModelResponse:
|
|
279
|
+
return ModelResponse(parts=[TextPart("mocked response")])
|
|
280
|
+
|
|
281
|
+
with agent.override(model=FunctionModel(mock_model)):
|
|
282
|
+
result = agent.run_sync("test")
|
|
283
|
+
assert result.output == "mocked response"
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Safety Guard
|
|
287
|
+
|
|
288
|
+
Prevent accidental real API calls in test suites:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from pydantic_ai import models
|
|
292
|
+
models.ALLOW_MODEL_REQUESTS = False # Raises error if a non-test model is used
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## VercelAIAdapter (Svelte Integration)
|
|
298
|
+
|
|
299
|
+
Bridge a PydanticAI agent to a Vercel AI SDK frontend with a single endpoint:
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from fastapi import FastAPI, Request, Response
|
|
303
|
+
from pydantic_ai import Agent
|
|
304
|
+
from pydantic_ai.ui.vercel_ai import VercelAIAdapter
|
|
305
|
+
|
|
306
|
+
app = FastAPI()
|
|
307
|
+
agent = Agent("anthropic:claude-sonnet-4-20250514", instructions="Be helpful.")
|
|
308
|
+
|
|
309
|
+
@app.post("/chat")
|
|
310
|
+
async def chat(request: Request) -> Response:
|
|
311
|
+
return await VercelAIAdapter.dispatch_request(request, agent=agent)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
On the Svelte side, consume with `@ai-sdk/svelte`:
|
|
315
|
+
|
|
316
|
+
```svelte
|
|
317
|
+
<script>
|
|
318
|
+
import { useChat } from '@ai-sdk/svelte';
|
|
319
|
+
const { messages, input, handleSubmit } = useChat({ api: '/chat' });
|
|
320
|
+
</script>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`VercelAIAdapter` translates PydanticAI's event format to the Vercel AI Data Stream Protocol automatically -- no custom SSE parsing needed.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Ambiguity Policy
|
|
328
|
+
|
|
329
|
+
These defaults apply when the user does not specify a preference. State the assumption when making a choice so the user can override:
|
|
330
|
+
|
|
331
|
+
- **Model selection:** Default to `"anthropic:claude-sonnet-4-20250514"` for agents requiring tool use. Use `"openai:gpt-5"` only when the user specifies OpenAI.
|
|
332
|
+
- **Output type:** Default to `str` for conversational agents. Use a Pydantic model when the caller needs structured data.
|
|
333
|
+
- **Dependency injection:** Default to a dataclass for `deps_type` when multiple resources are needed. Use a single value when only one resource is injected.
|
|
334
|
+
- **Streaming:** Default to `run_stream()` when output is displayed to a user. Use `run()` for background/batch processing.
|
|
335
|
+
- **Testing:** Default to `TestModel` for unit tests. Use `FunctionModel` when specific response sequences are needed.
|
|
336
|
+
- **Frontend bridge:** Default to `VercelAIAdapter` when the frontend uses `@ai-sdk/svelte` or any Vercel AI SDK client.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Reference Files
|
|
341
|
+
|
|
342
|
+
| File | Contents |
|
|
343
|
+
|------|----------|
|
|
344
|
+
| `references/agents-and-tools.md` | Agent constructor details, tool registration via constructor, `prepare` functions for dynamic tool sets, result types and validators, output type patterns (unions, ToolOutput, NativeOutput, PromptedOutput), ModelRetry mechanics |
|
|
345
|
+
| `references/models-and-streaming.md` | Model configuration, FallbackModel, streaming (text, structured, deltas), StreamedRunResult API, VercelAIAdapter integration, TestModel and FunctionModel for testing, usage tracking with UsageLimits, capture_run_messages |
|