@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,84 @@
1
+ # Architecture
2
+
3
+ ## Architecture at a Glance
4
+
5
+ ```text
6
+ ┌──────────────────────────────────────────────────────────────┐
7
+ │ Your Application │
8
+ └─────────────────────────┬────────────────────────────────────┘
9
+ │ ▲
10
+ .send(message) ChatResponse
11
+ │ { responseMessage,
12
+ │ state, goalAchieved }
13
+ ▼ │
14
+ ┌──────────────────────────────────────────────────────────────┐
15
+ │ ChatSession │
16
+ │ │
17
+ │ 1. Load session from store 6. Save updated session │
18
+ │ 2. Append user message 5. Append bot message │
19
+ │ │
20
+ └───────────┬──────────────────────────────────────────────────┘
21
+ │ ▲
22
+ │ manageFlow({ agent, │ { responseMessage,
23
+ │ sessionData }) │ newState,
24
+ │ │ nextAgentId }
25
+ ▼ │
26
+ ┌──────────────────────────────────────────────────────────────┐
27
+ │ ChatFlow │
28
+ │ │
29
+ │ Looks up active agent, delegates, checks routing │
30
+ │ │
31
+ │ If nextAgent ≠ activeAgent: │
32
+ │ ┌──────────────────────────────────────────────────────┐ │
33
+ │ │ Recursive call to manageFlow │ │
34
+ │ │ with new agent + updated state │ │
35
+ │ └──────────────────────────────────────────────────────┘ │
36
+ │ │
37
+ └───────────┬──────────────────────────────────────────────────┘
38
+ │ ▲
39
+ │ generateResponse │ { chatbotMessage,
40
+ │ ({ agent, sessionData }) │ newState,
41
+ │ │ nextAgentId }
42
+ ▼ │
43
+ ┌──────────────────────────────────────────────────────────────┐
44
+ │ Active Agent │
45
+ │ │
46
+ │ • Builds system prompt (prompt) │
47
+ │ • Defines output schema (Zod) │
48
+ │ • Determines next agent (transition) │
49
+ │ │
50
+ └───────────┬──────────────────────────────────────────────────┘
51
+ │ ▲
52
+ │ prompt + schema │ structured JSON
53
+ ▼ │
54
+ ┌──────────────────────────────────────────────────────────────┐
55
+ │ Provider │
56
+ │ (Bedrock, OpenAI, Anthropic) │
57
+ │ │
58
+ │ • Converts Zod → JSON Schema │
59
+ │ • Merges message history │
60
+ │ • Model fallback on error │
61
+ │ │
62
+ └───────────┬──────────────────────────────────────────────────┘
63
+ │ ▲
64
+ │ API request │ API response
65
+ ▼ │
66
+ ┌──────────────────────────────────────────────────────────────┐
67
+ │ LLM │
68
+ └──────────────────────────────────────────────────────────────┘
69
+ ```
70
+
71
+ ## How It Flows
72
+
73
+ 1. Your app calls `session.send(message)` with the user's input.
74
+ 1. **ChatSession** loads session data (history, state) from the configured store and appends the user message.
75
+ 1. **ChatFlow** looks up the active agent and calls `generateResponse`.
76
+ 1. The **Agent** builds a system prompt, defines its Zod output schema, and delegates to the provider.
77
+ 1. The **Provider** sends the request to the LLM and returns structured JSON.
78
+ 1. The **Agent** extracts state updates and a routing decision (`nextAgentId`) from the response.
79
+ 1. If the next agent differs from the active agent, ChatFlow **recursively calls `manageFlow`** with the new agent and updated state.
80
+ 1. **ChatSession** appends the bot message, saves the updated session, and returns a `ChatResponse` to your app.
81
+
82
+ Agents route dynamically.
83
+ There is no fixed graph.
84
+ Each agent decides whether to continue or hand off based on the conversation.
@@ -0,0 +1,347 @@
1
+ # Core Concepts
2
+
3
+ ## Agent
4
+
5
+ An agent is the fundamental building block.
6
+ Each agent has a focused responsibility and defines how it generates responses, what structured data it extracts, and when to hand off to another agent.
7
+
8
+ ```typescript
9
+ import { z } from 'zod'
10
+ import { Agent, BaseState } from '@genui/a3'
11
+
12
+ interface MyState extends BaseState {
13
+ userName?: string
14
+ }
15
+
16
+ const greetingAgent: Agent<MyState> = {
17
+ // Identity
18
+ id: 'greeting',
19
+ name: 'Greeting Agent',
20
+ description: 'Greets the user and collects their name',
21
+
22
+ // Prompt: instructions for the LLM
23
+ prompt: async () => `
24
+ You are a friendly greeting agent.
25
+ Ask the user for their name, then greet them.
26
+ Set goalAchieved to true once you know their name.
27
+ `,
28
+
29
+ // Output schema: Zod schema for structured data extraction
30
+ outputSchema: z.object({
31
+ userName: z.string().optional(),
32
+ }),
33
+
34
+ // Routing: decide the next agent after each turn
35
+ transition: (state, goalAchieved) => {
36
+ return goalAchieved ? 'next-agent' : 'greeting' // replace 'next-agent' with a registered agent ID
37
+ },
38
+ }
39
+ ```
40
+
41
+ ### Agent Properties
42
+
43
+ | Property | Required | Description |
44
+ |---|---|---|
45
+ | `id` | Yes | Unique identifier for the agent |
46
+ | `name` | No | Human-readable display name |
47
+ | `description` | Yes | What this agent does (used in agent pool prompts) |
48
+ | `prompt` | Yes | System prompt string, or async function returning the system prompt |
49
+ | `outputSchema` | Yes | Zod schema defining structured data to extract from LLM responses |
50
+ | `provider` | No | Per-agent provider override; falls back to the session-level provider |
51
+ | `generateResponse` | No | Custom response generator. Must check `input.stream` and return a `Promise` (blocking) or `AsyncGenerator` (streaming). See [Custom generateResponse](#custom-generateresponse). Defaults to the built-in pipeline |
52
+ | `setState` | No | Maps extracted LLM data into the shared state object (defaults to shallow merge) |
53
+ | `transition` | No | Routing config: a function `(state, goalAchieved) => AgentId` for deterministic routing, or an `AgentId[]` array for LLM-driven routing |
54
+ | `filterHistoryStrategy` | No | Custom function to filter conversation history before sending to the LLM |
55
+ | `widgets` | No | Zod schemas defining widgets available to the agent (static record or function) |
56
+
57
+ ## AgentRegistry
58
+
59
+ A singleton registry where all agents are registered before use.
60
+
61
+ ```typescript
62
+ import { AgentRegistry } from '@genui/a3'
63
+
64
+ const registry = AgentRegistry.getInstance<MyState>()
65
+
66
+ // Register one or many agents
67
+ registry.register(greetingAgent)
68
+ registry.register([authAgent, mainAgent, wrapUpAgent])
69
+
70
+ // Query the registry
71
+ registry.has('greeting') // true
72
+ registry.get('greeting') // Agent object
73
+ registry.getAll() // All registered agents
74
+ registry.getDescriptions() // { greeting: 'Greets the user...' }
75
+ registry.count // 4
76
+ ```
77
+
78
+ ## ChatSession
79
+
80
+ The primary interface your application uses to interact with A3.
81
+ Create a session, send messages, get responses.
82
+
83
+ ```typescript
84
+ import { ChatSession, MemorySessionStore } from '@genui/a3'
85
+ import { createBedrockProvider } from '@genui/a3-bedrock'
86
+
87
+ const provider = createBedrockProvider({
88
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'],
89
+ })
90
+
91
+ const session = new ChatSession<MyState>({
92
+ sessionId: 'user-123',
93
+ store: new MemorySessionStore(), // pluggable persistence
94
+ initialAgentId: 'greeting',
95
+ initialState: { userName: undefined },
96
+ provider, // required
97
+ })
98
+
99
+ // Blocking: send a message and get a structured response
100
+ const result = await session.send({ message: 'Hello!' })
101
+
102
+ result.responseMessage // "Hi there! What's your name?"
103
+ result.activeAgentId // 'greeting'
104
+ result.nextAgentId // 'greeting'
105
+ result.state // { userName: undefined }
106
+ result.goalAchieved // false
107
+ result.sessionId // 'user-123'
108
+
109
+ // Streaming: send a message and stream the response
110
+ for await (const event of session.send({ message: 'Hello!', stream: true })) {
111
+ console.log(event)
112
+ }
113
+ ```
114
+
115
+ ## State
116
+
117
+ A3 uses a shared global state object that flows across all agents in a session.
118
+ Define your state by extending `BaseState`.
119
+
120
+ ```typescript
121
+ import { BaseState } from '@genui/a3'
122
+
123
+ interface AppState extends BaseState {
124
+ userName?: string
125
+ isAuthenticated: boolean
126
+ currentStep: string
127
+ }
128
+ ```
129
+
130
+ Each agent's `setState` merges its extracted data into this shared state.
131
+ When agents switch, the full state carries over.
132
+
133
+ ## Output Schemas
134
+
135
+ Every agent defines a Zod schema for the structured data it needs to extract from LLM responses.
136
+ A3 merges this with base fields (`chatbotMessage`, `goalAchieved`, `redirectToAgent`) and validates the LLM output.
137
+
138
+ ```typescript
139
+ // Static schema
140
+ const schema = z.object({
141
+ userName: z.string().optional(),
142
+ sentiment: z.enum(['positive', 'neutral', 'negative']),
143
+ })
144
+
145
+ // Dynamic schema (based on current session state)
146
+ const dynamicSchema = (sessionData) => z.object({
147
+ userName: z.string().describe(`Current: ${sessionData.state.userName ?? 'unknown'}`),
148
+ })
149
+ ```
150
+
151
+ Schemas serve two purposes:
152
+
153
+ 1. **Instruct the LLM** on what data to extract (field names and descriptions become part of the prompt)
154
+ 1. **Validate the response** at runtime so your application always receives well-typed data
155
+
156
+ ## Routing
157
+
158
+ The `transition` property controls how an agent hands off to the next agent.
159
+ It supports three modes:
160
+
161
+ - **Default (omitted):** Agent stays active. No other agents are shown in the LLM's agent pool.
162
+ - **Non-deterministic (array):** The LLM picks from a bounded set of agent IDs via `redirectToAgent`.
163
+ - **Deterministic (function):** Your code decides the next agent — `redirectToAgent` is not exposed to the LLM.
164
+
165
+ ```typescript
166
+ // Default — agent stays active, omit transition entirely
167
+ // (no routing targets exposed to the LLM)
168
+
169
+ // Non-deterministic — LLM picks from these
170
+ transition: ['billing', 'support', 'account']
171
+
172
+ // Deterministic — code controls routing
173
+ transition: (state, goalAchieved) => goalAchieved ? 'main-menu' : 'auth'
174
+ ```
175
+
176
+ In all cases, when a transition happens, **ChatFlow recursively invokes the next agent** in the same request.
177
+ The user sees a single response, even if multiple agents were involved.
178
+
179
+ For the full transition reference — modes, mechanics, recursion limits, and examples — see [Transitions](./TRANSITIONS.md).
180
+
181
+ ## Session Stores
182
+
183
+ A3 uses pluggable session stores for persistence.
184
+ Any object implementing the `SessionStore` interface works.
185
+
186
+ ```typescript
187
+ interface SessionStore<TState extends BaseState> {
188
+ load(sessionId: string): Promise<SessionData<TState> | null>
189
+ save(sessionId: string, data: SessionData<TState>): Promise<void>
190
+ delete?(sessionId: string): Promise<void>
191
+ }
192
+ ```
193
+
194
+ **Built-in stores:**
195
+
196
+ | Store | Use case |
197
+ |---|---|
198
+ | `MemorySessionStore` | Development and testing (sessions lost on restart) |
199
+
200
+ Custom stores are straightforward to implement for Redis, DynamoDB, PostgreSQL, or any other backend.
201
+ See [Custom Stores](./CUSTOM_STORES.md) for a step-by-step implementation guide.
202
+
203
+ ## Providers
204
+
205
+ Providers handle communication with LLM backends.
206
+ A3 uses a pluggable `Provider` interface.
207
+ Providers are separate packages -- see the [Providers documentation](./PROVIDERS.md).
208
+
209
+ ```typescript
210
+ import { Provider } from '@genui/a3'
211
+ ```
212
+
213
+ The `Provider` interface requires three members:
214
+
215
+ | Member | Description |
216
+ |---|---|
217
+ | `sendRequest(request)` | Blocking request that returns a structured JSON response |
218
+ | `sendRequestStream(request)` | Streaming request that yields AG-UI compatible events |
219
+ | `name` | Human-readable name for logging |
220
+
221
+ **Built-in providers:**
222
+
223
+ ```typescript
224
+ import { createBedrockProvider } from '@genui/a3-bedrock'
225
+
226
+ const provider = createBedrockProvider({
227
+ models: [
228
+ 'us.anthropic.claude-sonnet-4-5-20250929-v1:0', // primary
229
+ 'us.anthropic.claude-haiku-4-5-20251001-v1:0', // fallback
230
+ ],
231
+ region: 'us-east-1', // optional, defaults to AWS SDK default
232
+ })
233
+ ```
234
+
235
+ ```typescript
236
+ import { createOpenAIProvider } from '@genui/a3-openai'
237
+
238
+ const provider = createOpenAIProvider({
239
+ models: ['gpt-4o', 'gpt-4o-mini'],
240
+ apiKey: process.env.OPENAI_API_KEY, // optional, defaults to OPENAI_API_KEY env var
241
+ })
242
+ ```
243
+
244
+ ```typescript
245
+ import { createAnthropicProvider } from '@genui/a3-anthropic'
246
+
247
+ const provider = createAnthropicProvider({
248
+ models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],
249
+ apiKey: process.env.ANTHROPIC_API_KEY, // optional, defaults to ANTHROPIC_API_KEY env var
250
+ })
251
+ ```
252
+
253
+ All three providers support:
254
+
255
+ - **Model fallback** (primary model fails -> falls back to next in list)
256
+ - **Blocking and streaming** modes
257
+ - **Structured output** via Zod schemas
258
+ - **Resilience** -- automatic retries with backoff, timeouts, and model fallback ([docs](./RESILIENCE.md))
259
+
260
+ See the [Providers documentation](./PROVIDERS.md) for full configuration options.
261
+ To build a provider for an LLM not listed above, see [Creating a Custom Provider](./CUSTOM_PROVIDERS.md).
262
+
263
+ **Per-agent provider override:**
264
+
265
+ Each agent can optionally specify its own `provider` to override the session-level provider:
266
+
267
+ ```typescript
268
+ const agent: Agent<MyState> = {
269
+ id: 'premium',
270
+ description: 'Handles premium tier requests using GPT-4o',
271
+ provider: createOpenAIProvider({ models: ['gpt-4o'] }),
272
+ // ...
273
+ }
274
+ ```
275
+
276
+ ## Streaming
277
+
278
+ A3 supports real-time token streaming via `send({ message, stream: true })`.
279
+ Instead of waiting for a complete response, your application receives events as they happen.
280
+
281
+ ```typescript
282
+ for await (const event of session.send({ message: 'Hello!', stream: true })) {
283
+ switch (event.type) {
284
+ case 'TextMessageContent':
285
+ process.stdout.write(event.delta) // real-time token output
286
+ break
287
+ case 'AgentTransition':
288
+ console.log(`${event.fromAgentId} → ${event.toAgentId}`)
289
+ break
290
+ case 'RunFinished':
291
+ console.log('Final state:', event.response.state)
292
+ break
293
+ case 'RunError':
294
+ console.error('Error:', event.error)
295
+ break
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### StreamEvent Types
301
+
302
+ | Event Type | Key Fields | Description |
303
+ |---|---|---|
304
+ | `RunStarted` | `runId`, `threadId` | Stream has begun |
305
+ | `TextMessageStart` | `messageId` | A new text message is starting |
306
+ | `TextMessageContent` | `delta`, `agentId` | A text chunk from the active agent |
307
+ | `TextMessageEnd` | `messageId` | Text message complete |
308
+ | `ToolCallStart` | `toolCallId`, `toolCallName` | Tool/function call initiated |
309
+ | `ToolCallArgs` | `toolCallId`, `delta` | Tool argument chunk |
310
+ | `ToolCallEnd` | `toolCallId` | Tool call complete |
311
+ | `ToolCallResult` | `data`, `agentId` | Tool execution result with extracted data |
312
+ | `AgentTransition` | `fromAgentId`, `toAgentId` | Agent handoff occurred |
313
+ | `RunFinished` | `response` | Stream complete with final `ChatResponse` |
314
+ | `RunError` | `error`, `agentId` | Error during stream |
315
+
316
+ ## Custom generateResponse
317
+
318
+ When you provide a custom `generateResponse`, it receives `input.stream` (a boolean) so you can branch on whether the caller requested streaming or blocking.
319
+ You **must** return the correct type for the mode:
320
+
321
+ - **Blocking** (`input.stream === false`): return a `Promise<AgentResponseResult>`
322
+ - **Streaming** (`input.stream === true`): return an `AsyncGenerator<StreamEvent, AgentResponseResult>`
323
+
324
+ `manageFlow()` in `chatFlow.ts` validates the return type at runtime.
325
+ If you return a `Promise` when streaming was requested (or vice-versa), you will get a descriptive error:
326
+
327
+ > Agent "my-agent" returned a Promise from generateResponse, but streaming was requested (input.stream = true).
328
+ > Return an AsyncGenerator instead, or check input.stream to branch behavior.
329
+
330
+ ```typescript
331
+ import { simpleAgentResponse, simpleAgentResponseStream } from '@genui/a3'
332
+
333
+ const agent: Agent<MyState> = {
334
+ id: 'custom',
335
+ description: 'A helpful assistant with custom response handling',
336
+ prompt: 'You are a helpful assistant.',
337
+ outputSchema: z.object({ sentiment: z.string() }),
338
+ generateResponse(input) {
339
+ // Custom pre-processing here...
340
+
341
+ if (input.stream) {
342
+ return simpleAgentResponseStream(input)
343
+ }
344
+ return simpleAgentResponse(input)
345
+ },
346
+ }
347
+ ```
@@ -0,0 +1,36 @@
1
+ # Custom Logging
2
+
3
+ By default, A3 logs internally using [tslog](https://tslog.js.org) with no configuration required.
4
+ If you want A3's internal logs to flow through your own logging infrastructure — for example, to include your app's trace IDs, ship logs to a provider like Datadog or OpenTelemetry, or change the output format — you can provide a custom logger.
5
+
6
+ ## How it works
7
+
8
+ A3 uses [LogLayer](https://loglayer.dev) as its logging interface.
9
+ LogLayer is a transport-agnostic logging layer: you configure it with a _transport_ that wraps your preferred logging library, and LogLayer routes A3's log calls through it.
10
+
11
+ You do not need to know the LogLayer API to use your own logger — you only need to wrap it in the appropriate LogLayer transport.
12
+
13
+ ## Default behaviour
14
+
15
+ Out of the box, A3:
16
+
17
+ - Outputs pretty, human-readable logs in development (`NODE_ENV !== 'production'`)
18
+ - Outputs structured JSON in production
19
+ - Defaults to log level `info`, overridden by the `A3_LOG_LEVEL` environment variable
20
+
21
+ ```bash
22
+ # Supported levels: silly | trace | debug | info | warn | error | fatal
23
+ A3_LOG_LEVEL=debug node your-app.js
24
+ ```
25
+
26
+ ## Configuring a custom logger
27
+
28
+ A3 uses the [LogLayer](https://loglayer.dev) interface.
29
+ Call `configureLogger()` with a `LogLayer` instance configured for your preferred logging library.
30
+ See the [LogLayer documentation](https://loglayer.dev) for setup instructions and the full list of available transports.
31
+
32
+ ## Further reading
33
+
34
+ - [LogLayer documentation](https://loglayer.dev) — full API, plugins, multi-transport, context, and more
35
+ - [LogLayer transports](https://loglayer.dev/transports) — all available transports with setup instructions
36
+ - [tslog documentation](https://tslog.js.org) — the default A3 logger