@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
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@genui/a3-create",
|
|
3
|
+
"version": "0.1.36",
|
|
4
|
+
"description": "CLI scaffolding tool for A3 agentic apps",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"genui",
|
|
7
|
+
"a3",
|
|
8
|
+
"ai",
|
|
9
|
+
"agentic",
|
|
10
|
+
"architecture",
|
|
11
|
+
"scaffold",
|
|
12
|
+
"cli"
|
|
13
|
+
],
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"author": "",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "dist/index.js",
|
|
18
|
+
"bin": {
|
|
19
|
+
"create-genui-a3": "dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"template"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "npm run prepare-template && tsup",
|
|
27
|
+
"postbuild": "chmod +x dist/index.js",
|
|
28
|
+
"clean": "rm -rf dist template",
|
|
29
|
+
"prepare-template": "node scripts/prepare-template.mjs",
|
|
30
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@aws-sdk/client-bedrock": "^3.750.0",
|
|
37
|
+
"@aws-sdk/credential-provider-ini": "^3.750.0",
|
|
38
|
+
"@clack/prompts": "1.1.0",
|
|
39
|
+
"chalk": "5.4.1",
|
|
40
|
+
"fs-extra": "11.3.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/fs-extra": "11.0.4",
|
|
44
|
+
"@types/node": "20.19.0",
|
|
45
|
+
"tsup": "8.5.1",
|
|
46
|
+
"typescript": "5.9.3"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/generalui/a3.git"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Example app rules (pointer to single source)
|
|
3
|
+
globs: "**"
|
|
4
|
+
alwaysApply: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Example App
|
|
8
|
+
|
|
9
|
+
When working in this directory, follow the rules in **CLAUDE.md** in this directory (same content as `.cursorrules` via symlink). Do not duplicate rule content here; CLAUDE.md is the single source of truth for Claude Code and Cursor.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Example App
|
|
2
|
+
|
|
3
|
+
Next.js example for @genui/a3. AI rules for this app (Claude Code and Cursor). Single source of truth; Cursor uses via symlink `.cursorrules` → this file.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- Next.js 16.1.6 (App Router), React 19, TypeScript 5.9+
|
|
8
|
+
- Testing infrastructure planned (not yet configured)
|
|
9
|
+
- MUI 7 (@mui/material) + styled-components for styling (`app/theme.ts`, `app/ThemeProvider.tsx`)
|
|
10
|
+
|
|
11
|
+
## Dependencies
|
|
12
|
+
|
|
13
|
+
- Pin all npm package versions (no `^` or `~`).
|
|
14
|
+
- Use exact versions in package.json so installs are reproducible.
|
|
15
|
+
|
|
16
|
+
## React / TypeScript
|
|
17
|
+
|
|
18
|
+
- Named functions over arrow function assignments
|
|
19
|
+
- Proper types and interfaces; follow React hooks rules and lifecycle
|
|
20
|
+
- Error boundaries and handling where appropriate
|
|
21
|
+
- Path aliases (from `tsconfig.json`):
|
|
22
|
+
- `@atoms` → `./app/components/atoms`
|
|
23
|
+
- `@molecules` → `./app/components/molecules`
|
|
24
|
+
- `@organisms` → `./app/components/organisms`
|
|
25
|
+
- `@components` → `./app/components`
|
|
26
|
+
- `@constants` → `./app/constants`
|
|
27
|
+
- `types` → `./app/types` (bare module, no @ prefix)
|
|
28
|
+
|
|
29
|
+
## Dev Commands
|
|
30
|
+
|
|
31
|
+
- `npm run dev` — Start Next.js dev server
|
|
32
|
+
- `npm run build` — Build for production
|
|
33
|
+
- `npm run start` — Start production server
|
|
34
|
+
|
|
35
|
+
## Next.js
|
|
36
|
+
|
|
37
|
+
- Use App Router, Server Components, Client Components, Server Actions, middleware as appropriate
|
|
38
|
+
|
|
39
|
+
## Code Organization
|
|
40
|
+
|
|
41
|
+
- Single-responsibility components; separation of concerns
|
|
42
|
+
- Constants for config and string literals; clean folder structure; DRY
|
|
43
|
+
- Use Atomic Design for component hierarchy (see below)
|
|
44
|
+
- `app/api/` — API routes (agui, chat, stream endpoints)
|
|
45
|
+
- `app/(pages)/` — Route groups (agui, chat, stream pages)
|
|
46
|
+
- `app/agents/` — Agent implementations (e.g. age.ts, greeting.ts)
|
|
47
|
+
- `app/lib/providers/` — Provider factory functions (Anthropic, Bedrock, OpenAI)
|
|
48
|
+
|
|
49
|
+
## Component hierarchy (Atomic Design)
|
|
50
|
+
|
|
51
|
+
- Place UI components under `app/components/` in Atomic Design layers:
|
|
52
|
+
1. **atoms/** — Single-purpose primitives (e.g. MessageBubble, buttons, inputs).
|
|
53
|
+
2. **molecules/** — Compositions of atoms (e.g. ChatMessage, ChatInput).
|
|
54
|
+
3. **organisms/** — Compositions of molecules and layout (e.g. Chat, ChatMessageList).
|
|
55
|
+
- Pages import from organisms (or molecules when no organism exists).
|
|
56
|
+
- Each layer may only use components from the same or lower layers (atoms use MUI/styled only; molecules use atoms; organisms use molecules/atoms).
|
|
57
|
+
- Export public components via `index.ts` per folder.
|
|
58
|
+
|
|
59
|
+
## State & Data
|
|
60
|
+
|
|
61
|
+
- Appropriate React hooks; clear data flow
|
|
62
|
+
- Handle loading, error, and success states; predictable updates
|
|
63
|
+
|
|
64
|
+
## Testing
|
|
65
|
+
|
|
66
|
+
Testing infrastructure is not yet configured for the example app.
|
|
67
|
+
When added, tests should be comprehensive, runnable in isolation, and cover error cases.
|
|
68
|
+
|
|
69
|
+
## Naming Conventions
|
|
70
|
+
|
|
71
|
+
- camelCase for variables, functions, and file names
|
|
72
|
+
- PascalCase for React components, interfaces, and type aliases
|
|
73
|
+
- SCREAMING_SNAKE_CASE for module-level constants
|
|
74
|
+
|
|
75
|
+
## API & UX
|
|
76
|
+
|
|
77
|
+
- Proper error handling and loading states; correct response types; separate API logic
|
|
78
|
+
- Loading states, clear error messages, responsive design, accessibility
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
- Sensitive data handled appropriately (HIPAA-aware)
|
|
83
|
+
- Auth/authz where needed; no keys or tokens in code
|
|
84
|
+
|
|
85
|
+
## Docs
|
|
86
|
+
|
|
87
|
+
- Markdown per markdownlint (new line per sentence, "1." for lists; config inherited from root `.markdownlint.json`)
|
|
88
|
+
|
|
89
|
+
## Workflow
|
|
90
|
+
|
|
91
|
+
- **Please understand the requirements of this project before building.**
|
|
92
|
+
- **Please ask any clarifying questions before making any changes or recommendations.**
|
|
93
|
+
|
|
94
|
+
1. Understand requirements (business, technical, non-functional)
|
|
95
|
+
2. Plan implementation
|
|
96
|
+
3. Write tests, then code that passes them
|
|
97
|
+
4. Document decisions; review for best practices
|
|
98
|
+
|
|
99
|
+
Use latest React and TypeScript syntax. Code should be clean, readable, maintainable, efficient, and DRY.
|
|
100
|
+
|
|
101
|
+
## A3 Framework Documentation
|
|
102
|
+
|
|
103
|
+
The `docs/` directory contains essential A3 framework documentation.
|
|
104
|
+
**You MUST read the relevant files below** before implementing or modifying any feature that touches A3 agents, providers, sessions, resilience, or logging.
|
|
105
|
+
|
|
106
|
+
| File | Topic |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `docs/API-REFERENCE.md` | Core exports, types, and API descriptions |
|
|
109
|
+
| `docs/ARCHITECTURE.md` | Framework architecture and system design |
|
|
110
|
+
| `docs/CORE-CONCEPTS.md` | Agents, providers, and fundamental building blocks |
|
|
111
|
+
| `docs/CUSTOM_LOGGING.md` | Supplying a custom logger via `configureLogger()` |
|
|
112
|
+
| `docs/CUSTOM_PROVIDERS.md` | Creating a custom provider and streaming support |
|
|
113
|
+
| `docs/INITIAL_PROMPT.md` | System prompts and instructions for AI agents |
|
|
114
|
+
| `docs/contributing/LOGGING.md` | Internal logging architecture, `log` singleton, log levels |
|
|
115
|
+
| `docs/PROVIDERS.md` | Provider setup (Bedrock, OpenAI, Anthropic), config options, model fallback, per-agent overrides |
|
|
116
|
+
| `docs/QUICK-START-EXAMPLES.md` | Agent definitions, registration, multi-agent flows, ChatSession usage |
|
|
117
|
+
| `docs/RESILIENCE.md` | Retry, backoff, timeout config, error classification, resilience error handling |
|
|
118
|
+
| `docs/TRANSITIONS.md` | Agent hand-offs and transition control |
|
|
119
|
+
|
|
120
|
+
When in doubt about A3 API usage, patterns, or configuration — **read the relevant doc file first**.
|
|
121
|
+
Any new `.md` files added to `docs/` are part of this documentation set and should be consulted as needed.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# A3 App
|
|
2
|
+
|
|
3
|
+
Built with [GenUI A3](https://www.npmjs.com/package/@genui/a3).
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Local documentation is available in the [`./docs`](./docs) folder. Check there for concepts, recipes, and API reference!
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm run dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
|
16
|
+
|
|
17
|
+
## Learn More
|
|
18
|
+
|
|
19
|
+
- [A3 Core Documentation](https://www.npmjs.com/package/@genui/a3)
|
|
20
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.js
|
|
7
|
+
|
|
8
|
+
# testing
|
|
9
|
+
/coverage
|
|
10
|
+
|
|
11
|
+
# next.js
|
|
12
|
+
/.next/
|
|
13
|
+
/out/
|
|
14
|
+
|
|
15
|
+
# production
|
|
16
|
+
/build
|
|
17
|
+
|
|
18
|
+
# misc
|
|
19
|
+
.DS_Store
|
|
20
|
+
*.pem
|
|
21
|
+
|
|
22
|
+
# debug
|
|
23
|
+
npm-debug.log*
|
|
24
|
+
yarn-debug.log*
|
|
25
|
+
yarn-error.log*
|
|
26
|
+
|
|
27
|
+
# local env files
|
|
28
|
+
.env
|
|
29
|
+
.env*.local
|
|
30
|
+
|
|
31
|
+
# vercel
|
|
32
|
+
.vercel
|
|
33
|
+
|
|
34
|
+
# typescript
|
|
35
|
+
*.tsbuildinfo
|
|
36
|
+
next-env.d.ts
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'
|
|
4
|
+
import { ThemeProvider as StyledThemeProvider } from 'styled-components'
|
|
5
|
+
import CssBaseline from '@mui/material/CssBaseline'
|
|
6
|
+
import { theme } from './theme'
|
|
7
|
+
|
|
8
|
+
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
|
9
|
+
return (
|
|
10
|
+
<MuiThemeProvider theme={theme}>
|
|
11
|
+
<StyledThemeProvider theme={theme}>
|
|
12
|
+
<CssBaseline />
|
|
13
|
+
{children}
|
|
14
|
+
</StyledThemeProvider>
|
|
15
|
+
</MuiThemeProvider>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Agent } from '@genui/a3'
|
|
3
|
+
import type { State } from '@agents/state'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sample age agent that gets the user's age.
|
|
7
|
+
*/
|
|
8
|
+
const agePayload = z.object({
|
|
9
|
+
userAge: z.string().optional(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const ageAgent: Agent<State> = {
|
|
13
|
+
id: 'age',
|
|
14
|
+
description: "Gets the user's age.",
|
|
15
|
+
prompt: `
|
|
16
|
+
You are a friendly agent. Your goal is to learn the user's age.
|
|
17
|
+
If you don't know their age yet, ask for it politely.
|
|
18
|
+
Once you have their age, confirm it and set goalAchieved to true.
|
|
19
|
+
|
|
20
|
+
If the user says they want to change their name or use a different name,
|
|
21
|
+
say "Of course, I can help you with that" and transition to the greeting agent.
|
|
22
|
+
`,
|
|
23
|
+
outputSchema: agePayload,
|
|
24
|
+
transition: ['greeting'],
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Agent } from '@genui/a3'
|
|
3
|
+
import type { State } from '@agents/state'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sample greeting agent that demonstrates the AgentRegistry pattern.
|
|
7
|
+
*/
|
|
8
|
+
const greetingPayload = z.object({
|
|
9
|
+
userName: z.string().optional(),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const greetingAgent: Agent<State> = {
|
|
13
|
+
id: 'greeting',
|
|
14
|
+
description: 'Greets the user and learns their name.',
|
|
15
|
+
prompt: `
|
|
16
|
+
You are a friendly greeting agent. Your goal is to greet the user and learn their name.
|
|
17
|
+
You also handle name changes — if the user wants to update their name, collect the new one.
|
|
18
|
+
If you don't know their name yet, ask for it politely.
|
|
19
|
+
Once you have their name, greet them by name and set goalAchieved to true.
|
|
20
|
+
|
|
21
|
+
Do not ask "How can I help you today?".
|
|
22
|
+
`,
|
|
23
|
+
outputSchema: greetingPayload,
|
|
24
|
+
transition: (state, agentGoalAchieved) => {
|
|
25
|
+
if (agentGoalAchieved) {
|
|
26
|
+
return 'age'
|
|
27
|
+
}
|
|
28
|
+
return 'greeting'
|
|
29
|
+
},
|
|
30
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ChatSession, MemorySessionStore, MessageSender } from '@genui/a3'
|
|
2
|
+
import { getProvider } from '@providers'
|
|
3
|
+
import type { State } from '@agents/state'
|
|
4
|
+
import type { Message } from '@genui/a3'
|
|
5
|
+
import { SESSION_INITIAL_MESSAGES } from '@constants/chat'
|
|
6
|
+
|
|
7
|
+
const globalForStore = globalThis as unknown as {
|
|
8
|
+
__a3SessionStore?: MemorySessionStore<State>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the shared session store, persisted on globalThis to survive HMR.
|
|
13
|
+
*/
|
|
14
|
+
function getSessionStore(): MemorySessionStore<State> {
|
|
15
|
+
globalForStore.__a3SessionStore ??= new MemorySessionStore<State>()
|
|
16
|
+
return globalForStore.__a3SessionStore
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a single assistant message from text.
|
|
21
|
+
*/
|
|
22
|
+
function createInitialMessage(text: string): Message {
|
|
23
|
+
return {
|
|
24
|
+
messageId: crypto.randomUUID(),
|
|
25
|
+
text,
|
|
26
|
+
metadata: { source: MessageSender.ASSISTANT, timestamp: Date.now() },
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the default initial messages for a given session ID.
|
|
32
|
+
*/
|
|
33
|
+
function getDefaultInitialMessages(sessionId: string): Message[] | undefined {
|
|
34
|
+
const text = SESSION_INITIAL_MESSAGES[sessionId]
|
|
35
|
+
return text ? [createInitialMessage(text)] : undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a ChatSession instance for the given session ID.
|
|
40
|
+
*
|
|
41
|
+
* @param options - Session configuration with optional overrides
|
|
42
|
+
* @returns A ChatSession instance configured with the shared store
|
|
43
|
+
*/
|
|
44
|
+
export function getChatSessionInstance(options: {
|
|
45
|
+
sessionId: string
|
|
46
|
+
initialAgentId?: string
|
|
47
|
+
initialMessages?: Message[]
|
|
48
|
+
}): ChatSession<State> {
|
|
49
|
+
return new ChatSession<State>({
|
|
50
|
+
sessionId: options.sessionId,
|
|
51
|
+
store: getSessionStore(),
|
|
52
|
+
initialAgentId: options.initialAgentId ?? 'greeting',
|
|
53
|
+
initialState: { userName: undefined },
|
|
54
|
+
provider: getProvider(),
|
|
55
|
+
initialMessages: options.initialMessages ?? getDefaultInitialMessages(options.sessionId),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { Agent } from '@genui/a3'
|
|
3
|
+
import type { State } from '@agents/state'
|
|
4
|
+
import { prompt } from './prompt'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Agent that knows everything there is to know about A3 framework, answers questions about it and guides users
|
|
8
|
+
* through the process of using it.
|
|
9
|
+
*/
|
|
10
|
+
export const onboardingAgent: Agent<State> = {
|
|
11
|
+
id: 'onboarding',
|
|
12
|
+
description: 'Onboards users to the A3 framework.',
|
|
13
|
+
prompt: (params) => prompt(params),
|
|
14
|
+
outputSchema: z.object({}),
|
|
15
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readdir, readFile } from 'fs/promises'
|
|
2
|
+
import { join, relative } from 'path'
|
|
3
|
+
import type { FlowInput } from '@genui/a3'
|
|
4
|
+
import type { State } from '@agents/state'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively find all .md files under a directory.
|
|
8
|
+
*/
|
|
9
|
+
async function findMarkdownFiles(dir: string): Promise<string[]> {
|
|
10
|
+
const results: string[] = []
|
|
11
|
+
const entries = await readdir(dir, { withFileTypes: true })
|
|
12
|
+
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = join(dir, entry.name)
|
|
15
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
16
|
+
results.push(...(await findMarkdownFiles(fullPath)))
|
|
17
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
18
|
+
results.push(fullPath)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return results
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build the onboarding agent prompt by reading all markdown documentation
|
|
27
|
+
* from the example project root.
|
|
28
|
+
*/
|
|
29
|
+
export async function prompt(_params: FlowInput<State>): Promise<string> {
|
|
30
|
+
const projectRoot = process.cwd()
|
|
31
|
+
const mdFiles = await findMarkdownFiles(projectRoot)
|
|
32
|
+
const sections: string[] = []
|
|
33
|
+
|
|
34
|
+
for (const filePath of mdFiles) {
|
|
35
|
+
const content = await readFile(filePath, 'utf-8')
|
|
36
|
+
const relPath = relative(projectRoot, filePath)
|
|
37
|
+
sections.push(`## ${relPath}\n\n${content}`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const documentation = sections.join('\n\n---\n\n')
|
|
41
|
+
|
|
42
|
+
return `
|
|
43
|
+
You are a friendly and knowledgeable onboarding agent for the A3 framework.
|
|
44
|
+
Your goal is to help users understand and get started with A3 by answering their questions
|
|
45
|
+
using the documentation provided below.
|
|
46
|
+
|
|
47
|
+
# INSTRUCTIONS
|
|
48
|
+
|
|
49
|
+
- Answer questions accurately based on the documentation below.
|
|
50
|
+
- Include relevant code snippets and examples when helpful.
|
|
51
|
+
- Use markdown formatting for clarity (headings, code blocks, lists, bold/italic).
|
|
52
|
+
- If a question falls outside the documentation scope, say so honestly.
|
|
53
|
+
- Be concise but thorough — provide enough detail to be actionable.
|
|
54
|
+
|
|
55
|
+
# DOCUMENTATION
|
|
56
|
+
|
|
57
|
+
${documentation}
|
|
58
|
+
`
|
|
59
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AgentRegistry } from '@genui/a3'
|
|
2
|
+
|
|
3
|
+
import type { State } from '@agents/state'
|
|
4
|
+
import { greetingAgent } from '@agents/greeting'
|
|
5
|
+
import { ageAgent } from '@agents/age'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared agent registry all API routes.
|
|
9
|
+
*/
|
|
10
|
+
const registry = AgentRegistry.getInstance<State>()
|
|
11
|
+
|
|
12
|
+
export function initRegistry() {
|
|
13
|
+
registry.clear()
|
|
14
|
+
registry.register([greetingAgent, ageAgent])
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const agentRegistry = registry
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { EventType, type RunAgentInput } from '@ag-ui/client'
|
|
3
|
+
import { EventEncoder } from '@ag-ui/encoder'
|
|
4
|
+
import { AGUIAgent } from '@genui/a3'
|
|
5
|
+
import { getChatSessionInstance } from '@agents'
|
|
6
|
+
import { initRegistry } from '@agents/registry'
|
|
7
|
+
|
|
8
|
+
const a3Agent = new AGUIAgent({
|
|
9
|
+
agentId: 'a3-demo',
|
|
10
|
+
createSession: (input: RunAgentInput) => getChatSessionInstance({ sessionId: input.threadId }),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
const body = (await request.json()) as RunAgentInput
|
|
15
|
+
|
|
16
|
+
const encoder = new EventEncoder()
|
|
17
|
+
const events$ = a3Agent.run(body)
|
|
18
|
+
|
|
19
|
+
initRegistry()
|
|
20
|
+
|
|
21
|
+
const stream = new ReadableStream({
|
|
22
|
+
start(controller) {
|
|
23
|
+
const textEncoder = new TextEncoder()
|
|
24
|
+
const subscription = events$.subscribe({
|
|
25
|
+
next(event) {
|
|
26
|
+
controller.enqueue(textEncoder.encode(encoder.encodeSSE(event)))
|
|
27
|
+
},
|
|
28
|
+
error(err) {
|
|
29
|
+
const errorEvent = {
|
|
30
|
+
type: EventType.RUN_ERROR,
|
|
31
|
+
message: String(err),
|
|
32
|
+
}
|
|
33
|
+
controller.enqueue(textEncoder.encode(encoder.encodeSSE(errorEvent)))
|
|
34
|
+
controller.close()
|
|
35
|
+
},
|
|
36
|
+
complete() {
|
|
37
|
+
controller.close()
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Clean up on cancel
|
|
42
|
+
request.signal.addEventListener('abort', () => {
|
|
43
|
+
subscription.unsubscribe()
|
|
44
|
+
controller.close()
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return new Response(stream, {
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': encoder.getContentType(),
|
|
52
|
+
'Cache-Control': 'no-cache',
|
|
53
|
+
Connection: 'keep-alive',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous (blocking / unary) chat endpoint.
|
|
3
|
+
* This is the non-streaming version of the /api/stream endpoint.
|
|
4
|
+
* It waits for the full agent response before returning a complete JSON payload.
|
|
5
|
+
*/
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
7
|
+
import { getChatSessionInstance } from '@agents'
|
|
8
|
+
import { initRegistry } from '@agents/registry'
|
|
9
|
+
|
|
10
|
+
export async function POST(request: NextRequest) {
|
|
11
|
+
try {
|
|
12
|
+
const body = (await request.json()) as { message?: string; sessionId?: string }
|
|
13
|
+
const { message, sessionId = 'demo-session' } = body
|
|
14
|
+
|
|
15
|
+
if (!message) {
|
|
16
|
+
return NextResponse.json({ error: 'Message is required' }, { status: 400 })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initRegistry()
|
|
20
|
+
|
|
21
|
+
const session = getChatSessionInstance({ sessionId })
|
|
22
|
+
const result = await session.send({ message })
|
|
23
|
+
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
response: result.responseMessage,
|
|
26
|
+
activeAgentId: result.activeAgentId,
|
|
27
|
+
nextAgentId: result.nextAgentId,
|
|
28
|
+
state: result.state,
|
|
29
|
+
goalAchieved: result.goalAchieved,
|
|
30
|
+
})
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Chat error:', error)
|
|
33
|
+
return NextResponse.json({ error: 'Internal server error', details: String(error) }, { status: 500 })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server'
|
|
2
|
+
import { getChatSessionInstance } from '@agents'
|
|
3
|
+
import { SESSION_IDS } from '@constants/chat'
|
|
4
|
+
import { AgentRegistry } from '@genui/a3'
|
|
5
|
+
import type { State } from '@agents/state'
|
|
6
|
+
import { onboardingAgent } from '@agents/onboarding'
|
|
7
|
+
import { initRegistry } from '@agents/registry'
|
|
8
|
+
|
|
9
|
+
export async function POST(request: NextRequest) {
|
|
10
|
+
const body = (await request.json()) as { message?: string; sessionId?: string }
|
|
11
|
+
const { message, sessionId = 'demo-stream-session' } = body
|
|
12
|
+
|
|
13
|
+
if (!message) {
|
|
14
|
+
return new Response(JSON.stringify({ error: 'Message is required' }), {
|
|
15
|
+
status: 400,
|
|
16
|
+
headers: { 'Content-Type': 'application/json' },
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let session = getChatSessionInstance({ sessionId })
|
|
21
|
+
initRegistry()
|
|
22
|
+
if (sessionId === SESSION_IDS.ONBOARDING) {
|
|
23
|
+
const registry = AgentRegistry.getInstance<State>()
|
|
24
|
+
registry.register(onboardingAgent)
|
|
25
|
+
session = getChatSessionInstance({ sessionId, initialAgentId: onboardingAgent.id })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const encoder = new TextEncoder()
|
|
29
|
+
|
|
30
|
+
const stream = new ReadableStream({
|
|
31
|
+
async start(controller) {
|
|
32
|
+
try {
|
|
33
|
+
for await (const event of session.send({ message, stream: true })) {
|
|
34
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
|
|
35
|
+
}
|
|
36
|
+
controller.enqueue(encoder.encode('data: [DONE]\n\n'))
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const errorEvent = {
|
|
39
|
+
type: 'error',
|
|
40
|
+
error: { message: String(error) },
|
|
41
|
+
agentId: 'unknown',
|
|
42
|
+
}
|
|
43
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`))
|
|
44
|
+
} finally {
|
|
45
|
+
controller.close()
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return new Response(stream, {
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'text/event-stream',
|
|
53
|
+
'Cache-Control': 'no-cache',
|
|
54
|
+
Connection: 'keep-alive',
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
Binary file
|
|
Binary file
|