@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,36 @@
1
+ import type { Metadata } from 'next'
2
+ import { ThemeProvider } from './ThemeProvider'
3
+ import { SidebarLayout } from '@organisms'
4
+ import { APP_TITLE, APP_DESCRIPTION } from '@constants/ui'
5
+
6
+ export const metadata: Metadata = {
7
+ title: APP_TITLE,
8
+ description: APP_DESCRIPTION,
9
+ icons: [
10
+ { rel: 'icon', url: '/favicon.ico', type: 'image/x-icon', sizes: '32x32', media: '(prefers-color-scheme: light)' },
11
+ {
12
+ rel: 'icon',
13
+ url: '/favicon-dark.ico',
14
+ type: 'image/x-icon',
15
+ sizes: '32x32',
16
+ media: '(prefers-color-scheme: dark)',
17
+ },
18
+ { rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', media: '(prefers-color-scheme: light)' },
19
+ { rel: 'icon', url: '/icon.svg', type: 'image/svg+xml', media: '(prefers-color-scheme: dark)' },
20
+ { rel: 'apple-touch-icon', url: '/apple-icon.png', media: '(prefers-color-scheme: light)' },
21
+ { rel: 'apple-touch-icon', url: '/apple-icon-dark.png', media: '(prefers-color-scheme: dark)' },
22
+ ],
23
+ manifest: '/site.webmanifest',
24
+ }
25
+
26
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
27
+ return (
28
+ <html lang="en">
29
+ <body>
30
+ <ThemeProvider>
31
+ <SidebarLayout>{children}</SidebarLayout>
32
+ </ThemeProvider>
33
+ </body>
34
+ </html>
35
+ )
36
+ }
@@ -0,0 +1,10 @@
1
+ 'use server'
2
+
3
+ import { getChatSessionInstance } from '@agents'
4
+ import { initRegistry } from '@agents/registry'
5
+
6
+ export async function restartSession(sessionId: string) {
7
+ initRegistry()
8
+ const session = getChatSessionInstance({ sessionId })
9
+ return await session.restart()
10
+ }
@@ -0,0 +1,43 @@
1
+ import { agentRegistry } from '@agents/registry'
2
+ import { getTransitionTargetMap } from './parseTransitionTargets'
3
+
4
+ export type AgentInfo = {
5
+ id: string
6
+ description: string
7
+ transition: { type: 'deterministic' | 'dynamic' | 'none'; targets: string[] }
8
+ }
9
+
10
+ /**
11
+ * Returns registered agents with transition metadata for the AgentGraph visualization component.
12
+ * Caller is responsible for ensuring agents are registered before calling this function.
13
+ *
14
+ * For deterministic transitions (functions), uses AST parsing to discover the actual
15
+ * target agent IDs from the source code rather than listing all other agents.
16
+ */
17
+ export function getAgentGraphData(): AgentInfo[] {
18
+ const agents = agentRegistry.getAll()
19
+ const allAgentIds = agents.map((a) => a.id)
20
+ const targetMap = getTransitionTargetMap()
21
+
22
+ return agents.map((agent) => {
23
+ let type: 'deterministic' | 'dynamic' | 'none'
24
+ let targets: string[]
25
+
26
+ if (Array.isArray(agent.transition)) {
27
+ type = 'dynamic'
28
+ targets = agent.transition
29
+ } else if (typeof agent.transition === 'function') {
30
+ type = 'deterministic'
31
+ targets = targetMap.get(agent.id) ?? allAgentIds.filter((id) => id !== agent.id)
32
+ } else {
33
+ type = 'none'
34
+ targets = []
35
+ }
36
+
37
+ return {
38
+ id: agent.id,
39
+ description: agent.description,
40
+ transition: { type, targets },
41
+ }
42
+ })
43
+ }
@@ -0,0 +1,99 @@
1
+ import dagre from '@dagrejs/dagre'
2
+ import { MarkerType, type Node, type Edge } from '@xyflow/react'
3
+ import type { AgentInfo } from './getAgentGraphData'
4
+
5
+ const NODE_WIDTH = 160
6
+ const NODE_HEIGHT = 44
7
+
8
+ /**
9
+ * Compute a top-to-bottom dagre layout for the agent graph and return
10
+ * React Flow–compatible nodes and edges.
11
+ *
12
+ * Self-loop edges are excluded from dagre (which expects a DAG) and added
13
+ * separately with a custom `selfLoop` edge type.
14
+ */
15
+ export function getGraphLayout(agents: AgentInfo[], activeAgentId: string | null): { nodes: Node[]; edges: Edge[] } {
16
+ const g = new dagre.graphlib.Graph()
17
+ g.setDefaultEdgeLabel(() => ({}))
18
+ g.setGraph({ rankdir: 'TB', ranksep: 60, nodesep: 40 })
19
+
20
+ for (const agent of agents) {
21
+ g.setNode(agent.id, { width: NODE_WIDTH, height: NODE_HEIGHT })
22
+ }
23
+
24
+ for (const agent of agents) {
25
+ for (const target of agent.transition.targets) {
26
+ if (target !== agent.id) {
27
+ g.setEdge(agent.id, target)
28
+ }
29
+ }
30
+ }
31
+
32
+ dagre.layout(g)
33
+
34
+ const resolvedActiveId = activeAgentId ?? agents[0]?.id ?? null
35
+
36
+ const nodes: Node[] = agents.map((agent) => {
37
+ const { x, y } = g.node(agent.id) as unknown as { x: number; y: number }
38
+ return {
39
+ id: agent.id,
40
+ position: { x: x - NODE_WIDTH / 2, y: y - NODE_HEIGHT / 2 },
41
+ data: {
42
+ label: agent.id,
43
+ isActive: agent.id === resolvedActiveId,
44
+ description: agent.description,
45
+ },
46
+ type: 'agent',
47
+ }
48
+ })
49
+
50
+ const positionOf = new Map<string, { x: number; y: number }>()
51
+ for (const agent of agents) {
52
+ const { x, y } = g.node(agent.id) as unknown as { x: number; y: number }
53
+ positionOf.set(agent.id, { x, y })
54
+ }
55
+
56
+ const edges: Edge[] = []
57
+ for (const agent of agents) {
58
+ const isDynamic = agent.transition.type === 'dynamic'
59
+ const strokeColor = isDynamic ? '#94a3b8' : '#64748b'
60
+
61
+ for (const target of agent.transition.targets) {
62
+ const marker = {
63
+ type: MarkerType.ArrowClosed,
64
+ color: strokeColor,
65
+ width: 15,
66
+ height: 15,
67
+ }
68
+
69
+ if (target === agent.id) {
70
+ edges.push({
71
+ id: `${agent.id}-${target}`,
72
+ source: agent.id,
73
+ target,
74
+ type: 'selfLoop',
75
+ data: { isDynamic },
76
+ markerEnd: marker,
77
+ sourceHandle: 'source-bottom',
78
+ targetHandle: 'target-top',
79
+ })
80
+ } else {
81
+ const sourceY = positionOf.get(agent.id)!.y
82
+ const targetY = positionOf.get(target)!.y
83
+ const isBackEdge = sourceY >= targetY
84
+
85
+ edges.push({
86
+ id: `${agent.id}-${target}`,
87
+ source: agent.id,
88
+ target,
89
+ type: isDynamic ? 'dynamic' : 'deterministic',
90
+ markerEnd: marker,
91
+ sourceHandle: isBackEdge ? 'source-right' : 'source-bottom',
92
+ targetHandle: isBackEdge ? 'target-right' : 'target-top',
93
+ })
94
+ }
95
+ }
96
+ }
97
+
98
+ return { nodes, edges }
99
+ }
@@ -0,0 +1,33 @@
1
+ import { useState, useCallback } from 'react'
2
+ import type { Dispatch, SetStateAction } from 'react'
3
+ import type { Message } from '@genui/a3'
4
+
5
+ export interface RestartResult {
6
+ messages: Message[]
7
+ activeAgentId: string | null
8
+ state: Record<string, unknown>
9
+ }
10
+
11
+ interface UseRestartOptions {
12
+ onRestart?: () => Promise<RestartResult>
13
+ setMessages: Dispatch<SetStateAction<Message[]>>
14
+ onSessionUpdate?: (update: { activeAgentId: string | null; state: Record<string, unknown> }) => void
15
+ }
16
+
17
+ export function useRestart({ onRestart, setMessages, onSessionUpdate }: UseRestartOptions) {
18
+ const [isRestarting, setIsRestarting] = useState(false)
19
+
20
+ const handleRestart = useCallback(async () => {
21
+ if (!onRestart) return
22
+ setIsRestarting(true)
23
+ try {
24
+ const fresh = await onRestart()
25
+ setMessages(fresh.messages)
26
+ onSessionUpdate?.({ activeAgentId: fresh.activeAgentId, state: fresh.state })
27
+ } finally {
28
+ setIsRestarting(false)
29
+ }
30
+ }, [onRestart, setMessages, onSessionUpdate])
31
+
32
+ return { isRestarting, handleRestart: onRestart ? handleRestart : undefined }
33
+ }
@@ -0,0 +1,140 @@
1
+ import * as ts from 'typescript'
2
+ import * as fs from 'fs'
3
+ import * as path from 'path'
4
+
5
+ const SKIP_FILES = new Set(['state.ts', 'registry.ts', 'index.ts'])
6
+
7
+ let cached: Map<string, string[]> | null = null
8
+
9
+ /**
10
+ * Parse agent source files to extract transition targets using TypeScript's AST.
11
+ * Results are cached for the server lifecycle.
12
+ *
13
+ * For array transitions, extracts string elements directly.
14
+ * For function transitions, walks the function body to collect string literals
15
+ * from return statements and ternary expressions, then cross-references against
16
+ * known agent IDs to filter out non-agent strings.
17
+ *
18
+ * @returns Map from agent ID to transition target agent IDs
19
+ */
20
+ export function getTransitionTargetMap(): Map<string, string[]> {
21
+ if (cached) return cached
22
+ cached = new Map()
23
+
24
+ const agentsDir = path.join(process.cwd(), 'app/agents')
25
+ let entries: fs.Dirent[]
26
+ try {
27
+ entries = fs.readdirSync(agentsDir, { withFileTypes: true })
28
+ } catch {
29
+ return cached
30
+ }
31
+
32
+ const fileNames = entries
33
+ .filter((e) => e.isFile() && e.name.endsWith('.ts') && !SKIP_FILES.has(e.name))
34
+ .map((e) => e.name)
35
+
36
+ // First pass: parse all files and collect agent IDs
37
+ const fileData: { agentId: string; transitionNode: ts.Node | null }[] = []
38
+ const allAgentIds = new Set<string>()
39
+
40
+ for (const fileName of fileNames) {
41
+ try {
42
+ const source = fs.readFileSync(path.join(agentsDir, fileName), 'utf-8')
43
+ const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true)
44
+ const info = extractAgentInfo(sf)
45
+ if (info) {
46
+ allAgentIds.add(info.agentId)
47
+ fileData.push(info)
48
+ }
49
+ } catch {
50
+ // Skip unparseable files
51
+ }
52
+ }
53
+
54
+ // Second pass: extract transition targets and cross-reference against known IDs
55
+ for (const { agentId, transitionNode } of fileData) {
56
+ if (!transitionNode) continue
57
+ const targets = extractTargets(transitionNode, allAgentIds)
58
+ if (targets.length > 0) {
59
+ cached.set(agentId, targets)
60
+ }
61
+ }
62
+
63
+ return cached
64
+ }
65
+
66
+ function extractAgentInfo(
67
+ sf: ts.SourceFile
68
+ ): { agentId: string; transitionNode: ts.Node | null } | null {
69
+ let agentId: string | null = null
70
+ let transitionNode: ts.Node | null = null
71
+
72
+ function visit(node: ts.Node) {
73
+ if (ts.isObjectLiteralExpression(node)) {
74
+ let id: string | null = null
75
+ let transition: ts.Node | null = null
76
+
77
+ for (const prop of node.properties) {
78
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue
79
+ if (prop.name.text === 'id' && ts.isStringLiteral(prop.initializer)) {
80
+ id = prop.initializer.text
81
+ }
82
+ if (prop.name.text === 'transition') {
83
+ transition = prop.initializer
84
+ }
85
+ }
86
+
87
+ if (id) {
88
+ agentId = id
89
+ transitionNode = transition
90
+ }
91
+ }
92
+ ts.forEachChild(node, visit)
93
+ }
94
+
95
+ ts.forEachChild(sf, visit)
96
+ return agentId ? { agentId, transitionNode } : null
97
+ }
98
+
99
+ function extractTargets(node: ts.Node, allAgentIds: Set<string>): string[] {
100
+ if (ts.isArrayLiteralExpression(node)) {
101
+ return node.elements
102
+ .filter(ts.isStringLiteral)
103
+ .map((el) => el.text)
104
+ .filter((text) => allAgentIds.has(text))
105
+ }
106
+
107
+ if (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
108
+ const strings = new Set<string>()
109
+ if (ts.isBlock(node.body)) {
110
+ collectReturnStrings(node.body, strings)
111
+ } else {
112
+ // Expression body (arrow function without braces)
113
+ collectStringLiterals(node.body, strings)
114
+ }
115
+ return Array.from(strings).filter((s) => allAgentIds.has(s))
116
+ }
117
+
118
+ return []
119
+ }
120
+
121
+ function collectReturnStrings(node: ts.Node, strings: Set<string>) {
122
+ if (ts.isReturnStatement(node) && node.expression) {
123
+ collectStringLiterals(node.expression, strings)
124
+ return
125
+ }
126
+ ts.forEachChild(node, (child) => collectReturnStrings(child, strings))
127
+ }
128
+
129
+ function collectStringLiterals(node: ts.Node, strings: Set<string>) {
130
+ if (ts.isStringLiteral(node)) {
131
+ strings.add(node.text)
132
+ return
133
+ }
134
+ if (ts.isConditionalExpression(node)) {
135
+ collectStringLiterals(node.whenTrue, strings)
136
+ collectStringLiterals(node.whenFalse, strings)
137
+ return
138
+ }
139
+ ts.forEachChild(node, (child) => collectStringLiterals(child, strings))
140
+ }
@@ -0,0 +1,12 @@
1
+ import { createAnthropicProvider } from '@genui/a3-anthropic'
2
+
3
+ let _instance: ReturnType<typeof createAnthropicProvider> | null = null
4
+
5
+ export function getAnthropicProvider() {
6
+ if (!_instance) {
7
+ _instance = createAnthropicProvider({
8
+ models: ['claude-sonnet-4-6', 'claude-haiku-4-5-20251001'],
9
+ })
10
+ }
11
+ return _instance
12
+ }
@@ -0,0 +1,12 @@
1
+ import { createBedrockProvider } from '@genui/a3-bedrock'
2
+
3
+ let _instance: ReturnType<typeof createBedrockProvider> | null = null
4
+
5
+ export function getBedrockProvider() {
6
+ if (!_instance) {
7
+ _instance = createBedrockProvider({
8
+ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0', 'us.anthropic.claude-haiku-4-5-20251001-v1:0'],
9
+ })
10
+ }
11
+ return _instance
12
+ }
@@ -0,0 +1,10 @@
1
+ import { createOpenAIProvider } from '@genui/a3-openai'
2
+
3
+ let _instance: ReturnType<typeof createOpenAIProvider> | null = null
4
+
5
+ export function getOpenAIProvider() {
6
+ if (!_instance) {
7
+ _instance = createOpenAIProvider({ models: ['gpt-4o', 'gpt-4o-mini'] })
8
+ }
9
+ return _instance
10
+ }
@@ -0,0 +1,21 @@
1
+ import { Box, Typography } from '@mui/material'
2
+ import { OnboardingChat } from '@organisms'
3
+ import { getChatSessionInstance } from '@agents'
4
+ import { SESSION_IDS } from '@constants/chat'
5
+ import { ONBOARDING_TAGLINE } from '@constants/ui'
6
+
7
+ export default async function OnboardingPage() {
8
+ const session = getChatSessionInstance({ sessionId: SESSION_IDS.ONBOARDING, initialAgentId: 'onboarding' })
9
+ const sessionData = await session.getOrCreateSessionData()
10
+
11
+ return (
12
+ <>
13
+ <Box sx={{ px: 3, py: 3, textAlign: 'center' }}>
14
+ <Typography variant="body1" color="text.secondary" dangerouslySetInnerHTML={{ __html: ONBOARDING_TAGLINE }} />
15
+ </Box>
16
+ <Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', pb: 3, px: { xs: 2, sm: 3, md: 5, lg: 8 } }}>
17
+ <OnboardingChat sessionId={SESSION_IDS.ONBOARDING} initialMessages={sessionData.messages} />
18
+ </Box>
19
+ </>
20
+ )
21
+ }
@@ -0,0 +1,16 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { Box, Container } from '@mui/material'
4
+ import { MarkdownRenderer } from '@atoms'
5
+
6
+ export default function Home() {
7
+ const readme = readFileSync(join(process.cwd(), 'docs/A3-README.md'), 'utf-8')
8
+
9
+ return (
10
+ <Container maxWidth="md" sx={{ py: 4 }}>
11
+ <Box sx={{ px: { xs: 1, sm: 2 } }}>
12
+ <MarkdownRenderer content={readme} />
13
+ </Box>
14
+ </Container>
15
+ )
16
+ }
@@ -0,0 +1,6 @@
1
+ import 'styled-components'
2
+ import type { Theme } from '@mui/material/styles'
3
+
4
+ declare module 'styled-components' {
5
+ export type DefaultTheme = Theme
6
+ }
@@ -0,0 +1,22 @@
1
+ import { createTheme } from '@mui/material/styles'
2
+
3
+ export const theme = createTheme({
4
+ palette: {
5
+ primary: {
6
+ main: '#2563eb',
7
+ },
8
+ background: {
9
+ default: '#f9fafb',
10
+ paper: '#ffffff',
11
+ },
12
+ },
13
+ components: {
14
+ MuiCssBaseline: {
15
+ styleOverrides: {
16
+ html: { maxWidth: '100vw', overflowX: 'hidden' },
17
+ body: { maxWidth: '100vw', overflowX: 'hidden' },
18
+ a: { color: 'inherit', textDecoration: 'none' },
19
+ },
20
+ },
21
+ },
22
+ })
@@ -0,0 +1,121 @@
1
+ # @genui/a3
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@genui)](https://www.npmjs.com/package/@genui/a3)
4
+ [![license](https://img.shields.io/npm/l/@genui/a3)](https://github.com/generalui/a3/blob/main/LICENSE)
5
+
6
+ **Predictable, governable multi-agent orchestration for TypeScript.**
7
+
8
+ Production agentic systems need more than prompt chains: They need
9
+ deterministic guardrails, structured output, and routing you can reason about.
10
+
11
+ A3 combines flexible LLM reasoning with hard-coded control:
12
+ Zod-validated responses, deterministic or LLM-driven routing, and shared typed state.
13
+ No graphs. No state machines. Just agents and code.
14
+
15
+ ## Feature Highlights
16
+
17
+ - **Deterministic + LLM-driven routing** -- code-controlled transitions or let the LLM pick from a bounded set
18
+ - **Structured output** -- Zod schemas validate every LLM response at runtime
19
+ - **Multi-agent orchestration** -- agents hand off to each other with shared typed state
20
+ - **Streaming** -- real-time token streaming with AG-UI-compatible events
21
+ - **Pluggable providers** -- Bedrock, OpenAI, Anthropic; or [build your own](./docs/CUSTOM_PROVIDERS.md)
22
+ - **Pluggable session stores** -- swap in-memory, Redis, or your own persistence
23
+ - **TypeScript-native** -- full type safety from agent definitions to response handling
24
+ - **Dual ESM/CJS** -- works in any Node.js environment
25
+
26
+ ## Why A3?
27
+
28
+ Most agentic frameworks optimize for demos, not production.
29
+ Prompt-only agents break when inputs drift.
30
+ Graph-based orchestration becomes unmaintainable at scale.
31
+ None of them give you compile-time safety or runtime validation out of the box.
32
+
33
+ A3's approach: **define agents, register them, and let the framework handle routing —
34
+ with guardrails at every layer.**
35
+
36
+ - **Every response validated:** Zod schemas enforce structure at runtime, not just in types
37
+ - **Routing you can reason about:** deterministic transitions via code, or bounded LLM-driven selection
38
+ - **Typed shared state:** one state object flows across agents with full TypeScript safety
39
+ - **Swap providers, not code:** Bedrock, OpenAI, Anthropic; switch with one line
40
+
41
+ ## Quick Start
42
+
43
+ Scaffold a new project:
44
+
45
+ ```bash
46
+ # Fastest way to start — scaffolds a full Next.js app
47
+ npx @genui/a3-create@latest my-app
48
+ cd my-app
49
+ ```
50
+
51
+ Or add A3 to an existing project:
52
+
53
+ ```bash
54
+ npm install @genui/a3 @genui/a3-bedrock zod
55
+ ```
56
+
57
+ ```typescript
58
+ import { z } from 'zod'
59
+ import { Agent, AgentRegistry, BaseState, ChatSession, MemorySessionStore } from '@genui/a3'
60
+ import { createBedrockProvider } from '@genui/a3-bedrock'
61
+
62
+ interface MyState extends BaseState { userName?: string }
63
+
64
+ const agent: Agent<MyState> = {
65
+ id: 'greeter',
66
+ description: 'Greets the user and collects their name',
67
+ prompt: 'You are a friendly greeting agent. Ask for the user\'s name.',
68
+ outputSchema: z.object({ userName: z.string().optional() }),
69
+ }
70
+
71
+ AgentRegistry.getInstance<MyState>().register(agent)
72
+
73
+ const session = new ChatSession<MyState>({
74
+ sessionId: 'demo',
75
+ store: new MemorySessionStore(),
76
+ initialAgentId: 'greeter',
77
+ initialState: { userName: undefined },
78
+ provider: createBedrockProvider({ models: ['us.anthropic.claude-sonnet-4-5-20250929-v1:0'] }),
79
+ })
80
+
81
+ const result = await session.send({ message: 'Hello!' })
82
+ console.log(result.responseMessage)
83
+ ```
84
+
85
+ See more examples in the [Quick Start & Examples guide](./docs/QUICK-START-EXAMPLES.md).
86
+
87
+ ## Documentation
88
+
89
+ - [Core Concepts](./docs/CORE-CONCEPTS.md) -- agents, routing, state, schemas, streaming, providers, stores
90
+ - [Architecture](./docs/ARCHITECTURE.md) -- system diagram and request flow
91
+ - [API Reference](./docs/API-REFERENCE.md) -- exports, methods, and response fields
92
+ - [Quick Start & Examples](./docs/QUICK-START-EXAMPLES.md) -- single-agent and multi-agent walkthroughs
93
+ - [Providers](./docs/PROVIDERS.md) -- configuring Bedrock, OpenAI, and Anthropic
94
+ - [Custom Providers](./docs/CUSTOM_PROVIDERS.md) -- building your own provider
95
+ - [Resilience](./docs/RESILIENCE.md) -- retries, timeouts, and model fallback
96
+ - [Custom Logging](./docs/CUSTOM_LOGGING.md) -- plugging in your own logger
97
+ - [Custom Stores](./docs/CUSTOM_STORES.md) -- implementing your own session store
98
+
99
+ ## Roadmap
100
+
101
+ - **Tool use** -- agent-invoked tool execution within the response cycle
102
+
103
+ ## Requirements
104
+
105
+ - Node.js 20.19.0+
106
+ - TypeScript 5.9+
107
+ - `zod` 4.x (included as a dependency)
108
+ - A configured LLM provider ([Bedrock, OpenAI, or Anthropic](./docs/PROVIDERS.md))
109
+
110
+ ## Contributing
111
+
112
+ ```bash
113
+ npm install # Install dependencies
114
+ npm run build # Build
115
+ npm run test:unit # Run unit tests
116
+ npm run lint # Lint
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,85 @@
1
+ # API Reference
2
+
3
+ ## Core Exports
4
+
5
+ | Export | Type | Description |
6
+ |---|---|---|
7
+ | `ChatSession` | Class | Primary interface for sending messages and managing conversations |
8
+ | `AgentRegistry` | Class | Singleton registry for agent registration and lookup |
9
+ | `AGUIAgent` | Class | AG-UI protocol integration for standardized agent-to-frontend streaming |
10
+ | `simpleAgentResponse` | Function | Default blocking response generator for agents |
11
+ | `simpleAgentResponseStream` | Function | Default streaming response generator for agents |
12
+ | `getAgentResponse` | Function | Low-level blocking agent response pipeline (prompt, schema, LLM call, validation) |
13
+ | `getAgentResponseStream` | Function | Low-level streaming agent response pipeline |
14
+ | `manageFlow` | Function | Blocking chat flow orchestration with automatic agent chaining |
15
+ | `manageFlowStream` | Function | Streaming variant of `manageFlow` yielding `StreamEvent`s |
16
+ | `createFullOutputSchema` | Function | Merges agent schema with base response fields |
17
+ | `MemorySessionStore` | Class | In-memory session store for development and testing |
18
+
19
+ ## ChatSession Methods
20
+
21
+ | Method | Returns | Description |
22
+ |---|---|---|
23
+ | `send({ message })` | `Promise<ChatResponse<TState>>` | Send a user message and get the agent's response (blocking) |
24
+ | `send({ message, stream: true })` | `AsyncGenerator<StreamEvent<TState>>` | Send a message and stream the response as events |
25
+ | `getSessionData()` | `Promise<SessionData<TState> \| null>` | Load current session state without sending a message |
26
+ | `getOrCreateSessionData()` | `Promise<SessionData<TState>>` | Load session or create with initial values if none exists |
27
+ | `upsertSessionData(updates)` | `Promise<void>` | Merge partial updates into the current session |
28
+ | `getHistory()` | `Promise<Message[]>` | Retrieve conversation history |
29
+ | `clear()` | `Promise<void>` | Delete the session from the store |
30
+
31
+ ## AgentRegistry Methods
32
+
33
+ | Method | Returns | Description |
34
+ |---|---|---|
35
+ | `getInstance()` | `AgentRegistry<TState>` | Get the singleton instance |
36
+ | `resetInstance()` | `void` | Reset the singleton (for testing) |
37
+ | `register(agents)` | `void` | Register one or more agents (throws on duplicate ID) |
38
+ | `unregister(agentOrId)` | `boolean` | Remove an agent by ID or agent instance |
39
+ | `get(id)` | `Agent<TState> \| undefined` | Look up an agent by ID |
40
+ | `getAll()` | `Agent<TState>[]` | Get all registered agents |
41
+ | `has(id)` | `boolean` | Check if an agent is registered |
42
+ | `getDescriptions()` | `Record<string, string>` | Map of agent IDs to their descriptions |
43
+ | `clear()` | `void` | Remove all registered agents (for testing) |
44
+ | `count` | `number` | Number of registered agents (getter) |
45
+
46
+ ## ChatResponse Fields
47
+
48
+ | Field | Type | Description |
49
+ |---|---|---|
50
+ | `responseMessage` | `string` | The agent's text response to the user |
51
+ | `activeAgentId` | `string \| null` | The agent that generated this response |
52
+ | `nextAgentId` | `string \| null` | The agent that will handle the next message |
53
+ | `state` | `TState` | Updated session state after this turn |
54
+ | `goalAchieved` | `boolean` | Whether the agent considers its goal complete |
55
+ | `sessionId` | `string` | Session identifier |
56
+ | `widgets` | `object \| undefined` | Optional widget data for rich UI rendering |
57
+
58
+ ## ChatSessionConfig
59
+
60
+ | Property | Type | Default | Description |
61
+ |---|---|---|---|
62
+ | `sessionId` | `string` | *required* | Unique session identifier |
63
+ | `store` | `SessionStore` | `undefined` | Storage adapter for session persistence |
64
+ | `initialAgentId` | `AgentId` | *required* | Agent to start the conversation |
65
+ | `initialState` | `TState` | `undefined` | Initial state for new sessions |
66
+ | `initialChatContext` | `TContext` | `undefined` | Initial chat context for new sessions |
67
+ | `initialMessages` | `Message[]` | `undefined` | Pre-populated conversation messages |
68
+ | `provider` | `Provider` | *required* | LLM provider instance for this session |
69
+ | `agentRecursionLimit` | `number` | `10` | Maximum automatic agent transitions per `send()` call. See [Transitions — Recursion Limit](./TRANSITIONS.md#recursion-limit) |
70
+
71
+ ## Agent Properties
72
+
73
+ | Property | Type | Required | Description |
74
+ |---|---|---|---|
75
+ | `id` | `AgentId` | Yes | Unique identifier for the agent |
76
+ | `description` | `string` | Yes | What this agent does (used in agent pool prompts) |
77
+ | `name` | `string` | No | Human-readable display name |
78
+ | `prompt` | `string \| (input) => Promise<string>` | Yes | System prompt string or async function returning the prompt |
79
+ | `outputSchema` | `ZodObject \| (sessionData) => ZodObject` | Yes | Zod schema defining structured data to extract from LLM responses |
80
+ | `provider` | `Provider` | No | Per-agent provider override; falls back to session-level provider |
81
+ | `generateResponse` | `(input) => Promise \| AsyncGenerator` | No | Custom response generator. See [Custom generateResponse](./CORE-CONCEPTS.md#custom-generateresponse) |
82
+ | `setState` | `(data, state) => TState` | No | Maps extracted LLM data into session state (defaults to shallow merge) |
83
+ | `transition` | `AgentId[] \| (state, goalAchieved) => AgentId` | No | Routing config. **Array**: LLM picks from listed IDs. **Function**: code decides. **Omitted**: LLM unconstrained. See [Transitions](./TRANSITIONS.md) |
84
+ | `filterHistoryStrategy` | `(messages) => Conversation` | No | Custom function to filter conversation history before sending to the LLM |
85
+ | `widgets` | `Record<string, ZodObject> \| (sessionData) => Record` | No | Zod schemas defining widgets available to the agent |