@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,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)
|