@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.
Files changed (91) hide show
  1. package/README.md +123 -0
  2. package/dist/index.js +684 -0
  3. package/package.json +52 -0
  4. package/template/.cursor/rules/example-app.mdc +9 -0
  5. package/template/CLAUDE.md +121 -0
  6. package/template/README.md +20 -0
  7. package/template/_gitignore +36 -0
  8. package/template/app/ThemeProvider.tsx +17 -0
  9. package/template/app/agents/age.ts +25 -0
  10. package/template/app/agents/greeting.ts +30 -0
  11. package/template/app/agents/index.ts +57 -0
  12. package/template/app/agents/onboarding/index.ts +15 -0
  13. package/template/app/agents/onboarding/prompt.ts +59 -0
  14. package/template/app/agents/registry.ts +17 -0
  15. package/template/app/agents/state.ts +10 -0
  16. package/template/app/api/agui/route.ts +56 -0
  17. package/template/app/api/chat/route.ts +35 -0
  18. package/template/app/api/stream/route.ts +57 -0
  19. package/template/app/apple-icon-dark.png +0 -0
  20. package/template/app/apple-icon.png +0 -0
  21. package/template/app/components/atoms/AgentNode.tsx +56 -0
  22. package/template/app/components/atoms/AppLogo.tsx +44 -0
  23. package/template/app/components/atoms/ChatContainer.tsx +13 -0
  24. package/template/app/components/atoms/ChatHeader.tsx +49 -0
  25. package/template/app/components/atoms/MarkdownRenderer.tsx +134 -0
  26. package/template/app/components/atoms/MessageBubble.tsx +21 -0
  27. package/template/app/components/atoms/TransitionEdge.tsx +49 -0
  28. package/template/app/components/atoms/index.ts +7 -0
  29. package/template/app/components/molecules/ChatInput.tsx +94 -0
  30. package/template/app/components/molecules/ChatMessage.tsx +45 -0
  31. package/template/app/components/molecules/index.ts +2 -0
  32. package/template/app/components/organisms/AgentGraph.tsx +75 -0
  33. package/template/app/components/organisms/AguiChat.tsx +133 -0
  34. package/template/app/components/organisms/Chat.tsx +88 -0
  35. package/template/app/components/organisms/ChatMessageList.tsx +35 -0
  36. package/template/app/components/organisms/ExamplePageLayout.tsx +118 -0
  37. package/template/app/components/organisms/OnboardingChat.tsx +24 -0
  38. package/template/app/components/organisms/Sidebar.tsx +147 -0
  39. package/template/app/components/organisms/SidebarLayout.tsx +58 -0
  40. package/template/app/components/organisms/StateViewer.tsx +126 -0
  41. package/template/app/components/organisms/StreamChat.tsx +173 -0
  42. package/template/app/components/organisms/index.ts +10 -0
  43. package/template/app/constants/chat.ts +52 -0
  44. package/template/app/constants/paths.ts +1 -0
  45. package/template/app/constants/ui.ts +61 -0
  46. package/template/app/examples/agui/page.tsx +26 -0
  47. package/template/app/examples/chat/page.tsx +26 -0
  48. package/template/app/examples/page.tsx +106 -0
  49. package/template/app/examples/stream/page.tsx +26 -0
  50. package/template/app/favicon-dark.ico +0 -0
  51. package/template/app/favicon.ico +0 -0
  52. package/template/app/icon.svg +13 -0
  53. package/template/app/layout.tsx +36 -0
  54. package/template/app/lib/actions/restartSession.ts +10 -0
  55. package/template/app/lib/getAgentGraphData.ts +43 -0
  56. package/template/app/lib/getGraphLayout.ts +99 -0
  57. package/template/app/lib/hooks/useRestart.ts +33 -0
  58. package/template/app/lib/parseTransitionTargets.ts +140 -0
  59. package/template/app/lib/providers/anthropic.ts +12 -0
  60. package/template/app/lib/providers/bedrock.ts +12 -0
  61. package/template/app/lib/providers/openai.ts +10 -0
  62. package/template/app/onboarding/page.tsx +21 -0
  63. package/template/app/page.tsx +16 -0
  64. package/template/app/styled.d.ts +6 -0
  65. package/template/app/theme.ts +22 -0
  66. package/template/docs/A3-README.md +121 -0
  67. package/template/docs/API-REFERENCE.md +85 -0
  68. package/template/docs/ARCHITECTURE.md +84 -0
  69. package/template/docs/CORE-CONCEPTS.md +347 -0
  70. package/template/docs/CUSTOM_LOGGING.md +36 -0
  71. package/template/docs/CUSTOM_PROVIDERS.md +642 -0
  72. package/template/docs/CUSTOM_STORES.md +228 -0
  73. package/template/docs/PROVIDER-ANTHROPIC.md +45 -0
  74. package/template/docs/PROVIDER-BEDROCK.md +45 -0
  75. package/template/docs/PROVIDER-OPENAI.md +47 -0
  76. package/template/docs/PROVIDERS.md +124 -0
  77. package/template/docs/QUICK-START-EXAMPLES.md +197 -0
  78. package/template/docs/RESILIENCE.md +226 -0
  79. package/template/docs/TRANSITIONS.md +245 -0
  80. package/template/docs/WIDGETS.md +331 -0
  81. package/template/docs/contributing/LOGGING.md +104 -0
  82. package/template/docs/designs/a3-gtm-strategy.md +280 -0
  83. package/template/docs/designs/a3-platform-vision.md +276 -0
  84. package/template/next-env.d.ts +6 -0
  85. package/template/next.config.mjs +15 -0
  86. package/template/package.json +41 -0
  87. package/template/public/android-chrome-192x192.png +0 -0
  88. package/template/public/android-chrome-512x512.png +0 -0
  89. package/template/public/site.webmanifest +11 -0
  90. package/template/scripts/dev.mjs +29 -0
  91. package/template/tsconfig.json +47 -0
