diff --git a/e2e-tests/copilotkit-frontend.test.ts b/e2e-tests/copilotkit-frontend.test.ts
new file mode 100644
index 000000000..7bdc5c176
--- /dev/null
+++ b/e2e-tests/copilotkit-frontend.test.ts
@@ -0,0 +1,172 @@
+import { parseJsonOutput, prereqs, spawnAndCollect } from '../src/test-utils/index.js';
+import { runAgentCoreCLI } from './e2e-helper.js';
+import { randomUUID } from 'node:crypto';
+import { existsSync } from 'node:fs';
+import { mkdir, readFile, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { afterAll, beforeAll, describe, expect, it } from 'vitest';
+
+const canRun = prereqs.npm && prereqs.git && prereqs.uv;
+
+describe.sequential('e2e: CopilotKit frontend scaffolding', () => {
+ let testDir: string;
+
+ beforeAll(async () => {
+ if (!canRun) return;
+ testDir = join(tmpdir(), `agentcore-e2e-cpk-${randomUUID()}`);
+ await mkdir(testDir, { recursive: true });
+ }, 30000);
+
+ afterAll(async () => {
+ if (testDir) {
+ await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 });
+ }
+ }, 30000);
+
+ it.skipIf(!canRun)(
+ 'create with --frontend copilotkit scaffolds frontend directory',
+ async () => {
+ const name = 'CpkYes';
+ const result = await runAgentCoreCLI(
+ [
+ 'create',
+ '--name',
+ name,
+ '--language',
+ 'Python',
+ '--protocol',
+ 'AGUI',
+ '--framework',
+ 'Strands',
+ '--model-provider',
+ 'Bedrock',
+ '--memory',
+ 'none',
+ '--frontend',
+ 'copilotkit',
+ '--skip-git',
+ '--skip-install',
+ '--skip-python-setup',
+ '--json',
+ ],
+ testDir
+ );
+
+ expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
+ const json = parseJsonOutput(result.stdout) as { projectPath: string };
+ const frontendDir = join(json.projectPath, 'app', name, 'frontend');
+
+ expect(existsSync(frontendDir), 'frontend/ directory should exist').toBe(true);
+ expect(existsSync(join(frontendDir, 'package.json'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'index.html'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'tsconfig.json'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'vite.config.ts'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'src', 'App.tsx'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'src', 'bridge.ts'))).toBe(true);
+ expect(existsSync(join(frontendDir, 'src', 'main.tsx'))).toBe(true);
+ },
+ 60000
+ );
+
+ it.skipIf(!canRun)(
+ 'create without --frontend does not scaffold frontend directory',
+ async () => {
+ const name = 'CpkNo';
+ const result = await runAgentCoreCLI(
+ [
+ 'create',
+ '--name',
+ name,
+ '--language',
+ 'Python',
+ '--protocol',
+ 'AGUI',
+ '--framework',
+ 'Strands',
+ '--model-provider',
+ 'Bedrock',
+ '--memory',
+ 'none',
+ '--skip-git',
+ '--skip-install',
+ '--skip-python-setup',
+ '--json',
+ ],
+ testDir
+ );
+
+ expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
+ const json = parseJsonOutput(result.stdout) as { projectPath: string };
+ const frontendDir = join(json.projectPath, 'app', name, 'frontend');
+
+ expect(existsSync(frontendDir), 'frontend/ directory should NOT exist').toBe(false);
+ },
+ 60000
+ );
+
+ it.skipIf(!canRun)(
+ 'frontend/package.json has correct name substitution',
+ async () => {
+ const name = 'CpkName';
+ const result = await runAgentCoreCLI(
+ [
+ 'create',
+ '--name',
+ name,
+ '--language',
+ 'Python',
+ '--protocol',
+ 'AGUI',
+ '--framework',
+ 'Strands',
+ '--model-provider',
+ 'Bedrock',
+ '--memory',
+ 'none',
+ '--frontend',
+ 'copilotkit',
+ '--skip-git',
+ '--skip-install',
+ '--skip-python-setup',
+ '--json',
+ ],
+ testDir
+ );
+
+ expect(result.exitCode, `Create failed: ${result.stderr}`).toBe(0);
+ const json = parseJsonOutput(result.stdout) as { projectPath: string };
+ const pkgPath = join(json.projectPath, 'app', name, 'frontend', 'package.json');
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
+
+ expect(pkg.name).toBe(`${name}-frontend`);
+ },
+ 60000
+ );
+
+ it.skipIf(!canRun)(
+ 'frontend/index.html has correct title substitution',
+ async () => {
+ const name = 'CpkName';
+ const projectPath = join(testDir, name);
+ const indexPath = join(projectPath, 'app', name, 'frontend', 'index.html');
+ const html = await readFile(indexPath, 'utf-8');
+
+ expect(html).toContain(`
${name}`);
+ },
+ 10000
+ );
+
+ it.skipIf(!canRun)(
+ 'npm install succeeds in scaffolded frontend',
+ async () => {
+ const name = 'CpkName';
+ const projectPath = join(testDir, name);
+ const frontendDir = join(projectPath, 'app', name, 'frontend');
+
+ const result = await spawnAndCollect('npm', ['install'], frontendDir);
+ expect(result.exitCode, `npm install failed: ${result.stderr}`).toBe(0);
+ },
+ 120000
+ );
+});
diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap
index a97bbeb1d..e950038aa 100644
--- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap
+++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap
@@ -450,6 +450,13 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
"evaluators/python-lambda/execution-role-policy.json",
"evaluators/python-lambda/lambda_function.py",
"evaluators/python-lambda/pyproject.toml",
+ "frontend/copilotkit/index.html",
+ "frontend/copilotkit/package.json",
+ "frontend/copilotkit/src/App.tsx",
+ "frontend/copilotkit/src/bridge.ts",
+ "frontend/copilotkit/src/main.tsx",
+ "frontend/copilotkit/tsconfig.json",
+ "frontend/copilotkit/vite.config.ts",
"mcp/python-lambda/README.md",
"mcp/python-lambda/handler.py",
"mcp/python-lambda/pyproject.toml",
@@ -546,6 +553,138 @@ exports[`Assets Directory Snapshots > File listing > should match the expected f
]
`;
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/index.html should match snapshot 1`] = `
+"
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/package.json should match snapshot 1`] = `
+"{
+ "name": "{{ name }}-frontend",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "concurrently -n bridge,ui -c blue,green \\"tsx watch src/bridge.ts\\" \\"vite\\"",
+ "build": "tsc && vite build"
+ },
+ "dependencies": {
+ "@copilotkit/react-core": "^1.54.0",
+ "@ag-ui/client": "^0.0.47",
+ "@copilotkit/runtime": "^1.54.0",
+ "@hono/node-server": "^1.13.7",
+ "hono": "^4.11.4",
+ "react": "^19.2.1",
+ "react-dom": "^19.2.1"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.0",
+ "@types/react-dom": "^19.1.0",
+ "@vitejs/plugin-react": "^4.2.0",
+ "concurrently": "^9.0.0",
+ "tsx": "^4.19.0",
+ "typescript": "^5.0.0",
+ "vite": "^6.0.0"
+ }
+}
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/src/App.tsx should match snapshot 1`] = `
+"import { CopilotChat, CopilotKitProvider } from '@copilotkit/react-core/v2';
+import '@copilotkit/react-core/v2/styles.css';
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/src/bridge.ts should match snapshot 1`] = `
+"import { HttpAgent } from '@ag-ui/client';
+import { CopilotRuntime, createCopilotEndpoint } from '@copilotkit/runtime/v2';
+import { serve } from '@hono/node-server';
+
+const AGENT_PORT = process.env.AGENT_PORT ?? '8080';
+const AGENT_URL = process.env.AGENT_URL ?? \`http://localhost:\${AGENT_PORT}/invocations\`;
+
+const agent = new HttpAgent({ url: AGENT_URL, headers: {} });
+const runtime = new CopilotRuntime({ agents: { default: agent } });
+const app = createCopilotEndpoint({ runtime, basePath: '/copilotkit' });
+
+serve({ fetch: app.fetch, port: 3001 }, () => {
+ console.log(\`CopilotKit bridge → \${AGENT_URL}\`);
+ console.log('Bridge running on http://localhost:3001/copilotkit');
+});
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/src/main.tsx should match snapshot 1`] = `
+"import App from './App';
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+);
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/tsconfig.json should match snapshot 1`] = `
+"{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true
+ },
+ "include": ["src"]
+}
+"
+`;
+
+exports[`Assets Directory Snapshots > Frontend assets > frontend/frontend/copilotkit/vite.config.ts should match snapshot 1`] = `
+"import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ open: true,
+ },
+});
+"
+`;
+
exports[`Assets Directory Snapshots > MCP assets > mcp/mcp/python/README.md should match snapshot 1`] = `
"# {{ name }}
@@ -1961,6 +2100,34 @@ GET /ping
Returns \`{"status": "healthy"}\`.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+\`\`\`bash
+agentcore dev --logs
+\`\`\`
+
+Then in another terminal:
+
+\`\`\`bash
+cd frontend
+npm install
+npm run dev
+\`\`\`
+
+Open http://localhost:3000.
+
+The frontend connects to \`http://localhost:8080/invocations\` by default. If your agent runs on a different port (check the \`agentcore dev --logs\` output), set \`AGENT_PORT\`:
+
+\`\`\`bash
+AGENT_PORT=8081 npm run dev
+\`\`\`
+
+{{/if}}
## Deploy
\`\`\`bash
@@ -2008,6 +2175,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
@@ -2145,6 +2315,34 @@ uv run python main.py
The agent starts on port 8080.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+\`\`\`bash
+agentcore dev --logs
+\`\`\`
+
+Then in another terminal:
+
+\`\`\`bash
+cd frontend
+npm install
+npm run dev
+\`\`\`
+
+Open http://localhost:3000.
+
+The frontend connects to \`http://localhost:8080/invocations\` by default. If your agent runs on a different port (check the \`agentcore dev --logs\` output), set \`AGENT_PORT\`:
+
+\`\`\`bash
+AGENT_PORT=8081 npm run dev
+\`\`\`
+
+{{/if}}
## Deploy
\`\`\`bash
@@ -2192,6 +2390,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
@@ -2460,6 +2661,34 @@ uv run python main.py
The agent starts on port 8080.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+\`\`\`bash
+agentcore dev --logs
+\`\`\`
+
+Then in another terminal:
+
+\`\`\`bash
+cd frontend
+npm install
+npm run dev
+\`\`\`
+
+Open http://localhost:3000.
+
+The frontend connects to \`http://localhost:8080/invocations\` by default. If your agent runs on a different port (check the \`agentcore dev --logs\` output), set \`AGENT_PORT\`:
+
+\`\`\`bash
+AGENT_PORT=8081 npm run dev
+\`\`\`
+
+{{/if}}
## Deploy
\`\`\`bash
@@ -2507,6 +2736,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
diff --git a/src/assets/__tests__/assets.snapshot.test.ts b/src/assets/__tests__/assets.snapshot.test.ts
index e0d3735cf..a418a0d49 100644
--- a/src/assets/__tests__/assets.snapshot.test.ts
+++ b/src/assets/__tests__/assets.snapshot.test.ts
@@ -87,6 +87,15 @@ describe('Assets Directory Snapshots', () => {
});
});
+ describe('Frontend assets', () => {
+ const frontendFiles = assetFiles.filter(f => f.startsWith('frontend/'));
+
+ it.each(frontendFiles)('frontend/%s should match snapshot', file => {
+ const content = readFileContent(path.join(ASSETS_DIR, file));
+ expect(content).toMatchSnapshot();
+ });
+ });
+
describe.skipIf(assetFiles.filter(f => f.startsWith('static/')).length === 0)('Static assets', () => {
const staticFiles = assetFiles.filter(f => f.startsWith('static/'));
diff --git a/src/assets/frontend/copilotkit/index.html b/src/assets/frontend/copilotkit/index.html
new file mode 100644
index 000000000..ebdb1a0f2
--- /dev/null
+++ b/src/assets/frontend/copilotkit/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
diff --git a/src/assets/frontend/copilotkit/package.json b/src/assets/frontend/copilotkit/package.json
new file mode 100644
index 000000000..a63b5b699
--- /dev/null
+++ b/src/assets/frontend/copilotkit/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "{{ name }}-frontend",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "concurrently -n bridge,ui -c blue,green \"tsx watch src/bridge.ts\" \"vite\"",
+ "build": "tsc && vite build"
+ },
+ "dependencies": {
+ "@copilotkit/react-core": "^1.54.0",
+ "@ag-ui/client": "^0.0.47",
+ "@copilotkit/runtime": "^1.54.0",
+ "@hono/node-server": "^1.13.7",
+ "hono": "^4.11.4",
+ "react": "^19.2.1",
+ "react-dom": "^19.2.1"
+ },
+ "devDependencies": {
+ "@types/react": "^19.1.0",
+ "@types/react-dom": "^19.1.0",
+ "@vitejs/plugin-react": "^4.2.0",
+ "concurrently": "^9.0.0",
+ "tsx": "^4.19.0",
+ "typescript": "^5.0.0",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/src/assets/frontend/copilotkit/src/App.tsx b/src/assets/frontend/copilotkit/src/App.tsx
new file mode 100644
index 000000000..c2844355a
--- /dev/null
+++ b/src/assets/frontend/copilotkit/src/App.tsx
@@ -0,0 +1,10 @@
+import { CopilotChat, CopilotKitProvider } from '@copilotkit/react-core/v2';
+import '@copilotkit/react-core/v2/styles.css';
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/src/assets/frontend/copilotkit/src/bridge.ts b/src/assets/frontend/copilotkit/src/bridge.ts
new file mode 100644
index 000000000..bcc3ce58d
--- /dev/null
+++ b/src/assets/frontend/copilotkit/src/bridge.ts
@@ -0,0 +1,15 @@
+import { HttpAgent } from '@ag-ui/client';
+import { CopilotRuntime, createCopilotEndpoint } from '@copilotkit/runtime/v2';
+import { serve } from '@hono/node-server';
+
+const AGENT_PORT = process.env.AGENT_PORT ?? '8080';
+const AGENT_URL = process.env.AGENT_URL ?? `http://localhost:${AGENT_PORT}/invocations`;
+
+const agent = new HttpAgent({ url: AGENT_URL, headers: {} });
+const runtime = new CopilotRuntime({ agents: { default: agent } });
+const app = createCopilotEndpoint({ runtime, basePath: '/copilotkit' });
+
+serve({ fetch: app.fetch, port: 3001 }, () => {
+ console.log(`CopilotKit bridge → ${AGENT_URL}`);
+ console.log('Bridge running on http://localhost:3001/copilotkit');
+});
diff --git a/src/assets/frontend/copilotkit/src/main.tsx b/src/assets/frontend/copilotkit/src/main.tsx
new file mode 100644
index 000000000..60feef333
--- /dev/null
+++ b/src/assets/frontend/copilotkit/src/main.tsx
@@ -0,0 +1,9 @@
+import App from './App';
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/src/assets/frontend/copilotkit/tsconfig.json b/src/assets/frontend/copilotkit/tsconfig.json
new file mode 100644
index 000000000..0dbb7cf5d
--- /dev/null
+++ b/src/assets/frontend/copilotkit/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true
+ },
+ "include": ["src"]
+}
diff --git a/src/assets/frontend/copilotkit/vite.config.ts b/src/assets/frontend/copilotkit/vite.config.ts
new file mode 100644
index 000000000..a2e3cf904
--- /dev/null
+++ b/src/assets/frontend/copilotkit/vite.config.ts
@@ -0,0 +1,10 @@
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ open: true,
+ },
+});
diff --git a/src/assets/python/agui/googleadk/base/README.md b/src/assets/python/agui/googleadk/base/README.md
index 5258d4b9a..5bfb05873 100644
--- a/src/assets/python/agui/googleadk/base/README.md
+++ b/src/assets/python/agui/googleadk/base/README.md
@@ -23,6 +23,34 @@ GET /ping
Returns `{"status": "healthy"}`.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+```bash
+agentcore dev --logs
+```
+
+Then in another terminal:
+
+```bash
+cd frontend
+npm install
+npm run dev
+```
+
+Open http://localhost:3000.
+
+The frontend connects to `http://localhost:8080/invocations` by default. If your agent runs on a different port (check the `agentcore dev --logs` output), set `AGENT_PORT`:
+
+```bash
+AGENT_PORT=8081 npm run dev
+```
+
+{{/if}}
## Deploy
```bash
diff --git a/src/assets/python/agui/googleadk/base/gitignore.template b/src/assets/python/agui/googleadk/base/gitignore.template
index fa1c60aea..90c9299cc 100644
--- a/src/assets/python/agui/googleadk/base/gitignore.template
+++ b/src/assets/python/agui/googleadk/base/gitignore.template
@@ -36,6 +36,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
diff --git a/src/assets/python/agui/langchain_langgraph/base/README.md b/src/assets/python/agui/langchain_langgraph/base/README.md
index d0b2793b3..8c21195b8 100644
--- a/src/assets/python/agui/langchain_langgraph/base/README.md
+++ b/src/assets/python/agui/langchain_langgraph/base/README.md
@@ -15,6 +15,34 @@ uv run python main.py
The agent starts on port 8080.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+```bash
+agentcore dev --logs
+```
+
+Then in another terminal:
+
+```bash
+cd frontend
+npm install
+npm run dev
+```
+
+Open http://localhost:3000.
+
+The frontend connects to `http://localhost:8080/invocations` by default. If your agent runs on a different port (check the `agentcore dev --logs` output), set `AGENT_PORT`:
+
+```bash
+AGENT_PORT=8081 npm run dev
+```
+
+{{/if}}
## Deploy
```bash
diff --git a/src/assets/python/agui/langchain_langgraph/base/gitignore.template b/src/assets/python/agui/langchain_langgraph/base/gitignore.template
index fa1c60aea..90c9299cc 100644
--- a/src/assets/python/agui/langchain_langgraph/base/gitignore.template
+++ b/src/assets/python/agui/langchain_langgraph/base/gitignore.template
@@ -36,6 +36,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
diff --git a/src/assets/python/agui/strands/base/README.md b/src/assets/python/agui/strands/base/README.md
index 57a8cda94..93592e24d 100644
--- a/src/assets/python/agui/strands/base/README.md
+++ b/src/assets/python/agui/strands/base/README.md
@@ -15,6 +15,34 @@ uv run python main.py
The agent starts on port 8080.
+{{#if hasFrontend}}
+## Frontend (CopilotKit)
+
+A React chat interface that connects to your agent via the AG-UI protocol.
+
+Start your agent in one terminal:
+
+```bash
+agentcore dev --logs
+```
+
+Then in another terminal:
+
+```bash
+cd frontend
+npm install
+npm run dev
+```
+
+Open http://localhost:3000.
+
+The frontend connects to `http://localhost:8080/invocations` by default. If your agent runs on a different port (check the `agentcore dev --logs` output), set `AGENT_PORT`:
+
+```bash
+AGENT_PORT=8081 npm run dev
+```
+
+{{/if}}
## Deploy
```bash
diff --git a/src/assets/python/agui/strands/base/gitignore.template b/src/assets/python/agui/strands/base/gitignore.template
index fa1c60aea..90c9299cc 100644
--- a/src/assets/python/agui/strands/base/gitignore.template
+++ b/src/assets/python/agui/strands/base/gitignore.template
@@ -36,6 +36,9 @@ env/
*.swo
*~
+# Frontend
+node_modules/
+
# OS
.DS_Store
Thumbs.db
diff --git a/src/cli/commands/create/action.ts b/src/cli/commands/create/action.ts
index 0f97de294..e3fe141bb 100644
--- a/src/cli/commands/create/action.ts
+++ b/src/cli/commands/create/action.ts
@@ -149,6 +149,7 @@ export interface CreateWithAgentOptions {
maxLifetime?: number;
sessionStorageMountPath?: string;
withConfigBundle?: boolean;
+ frontend?: 'none' | 'copilotkit';
skipGit?: boolean;
skipInstall?: boolean;
skipPythonSetup?: boolean;
@@ -175,6 +176,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
maxLifetime: maxLifetimeOpt,
sessionStorageMountPath,
withConfigBundle,
+ frontend,
skipGit,
skipInstall,
skipPythonSetup,
@@ -251,14 +253,16 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
onProgress?.('Add agent to project', 'start');
const agentName = name;
const isMcp = protocol === 'MCP';
- const resolvedFramework = isMcp ? ('Strands' as SDKFramework) : (framework ?? ('Strands' as SDKFramework));
- const resolvedModelProvider = isMcp
- ? ('Bedrock' as ModelProvider)
- : (modelProvider ?? ('Bedrock' as ModelProvider));
+ const resolvedFramework = isMcp ? ('Strands' as SDKFramework) : (framework ?? 'Strands');
+ const resolvedModelProvider = isMcp ? ('Bedrock' as ModelProvider) : (modelProvider ?? 'Bedrock');
+
+ if (frontend && frontend !== 'none' && protocol !== 'AGUI') {
+ throw new Error('--frontend is only supported with AGUI protocol agents');
+ }
const generateConfig = {
projectName: agentName,
- buildType: buildType ?? ('CodeZip' as BuildType),
+ buildType: buildType ?? 'CodeZip',
sdk: resolvedFramework,
modelProvider: resolvedModelProvider,
apiKey,
@@ -273,6 +277,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
...(maxLifetimeOpt !== undefined && { maxLifetime: maxLifetimeOpt }),
...(sessionStorageMountPath && { sessionStorageMountPath }),
...(withConfigBundle && { withConfigBundle }),
+ ...(frontend && frontend !== 'none' && { frontend }),
};
// Resolve credential strategy FIRST (new project has no existing credentials)
diff --git a/src/cli/commands/create/command.tsx b/src/cli/commands/create/command.tsx
index a21ece491..262545c5d 100644
--- a/src/cli/commands/create/command.tsx
+++ b/src/cli/commands/create/command.tsx
@@ -14,6 +14,7 @@ import {
AgentType,
Build,
Framework,
+ Frontend,
Language,
Memory,
ModelProvider as ModelProviderEnum,
@@ -25,7 +26,13 @@ import { COMMAND_DESCRIPTIONS } from '../../tui/copy';
import { requireTTY } from '../../tui/guards';
import { CreateScreen } from '../../tui/screens/create';
import { parseCommaSeparatedList } from '../shared/vpc-utils';
-import { type ProgressCallback, createProject, createProjectWithAgent, getDryRunInfo } from './action';
+import {
+ type CreateWithAgentOptions,
+ type ProgressCallback,
+ createProject,
+ createProjectWithAgent,
+ getDryRunInfo,
+} from './action';
import type { CreateOptions } from './types';
import { validateCreateOptions } from './validate';
import type { Command } from '@commander-js/extra-typings';
@@ -123,6 +130,7 @@ async function handleCreateCLI(options: CreateOptions): Promise {
build: standardize(Build, options.build ?? 'codezip'),
agent_type: standardize(AgentType, options.type ?? 'create'),
network_mode: standardize(NetworkModeEnum, options.networkMode ?? 'public'),
+ frontend: standardize(Frontend, options.frontend ?? 'none'),
has_agent: options.agent !== false,
};
@@ -182,6 +190,7 @@ async function handleCreateCLI(options: CreateOptions): Promise {
maxLifetime: options.maxLifetime ? Number(options.maxLifetime) : undefined,
sessionStorageMountPath: options.sessionStorageMountPath,
withConfigBundle: options.withConfigBundle,
+ frontend: options.frontend as CreateWithAgentOptions['frontend'],
skipGit: options.skipGit,
skipInstall: options.skipInstall,
skipPythonSetup: options.skipPythonSetup,
@@ -250,6 +259,7 @@ export const registerCreate = (program: Command) => {
'Absolute mount path for session filesystem storage under /mnt (e.g. /mnt/data) [non-interactive]'
)
.option('--with-config-bundle', 'Create a config bundle wired into the agent template [preview] [non-interactive]')
+ .option('--frontend ', 'Frontend UI to scaffold: copilotkit or none (AGUI only) [non-interactive]')
.option('--output-dir ', 'Output directory (default: current directory) [non-interactive]')
.option('--skip-git', 'Skip git repository initialization [non-interactive]')
.option('--skip-python-setup', 'Skip Python virtual environment setup [non-interactive]')
@@ -290,7 +300,7 @@ export const registerCreate = (program: Command) => {
if (hasAnyFlag) {
// Default language to Python (only supported option) for CLI mode
options.language = options.language ?? 'Python';
- await handleCreateCLI(options as CreateOptions);
+ await handleCreateCLI(options);
} else {
requireTTY();
handleCreateTUI();
diff --git a/src/cli/commands/create/types.ts b/src/cli/commands/create/types.ts
index cd42c545b..9a5c23707 100644
--- a/src/cli/commands/create/types.ts
+++ b/src/cli/commands/create/types.ts
@@ -21,6 +21,7 @@ export interface CreateOptions extends VpcOptions {
maxLifetime?: number | string;
sessionStorageMountPath?: string;
withConfigBundle?: boolean;
+ frontend?: string;
outputDir?: string;
skipGit?: boolean;
skipPythonSetup?: boolean;
diff --git a/src/cli/commands/create/validate.ts b/src/cli/commands/create/validate.ts
index a59c7d752..e5ae7ccd5 100644
--- a/src/cli/commands/create/validate.ts
+++ b/src/cli/commands/create/validate.ts
@@ -24,6 +24,7 @@ export interface ValidationResult {
}
const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
+const FRONTEND_OPTIONS = ['none', 'copilotkit'] as const;
/** Check if a folder with the given name already exists in the directory */
export function validateFolderNotExists(name: string, cwd: string): true | string {
@@ -202,6 +203,22 @@ export function validateCreateOptions(options: CreateOptions, cwd?: string): Val
error: `Invalid memory option: ${options.memory}. Use none, shortTerm, or longAndShortTerm`,
};
}
+
+ // Validate frontend option
+ if (options.frontend) {
+ if (!FRONTEND_OPTIONS.includes(options.frontend as (typeof FRONTEND_OPTIONS)[number])) {
+ return {
+ valid: false,
+ error: `Invalid frontend option: ${options.frontend}. Use none or copilotkit`,
+ };
+ }
+ if (options.frontend !== 'none' && protocol !== 'AGUI') {
+ return {
+ valid: false,
+ error: '--frontend is only supported with AGUI protocol',
+ };
+ }
+ }
}
// Validate VPC options
diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts
index 3ed449236..0c69cade3 100644
--- a/src/cli/operations/agent/generate/schema-mapper.ts
+++ b/src/cli/operations/agent/generate/schema-mapper.ts
@@ -285,5 +285,6 @@ export async function mapGenerateConfigToRenderConfig(
sessionStorageMountPath: config.sessionStorageMountPath,
enableOtel,
hasConfigBundle: config.withConfigBundle,
+ hasFrontend: config.frontend === 'copilotkit',
};
}
diff --git a/src/cli/primitives/AgentPrimitive.tsx b/src/cli/primitives/AgentPrimitive.tsx
index d82d9e808..cea8801ed 100644
--- a/src/cli/primitives/AgentPrimitive.tsx
+++ b/src/cli/primitives/AgentPrimitive.tsx
@@ -52,6 +52,7 @@ import {
AuthorizerType,
Build,
Framework,
+ Frontend,
Language,
Memory,
ModelProvider as ModelProviderEnum,
@@ -370,6 +371,7 @@ export class AgentPrimitive extends BasePrimitive {
build: 'codezip',
agent_type: 'create',
network_mode: 'public',
+ frontend: 'none',
has_agent: true,
};
expect(COMMAND_SCHEMAS.create.parse(attrs)).toEqual(attrs);
@@ -182,6 +183,7 @@ describe('resilientParse', () => {
build: 'codezip',
agent_type: 'create',
network_mode: 'public',
+ frontend: 'none',
has_agent: true,
};
expect(resilientParse(COMMAND_SCHEMAS.create, attrs)).toEqual(attrs);
diff --git a/src/cli/telemetry/schemas/command-run.ts b/src/cli/telemetry/schemas/command-run.ts
index bf79f42eb..02168caeb 100644
--- a/src/cli/telemetry/schemas/command-run.ts
+++ b/src/cli/telemetry/schemas/command-run.ts
@@ -11,6 +11,7 @@ import {
FilterState,
FilterType,
Framework,
+ Frontend,
GatewayTargetHost,
GatewayTargetType,
Language,
@@ -43,6 +44,7 @@ const CreateAttrs = safeSchema({
build: Build,
agent_type: AgentType,
network_mode: NetworkMode,
+ frontend: Frontend,
has_agent: z.boolean(),
});
@@ -56,6 +58,7 @@ const AddAgentAttrs = safeSchema({
network_mode: NetworkMode,
authorizer_type: AuthorizerType,
memory: Memory,
+ frontend: Frontend,
});
const AddMemoryAttrs = safeSchema({
diff --git a/src/cli/telemetry/schemas/common-shapes.ts b/src/cli/telemetry/schemas/common-shapes.ts
index 4624883cd..e2a8c00b1 100644
--- a/src/cli/telemetry/schemas/common-shapes.ts
+++ b/src/cli/telemetry/schemas/common-shapes.ts
@@ -94,6 +94,7 @@ export const ModelProvider = z.enum(['bedrock', 'anthropic', 'openai', 'gemini']
export const NetworkMode = z.enum(['public', 'vpc']);
export const OutboundAuth = z.enum(['oauth', 'api-key', 'none']);
export const PolicyEngineMode = z.enum(['log_only', 'enforce']);
+export const Frontend = z.enum(['none', 'copilotkit']);
export const Protocol = z.enum(['http', 'mcp', 'a2a']);
export const RefType = z.enum(['arn', 'name']);
export const ResourceType = z.enum(['gateway', 'agent']);
diff --git a/src/cli/templates/BaseRenderer.ts b/src/cli/templates/BaseRenderer.ts
index 659722926..4ef7855e7 100644
--- a/src/cli/templates/BaseRenderer.ts
+++ b/src/cli/templates/BaseRenderer.ts
@@ -1,7 +1,9 @@
import { APP_DIR } from '../../lib';
-import { copyAndRenderDir } from './render';
+import { copyAndRenderDir, copyDir } from './render';
import type { AgentRenderConfig } from './types';
+import Handlebars from 'handlebars';
import { existsSync } from 'node:fs';
+import * as fs from 'node:fs/promises';
import * as path from 'node:path';
export interface RendererContext {
@@ -65,6 +67,26 @@ export abstract class BaseRenderer {
}
}
+ // Render CopilotKit frontend (AGUI with frontend enabled)
+ // Uses plain copy (not Handlebars) because TSX files contain {{ in JSX syntax.
+ // Only package.json and index.html need template substitution.
+ if (this.config.hasFrontend) {
+ const frontendTemplateDir = path.join(this.baseTemplateDir, 'frontend', 'copilotkit');
+ if (existsSync(frontendTemplateDir)) {
+ const frontendTargetDir = path.join(projectDir, 'frontend');
+ await copyDir(frontendTemplateDir, frontendTargetDir);
+ // Render Handlebars in the files that need variable substitution
+ for (const file of ['package.json', 'index.html']) {
+ const filePath = path.join(frontendTargetDir, file);
+ if (existsSync(filePath)) {
+ const content = await fs.readFile(filePath, 'utf-8');
+ const rendered = Handlebars.compile(content)(templateData);
+ await fs.writeFile(filePath, rendered, 'utf-8');
+ }
+ }
+ }
+ }
+
// Generate Dockerfile and .dockerignore for Container builds
if (this.config.buildType === 'Container') {
const language = this.config.targetLanguage.toLowerCase();
diff --git a/src/cli/templates/types.ts b/src/cli/templates/types.ts
index 907cc99dd..8f10e57c8 100644
--- a/src/cli/templates/types.ts
+++ b/src/cli/templates/types.ts
@@ -74,4 +74,6 @@ export interface AgentRenderConfig {
enableOtel?: boolean;
/** Whether a config bundle is wired into the agent template */
hasConfigBundle?: boolean;
+ /** Whether to scaffold a CopilotKit frontend (AGUI only) */
+ hasFrontend?: boolean;
}
diff --git a/src/cli/tui/screens/agent/AddAgentScreen.tsx b/src/cli/tui/screens/agent/AddAgentScreen.tsx
index c8961c065..5e19d7024 100644
--- a/src/cli/tui/screens/agent/AddAgentScreen.tsx
+++ b/src/cli/tui/screens/agent/AddAgentScreen.tsx
@@ -175,16 +175,16 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
codeLocation: '',
entrypoint: DEFAULT_ENTRYPOINT,
buildType: 'CodeZip' as BuildType,
- dockerfile: '' as string,
+ dockerfile: '',
modelProvider: 'Bedrock' as ModelProvider,
apiKey: undefined as string | undefined,
networkMode: 'PUBLIC' as NetworkMode,
- subnets: '' as string,
- securityGroups: '' as string,
- requestHeaderAllowlist: '' as string,
- idleTimeout: '' as string,
- maxLifetime: '' as string,
- sessionStorageMountPath: '' as string,
+ subnets: '',
+ securityGroups: '',
+ requestHeaderAllowlist: '',
+ idleTimeout: '',
+ maxLifetime: '',
+ sessionStorageMountPath: '',
withConfigBundle: undefined as boolean | undefined,
});
const [byoAdvancedSettings, setByoAdvancedSettings] = useState>(new Set());
@@ -313,6 +313,7 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
maxLifetime: generateWizard.config.maxLifetime,
sessionStorageMountPath: generateWizard.config.sessionStorageMountPath,
withConfigBundle: generateWizard.config.withConfigBundle,
+ frontend: generateWizard.config.frontend,
pythonVersion: DEFAULT_PYTHON_VERSION,
memory: generateWizard.config.memory,
};
@@ -490,7 +491,7 @@ export function AddAgentScreen({ existingAgentNames, onComplete, onExit }: AddAg
setByoConfig(c => ({
...c,
dockerfile: '',
- networkMode: 'PUBLIC' as NetworkMode,
+ networkMode: 'PUBLIC',
subnets: '',
securityGroups: '',
requestHeaderAllowlist: '',
diff --git a/src/cli/tui/screens/agent/types.ts b/src/cli/tui/screens/agent/types.ts
index c708bcac0..d0e0638b5 100644
--- a/src/cli/tui/screens/agent/types.ts
+++ b/src/cli/tui/screens/agent/types.ts
@@ -10,7 +10,7 @@ import type {
} from '../../../../schema';
import { DEFAULT_MODEL_IDS, getSupportedModelProviders } from '../../../../schema';
import type { JwtConfigOptions } from '../../../primitives/auth-utils';
-import type { MemoryOption } from '../generate/types';
+import type { FrontendOption, MemoryOption } from '../generate/types';
// ─────────────────────────────────────────────────────────────────────────────
// Add Agent Flow Types
@@ -57,6 +57,7 @@ export type AddAgentStep =
| 'maxLifetime'
| 'sessionStorageMountPath'
| 'memory'
+ | 'frontend'
| 'region'
| 'bedrockAgent'
| 'bedrockAlias'
@@ -99,6 +100,8 @@ export interface AddAgentConfig {
sessionStorageMountPath?: string;
/** When true, create a config bundle wired into the agent template */
withConfigBundle?: boolean;
+ /** Frontend UI to scaffold (AGUI only) */
+ frontend?: FrontendOption;
/** Python version (only for Python agents) */
pythonVersion: PythonRuntime;
/** Memory option (create path only) */
@@ -133,6 +136,7 @@ export const ADD_AGENT_STEP_LABELS: Record = {
maxLifetime: 'Max Lifetime',
sessionStorageMountPath: 'Session Storage',
memory: 'Memory',
+ frontend: 'Frontend',
region: 'Region',
bedrockAgent: 'Agent',
bedrockAlias: 'Alias',
diff --git a/src/cli/tui/screens/agent/useAddAgent.ts b/src/cli/tui/screens/agent/useAddAgent.ts
index 2c3539dd4..135dccc3a 100644
--- a/src/cli/tui/screens/agent/useAddAgent.ts
+++ b/src/cli/tui/screens/agent/useAddAgent.ts
@@ -26,6 +26,7 @@ import {
AuthorizerType as AuthorizerTypeEnum,
Build,
Framework,
+ Frontend,
Language,
Memory as MemoryEnum,
ModelProvider,
@@ -140,6 +141,7 @@ function mapAddAgentConfigToGenerateConfig(config: AddAgentConfig): GenerateConf
maxLifetime: config.maxLifetime,
sessionStorageMountPath: config.sessionStorageMountPath,
withConfigBundle: config.withConfigBundle,
+ frontend: config.frontend,
};
}
@@ -169,6 +171,7 @@ export function useAddAgent() {
network_mode: standardize(NetworkMode, config.networkMode ?? 'PUBLIC'),
authorizer_type: standardize(AuthorizerTypeEnum, config.authorizerType ?? 'NONE'),
memory: standardize(MemoryEnum, config.memory ?? 'none'),
+ frontend: standardize(Frontend, config.frontend ?? 'none'),
},
() => addAgentInner(config)
);
diff --git a/src/cli/tui/screens/create/useCreateFlow.ts b/src/cli/tui/screens/create/useCreateFlow.ts
index cff113610..89bf62bc7 100644
--- a/src/cli/tui/screens/create/useCreateFlow.ts
+++ b/src/cli/tui/screens/create/useCreateFlow.ts
@@ -28,6 +28,7 @@ import {
AgentType,
Build,
Framework,
+ Frontend,
Language,
Memory as MemoryEnum,
ModelProvider,
@@ -210,6 +211,7 @@ export function useCreateFlow(cwd: string): CreateFlowState {
build: standardize(Build, addAgentConfig?.buildType ?? 'CodeZip'),
agent_type: standardize(AgentType, addAgentConfig?.agentType ?? 'create'),
network_mode: standardize(NetworkMode, addAgentConfig?.networkMode ?? 'PUBLIC'),
+ frontend: standardize(Frontend, addAgentConfig?.frontend ?? 'none'),
has_agent: addAgentConfig !== null,
};
@@ -310,6 +312,7 @@ export function useCreateFlow(cwd: string): CreateFlowState {
maxLifetime: addAgentConfig.maxLifetime,
sessionStorageMountPath: addAgentConfig.sessionStorageMountPath,
withConfigBundle: addAgentConfig.withConfigBundle,
+ frontend: addAgentConfig.frontend,
};
logger.logSubStep(`Framework: ${generateConfig.sdk}`);
diff --git a/src/cli/tui/screens/generate/GenerateWizardUI.tsx b/src/cli/tui/screens/generate/GenerateWizardUI.tsx
index 9c6c79599..4e65e334c 100644
--- a/src/cli/tui/screens/generate/GenerateWizardUI.tsx
+++ b/src/cli/tui/screens/generate/GenerateWizardUI.tsx
@@ -22,10 +22,19 @@ import type { SelectableItem } from '../../components';
import { JwtConfigInput, useJwtConfigFlow } from '../../components/jwt-config';
import { useListNavigation, useMultiSelectNavigation } from '../../hooks';
import { RUNTIME_AUTHORIZER_TYPE_OPTIONS } from '../agent/types';
-import type { AdvancedSettingId, BuildType, GenerateConfig, GenerateStep, MemoryOption, ProtocolMode } from './types';
+import type {
+ AdvancedSettingId,
+ BuildType,
+ FrontendOption,
+ GenerateConfig,
+ GenerateStep,
+ MemoryOption,
+ ProtocolMode,
+} from './types';
import {
ADVANCED_SETTING_OPTIONS,
BUILD_TYPE_OPTIONS,
+ FRONTEND_OPTIONS,
LANGUAGE_OPTIONS,
MEMORY_OPTIONS,
NETWORK_MODE_OPTIONS,
@@ -98,6 +107,8 @@ export function GenerateWizardUI({
}));
case 'memory':
return MEMORY_OPTIONS.map(o => ({ id: o.id, title: o.title, description: o.description }));
+ case 'frontend':
+ return FRONTEND_OPTIONS.map(o => ({ id: o.id, title: o.title, description: o.description }));
case 'networkMode':
return NETWORK_MODE_OPTIONS.map(o => ({ id: o.id, title: o.title, description: o.description }));
case 'authorizerType':
@@ -147,6 +158,9 @@ export function GenerateWizardUI({
case 'memory':
wizard.setMemory(item.id as MemoryOption);
break;
+ case 'frontend':
+ wizard.setFrontend(item.id as FrontendOption);
+ break;
case 'networkMode':
wizard.setNetworkMode(item.id as NetworkMode);
break;
@@ -577,6 +591,12 @@ function ConfirmView({ config, credentialProjectName }: { config: GenerateConfig
{config.sessionStorageMountPath}
)}
+ {config.frontend && config.frontend !== 'none' && (
+
+ Frontend:
+ CopilotKit
+
+ )}
{config.withConfigBundle && (
Config Bundle:
diff --git a/src/cli/tui/screens/generate/types.ts b/src/cli/tui/screens/generate/types.ts
index 1b764ea80..b2dc8ab5a 100644
--- a/src/cli/tui/screens/generate/types.ts
+++ b/src/cli/tui/screens/generate/types.ts
@@ -20,6 +20,7 @@ export type GenerateStep =
| 'modelProvider'
| 'apiKey'
| 'memory'
+ | 'frontend'
| 'advanced'
| 'networkMode'
| 'subnets'
@@ -34,6 +35,8 @@ export type GenerateStep =
export type MemoryOption = 'none' | 'shortTerm' | 'longAndShortTerm';
+export type FrontendOption = 'none' | 'copilotkit';
+
// Re-export types from schema for convenience
export type { BuildType, ModelProvider, ProtocolMode, SDKFramework, TargetLanguage };
@@ -66,6 +69,8 @@ export interface GenerateConfig {
sessionStorageMountPath?: string;
/** When true, create a config bundle wired into the agent template */
withConfigBundle?: boolean;
+ /** Frontend UI to scaffold (AGUI only) */
+ frontend?: FrontendOption;
}
/** Base steps - apiKey, memory, subnets, securityGroups are conditionally added based on selections */
@@ -91,6 +96,7 @@ export const STEP_LABELS: Record = {
modelProvider: 'Model',
apiKey: 'API Key',
memory: 'Memory',
+ frontend: 'Frontend',
advanced: 'Advanced',
networkMode: 'Network',
subnets: 'Subnets',
@@ -212,3 +218,8 @@ export const MEMORY_OPTIONS = [
{ id: 'shortTerm', title: 'Short-term memory', description: 'Context within a session' },
{ id: 'longAndShortTerm', title: 'Long-term and short-term', description: 'Persists across sessions' },
] as const;
+
+export const FRONTEND_OPTIONS = [
+ { id: 'copilotkit', title: 'Yes — CopilotKit', description: 'Chat UI with AG-UI streaming' },
+ { id: 'none', title: 'No', description: 'No frontend scaffolded' },
+] as const;
diff --git a/src/cli/tui/screens/generate/useGenerateWizard.ts b/src/cli/tui/screens/generate/useGenerateWizard.ts
index 411cfb151..8d36a921c 100644
--- a/src/cli/tui/screens/generate/useGenerateWizard.ts
+++ b/src/cli/tui/screens/generate/useGenerateWizard.ts
@@ -1,7 +1,15 @@
import type { NetworkMode, RuntimeAuthorizerType } from '../../../../schema';
import { ProjectNameSchema, SessionStorageSchema } from '../../../../schema';
import type { JwtConfigOptions } from '../../../primitives/auth-utils';
-import type { AdvancedSettingId, BuildType, GenerateConfig, GenerateStep, MemoryOption, ProtocolMode } from './types';
+import type {
+ AdvancedSettingId,
+ BuildType,
+ FrontendOption,
+ GenerateConfig,
+ GenerateStep,
+ MemoryOption,
+ ProtocolMode,
+} from './types';
import { BASE_GENERATE_STEPS, getModelProviderOptionsForSdk } from './types';
import { useCallback, useMemo, useState } from 'react';
@@ -57,6 +65,10 @@ export function useGenerateWizard(options?: UseGenerateWizardOptions) {
const advancedIndex = filtered.indexOf('advanced');
filtered = [...filtered.slice(0, advancedIndex), 'memory', ...filtered.slice(advancedIndex)];
}
+ if (config.protocol === 'AGUI') {
+ const advancedIndex = filtered.indexOf('advanced');
+ filtered = [...filtered.slice(0, advancedIndex), 'frontend', ...filtered.slice(advancedIndex)];
+ }
}
if (advancedSelected) {
const advancedIndex = filtered.indexOf('advanced');
@@ -161,16 +173,17 @@ export function useGenerateWizard(options?: UseGenerateWizardOptions) {
const setModelProvider = useCallback(
(modelProvider: GenerateConfig['modelProvider']) => {
setConfig(c => ({ ...c, modelProvider }));
- // Non-Bedrock providers need API key step
if (modelProvider !== 'Bedrock') {
setStep('apiKey');
} else if (config.sdk === 'Strands') {
setStep('memory');
+ } else if (config.protocol === 'AGUI') {
+ setStep('frontend');
} else {
setStep('advanced');
}
},
- [config.sdk]
+ [config.sdk, config.protocol]
);
const setApiKey = useCallback(
@@ -178,23 +191,39 @@ export function useGenerateWizard(options?: UseGenerateWizardOptions) {
setConfig(c => ({ ...c, apiKey }));
if (config.sdk === 'Strands') {
setStep('memory');
+ } else if (config.protocol === 'AGUI') {
+ setStep('frontend');
} else {
setStep('advanced');
}
},
- [config.sdk]
+ [config.sdk, config.protocol]
);
const skipApiKey = useCallback(() => {
if (config.sdk === 'Strands') {
setStep('memory');
+ } else if (config.protocol === 'AGUI') {
+ setStep('frontend');
} else {
setStep('advanced');
}
- }, [config.sdk]);
+ }, [config.sdk, config.protocol]);
+
+ const setMemory = useCallback(
+ (memory: MemoryOption) => {
+ setConfig(c => ({ ...c, memory }));
+ if (config.protocol === 'AGUI') {
+ setStep('frontend');
+ } else {
+ setStep('advanced');
+ }
+ },
+ [config.protocol]
+ );
- const setMemory = useCallback((memory: MemoryOption) => {
- setConfig(c => ({ ...c, memory }));
+ const setFrontend = useCallback((frontend: FrontendOption) => {
+ setConfig(c => ({ ...c, frontend }));
setStep('advanced');
}, []);
@@ -411,6 +440,7 @@ export function useGenerateWizard(options?: UseGenerateWizardOptions) {
setApiKey,
skipApiKey,
setMemory,
+ setFrontend,
setAdvanced,
advancedSelected,
advancedSettings,