@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,331 @@
1
+ # Widgets
2
+
3
+ Widgets are optional, schema-driven UI components that agents can include alongside their text responses.
4
+ The LLM populates widget data based on Zod schemas; the consuming application renders corresponding UI.
5
+
6
+ Widgets are not required to use A3.
7
+ They are an opt-in pattern for agents that need to surface structured, visual information beyond plain text.
8
+
9
+ For agent basics, see [Core Concepts](./CORE-CONCEPTS.md#agent).
10
+
11
+ ## How Widgets Work
12
+
13
+ 1. You define a `widgets` property on your agent — a record of Zod schemas keyed by widget name.
14
+ 1. A3 automatically injects the schema descriptions into the LLM prompt, so the LLM knows what data to produce.
15
+ 1. The LLM returns structured data including a `widgets` object with populated fields.
16
+ 1. A3 validates the response against your schemas and strips any empty widget objects.
17
+ 1. Your application receives `result.widgets` from `ChatSession.send()` and renders the appropriate UI.
18
+
19
+ You define the schemas.
20
+ A3 handles prompt injection, schema merging, validation, and cleanup.
21
+ Your application handles rendering.
22
+
23
+ ## Defining Widgets on an Agent
24
+
25
+ The `widgets` property on an [Agent](./CORE-CONCEPTS.md#agent) accepts either a static record of Zod schemas or a function that returns one.
26
+
27
+ ### Static Widgets
28
+
29
+ When widget schemas are constant, define `widgets` as a plain object.
30
+ Each key becomes a widget name in the LLM response; each value is a Zod schema the LLM fills.
31
+
32
+ ```typescript
33
+ import { z } from 'zod'
34
+ import { Agent, BaseState } from '@genui/a3'
35
+
36
+ interface MyState extends BaseState {
37
+ appointmentDate?: string
38
+ }
39
+
40
+ const BookingSchema = z.object({
41
+ date: z.string().describe('Appointment date in human-readable format'),
42
+ time: z.string().describe('Appointment time'),
43
+ location: z.string().describe('Appointment location address'),
44
+ })
45
+
46
+ const reviewAgent: Agent<MyState> = {
47
+ id: 'review',
48
+ name: 'Review Agent',
49
+ description: 'Reviews appointment details with the user',
50
+ prompt: async () => `
51
+ You review appointment details with the user.
52
+ Populate the booking widget with the confirmed details.
53
+ `,
54
+ outputSchema: z.object({
55
+ appointmentDate: z.string().optional(),
56
+ }),
57
+ widgets: {
58
+ booking: BookingSchema,
59
+ },
60
+ transition: ['next-agent'],
61
+ }
62
+ ```
63
+
64
+ ### Dynamic Widgets
65
+
66
+ When widget configuration depends on runtime state, define `widgets` as a function.
67
+ It receives `sessionData` and returns the widget record.
68
+
69
+ ```typescript
70
+ import { z } from 'zod'
71
+ import { Agent, BaseState, BaseChatContext, SessionData } from '@genui/a3'
72
+
73
+ interface MyState extends BaseState {
74
+ hasInsurance?: boolean
75
+ }
76
+
77
+ const PatientSchema = z.object({
78
+ name: z.string().describe('Patient full name'),
79
+ dob: z.string().describe('Date of birth'),
80
+ })
81
+
82
+ const InsuranceSchema = z.object({
83
+ provider: z.string().describe('Insurance provider name'),
84
+ policyNumber: z.string().describe('Policy number'),
85
+ })
86
+
87
+ const summaryAgent: Agent<MyState> = {
88
+ id: 'summary',
89
+ name: 'Summary Agent',
90
+ description: 'Displays a summary of collected information',
91
+ prompt: async () => `Summarize the information collected so far.`,
92
+ outputSchema: z.object({}),
93
+ widgets: (sessionData: SessionData<MyState>) => ({
94
+ patient: PatientSchema,
95
+ ...(sessionData.state.hasInsurance && {
96
+ insurance: InsuranceSchema,
97
+ }),
98
+ }),
99
+ transition: (state, goalAchieved) => (goalAchieved ? 'done' : 'summary'),
100
+ }
101
+ ```
102
+
103
+ Use dynamic widgets to conditionally include or exclude widgets based on state, context, or any runtime condition.
104
+
105
+ ## The `interactive` Pattern
106
+
107
+ Widgets can be extended with an `interactive` field to signal whether the widget is interactive or readonly.
108
+ This is a convention — A3 does not enforce it at the framework level.
109
+ The consuming application's widget renderer uses this field to decide how to render.
110
+
111
+ When `interactive` defaults to `false`, the LLM returns `{ "interactive": false, ...data }`.
112
+ The application can strip the `interactive` property and use its value to control rendering behavior — for example, hiding action buttons or disabling form inputs.
113
+
114
+ This pattern is commonly used with [dynamic widgets](#dynamic-widgets) for summary or recap views:
115
+
116
+ ```typescript
117
+ const nonInteractive = { interactive: z.boolean().default(false) }
118
+
119
+ const wrapUpAgent: Agent<MyState> = {
120
+ id: 'wrap-up',
121
+ // ...other properties
122
+ widgets: (sessionData) => ({
123
+ patient: PatientSchema.extend(nonInteractive),
124
+ booking: BookingSchema.extend(nonInteractive),
125
+ }),
126
+ }
127
+ ```
128
+
129
+ ## Widget Data in the Response
130
+
131
+ ### `ChatResponse.widgets`
132
+
133
+ `ChatSession.send()` returns a [`ChatResponse`](./CORE-CONCEPTS.md#chat-response) with an optional `widgets?: object` property.
134
+ If no widgets were populated (or all widget objects were empty), `widgets` is `undefined`.
135
+
136
+ ### `Message.widgets`
137
+
138
+ Widget data is also stored on individual messages in the conversation history:
139
+
140
+ ```typescript
141
+ type Message = {
142
+ text: string
143
+ widgets?: object
144
+ // ...other properties
145
+ }
146
+ ```
147
+
148
+ This allows the application to re-render widgets when displaying conversation history.
149
+
150
+ ### Empty Widget Stripping
151
+
152
+ A3 automatically strips empty widget objects.
153
+ If the LLM returns `"widgets": {}`, it becomes `undefined` in the response.
154
+ Your application only needs to check `if (result.widgets)` — no need to guard against empty objects.
155
+
156
+ ## Rendering Widgets in Your Application
157
+
158
+ A3 handles schemas, prompt injection, validation, and cleanup.
159
+ Your application handles rendering.
160
+ This section covers recommended patterns for building widget components and wiring them into your chat UI.
161
+ Examples use React, but the concepts — registry, context, dispatcher — apply to any framework.
162
+
163
+ ### Widget Component Structure
164
+
165
+ Each widget pairs a Zod schema (defined on the agent) with a UI component that receives `z.infer<typeof Schema>` as props.
166
+ Components read `disabled` and `readonly` flags from widget context to control interactivity.
167
+
168
+ **BookingCard** — a display card with action buttons:
169
+
170
+ ```tsx
171
+ function BookingCard({ date, time, location }: z.infer<typeof BookingSchema>) {
172
+ const { disabled, readonly } = useWidgetContext()
173
+ return (
174
+ <div>
175
+ <p>{date} at {time}</p>
176
+ <p>{location}</p>
177
+ {!readonly && (
178
+ <>
179
+ <button disabled={disabled}>Confirm</button>
180
+ <button disabled={disabled}>Update</button>
181
+ </>
182
+ )}
183
+ </div>
184
+ )
185
+ }
186
+ ```
187
+
188
+ **TimeSlotsCard** — interactive selection where each button sends a message:
189
+
190
+ ```tsx
191
+ function TimeSlotsCard({ slots }: z.infer<typeof TimeSlotsSchema>) {
192
+ const { disabled } = useWidgetContext()
193
+ const { sendMessage } = useChatContext()
194
+ return (
195
+ <div>
196
+ {slots.map((slot) => (
197
+ <button key={slot} disabled={disabled} onClick={() => sendMessage(slot)}>
198
+ {slot}
199
+ </button>
200
+ ))}
201
+ </div>
202
+ )
203
+ }
204
+ ```
205
+
206
+ ### Recommended Patterns
207
+
208
+ Here are patterns that work well for rendering A3 widgets.
209
+ Your application can implement these however you like — these are proven patterns from production A3 apps.
210
+
211
+ **Widget Registry** — a mapping from widget name strings to components.
212
+ Keys must match the agent's `widgets` keys.
213
+
214
+ ```typescript
215
+ const widgetRegistry: Record<string, ComponentType<any>> = {
216
+ booking: BookingCard,
217
+ time_slots: TimeSlotsCard,
218
+ patient: PatientCard,
219
+ }
220
+ ```
221
+
222
+ **Widget Dispatcher** — a component that looks up the registry, strips `interactive`, and wraps in context.
223
+
224
+ ```tsx
225
+ function Widget({ widget, data, disabled }: { widget: string; data: any; disabled: boolean }) {
226
+ const Component = widgetRegistry[widget]
227
+ if (!Component) return null
228
+ const { interactive, ...props } = data
229
+ return (
230
+ <WidgetContext.Provider value={{ disabled, readonly: interactive === false }}>
231
+ <Component {...props} />
232
+ </WidgetContext.Provider>
233
+ )
234
+ }
235
+ ```
236
+
237
+ **Widget Context** — a React context providing rendering flags.
238
+
239
+ ```typescript
240
+ const WidgetContext = createContext({ disabled: false, readonly: false })
241
+ const useWidgetContext = () => useContext(WidgetContext)
242
+ ```
243
+
244
+ | Flag | Meaning | Set when |
245
+ |------|---------|----------|
246
+ | `disabled` | Widget cannot accept input | Message is not the latest |
247
+ | `readonly` | Widget hides action buttons | `interactive` is `false` |
248
+
249
+ ### Rendering Widgets from Messages
250
+
251
+ The message renderer iterates `message.widgets` and passes each entry to the dispatcher:
252
+
253
+ ```tsx
254
+ {message.widgets && Object.entries(message.widgets).map(([name, data]) => (
255
+ <Widget key={name} widget={name} data={data} disabled={!isLastMessage} />
256
+ ))}
257
+ ```
258
+
259
+ Key points:
260
+
261
+ 1. Pass `disabled={!isLastMessage}` so only the latest message's widgets accept input.
262
+ 1. A single message can contain multiple widgets.
263
+ 1. Guard with `message.widgets &&` since widgets are optional.
264
+
265
+ ### The User Interaction Loop
266
+
267
+ The pattern that makes widgets interactive:
268
+
269
+ 1. Widget accesses `sendMessage` from chat context.
270
+ 1. On user action, calls `sendMessage('user choice')`.
271
+ 1. This triggers another `ChatSession.send()` cycle.
272
+ 1. The new response arrives with new widgets and/or text.
273
+ 1. Previous message widgets become `disabled`.
274
+
275
+ ```text
276
+ User clicks → sendMessage() → ChatSession.send() → new response → new widgets → previous widgets disabled
277
+ ```
278
+
279
+ Example — a time slot button that continues the conversation:
280
+
281
+ ```tsx
282
+ <button disabled={disabled} onClick={() => sendMessage(slot)}>
283
+ {slot}
284
+ </button>
285
+ ```
286
+
287
+ When the user clicks, the selected slot is sent as a message.
288
+ The agent processes it, updates state, and responds — potentially with a new widget for the next step.
289
+
290
+ ## Best Practices
291
+
292
+ 1. **Use `.describe()` on all schema fields.**
293
+ A3 injects schema descriptions into the LLM prompt automatically.
294
+ Clear descriptions lead to better-populated widget data.
295
+
296
+ 1. **Keep widget schemas focused.**
297
+ One widget per concern.
298
+ A booking widget shows booking data; a patient widget shows patient data.
299
+ Avoid monolithic schemas that combine unrelated information.
300
+
301
+ 1. **Use dynamic widgets to conditionally show or hide based on state.**
302
+ Rather than having the LLM decide whether to populate a widget, use the function form to only include widgets that are relevant to the current state.
303
+
304
+ 1. **Use the `interactive` extension for readonly summary views.**
305
+ When re-displaying previously collected data (e.g., during a wrap-up step), extend schemas with `interactive: z.boolean().default(false)` so the application can render them as non-interactive.
306
+
307
+ 1. **Share Zod schemas between agent definitions and component types.**
308
+ Use `z.infer<typeof Schema>` for component props so they stay in sync with the LLM output automatically.
309
+ If the schema changes, TypeScript catches mismatches at compile time — no manual type maintenance.
310
+
311
+ 1. **Centralize widget mapping in a registry.**
312
+ A single `widgetRegistry` record decouples message rendering from component details.
313
+ Adding a new widget means one registry entry and one component — no changes to the message renderer.
314
+
315
+ 1. **Consider disabling text input when interactive widgets are active.**
316
+ When a message contains interactive widgets, guide users to interact via the widget rather than free-form text.
317
+
318
+ ```typescript
319
+ function hasInteractiveWidget(widgets?: object): boolean {
320
+ if (!widgets) return false
321
+ return Object.values(widgets).some(
322
+ (data) => typeof data === 'object' && data !== null && ('interactive' in data ? data.interactive !== false : true)
323
+ )
324
+ }
325
+ ```
326
+
327
+ Pass the result to your chat input to conditionally disable it:
328
+
329
+ ```tsx
330
+ <ChatInput disabled={hasInteractiveWidget(latestMessage?.widgets)} />
331
+ ```
@@ -0,0 +1,104 @@
1
+ # Logging
2
+
3
+ A3 uses [LogLayer](https://loglayer.dev) as its logging abstraction and [tslog](https://tslog.js.org) as the default output backend.
4
+
5
+ ## For package users
6
+
7
+ If you want A3's logs to flow through your own logging infrastructure, see [Custom Logging](../CUSTOM_LOGGING.md).
8
+
9
+ ---
10
+
11
+ ## Architecture
12
+
13
+ ### The `log` singleton
14
+
15
+ All logging within the A3 package is done through a single `log` object exported from `src/utils/logger/`.
16
+
17
+ ```typescript
18
+ import { log } from '@utils/logger'
19
+ ```
20
+
21
+ Internally, `log` is a JavaScript `Proxy` that delegates every property access to `getLogger()` at call time.
22
+ This means:
23
+
24
+ - Any file can import `log` once at the top and use it directly — no need to call `getLogger()` at each use site.
25
+ - If a user calls `configureLogger()` at application startup (before the first log statement fires), the new logger takes effect automatically.
26
+ The `log` reference does not need to be re-imported.
27
+
28
+ ### `getLogger()` and `configureLogger()`
29
+
30
+ - `getLogger()` — lazily initialises and returns the active [`ILogLayer`](https://loglayer.dev) instance.
31
+ On first call, if no custom logger has been set, it creates the default tslog-backed logger.
32
+ - `configureLogger(logger: ILogLayer)` — replaces the active logger.
33
+ Should be called once at application startup, before any `ChatSession` is created.
34
+
35
+ Both are exported from `@genui/a3` as part of the public API.
36
+
37
+ ### Default logger
38
+
39
+ The default logger is a `LogLayer` instance wrapping a `tslog` transport:
40
+
41
+ - Pretty, human-readable output when `NODE_ENV !== 'production'`
42
+ - Structured JSON output when `NODE_ENV === 'production'`
43
+ - Log level controlled by the `A3_LOG_LEVEL` environment variable (default: `info`)
44
+
45
+ ---
46
+
47
+ ## Adding logs in A3 code
48
+
49
+ Import `log` and use the [LogLayer API](https://loglayer.dev):
50
+
51
+ ```typescript
52
+ import { log } from '@utils/logger'
53
+
54
+ // Basic logging
55
+ log.info('Hello world!')
56
+
57
+ // Logging with metadata (attached to this log entry only)
58
+ log.withMetadata({ agentId: 'greeting', sessionId: 'abc' }).debug('Agent selected')
59
+
60
+ // Logging with context (persists across subsequent log calls on this instance)
61
+ log.withContext({ sessionId: 'abc' })
62
+ log.info('Processing request')
63
+
64
+ // Logging errors
65
+ log.withError(new Error('Something went wrong')).error('Failed to process request')
66
+ ```
67
+
68
+ For the full LogLayer API — including `withPrefix`, child loggers, plugins, and multi-transport — see the [LogLayer documentation](https://loglayer.dev).
69
+
70
+ ### ⚠️ `withContext()` and shared server state
71
+
72
+ `log` is a module-level singleton shared across all requests in a running process.
73
+ `withContext()` mutates the logger instance and **persists across all subsequent log calls** — including those from other users' requests on the same pod.
74
+
75
+ Use `withMetadata()` for any request-scoped data (agentId, sessionId, etc.).
76
+ It applies only to the single log call it's chained on.
77
+
78
+ ```typescript
79
+ // ✅ Safe — applies to this log call only
80
+ log.withMetadata({ agentId: 'greeting', sessionId: 'abc' }).debug('Agent selected')
81
+
82
+ // ❌ Dangerous in a server — persists on the shared instance across all requests
83
+ log.withContext({ sessionId: 'abc' })
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Log levels
89
+
90
+ The `A3_LOG_LEVEL` environment variable accepts the following values (lowest to highest):
91
+
92
+ `silly` → `trace` → `debug` → `info` _(default)_ → `warn` → `error` → `fatal`
93
+
94
+ ```bash
95
+ A3_LOG_LEVEL=debug node your-app.js
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Key files
101
+
102
+ - `src/utils/logger/index.ts` — logger module: `log`, `getLogger()`, `configureLogger()`, default tslog setup
103
+ - `src/index.ts` — public exports: `configureLogger`, `getLogger`, `ILogLayer`
104
+ - `jest.setup.ts` — global Jest mock for `@utils/logger` (replaces `log` with a mock object in tests)