@genui/a3-create 0.1.36
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/README.md +123 -0
- package/dist/index.js +684 -0
- package/package.json +52 -0
- package/template/.cursor/rules/example-app.mdc +9 -0
- package/template/CLAUDE.md +121 -0
- package/template/README.md +20 -0
- package/template/_gitignore +36 -0
- package/template/app/ThemeProvider.tsx +17 -0
- package/template/app/agents/age.ts +25 -0
- package/template/app/agents/greeting.ts +30 -0
- package/template/app/agents/index.ts +57 -0
- package/template/app/agents/onboarding/index.ts +15 -0
- package/template/app/agents/onboarding/prompt.ts +59 -0
- package/template/app/agents/registry.ts +17 -0
- package/template/app/agents/state.ts +10 -0
- package/template/app/api/agui/route.ts +56 -0
- package/template/app/api/chat/route.ts +35 -0
- package/template/app/api/stream/route.ts +57 -0
- package/template/app/apple-icon-dark.png +0 -0
- package/template/app/apple-icon.png +0 -0
- package/template/app/components/atoms/AgentNode.tsx +56 -0
- package/template/app/components/atoms/AppLogo.tsx +44 -0
- package/template/app/components/atoms/ChatContainer.tsx +13 -0
- package/template/app/components/atoms/ChatHeader.tsx +49 -0
- package/template/app/components/atoms/MarkdownRenderer.tsx +134 -0
- package/template/app/components/atoms/MessageBubble.tsx +21 -0
- package/template/app/components/atoms/TransitionEdge.tsx +49 -0
- package/template/app/components/atoms/index.ts +7 -0
- package/template/app/components/molecules/ChatInput.tsx +94 -0
- package/template/app/components/molecules/ChatMessage.tsx +45 -0
- package/template/app/components/molecules/index.ts +2 -0
- package/template/app/components/organisms/AgentGraph.tsx +75 -0
- package/template/app/components/organisms/AguiChat.tsx +133 -0
- package/template/app/components/organisms/Chat.tsx +88 -0
- package/template/app/components/organisms/ChatMessageList.tsx +35 -0
- package/template/app/components/organisms/ExamplePageLayout.tsx +118 -0
- package/template/app/components/organisms/OnboardingChat.tsx +24 -0
- package/template/app/components/organisms/Sidebar.tsx +147 -0
- package/template/app/components/organisms/SidebarLayout.tsx +58 -0
- package/template/app/components/organisms/StateViewer.tsx +126 -0
- package/template/app/components/organisms/StreamChat.tsx +173 -0
- package/template/app/components/organisms/index.ts +10 -0
- package/template/app/constants/chat.ts +52 -0
- package/template/app/constants/paths.ts +1 -0
- package/template/app/constants/ui.ts +61 -0
- package/template/app/examples/agui/page.tsx +26 -0
- package/template/app/examples/chat/page.tsx +26 -0
- package/template/app/examples/page.tsx +106 -0
- package/template/app/examples/stream/page.tsx +26 -0
- package/template/app/favicon-dark.ico +0 -0
- package/template/app/favicon.ico +0 -0
- package/template/app/icon.svg +13 -0
- package/template/app/layout.tsx +36 -0
- package/template/app/lib/actions/restartSession.ts +10 -0
- package/template/app/lib/getAgentGraphData.ts +43 -0
- package/template/app/lib/getGraphLayout.ts +99 -0
- package/template/app/lib/hooks/useRestart.ts +33 -0
- package/template/app/lib/parseTransitionTargets.ts +140 -0
- package/template/app/lib/providers/anthropic.ts +12 -0
- package/template/app/lib/providers/bedrock.ts +12 -0
- package/template/app/lib/providers/openai.ts +10 -0
- package/template/app/onboarding/page.tsx +21 -0
- package/template/app/page.tsx +16 -0
- package/template/app/styled.d.ts +6 -0
- package/template/app/theme.ts +22 -0
- package/template/docs/A3-README.md +121 -0
- package/template/docs/API-REFERENCE.md +85 -0
- package/template/docs/ARCHITECTURE.md +84 -0
- package/template/docs/CORE-CONCEPTS.md +347 -0
- package/template/docs/CUSTOM_LOGGING.md +36 -0
- package/template/docs/CUSTOM_PROVIDERS.md +642 -0
- package/template/docs/CUSTOM_STORES.md +228 -0
- package/template/docs/PROVIDER-ANTHROPIC.md +45 -0
- package/template/docs/PROVIDER-BEDROCK.md +45 -0
- package/template/docs/PROVIDER-OPENAI.md +47 -0
- package/template/docs/PROVIDERS.md +124 -0
- package/template/docs/QUICK-START-EXAMPLES.md +197 -0
- package/template/docs/RESILIENCE.md +226 -0
- package/template/docs/TRANSITIONS.md +245 -0
- package/template/docs/WIDGETS.md +331 -0
- package/template/docs/contributing/LOGGING.md +104 -0
- package/template/docs/designs/a3-gtm-strategy.md +280 -0
- package/template/docs/designs/a3-platform-vision.md +276 -0
- package/template/next-env.d.ts +6 -0
- package/template/next.config.mjs +15 -0
- package/template/package.json +41 -0
- package/template/public/android-chrome-192x192.png +0 -0
- package/template/public/android-chrome-512x512.png +0 -0
- package/template/public/site.webmanifest +11 -0
- package/template/scripts/dev.mjs +29 -0
- package/template/tsconfig.json +47 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Creating a Custom Session Store
|
|
2
|
+
|
|
3
|
+
This guide walks you through implementing a custom `SessionStore` so A3 can persist sessions to any backend — DynamoDB, Redis, PostgreSQL, or anything else.
|
|
4
|
+
|
|
5
|
+
## When to Create a Custom Store
|
|
6
|
+
|
|
7
|
+
Create a custom store when you need:
|
|
8
|
+
|
|
9
|
+
1. **Production persistence** — sessions that survive process restarts (the built-in `MemorySessionStore` doesn't)
|
|
10
|
+
1. **Shared storage** — multiple server instances reading/writing the same sessions (e.g. behind a load balancer)
|
|
11
|
+
1. **TTL / expiration** — automatic cleanup of stale sessions
|
|
12
|
+
1. **Audit or compliance** — durable session records with timestamps
|
|
13
|
+
|
|
14
|
+
## The SessionStore Interface
|
|
15
|
+
|
|
16
|
+
Every store implements the `SessionStore` interface from `@genui/a3`:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { SessionData, BaseState, BaseChatContext } from '@genui/a3'
|
|
20
|
+
|
|
21
|
+
interface SessionStore<
|
|
22
|
+
TState extends BaseState = BaseState,
|
|
23
|
+
TContext extends BaseChatContext = BaseChatContext,
|
|
24
|
+
> {
|
|
25
|
+
/** Load session data, returns null if not found */
|
|
26
|
+
load(sessionId: string): Promise<SessionData<TState, TContext> | null>
|
|
27
|
+
|
|
28
|
+
/** Save session data */
|
|
29
|
+
save(sessionId: string, data: SessionData<TState, TContext>): Promise<void>
|
|
30
|
+
|
|
31
|
+
/** Delete a session (optional) */
|
|
32
|
+
delete?(sessionId: string): Promise<void>
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Method | Required | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `load(sessionId)` | Yes | Retrieve session data by ID. Return `null` if no session exists. |
|
|
39
|
+
| `save(sessionId, data)` | Yes | Persist the full `SessionData` object. Called after every turn. |
|
|
40
|
+
| `delete(sessionId)` | No | Remove a session. Useful for cleanup, logout, or TTL expiration. |
|
|
41
|
+
|
|
42
|
+
## SessionData Structure
|
|
43
|
+
|
|
44
|
+
`SessionData` is the object your store serializes and deserializes:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface SessionData<
|
|
48
|
+
TState extends BaseState = BaseState,
|
|
49
|
+
TContext extends BaseChatContext = BaseChatContext,
|
|
50
|
+
> {
|
|
51
|
+
sessionId: string
|
|
52
|
+
messages: Conversation // Array of Message objects (full chat history)
|
|
53
|
+
conversationHistory?: Conversation // Previous messages when re-authenticating
|
|
54
|
+
activeAgentId: AgentId | null // Currently active agent
|
|
55
|
+
state: TState // Shared state across all agents
|
|
56
|
+
chatContext: TContext // Context variables for the current session
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Field | Description |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `sessionId` | Unique identifier for the session |
|
|
63
|
+
| `messages` | Full conversation history (user and assistant messages) |
|
|
64
|
+
| `conversationHistory` | Optional backup of previous messages (used during re-authentication flows) |
|
|
65
|
+
| `activeAgentId` | The agent that will handle the next user message, or `null` |
|
|
66
|
+
| `state` | Shared typed state flowing across all agents — your `TState` extension of `BaseState` |
|
|
67
|
+
| `chatContext` | Session-level context — your `TContext` extension of `BaseChatContext` |
|
|
68
|
+
|
|
69
|
+
## Implementing a Custom Store
|
|
70
|
+
|
|
71
|
+
### Step 1: Implement `load`
|
|
72
|
+
|
|
73
|
+
Retrieve the session from your backend and deserialize it.
|
|
74
|
+
Return `null` if the session doesn't exist.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
async load(sessionId: string): Promise<SessionData<TState, TContext> | null> {
|
|
78
|
+
const raw = await this.client.get(sessionId)
|
|
79
|
+
return raw ? JSON.parse(raw) : null
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 2: Implement `save`
|
|
84
|
+
|
|
85
|
+
Serialize the `SessionData` and write it to your backend.
|
|
86
|
+
This is called after every turn, so it must handle both inserts and updates.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
async save(sessionId: string, data: SessionData<TState, TContext>): Promise<void> {
|
|
90
|
+
await this.client.set(sessionId, JSON.stringify(data))
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Step 3: Implement `delete` (optional)
|
|
95
|
+
|
|
96
|
+
Remove a session from your backend.
|
|
97
|
+
If your backend supports TTL natively, you may not need this.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
async delete(sessionId: string): Promise<void> {
|
|
101
|
+
await this.client.delete(sessionId)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Example: DynamoDB Store
|
|
106
|
+
|
|
107
|
+
A complete, copy-pasteable implementation using the AWS SDK v3:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { GetCommand, PutCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'
|
|
111
|
+
import type { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
|
|
112
|
+
import type { SessionStore, SessionData, BaseState, BaseChatContext } from '@genui/a3'
|
|
113
|
+
|
|
114
|
+
export class DynamoSessionStore<
|
|
115
|
+
TState extends BaseState = BaseState,
|
|
116
|
+
TContext extends BaseChatContext = BaseChatContext,
|
|
117
|
+
> implements SessionStore<TState, TContext> {
|
|
118
|
+
private tableName: string
|
|
119
|
+
private client: DynamoDBDocumentClient
|
|
120
|
+
|
|
121
|
+
constructor(client: DynamoDBDocumentClient, tableName: string) {
|
|
122
|
+
this.client = client
|
|
123
|
+
this.tableName = tableName
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async load(sessionId: string): Promise<SessionData<TState, TContext> | null> {
|
|
127
|
+
const result = await this.client.send(
|
|
128
|
+
new GetCommand({
|
|
129
|
+
TableName: this.tableName,
|
|
130
|
+
Key: { id: sessionId },
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if (!result.Item?.data) {
|
|
135
|
+
return null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return JSON.parse(result.Item.data as string) as SessionData<TState, TContext>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async save(sessionId: string, data: SessionData<TState, TContext>): Promise<void> {
|
|
142
|
+
await this.client.send(
|
|
143
|
+
new PutCommand({
|
|
144
|
+
TableName: this.tableName,
|
|
145
|
+
Item: {
|
|
146
|
+
id: sessionId,
|
|
147
|
+
data: JSON.stringify(data),
|
|
148
|
+
updatedAt: new Date().toISOString(),
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async delete(sessionId: string): Promise<void> {
|
|
155
|
+
await this.client.send(
|
|
156
|
+
new DeleteCommand({
|
|
157
|
+
TableName: this.tableName,
|
|
158
|
+
Key: { id: sessionId },
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Using Your Store
|
|
166
|
+
|
|
167
|
+
Pass your store to `ChatSession` via the `store` option:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { ChatSession } from '@genui/a3'
|
|
171
|
+
import { createBedrockProvider } from '@genui/a3-bedrock'
|
|
172
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb'
|
|
173
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
|
|
174
|
+
import { DynamoSessionStore } from './dynamoSessionStore'
|
|
175
|
+
|
|
176
|
+
const dynamoClient = DynamoDBDocumentClient.from(new DynamoDBClient({}))
|
|
177
|
+
|
|
178
|
+
const session = new ChatSession({
|
|
179
|
+
sessionId: 'user-123',
|
|
180
|
+
store: new DynamoSessionStore(dynamoClient, 'my-sessions-table'),
|
|
181
|
+
initialAgentId: 'greeting',
|
|
182
|
+
provider: createBedrockProvider({ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'] }),
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const result = await session.send({ message: 'Hello!' })
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Gotchas and Tips
|
|
189
|
+
|
|
190
|
+
### Serialization
|
|
191
|
+
|
|
192
|
+
`SessionData` contains plain objects and arrays — `JSON.stringify` / `JSON.parse` works for most backends.
|
|
193
|
+
If your state includes `Date` objects, `Map`, `Set`, or class instances, you'll need a custom serializer (e.g. `superjson`).
|
|
194
|
+
|
|
195
|
+
### TTL and Expiration
|
|
196
|
+
|
|
197
|
+
A3 doesn't manage TTL.
|
|
198
|
+
Use your backend's native TTL feature (e.g. DynamoDB TTL, Redis `EXPIRE`) and set it in `save`.
|
|
199
|
+
|
|
200
|
+
### Concurrency
|
|
201
|
+
|
|
202
|
+
`save` is called after every turn.
|
|
203
|
+
If a user sends rapid messages, two `save` calls may race.
|
|
204
|
+
For most backends (Redis `SET`, DynamoDB `PutItem`) last-write-wins is fine.
|
|
205
|
+
If you need stronger guarantees, use conditional writes or optimistic locking.
|
|
206
|
+
|
|
207
|
+
### Typing
|
|
208
|
+
|
|
209
|
+
`SessionStore` is generic over `TState` and `TContext`.
|
|
210
|
+
Type your store class to match your application's state:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const store = new DynamoSessionStore<MyAppState, MyAppContext>(client, 'sessions')
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Store is Optional
|
|
217
|
+
|
|
218
|
+
`ChatSession` works without a store — sessions live only in the `ChatSession` instance's memory.
|
|
219
|
+
If you omit `store`, session data is never persisted and is lost when the instance is garbage collected.
|
|
220
|
+
|
|
221
|
+
## Reference
|
|
222
|
+
|
|
223
|
+
| File | Description |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `src/types/storage.ts` | `SessionStore` interface |
|
|
226
|
+
| `src/types/session.ts` | `SessionData`, `BaseState`, `BaseChatContext` interfaces |
|
|
227
|
+
| `src/stores/memoryStore.ts` | `MemorySessionStore` — built-in in-memory implementation |
|
|
228
|
+
| `src/core/chatSession.ts` | `ChatSession` — where stores are consumed |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @genui/a3-anthropic
|
|
2
|
+
|
|
3
|
+
Anthropic provider for the [A3 agentic framework](https://www.npmjs.com/package/@genui/a3).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @genui/a3-anthropic @genui/a3
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createAnthropicProvider } from '@genui/a3-anthropic'
|
|
15
|
+
|
|
16
|
+
const provider = createAnthropicProvider({
|
|
17
|
+
models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],
|
|
18
|
+
apiKey: process.env.ANTHROPIC_API_KEY, // optional, defaults to ANTHROPIC_API_KEY env var
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
`createAnthropicProvider(config)` communicates with the Anthropic Messages API using the [Vercel AI SDK](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic) (`@ai-sdk/anthropic`) for structured output.
|
|
25
|
+
|
|
26
|
+
| Option | Type | Required | Description |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `models` | `string[]` | Yes | Model IDs in preference order (first = primary, rest = fallbacks) |
|
|
29
|
+
| `apiKey` | `string` | No | API key. Defaults to `ANTHROPIC_API_KEY` env var |
|
|
30
|
+
| `baseURL` | `string` | No | Custom base URL for the Anthropic API |
|
|
31
|
+
| `resilience` | `ResilienceConfig` | No | Retry, backoff, and timeout settings. Uses defaults if omitted |
|
|
32
|
+
|
|
33
|
+
## Behaviour
|
|
34
|
+
|
|
35
|
+
- Uses the Vercel AI SDK's `Output.object()` for structured output — Zod schema conversion and partial JSON parsing handled internally
|
|
36
|
+
- **Streaming** yields text deltas in real-time via partial object tracking, then emits a validated tool-call result at the end
|
|
37
|
+
- **Appends a `"Continue"` user message** if the last message has an assistant role, to satisfy the alternating-role requirement
|
|
38
|
+
|
|
39
|
+
## Prerequisites
|
|
40
|
+
|
|
41
|
+
An Anthropic API key, either passed directly or set as `ANTHROPIC_API_KEY`.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
See the [Providers documentation](https://github.com/generalui/a3/blob/main/docs/PROVIDERS.md) for model fallback, per-agent overrides, the provider interface, and more.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @genui/a3-bedrock
|
|
2
|
+
|
|
3
|
+
AWS Bedrock provider for the [A3 agentic framework](https://www.npmjs.com/package/@genui/a3).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @genui/a3-bedrock @genui/a3
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createBedrockProvider } from '@genui/a3-bedrock'
|
|
15
|
+
|
|
16
|
+
const provider = createBedrockProvider({
|
|
17
|
+
models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
|
|
18
|
+
region: 'us-east-1', // optional, defaults to AWS SDK default
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
`createBedrockProvider(config)` communicates with AWS Bedrock via the [Converse API](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html).
|
|
25
|
+
|
|
26
|
+
| Option | Type | Required | Description |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `models` | `string[]` | Yes | Model IDs in preference order (first = primary, rest = fallbacks) |
|
|
29
|
+
| `region` | `string` | No | AWS region. Defaults to AWS SDK default |
|
|
30
|
+
| `resilience` | `ResilienceConfig` | No | Retry, backoff, and timeout settings. Uses defaults if omitted |
|
|
31
|
+
|
|
32
|
+
## Behaviour
|
|
33
|
+
|
|
34
|
+
- Uses **tool-based JSON extraction** (`structuredResponse` tool) for reliable structured output
|
|
35
|
+
- **Streaming** yields text deltas in real-time, then emits a validated tool-call result at the end
|
|
36
|
+
- **Merges sequential same-role messages** to satisfy Bedrock's alternating-role requirement
|
|
37
|
+
- **Prepends an initial user message** (`"Hi"`) so the conversation always starts with a user turn
|
|
38
|
+
|
|
39
|
+
## Prerequisites
|
|
40
|
+
|
|
41
|
+
AWS credentials configured via environment variables, IAM role, or AWS profile — the same setup the AWS SDK expects.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
See the [Providers documentation](https://github.com/generalui/a3/blob/main/docs/PROVIDERS.md) for model fallback, per-agent overrides, the provider interface, and more.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @genui/a3-openai
|
|
2
|
+
|
|
3
|
+
OpenAI provider for the [A3 agentic framework](https://www.npmjs.com/package/@genui/a3).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @genui/a3-openai @genui/a3
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createOpenAIProvider } from '@genui/a3-openai'
|
|
15
|
+
|
|
16
|
+
const provider = createOpenAIProvider({
|
|
17
|
+
models: ['gpt-4o', 'gpt-4o-mini'],
|
|
18
|
+
apiKey: process.env.OPENAI_API_KEY, // optional, defaults to OPENAI_API_KEY env var
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Configuration
|
|
23
|
+
|
|
24
|
+
`createOpenAIProvider(config)` communicates with the OpenAI Chat Completions API using [structured output](https://platform.openai.com/docs/guides/structured-outputs) (`response_format: json_schema`).
|
|
25
|
+
|
|
26
|
+
| Option | Type | Required | Description |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| `models` | `string[]` | Yes | Model IDs in preference order (first = primary, rest = fallbacks) |
|
|
29
|
+
| `apiKey` | `string` | No | API key. Defaults to `OPENAI_API_KEY` env var |
|
|
30
|
+
| `baseURL` | `string` | No | Custom base URL for Azure OpenAI or compatible endpoints |
|
|
31
|
+
| `organization` | `string` | No | OpenAI organization ID |
|
|
32
|
+
| `resilience` | `ResilienceConfig` | No | Retry, backoff, and timeout settings. Uses defaults if omitted |
|
|
33
|
+
|
|
34
|
+
## Behaviour
|
|
35
|
+
|
|
36
|
+
- Uses **structured output** (`response_format` with `json_schema`) — no tool calls required
|
|
37
|
+
- **Enforces strict schemas** automatically (`additionalProperties: false`, all properties `required`)
|
|
38
|
+
- **Streaming** extracts `chatbotMessage` text progressively from the JSON response via a character-level state machine, yielding text deltas in real-time
|
|
39
|
+
- Detects **truncated responses** (`finish_reason: length`) and surfaces them as errors
|
|
40
|
+
|
|
41
|
+
## Prerequisites
|
|
42
|
+
|
|
43
|
+
An OpenAI API key, either passed directly or set as `OPENAI_API_KEY`.
|
|
44
|
+
|
|
45
|
+
## Documentation
|
|
46
|
+
|
|
47
|
+
See the [Providers documentation](https://github.com/generalui/a3/blob/main/docs/PROVIDERS.md) for model fallback, per-agent overrides, the provider interface, and more.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Providers
|
|
2
|
+
|
|
3
|
+
LLM provider implementations for the A3 agentic framework.
|
|
4
|
+
|
|
5
|
+
A provider is a thin adapter that connects A3 to an LLM API.
|
|
6
|
+
Its job is to convert A3's provider-agnostic request format into the LLM's API format, send the request, and convert the response back into A3's expected format (JSON string for blocking, AG-UI events for streaming).
|
|
7
|
+
|
|
8
|
+
A3 ships with **AWS Bedrock**, **Anthropic**, and **OpenAI** providers out of the box.
|
|
9
|
+
All three support blocking and streaming modes, model fallback, and structured output via Zod schemas.
|
|
10
|
+
|
|
11
|
+
For information on specific providers, please see their documentation:
|
|
12
|
+
|
|
13
|
+
- [AWS Bedrock (`@genui/a3-bedrock`)](https://www.npmjs.com/package/@genui/a3-bedrock)
|
|
14
|
+
- [Anthropic (`@genui/a3-anthropic`)](https://www.npmjs.com/package/@genui/a3-anthropic)
|
|
15
|
+
- [OpenAI (`@genui/a3-openai`)](https://www.npmjs.com/package/@genui/a3-openai)
|
|
16
|
+
|
|
17
|
+
To connect A3 to an LLM that isn't covered by the built-in providers, see [Creating a Custom Provider](./CUSTOM_PROVIDERS.md).
|
|
18
|
+
|
|
19
|
+
## Use with A3
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { ChatSession, MemorySessionStore } from '@genui/a3'
|
|
23
|
+
|
|
24
|
+
const session = new ChatSession({
|
|
25
|
+
sessionId: 'user-123',
|
|
26
|
+
store: new MemorySessionStore(),
|
|
27
|
+
initialAgentId: 'greeting',
|
|
28
|
+
initialState: {},
|
|
29
|
+
provider, // any provider from above
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Blocking
|
|
33
|
+
const response = await session.send({ message: 'Hello!' })
|
|
34
|
+
|
|
35
|
+
// Streaming
|
|
36
|
+
for await (const event of session.send({ message: 'Hello!', stream: true })) {
|
|
37
|
+
console.log(event)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Model Fallback
|
|
42
|
+
|
|
43
|
+
All providers support automatic model fallback.
|
|
44
|
+
List models in order of preference:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const provider = createBedrockProvider({
|
|
48
|
+
models: [
|
|
49
|
+
'us.anthropic.claude-sonnet-4-5-20250929-v1:0', // primary
|
|
50
|
+
'us.anthropic.claude-haiku-4-5-20251001-v1:0', // fallback
|
|
51
|
+
],
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If the primary model fails, the provider automatically retries with the next model in the list.
|
|
56
|
+
If all models fail, the last error is thrown.
|
|
57
|
+
|
|
58
|
+
All providers include built-in resilience: automatic retries with exponential backoff, per-request and total timeouts, and model fallback.
|
|
59
|
+
See the [Resilience documentation](./RESILIENCE.md) for configuration options and defaults.
|
|
60
|
+
|
|
61
|
+
## Per-Agent Provider Override
|
|
62
|
+
|
|
63
|
+
Each agent can override the session-level provider:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createOpenAIProvider } from '@genui/a3-openai'
|
|
67
|
+
import { createBedrockProvider } from '@genui/a3-bedrock'
|
|
68
|
+
|
|
69
|
+
// Session uses Bedrock by default
|
|
70
|
+
const session = new ChatSession({
|
|
71
|
+
provider: createBedrockProvider({ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'] }),
|
|
72
|
+
// ...
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// This agent uses OpenAI instead
|
|
76
|
+
const premiumAgent = {
|
|
77
|
+
id: 'premium',
|
|
78
|
+
description: 'Handles premium tier requests using GPT-4o',
|
|
79
|
+
provider: createOpenAIProvider({ models: ['gpt-4o'] }),
|
|
80
|
+
// ...
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Provider Interface
|
|
85
|
+
|
|
86
|
+
All providers implement the `Provider` interface from `@genui/a3`:
|
|
87
|
+
|
|
88
|
+
| Member | Description |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `sendRequest(request)` | Blocking request → `Promise<ProviderResponse>` |
|
|
91
|
+
| `sendRequestStream(request)` | Streaming request → `AsyncGenerator<StreamEvent>` |
|
|
92
|
+
| `name` | Human-readable name (`'bedrock'`, `'anthropic'`, or `'openai'`) |
|
|
93
|
+
|
|
94
|
+
To create a custom provider, implement this interface and pass it to `ChatSession` or an individual agent.
|
|
95
|
+
See [Creating a Custom Provider](./CUSTOM_PROVIDERS.md) for a step-by-step guide.
|
|
96
|
+
|
|
97
|
+
## Packages
|
|
98
|
+
|
|
99
|
+
Each provider is a separate npm package.
|
|
100
|
+
Install the one(s) you need:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm install @genui/a3-bedrock @genui/a3
|
|
104
|
+
npm install @genui/a3-openai @genui/a3
|
|
105
|
+
npm install @genui/a3-anthropic @genui/a3
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
| Package | Export | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `@genui/a3-bedrock` | `createBedrockProvider` | Factory function returning a Bedrock `Provider` |
|
|
111
|
+
| `@genui/a3-bedrock` | `BedrockProviderConfig` | TypeScript config interface |
|
|
112
|
+
| `@genui/a3-anthropic` | `createAnthropicProvider` | Factory function returning an Anthropic `Provider` |
|
|
113
|
+
| `@genui/a3-anthropic` | `AnthropicProviderConfig` | TypeScript config interface |
|
|
114
|
+
| `@genui/a3-openai` | `createOpenAIProvider` | Factory function returning an OpenAI `Provider` |
|
|
115
|
+
| `@genui/a3-openai` | `OpenAIProviderConfig` | TypeScript config interface |
|
|
116
|
+
|
|
117
|
+
## Requirements
|
|
118
|
+
|
|
119
|
+
- Node.js 20.19.0+
|
|
120
|
+
- TypeScript 5.9+
|
|
121
|
+
- `@genui/a3` (peer dependency)
|
|
122
|
+
- **Bedrock**: AWS credentials configured in the environment
|
|
123
|
+
- **Anthropic**: `ANTHROPIC_API_KEY` environment variable or `apiKey` config option
|
|
124
|
+
- **OpenAI**: `OPENAI_API_KEY` environment variable or `apiKey` config option
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Quick Start
|
|
2
|
+
|
|
3
|
+
The fastest way to get started with A3 is using the interactive CLI. It scaffolds a full Next.js application, configures your LLM providers, and installs dependencies.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @genui/a3-create@latest
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Follow the prompts to name your project and provide your API keys. Once finished:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
cd your-project-name
|
|
13
|
+
npm run dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For more details on the CLI, see the [@genui/a3-create README](https://www.npmjs.com/package/@genui/a3-create).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Manual Installation
|
|
21
|
+
|
|
22
|
+
If you are adding A3 to an existing project or prefer a manual setup, follow these steps.
|
|
23
|
+
|
|
24
|
+
### Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @genui/a3
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Define an agent
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { z } from 'zod'
|
|
34
|
+
import { Agent, BaseState } from '@genui/a3'
|
|
35
|
+
|
|
36
|
+
interface State extends BaseState {
|
|
37
|
+
userName?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const greetingAgent: Agent<State> = {
|
|
41
|
+
id: 'greeting',
|
|
42
|
+
name: 'Greeting Agent',
|
|
43
|
+
description: 'Greets the user and collects their name',
|
|
44
|
+
prompt: async () => `
|
|
45
|
+
You are a friendly greeting agent. Your goal is to greet the user
|
|
46
|
+
and learn their name. Once you have their name, set goalAchieved to true.
|
|
47
|
+
`,
|
|
48
|
+
outputSchema: z.object({
|
|
49
|
+
userName: z.string().optional(),
|
|
50
|
+
}),
|
|
51
|
+
// `transition` is optional. When omitted, the agent stays active by default
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Register and run
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { AgentRegistry, ChatSession, MemorySessionStore } from '@genui/a3'
|
|
59
|
+
import { createBedrockProvider } from '@genui/a3-bedrock'
|
|
60
|
+
|
|
61
|
+
const registry = AgentRegistry.getInstance<State>()
|
|
62
|
+
registry.register(greetingAgent)
|
|
63
|
+
|
|
64
|
+
const provider = createBedrockProvider({
|
|
65
|
+
models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const session = new ChatSession<State>({
|
|
69
|
+
sessionId: 'demo',
|
|
70
|
+
store: new MemorySessionStore(),
|
|
71
|
+
initialAgentId: 'greeting',
|
|
72
|
+
initialState: { userName: undefined },
|
|
73
|
+
provider,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const response = await session.send({ message: 'Hi there!' })
|
|
77
|
+
console.log(response.responseMessage)
|
|
78
|
+
// => "Hello! I'd love to get to know you. What's your name?"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
That's it.
|
|
82
|
+
One agent, one session, one function call.
|
|
83
|
+
|
|
84
|
+
## Multi-Agent Example
|
|
85
|
+
|
|
86
|
+
Here's a pattern with three agents that route between each other, demonstrating how state flows across agent boundaries.
|
|
87
|
+
|
|
88
|
+
### Define the agents
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { z } from 'zod'
|
|
92
|
+
import { Agent, BaseState } from '@genui/a3'
|
|
93
|
+
|
|
94
|
+
interface AppState extends BaseState {
|
|
95
|
+
userName?: string
|
|
96
|
+
isAuthenticated: boolean
|
|
97
|
+
issueCategory?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Agent 1: Greeting -- collects the user's name, then routes to auth
|
|
101
|
+
const greetingAgent: Agent<AppState> = {
|
|
102
|
+
id: 'greeting',
|
|
103
|
+
name: 'Greeting Agent',
|
|
104
|
+
description: 'Greets the user and collects their name',
|
|
105
|
+
prompt: async () => `
|
|
106
|
+
Greet the user warmly. Ask for their name.
|
|
107
|
+
Once you have it, set goalAchieved to true.
|
|
108
|
+
`,
|
|
109
|
+
outputSchema: z.object({ userName: z.string().optional() }),
|
|
110
|
+
transition: (_state, goalAchieved) =>
|
|
111
|
+
goalAchieved ? 'auth' : 'greeting',
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Agent 2: Auth -- verifies identity, then routes to support
|
|
115
|
+
const authAgent: Agent<AppState> = {
|
|
116
|
+
id: 'auth',
|
|
117
|
+
name: 'Auth Agent',
|
|
118
|
+
description: 'Verifies user identity',
|
|
119
|
+
prompt: async ({ sessionData }) => `
|
|
120
|
+
The user's name is ${sessionData.state.userName}.
|
|
121
|
+
Ask them to confirm their email to verify identity.
|
|
122
|
+
Set goalAchieved to true once verified.
|
|
123
|
+
`,
|
|
124
|
+
outputSchema: z.object({ isAuthenticated: z.boolean() }),
|
|
125
|
+
transition: (_state, goalAchieved) =>
|
|
126
|
+
goalAchieved ? 'support' : 'auth',
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Agent 3: Support -- handles the user's issue
|
|
130
|
+
const supportAgent: Agent<AppState> = {
|
|
131
|
+
id: 'support',
|
|
132
|
+
name: 'Support Agent',
|
|
133
|
+
description: 'Helps resolve user issues',
|
|
134
|
+
prompt: async ({ sessionData }) => `
|
|
135
|
+
The user ${sessionData.state.userName} is authenticated.
|
|
136
|
+
Help them with their issue. Categorize it.
|
|
137
|
+
Set goalAchieved when resolved.
|
|
138
|
+
`,
|
|
139
|
+
outputSchema: z.object({
|
|
140
|
+
issueCategory: z.string().optional(),
|
|
141
|
+
}),
|
|
142
|
+
// `transition` is optional. If omitted, the flow of the interaction will stay in this agent.
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Wire them up
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { AgentRegistry, ChatSession, MemorySessionStore } from '@genui/a3'
|
|
150
|
+
import { createBedrockProvider } from '@genui/a3-bedrock'
|
|
151
|
+
|
|
152
|
+
const registry = AgentRegistry.getInstance<AppState>()
|
|
153
|
+
registry.register([greetingAgent, authAgent, supportAgent])
|
|
154
|
+
|
|
155
|
+
const provider = createBedrockProvider({
|
|
156
|
+
models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const session = new ChatSession<AppState>({
|
|
160
|
+
sessionId: 'user-456',
|
|
161
|
+
store: new MemorySessionStore(),
|
|
162
|
+
initialAgentId: 'greeting',
|
|
163
|
+
initialState: { isAuthenticated: false },
|
|
164
|
+
provider,
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Conversation flow
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Turn 1: User greets, greeting agent responds
|
|
172
|
+
await session.send({ message: 'Hello!' })
|
|
173
|
+
// => Greeting agent asks for name
|
|
174
|
+
|
|
175
|
+
// Turn 2: User provides name, greeting agent completes and chains to auth
|
|
176
|
+
await session.send({ message: "I'm Alex" })
|
|
177
|
+
// => Auth agent asks for email verification
|
|
178
|
+
// (greeting → auth happened automatically in one request)
|
|
179
|
+
|
|
180
|
+
// Turn 3: User verifies, auth completes and chains to support
|
|
181
|
+
await session.send({ message: 'alex@example.com' })
|
|
182
|
+
// => Support agent asks how it can help
|
|
183
|
+
// State now: { userName: 'Alex', isAuthenticated: true }
|
|
184
|
+
|
|
185
|
+
// Turn 4: Support agent handles the issue
|
|
186
|
+
await session.send({ message: 'I need help with my billing' })
|
|
187
|
+
// => Support agent resolves the issue
|
|
188
|
+
// State: { userName: 'Alex', isAuthenticated: true, issueCategory: 'billing' }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Notice that:
|
|
192
|
+
|
|
193
|
+
- **State persists across agents**: `userName` set by the greeting agent is available to auth and support
|
|
194
|
+
- **Agent chaining is automatic**: when greeting completes, auth starts in the same request
|
|
195
|
+
- **Each agent has its own prompt and schema**: they extract different data but share the same state
|
|
196
|
+
|
|
197
|
+
For non-deterministic routing (LLM-driven agent selection), see [Transitions](./TRANSITIONS.md#non-deterministic-agentid).
|