@@ -0,0 +1,226 @@
1
+ # Resilience
2
+
3
+ A3 providers include built-in resilience: automatic retries with backoff, per-request and total timeouts, and model fallback.
4
+ When a request fails, the provider retries against the same model before falling back to the next model in your list — all with zero configuration required.
5
+
6
+ ## How it works
7
+
8
+ When you call `sendRequest` or `sendRequestStream`, the provider delegates to `executeWithFallback`, which runs this loop:
9
+
10
+ ```text
11
+ for each model (in priority order):
12
+ for each attempt (1 … 1 + maxAttempts):
13
+ 1. Check total timeout — abort if exceeded
14
+ 2. Build an AbortSignal combining per-request timeout + total timeout
15
+ 3. Call the provider action with (model, signal)
16
+ 4. On success → return result
17
+ 5. On failure:
18
+ a. Record the error
19
+ b. If retryable and attempts remain → backoff delay → retry same model
20
+ c. Otherwise → move to next model
21
+
22
+ All models exhausted → throw A3ResilienceError
23
+ Total timeout exceeded → throw A3TimeoutError
24
+ ```
25
+
26
+ ## Defaults
27
+
28
+ With zero configuration, every provider gets:
29
+
30
+ | Setting | Default |
31
+ |---|---|
32
+ | `retry.maxAttempts` | `2` (3 total attempts per model) |
33
+ | `retry.retryOn` | `'transient'` |
34
+ | `backoff.strategy` | `'exponential'` |
35
+ | `backoff.baseDelayMs` | `500` |
36
+ | `backoff.maxDelayMs` | `30000` |
37
+ | `backoff.jitter` | `true` |
38
+ | `timeout.requestTimeoutMs` | `undefined` (SDK default) |
39
+ | `timeout.totalTimeoutMs` | `undefined` (no limit) |
40
+ | `isRetryableError` | Built-in classifier |
41
+
42
+ These defaults are exported as `DEFAULT_RESILIENCE_CONFIG` from `@genui/a3`.
43
+
44
+ ## Configuration examples
45
+
46
+ Pass a `resilience` object when creating a provider.
47
+ All fields are optional — unspecified fields keep their defaults.
48
+
49
+ ### Minimal — just increase retries
50
+
51
+ ```typescript
52
+ const provider = createBedrockProvider({
53
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
54
+ resilience: {
55
+ retry: { maxAttempts: 3 },
56
+ },
57
+ })
58
+ ```
59
+
60
+ ### Medium — linear backoff with timeouts
61
+
62
+ ```typescript
63
+ const provider = createOpenAIProvider({
64
+ models: ['gpt-4o', 'gpt-4o-mini'],
65
+ resilience: {
66
+ retry: { maxAttempts: 2 },
67
+ backoff: { strategy: 'linear', baseDelayMs: 1000 },
68
+ timeout: {
69
+ requestTimeoutMs: 30_000,
70
+ totalTimeoutMs: 90_000,
71
+ },
72
+ },
73
+ })
74
+ ```
75
+
76
+ ### Maximum — full control
77
+
78
+ ```typescript
79
+ const provider = createBedrockProvider({
80
+ models: [
81
+ 'us.anthropic.claude-sonnet-4-5-20250929-v1:0',
82
+ 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
83
+ ],
84
+ resilience: {
85
+ retry: { maxAttempts: 5, retryOn: 'all' },
86
+ backoff: {
87
+ strategy: 'exponential',
88
+ baseDelayMs: 200,
89
+ maxDelayMs: 10_000,
90
+ jitter: true,
91
+ },
92
+ timeout: {
93
+ requestTimeoutMs: 15_000,
94
+ totalTimeoutMs: 120_000,
95
+ },
96
+ isRetryableError: (error) => {
97
+ // Custom logic — retry everything except auth errors
98
+ return !error.message.includes('401')
99
+ },
100
+ },
101
+ })
102
+ ```
103
+
104
+ ### Disable retries entirely
105
+
106
+ ```typescript
107
+ const provider = createBedrockProvider({
108
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
109
+ resilience: {
110
+ retry: false,
111
+ },
112
+ })
113
+ ```
114
+
115
+ With `retry: false`, each model is attempted exactly once with no backoff.
116
+ Model fallback still applies if you provide multiple models.
117
+
118
+ ## Backoff strategies
119
+
120
+ | Strategy | Formula | Example (baseDelay=500ms) |
121
+ |---|---|---|
122
+ | `'exponential'` (default) | `baseDelay * 2^attempt` | 500ms, 1000ms, 2000ms, 4000ms… |
123
+ | `'linear'` | `baseDelay * (attempt + 1)` | 500ms, 1000ms, 1500ms, 2000ms… |
124
+ | `'fixed'` | `baseDelay` | 500ms, 500ms, 500ms, 500ms… |
125
+
126
+ All strategies are capped at `maxDelayMs`.
127
+ When `jitter` is `true`, the actual delay is randomized between `0` and the calculated value.
128
+
129
+ ## Error classification
130
+
131
+ The built-in `isRetryableError` classifier determines which errors are transient (safe to retry) and which are permanent (skip to next model).
132
+
133
+ **Retryable (transient):**
134
+
135
+ - Network errors: `ECONNRESET`, `ECONNREFUSED`, `ECONNABORTED`, `ETIMEDOUT`, `ENETUNREACH`, `EPIPE`, `EHOSTUNREACH`
136
+ - Throttling: messages containing `throttl`, `rate limit`, `too many requests`, `request limit`, `quota`
137
+ - Timeouts: messages containing `timeout` or `timed out`
138
+ - HTTP status codes: `408` (Request Timeout), `429` (Too Many Requests), `500+` (Server Errors, including `529` Overloaded)
139
+ - AWS SDK v3: reads `$metadata.httpStatusCode` automatically
140
+
141
+ **Not retryable:**
142
+
143
+ - `AbortError` / `TimeoutError` (intentional cancellation by the timeout system)
144
+ - `400` Bad Request, `401` Unauthorized, `403` Forbidden, `404` Not Found
145
+ - Any error not matching the patterns above
146
+
147
+ When `retryOn` is set to `'all'`, the classifier is bypassed and every error triggers a retry (up to `maxAttempts`).
148
+
149
+ ## Error handling
150
+
151
+ When all models and retries are exhausted, A3 throws typed errors that preserve the full failure history.
152
+
153
+ ### A3ResilienceError
154
+
155
+ Thrown when every model has failed.
156
+ Contains an `errors` array with one entry per failed attempt.
157
+
158
+ ```typescript
159
+ import { A3ResilienceError } from '@genui/a3'
160
+
161
+ try {
162
+ await provider.sendRequest(request)
163
+ } catch (err) {
164
+ if (err instanceof A3ResilienceError) {
165
+ for (const entry of err.errors) {
166
+ console.log(`${entry.model} attempt ${entry.attempt}: ${entry.error.message}`)
167
+ }
168
+ }
169
+ }
170
+ ```
171
+
172
+ Each `ResilienceErrorEntry` contains:
173
+
174
+ - `model` — the model identifier that was attempted
175
+ - `attempt` — 1-based attempt number within that model's retry cycle
176
+ - `error` — the original `Error` object
177
+
178
+ ### A3TimeoutError
179
+
180
+ Thrown when `totalTimeoutMs` is exceeded.
181
+ Extends `A3ResilienceError`, so it includes the same `errors` array and can be caught by either type.
182
+
183
+ ```typescript
184
+ import { A3TimeoutError, A3ResilienceError } from '@genui/a3'
185
+
186
+ try {
187
+ await provider.sendRequest(request)
188
+ } catch (err) {
189
+ if (err instanceof A3TimeoutError) {
190
+ console.log('Total timeout exceeded')
191
+ } else if (err instanceof A3ResilienceError) {
192
+ console.log('All models exhausted')
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Custom error classifier
198
+
199
+ Override the built-in classification by providing an `isRetryableError` function.
200
+ When provided, this replaces the default classifier entirely.
201
+
202
+ ```typescript
203
+ const provider = createBedrockProvider({
204
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
205
+ resilience: {
206
+ isRetryableError: (error) => {
207
+ // Only retry rate-limit errors
208
+ return error.message.includes('429') || error.message.includes('Too Many Requests')
209
+ },
210
+ },
211
+ })
212
+ ```
213
+
214
+ The custom classifier is ignored when `retryOn` is set to `'all'`.
215
+
216
+ ## Key files
217
+
218
+ | File | Description |
219
+ |---|---|
220
+ | `src/types/resilience.ts` | `ResilienceConfig`, `RetryConfig`, `BackoffConfig`, `TimeoutConfig`, `ResolvedResilienceConfig` |
221
+ | `src/utils/resilience/defaults.ts` | `DEFAULT_RESILIENCE_CONFIG`, `resolveResilienceConfig()` |
222
+ | `src/utils/resilience/errorClassification.ts` | `isRetryableError()` — built-in error classifier |
223
+ | `src/errors/resilience.ts` | `A3ResilienceError`, `A3TimeoutError`, `ResilienceErrorEntry` |
224
+ | `providers/utils/executeWithFallback.ts` | `executeWithFallback()` — core execution loop |
225
+ | `providers/utils/backoff.ts` | `calculateBackoff()`, `sleep()` |
226
+ | `src/index.ts` | Public exports: `resolveResilienceConfig`, `DEFAULT_RESILIENCE_CONFIG`, `isRetryableError`, `A3ResilienceError`, `A3TimeoutError` |
@@ -0,0 +1,245 @@
1
+ # Transitions
2
+
3
+ Transitions control how agents hand off to each other in a multi-agent conversation.
4
+ Each agent's `transition` property determines when and how the conversation moves to the next agent.
5
+
6
+ For agent basics, see [Core Concepts](./CORE-CONCEPTS.md#agent).
7
+
8
+ ## Three Transition Modes
9
+
10
+ A3 supports three transition modes based on what you assign to the `transition` property: **default** (omitted), **non-deterministic** (array), and **deterministic** (function).
11
+
12
+ ### Default (`transition` Omitted)
13
+
14
+ When `transition` is not set, the agent stays active by default.
15
+ The `redirectToAgent` field is technically present in the LLM output schema as `z.string().nullable()`, but the base prompt restricts the LLM to only redirect to agents listed in its SPECIALIST AGENT POOL — which, without a `transition` array, contains only the agent itself.
16
+
17
+ ```typescript
18
+ import { z } from 'zod'
19
+ import { Agent, BaseState } from '@genui/a3'
20
+
21
+ interface MyState extends BaseState {
22
+ topic?: string
23
+ }
24
+
25
+ const assistantAgent: Agent<MyState> = {
26
+ id: 'assistant',
27
+ description: 'General assistant',
28
+ prompt: 'You are a helpful assistant.',
29
+ outputSchema: z.object({
30
+ topic: z.string().optional(),
31
+ }),
32
+ // transition is omitted:
33
+ // - Agent stays active each turn
34
+ // - No other agents are shown in the SPECIALIST AGENT POOL
35
+ }
36
+ ```
37
+
38
+ This is the right choice for single-agent setups or leaf agents that should not route anywhere.
39
+
40
+ ### Non-Deterministic (`AgentId[]`)
41
+
42
+ When `transition` is an array of agent IDs, `redirectToAgent` is constrained to a `z.enum(...)` of those IDs.
43
+ The LLM must pick from the listed agents.
44
+ If the LLM returns an ID not in the array, Zod validation throws at parse time.
45
+
46
+ ```typescript
47
+ const triageAgent: Agent<MyState> = {
48
+ id: 'triage',
49
+ description: 'Routes users to the appropriate department',
50
+ prompt: `
51
+ You are a triage agent. Based on the user's request, route them to:
52
+ - "billing" for payment or invoice questions
53
+ - "technical-support" for product issues
54
+ - "account" for account management
55
+ `,
56
+ outputSchema: z.object({}),
57
+ // LLM must pick from these three agents via redirectToAgent
58
+ transition: ['billing', 'technical-support', 'account'],
59
+ }
60
+ ```
61
+
62
+ The LLM output schema for this agent will include:
63
+
64
+ ```typescript
65
+ redirectToAgent: z.enum(['billing', 'technical-support', 'account']).nullable()
66
+ ```
67
+
68
+ ### Deterministic (`(state, goalAchieved) => AgentId`)
69
+
70
+ When `transition` is a function, `redirectToAgent` is completely omitted from the LLM output schema.
71
+ Your code controls routing based on state and goal status.
72
+
73
+ ```typescript
74
+ const authAgent: Agent<AppState> = {
75
+ id: 'auth',
76
+ description: 'Verifies user identity',
77
+ prompt: 'Verify the user. Set goalAchieved when done.',
78
+ outputSchema: z.object({
79
+ isAuthenticated: z.boolean(),
80
+ }),
81
+ // Code decides routing — LLM has no say
82
+ transition: (state, goalAchieved) => {
83
+ if (goalAchieved && state.isAuthenticated) return 'main-menu'
84
+ if (state.failedAttempts > 3) return 'escalation'
85
+ return 'auth' // returning own ID = stay here
86
+ },
87
+ }
88
+ ```
89
+
90
+ The function receives:
91
+
92
+ - `state` -- the session state *after* `setState` has been applied for this turn
93
+ - `goalAchieved` -- the `goalAchieved` boolean from the LLM's response
94
+
95
+ Returning the agent's own ID keeps it active for the next turn.
96
+
97
+ ## Choosing a Transition Mode
98
+
99
+ | Scenario | Mode | Why |
100
+ |---|---|---|
101
+ | Single agent or leaf agent | Omit `transition` | Agent stays active, no routing exposed |
102
+ | Triage hub with known targets | `AgentId[]` | LLM picks from a bounded set |
103
+ | Strict sequential workflow | `function` | Code controls ordering |
104
+ | Routing depends on state conditions | `function` | Logic in code, not prompt |
105
+
106
+ ## Transition Mechanics
107
+
108
+ ### Recursive Chaining
109
+
110
+ When an agent transitions to a different agent, `manageFlow` recursively invokes the next agent within the same `send()` call.
111
+ The intermediate agent's `chatbotMessage` becomes `lastAgentUnsentMessage`, which is injected into the next agent's system prompt as transition context.
112
+ The user sees a single response even if multiple agents were involved.
113
+
114
+ ```text
115
+ User sends message
116
+ → Agent A responds (chatbotMessage: "Let me transfer you...")
117
+ → Agent B receives lastAgentUnsentMessage: "Let me transfer you..."
118
+ → Agent B responds to user (this is what the user sees)
119
+ ```
120
+
121
+ ### Recursion Limit
122
+
123
+ A3 enforces a maximum number of automatic transitions per `send()` call to prevent infinite loops.
124
+ The default limit is **10**, configurable via `ChatSessionConfig.agentRecursionLimit`.
125
+ Only transitions to a *different* agent count toward the limit (staying on the same agent does not increment the counter).
126
+
127
+ When the limit is reached:
128
+
129
+ - The current agent's response is returned to the user
130
+ - `nextAgentId` in the response points to the blocked target agent
131
+ - A warning is logged
132
+
133
+ ```typescript
134
+ const session = new ChatSession<MyState>({
135
+ sessionId: 'user-123',
136
+ store: new MemorySessionStore(),
137
+ initialAgentId: 'triage',
138
+ initialState: {},
139
+ provider,
140
+ agentRecursionLimit: 5, // lower limit for tighter control
141
+ })
142
+ ```
143
+
144
+ ### Streaming Transitions
145
+
146
+ During streaming, each transition yields an `AgentTransition` event with `{ fromAgentId, toAgentId }` between agents.
147
+ See the [StreamEvent table](./CORE-CONCEPTS.md#streamevent-types) in Core Concepts.
148
+
149
+ ```typescript
150
+ for await (const event of session.send({ message: 'Help me', stream: true })) {
151
+ if (event.type === 'AgentTransition') {
152
+ console.log(`${event.fromAgentId} → ${event.toAgentId}`)
153
+ }
154
+ }
155
+ ```
156
+
157
+ ## Non-Deterministic Routing Example
158
+
159
+ A complete triage pattern with LLM-driven routing:
160
+
161
+ ```typescript
162
+ import { z } from 'zod'
163
+ import { Agent, AgentRegistry, ChatSession, MemorySessionStore, BaseState } from '@genui/a3'
164
+ import { createBedrockProvider } from '@genui/a3-bedrock'
165
+
166
+ interface SupportState extends BaseState {
167
+ category?: string
168
+ resolved: boolean
169
+ }
170
+
171
+ // Triage agent — LLM picks the target from a constrained set
172
+ const triageAgent: Agent<SupportState> = {
173
+ id: 'triage',
174
+ description: 'Routes users to the correct support department',
175
+ prompt: `
176
+ You are a triage agent. Determine what the user needs and route them:
177
+ - "billing" for payment, invoices, or subscription questions
178
+ - "technical-support" for bugs, errors, or product issues
179
+ - "account" for password resets, profile changes, or access
180
+ Always set redirectToAgent on your first response.
181
+ `,
182
+ outputSchema: z.object({
183
+ category: z.string().optional(),
184
+ }),
185
+ transition: ['billing', 'technical-support', 'account'],
186
+ }
187
+
188
+ const billingAgent: Agent<SupportState> = {
189
+ id: 'billing',
190
+ description: 'Handles billing and payment questions',
191
+ prompt: `
192
+ You are a billing specialist. Help the user with payment or invoice issues.
193
+ Set goalAchieved to true once the billing question is fully answered.
194
+ `,
195
+ outputSchema: z.object({ resolved: z.boolean() }),
196
+ // Deterministic (function): code decides the next agent.
197
+ transition: (_state, goalAchieved) => goalAchieved ? 'triage' : 'billing',
198
+ }
199
+
200
+ const technicalSupportAgent: Agent<SupportState> = {
201
+ id: 'technical-support',
202
+ description: 'Handles technical issues and bugs',
203
+ prompt: `
204
+ You are a technical support specialist. Help the user troubleshoot their issue.
205
+ If the issue is resolved, redirect to triage so the user can raise other concerns.
206
+ If you need more information, stay on this agent to continue troubleshooting.
207
+ `,
208
+ outputSchema: z.object({ resolved: z.boolean() }),
209
+ // Non-deterministic (array): the LLM picks from the listed agents.
210
+ transition: ['triage', 'technical-support'],
211
+ }
212
+
213
+ const accountAgent: Agent<SupportState> = {
214
+ id: 'account',
215
+ description: 'Handles account management',
216
+ prompt: `
217
+ You are an account specialist. Help the user manage their account.
218
+ `,
219
+ outputSchema: z.object({ resolved: z.boolean() }),
220
+ // Default (omitted): agent stays active every turn.
221
+ }
222
+
223
+ // Setup
224
+ const registry = AgentRegistry.getInstance<SupportState>()
225
+ registry.register([triageAgent, billingAgent, technicalSupportAgent, accountAgent])
226
+
227
+ const provider = createBedrockProvider({
228
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
229
+ })
230
+
231
+ const session = new ChatSession<SupportState>({
232
+ sessionId: 'support-001',
233
+ store: new MemorySessionStore(),
234
+ initialAgentId: 'triage',
235
+ initialState: { resolved: false },
236
+ provider,
237
+ })
238
+
239
+ // Conversation
240
+ const response = await session.send({ message: 'I got charged twice last month' })
241
+ // Triage agent sets redirectToAgent to "billing"
242
+ // → billing agent responds to the user in the same request
243
+ console.log(response.activeAgentId) // 'billing'
244
+ console.log(response.responseMessage) // Billing agent's response
245
+ ```