veryfront 0.1.547 → 0.1.549
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/esm/cli/mcp/tools/project-tools.d.ts.map +1 -1
- package/esm/cli/mcp/tools/project-tools.js +7 -4
- package/esm/cli/templates/manifest.d.ts +5 -5
- package/esm/cli/templates/manifest.js +21 -21
- package/esm/deno.js +1 -1
- package/esm/src/agent/child-run/invoke-agent-child-runs.d.ts +2 -2
- package/esm/src/agent/child-run/invoke-agent-child-runs.d.ts.map +1 -1
- package/esm/src/agent/child-run/invoke-agent-child-runs.js +2 -2
- package/esm/src/agent/conversation/durable.js +2 -2
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.js +1 -1
- package/esm/src/agent/runtime/agent-invocation-contract.js +3 -3
- package/esm/src/agent/service/routes.d.ts +1 -0
- package/esm/src/agent/service/routes.d.ts.map +1 -1
- package/esm/src/agent/service/routes.js +6 -8
- package/esm/src/agent/testing/durable-run-canaries/runner.d.ts.map +1 -1
- package/esm/src/agent/testing/durable-run-canaries/runner.js +19 -7
- package/esm/src/channels/control-plane.d.ts +2 -2
- package/esm/src/channels/control-plane.d.ts.map +1 -1
- package/esm/src/channels/control-plane.js +2 -2
- package/esm/src/chat/index.d.ts +2 -2
- package/esm/src/chat/index.js +2 -2
- package/esm/src/proxy/handler.js +1 -1
- package/esm/src/react/components/chat/chat/index.d.ts +1 -1
- package/esm/src/react/components/chat/chat/index.js +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +1 -1
- package/esm/src/server/handlers/request/agent-run-cancel.handler.js +3 -3
- package/esm/src/server/handlers/request/agent-run-resume.handler.js +3 -3
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/agent-stream.handler.js +10 -2
- package/esm/src/server/runtime-handler/environment-resolution.js +2 -2
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-tools.d.ts","sourceRoot":"","sources":["../../../../src/cli/mcp/tools/project-tools.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAK3E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAKL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAMtB,QAAA,MAAM,kBAAkB;;;GASvB,CAAC;AAGF,KAAK,eAAe,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE1E,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,CA2B9D,CAAC;AAMF,QAAA,MAAM,yBAAyB;;GAM9B,CAAC;AAGF,KAAK,sBAAsB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"project-tools.d.ts","sourceRoot":"","sources":["../../../../src/cli/mcp/tools/project-tools.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAK3E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAKL,KAAK,cAAc,EAEnB,KAAK,SAAS,EAGf,MAAM,cAAc,CAAC;AAMtB,QAAA,MAAM,kBAAkB;;;GASvB,CAAC;AAGF,KAAK,eAAe,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAC;AAE1E,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,CA2B9D,CAAC;AAMF,QAAA,MAAM,yBAAyB;;GAM9B,CAAC;AAGF,KAAK,sBAAsB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAAC;AAwExF,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,sBAAsB,EAAE,cAAc,CAkC/E,CAAC;AAMF,QAAA,MAAM,wBAAwB;;;GAO7B,CAAC;AAGF,KAAK,qBAAqB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC,CAAC;AAEtF,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiBD,eAAO,MAAM,kBAAkB,EAAE,OAAO,CAAC,qBAAqB,EAAE,mBAAmB,CAwDlF,CAAC;AAMF,QAAA,MAAM,yBAAyB;;;GAS9B,CAAC;AAGF,KAAK,sBAAsB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC,CAAC;AAExF,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA8ED,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,sBAAsB,EAAE,gBAAgB,EAAE,CAiBnF,CAAC"}
|
|
@@ -85,6 +85,9 @@ async function detectFeatures(projectDir, hasAI) {
|
|
|
85
85
|
}
|
|
86
86
|
return features;
|
|
87
87
|
}
|
|
88
|
+
async function hasAgUiRoute(projectDir) {
|
|
89
|
+
return await fileExists(join(projectDir, "app/api/ag-ui/route.ts"));
|
|
90
|
+
}
|
|
88
91
|
async function getProjectName(projectDir, fs) {
|
|
89
92
|
try {
|
|
90
93
|
const content = await fs.readTextFile(join(projectDir, "package.json"));
|
|
@@ -115,7 +118,7 @@ export const vfGetProjectContext = {
|
|
|
115
118
|
}
|
|
116
119
|
const directories = await detectDirectories(projectDir);
|
|
117
120
|
const hasAI = await directoryExists(join(projectDir, "ai")) ||
|
|
118
|
-
await
|
|
121
|
+
await hasAgUiRoute(projectDir);
|
|
119
122
|
const integrations = await detectIntegrations(projectDir, fs);
|
|
120
123
|
const features = await detectFeatures(projectDir, hasAI);
|
|
121
124
|
const name = await getProjectName(projectDir, fs);
|
|
@@ -217,9 +220,9 @@ async function detectVeryfrontProject(projectPath) {
|
|
|
217
220
|
}
|
|
218
221
|
const hasAppDir = await directoryExists(join(projectPath, "app"));
|
|
219
222
|
const hasAIDir = await directoryExists(join(projectPath, "ai"));
|
|
220
|
-
const
|
|
223
|
+
const hasAgentRoute = await hasAgUiRoute(projectPath);
|
|
221
224
|
let template;
|
|
222
|
-
if (hasAIDir ||
|
|
225
|
+
if (hasAIDir || hasAgentRoute)
|
|
223
226
|
template = "ai-agent";
|
|
224
227
|
else if (hasAppDir)
|
|
225
228
|
template = "minimal";
|
|
@@ -244,7 +247,7 @@ async function detectVeryfrontProject(projectPath) {
|
|
|
244
247
|
name,
|
|
245
248
|
path: projectPath,
|
|
246
249
|
template,
|
|
247
|
-
hasAI: hasAIDir ||
|
|
250
|
+
hasAI: hasAIDir || hasAgentRoute,
|
|
248
251
|
integrations,
|
|
249
252
|
};
|
|
250
253
|
}
|
|
@@ -17,7 +17,7 @@ declare namespace _default {
|
|
|
17
17
|
"ai-agent": {
|
|
18
18
|
files: {
|
|
19
19
|
"agents/assistant.ts": string;
|
|
20
|
-
"app/api/
|
|
20
|
+
"app/api/ag-ui/route.ts": string;
|
|
21
21
|
"app/layout.tsx": string;
|
|
22
22
|
"app/page.tsx": string;
|
|
23
23
|
"globals.css": string;
|
|
@@ -29,7 +29,7 @@ declare namespace _default {
|
|
|
29
29
|
"coding-agent": {
|
|
30
30
|
files: {
|
|
31
31
|
"agents/coder.ts": string;
|
|
32
|
-
"app/api/
|
|
32
|
+
"app/api/ag-ui/route.ts": string;
|
|
33
33
|
"app/layout.tsx": string;
|
|
34
34
|
"app/page.tsx": string;
|
|
35
35
|
"globals.css": string;
|
|
@@ -43,7 +43,7 @@ declare namespace _default {
|
|
|
43
43
|
"docs-agent": {
|
|
44
44
|
files: {
|
|
45
45
|
"agents/rag.ts": string;
|
|
46
|
-
"app/api/
|
|
46
|
+
"app/api/ag-ui/route.ts": string;
|
|
47
47
|
"app/api/uploads/[id]/route.ts": string;
|
|
48
48
|
"app/api/uploads/route.ts": string;
|
|
49
49
|
"app/layout.tsx": string;
|
|
@@ -69,7 +69,7 @@ declare namespace _default {
|
|
|
69
69
|
"agents/orchestrator.ts": string;
|
|
70
70
|
"agents/researcher.ts": string;
|
|
71
71
|
"agents/writer.ts": string;
|
|
72
|
-
"app/api/
|
|
72
|
+
"app/api/ag-ui/route.ts": string;
|
|
73
73
|
"app/layout.tsx": string;
|
|
74
74
|
"app/page.tsx": string;
|
|
75
75
|
"globals.css": string;
|
|
@@ -81,7 +81,7 @@ declare namespace _default {
|
|
|
81
81
|
"saas-starter": {
|
|
82
82
|
files: {
|
|
83
83
|
"agents/assistant.ts": string;
|
|
84
|
-
"app/api/
|
|
84
|
+
"app/api/ag-ui/route.ts": string;
|
|
85
85
|
"app/dashboard/page.tsx": string;
|
|
86
86
|
"app/layout.tsx": string;
|
|
87
87
|
"app/login/page.tsx": string;
|
|
@@ -9,7 +9,7 @@ export default {
|
|
|
9
9
|
"app/page.tsx": "'use client'\n\nimport { useState } from 'react'\nimport { useWorkflowStart, useWorkflowList } from 'veryfront/workflow'\n\nconst STATUS_STYLES: Record<string, string> = {\n running: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',\n completed: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400',\n waiting_for_approval: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400',\n failed: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400',\n pending: 'bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400',\n}\n\nexport default function WorkflowDashboard(): JSX.Element {\n const [topic, setTopic] = useState('')\n const { start, isStarting } = useWorkflowStart({ workflowId: 'content-pipeline' })\n const { runs, isLoading } = useWorkflowList()\n\n async function handleStart(e: React.FormEvent) {\n e.preventDefault()\n if (!topic.trim()) return\n await start({ topic: topic.trim() })\n setTopic('')\n }\n\n return (\n <div className=\"min-h-screen bg-neutral-50 dark:bg-neutral-950\">\n <div className=\"max-w-2xl mx-auto px-4 py-12\">\n <div className=\"mb-10\">\n <h1 className=\"text-2xl font-bold text-neutral-900 dark:text-white\">Content Pipeline</h1>\n <p className=\"mt-1 text-neutral-500 dark:text-neutral-400\">Research → Write → Review → Publish</p>\n </div>\n\n {/* Start new workflow */}\n <form onSubmit={handleStart} className=\"mb-10\">\n <div className=\"flex gap-3\">\n <input\n type=\"text\"\n value={topic}\n onChange={(e) => setTopic(e.target.value)}\n placeholder=\"Enter a topic to research and write about...\"\n className=\"flex-1 px-4 py-2.5 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-400 focus:outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500\"\n />\n <button\n type=\"submit\"\n disabled={isStarting || !topic.trim()}\n className=\"px-5 py-2.5 bg-blue-500 text-white font-medium rounded-xl hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n {isStarting ? 'Starting...' : 'Start'}\n </button>\n </div>\n </form>\n\n {/* Workflow runs */}\n <div>\n <h2 className=\"text-sm font-medium text-neutral-500 dark:text-neutral-400 uppercase tracking-wider mb-4\">Recent Runs</h2>\n\n {isLoading ? (\n <p className=\"text-neutral-400 text-sm py-8 text-center\">Loading...</p>\n ) : runs.length === 0 ? (\n <div className=\"text-center py-12 bg-white dark:bg-neutral-900 rounded-2xl border border-neutral-200 dark:border-neutral-800\">\n <p className=\"text-neutral-500 dark:text-neutral-400\">No workflows yet. Start one above.</p>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {runs.map((wf) => (\n <a\n key={wf.id}\n href={`/workflows/${wf.id}`}\n className=\"block bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-4 hover:border-neutral-300 dark:hover:border-neutral-700 transition-colors\"\n >\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"font-medium text-neutral-900 dark:text-white text-sm\">{wf.input?.topic || 'Untitled'}</p>\n <p className=\"text-xs text-neutral-500 mt-1\">{new Date(wf.createdAt).toLocaleString()}</p>\n </div>\n <span className={`px-2.5 py-1 rounded-full text-xs font-medium ${STATUS_STYLES[wf.status] || STATUS_STYLES.pending}`}>\n {wf.status.replace(/_/g, ' ')}\n </span>\n </div>\n </a>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n",
|
|
10
10
|
"app/workflows/[id]/page.tsx": "'use client'\n\nimport { useState } from 'react'\nimport { usePageContext } from 'veryfront/context'\nimport { useWorkflow } from 'veryfront/workflow'\n\nconst STEP_ICONS: Record<string, string> = {\n completed: '\\u2713',\n running: '\\u25C9',\n pending: '\\u25CB',\n waiting_for_approval: '\\u23F8',\n failed: '\\u2717',\n}\n\nexport default function WorkflowDetail(): JSX.Element {\n const { params } = usePageContext()\n const { run, pendingApprovals, isLoading, refresh } = useWorkflow({ runId: params.id })\n const [isSubmitting, setIsSubmitting] = useState(false)\n\n async function handleApproval(approvalId: string, approved: boolean) {\n setIsSubmitting(true)\n try {\n await fetch(`/api/workflows/runs/${params.id}/approvals/${approvalId}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ approved, approver: 'user' }),\n })\n await refresh()\n } finally {\n setIsSubmitting(false)\n }\n }\n\n if (isLoading) {\n return (\n <div className=\"min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950\">\n <p className=\"text-neutral-400\">Loading workflow...</p>\n </div>\n )\n }\n\n if (!run) {\n return (\n <div className=\"min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950\">\n <p className=\"text-neutral-400\">Workflow not found</p>\n </div>\n )\n }\n\n return (\n <div className=\"min-h-screen bg-neutral-50 dark:bg-neutral-950\">\n <div className=\"max-w-2xl mx-auto px-4 py-12\">\n <a href=\"/\" className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:hover:text-neutral-300 mb-6 inline-block\">← Back</a>\n\n <h1 className=\"text-2xl font-bold text-neutral-900 dark:text-white mb-1\">{run.input?.topic || 'Workflow'}</h1>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400 mb-8\">Started {new Date(run.createdAt).toLocaleString()}</p>\n\n {/* Steps */}\n <div className=\"space-y-4 mb-8\">\n {run.steps?.map((step: any) => (\n <div key={step.id} className=\"flex items-start gap-3 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-xl p-4\">\n <span className=\"text-lg mt-0.5\">{STEP_ICONS[step.status] || '\\u25CB'}</span>\n <div className=\"flex-1\">\n <p className=\"font-medium text-neutral-900 dark:text-white text-sm\">{step.name}</p>\n {step.output && (\n <p className=\"text-xs text-neutral-500 mt-1 line-clamp-2\">{typeof step.output === 'string' ? step.output : JSON.stringify(step.output)}</p>\n )}\n </div>\n <span className=\"text-xs text-neutral-400\">{step.status}</span>\n </div>\n ))}\n </div>\n\n {/* Approval */}\n {pendingApprovals.length > 0 && (\n <div className=\"bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-xl p-6\">\n <h2 className=\"font-medium text-amber-900 dark:text-amber-200 mb-2\">Approval Required</h2>\n <p className=\"text-sm text-amber-700 dark:text-amber-300 mb-4\">Review the draft before publishing.</p>\n <div className=\"flex gap-3\">\n <button\n onClick={() => handleApproval(pendingApprovals[0].id, true)}\n disabled={isSubmitting}\n className=\"px-4 py-2 bg-emerald-500 text-white font-medium rounded-lg hover:bg-emerald-600 disabled:opacity-50 transition-colors text-sm\"\n >\n Approve\n </button>\n <button\n onClick={() => handleApproval(pendingApprovals[0].id, false)}\n disabled={isSubmitting}\n className=\"px-4 py-2 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 text-neutral-700 dark:text-neutral-300 font-medium rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-700 disabled:opacity-50 transition-colors text-sm\"\n >\n Reject\n </button>\n </div>\n </div>\n )}\n </div>\n </div>\n )\n}\n",
|
|
11
11
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
12
|
-
"README.md": "# Agentic Workflow\n\nOrchestrated multi-step processes with human approval gates.\n\n## What's included\n\n- Content pipeline workflow (research, write, review, publish)\n- Parallel step execution\n- Human-in-the-loop approval gates\n- Dashboard to start, monitor, and approve workflow runs\n\n## Structure\n\n```\nagents/\n researcher.ts Research agent\n writer.ts Writing agent\nworkflows/content-pipeline.ts Workflow definition\napp/\n page.tsx Workflow dashboard\n workflows/[id]/page.tsx Run detail and approval UI\n```\n\nThis
|
|
12
|
+
"README.md": "# Agentic Workflow\n\nOrchestrated multi-step processes with human approval gates.\n\n## What's included\n\n- Content pipeline workflow (research, write, review, publish)\n- Parallel step execution\n- Human-in-the-loop approval gates\n- Dashboard to start, monitor, and approve workflow runs\n\n## Structure\n\n```\nagents/\n researcher.ts Research agent\n writer.ts Writing agent\nworkflows/content-pipeline.ts Workflow definition\napp/\n page.tsx Workflow dashboard\n workflows/[id]/page.tsx Run detail and approval UI\n```\n\nThis starter is not production-ready.\n",
|
|
13
13
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n",
|
|
14
14
|
"workflows/content-pipeline.ts": "import { workflow, step, parallel, waitForApproval } from \"veryfront/workflow\";\n\nexport default workflow({\n id: \"content-pipeline\",\n description: \"Research, write, review, and publish content\",\n steps: ({ input }) => [\n step(\"research\", {\n agent: \"researcher\",\n input: { topic: input.topic },\n }),\n\n parallel(\"draft\", [\n step(\"write-article\", { agent: \"writer\" }),\n step(\"write-summary\", { agent: \"writer\", input: { format: \"summary\" } }),\n ]),\n\n waitForApproval(\"editorial-review\", {\n message: \"Review the draft before publishing\",\n timeout: \"24h\",\n }),\n\n step(\"publish\", {\n execute: async ({ previous }) => {\n // Replace with your publishing logic\n return { published: true, url: `/articles/${Date.now()}` };\n },\n }),\n ],\n});\n"
|
|
15
15
|
}
|
|
@@ -17,11 +17,11 @@ export default {
|
|
|
17
17
|
"ai-agent": {
|
|
18
18
|
"files": {
|
|
19
19
|
"agents/assistant.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"assistant\",\n system: \"You are a helpful assistant. Answer questions clearly and concisely.\",\n tools: true,\n maxSteps: 10,\n});\n",
|
|
20
|
-
"app/api/
|
|
20
|
+
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"assistant\");\n",
|
|
21
21
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>AI Chat</title>\n </Head>\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-900\">\n {children}\n </div>\n </>\n );\n}\n",
|
|
22
|
-
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function ChatPage(): JSX.Element {\n const chat = useChat({ api: '/api/
|
|
22
|
+
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function ChatPage(): JSX.Element {\n const chat = useChat({ api: '/api/ag-ui' })\n\n return <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message\" />\n}\n",
|
|
23
23
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
24
|
-
"README.md": "# AI Agent\n\nA simple conversational AI with tool support.\n\n## What's included\n\n- Single assistant agent with streaming chat UI\n- Example calculator tool\n- `useChat` hook for real-time responses\n\n## Structure\n\n```\nagents/assistant.ts Agent definition\ntools/calculator.ts Example tool\napp/\n api/
|
|
24
|
+
"README.md": "# AI Agent\n\nA simple conversational AI with tool support.\n\n## What's included\n\n- Single assistant agent with streaming chat UI\n- Example calculator tool\n- `useChat` hook for real-time responses\n\n## Structure\n\n```\nagents/assistant.ts Agent definition\ntools/calculator.ts Example tool\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Chat interface\n```\n\nThis starter is not production-ready.\n",
|
|
25
25
|
"tools/calculator.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"calculator\",\n description: \"Perform basic arithmetic operations\",\n inputSchema: defineSchema((v) => v.object({\n operation: v.enum([\"add\", \"subtract\", \"multiply\", \"divide\"]),\n a: v.number(),\n b: v.number(),\n }))(),\n execute: async ({ operation, a, b }) => {\n if (operation === \"divide\" && b === 0) {\n throw new Error(\"Cannot divide by zero\");\n }\n\n if (operation === \"add\") return { result: a + b };\n if (operation === \"subtract\") return { result: a - b };\n if (operation === \"multiply\") return { result: a * b };\n return { result: a / b };\n },\n});\n",
|
|
26
26
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
27
27
|
}
|
|
@@ -29,11 +29,11 @@ export default {
|
|
|
29
29
|
"coding-agent": {
|
|
30
30
|
"files": {
|
|
31
31
|
"agents/coder.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"coder\",\n system: `You are an expert coding assistant. You can read, search, and modify code files in the project.\n\nWhen asked to make changes:\n1. First read the relevant files to understand the codebase\n2. Explain what you'll change and why\n3. Make the changes\n4. Verify the result\n\nAlways explain your reasoning before making edits.`,\n tools: true,\n maxSteps: 15,\n});\n",
|
|
32
|
-
"app/api/
|
|
32
|
+
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"coder\");\n",
|
|
33
33
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>Code Agent</title>\n </Head>\n <div className=\"dark\">\n <div className=\"flex flex-col h-screen bg-neutral-950\">\n <header className=\"flex-shrink-0 border-b border-neutral-800\">\n <div className=\"max-w-4xl mx-auto flex items-center gap-3 px-4 py-3\">\n <div className=\"w-8 h-8 rounded-lg bg-emerald-500/10 flex items-center justify-center\">\n <svg\n className=\"w-4 h-4 text-emerald-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"\n />\n </svg>\n </div>\n <div>\n <h1 className=\"font-medium text-white text-sm font-mono\">\n code-agent\n </h1>\n <p className=\"text-xs text-neutral-500\">\n read, search, edit project files\n </p>\n </div>\n <div className=\"ml-auto flex items-center gap-1.5\">\n <span className=\"w-2 h-2 rounded-full bg-emerald-400 animate-pulse\" />\n <span className=\"text-xs text-neutral-500 font-mono\">ready</span>\n </div>\n </div>\n </header>\n {children}\n </div>\n </div>\n </>\n );\n}\n",
|
|
34
|
-
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function CodeAgent(): JSX.Element {\n const chat = useChat({ api: '/api/
|
|
34
|
+
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function CodeAgent(): JSX.Element {\n const chat = useChat({ api: '/api/ag-ui' })\n\n return (\n <Chat\n {...chat}\n className=\"flex-1 min-h-0\"\n placeholder=\"Describe what you want to build or fix...\"\n />\n )\n}\n",
|
|
35
35
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
36
|
-
"README.md": "# Coding Agent\n\nAn AI assistant that can read, understand, and modify project files.\n\n## What's included\n\n- Coder agent with file system tools\n- Read, list, and edit files through conversation\n- Safe search/replace editing pattern\n\n## Structure\n\n```\nagents/coder.ts Agent with coding instructions\ntools/\n read-file.ts Read file contents\n list-files.ts List directory contents\n edit-file.ts Search and replace in files\napp/\n api/
|
|
36
|
+
"README.md": "# Coding Agent\n\nAn AI assistant that can read, understand, and modify project files.\n\n## What's included\n\n- Coder agent with file system tools\n- Read, list, and edit files through conversation\n- Safe search/replace editing pattern\n\n## Structure\n\n```\nagents/coder.ts Agent with coding instructions\ntools/\n read-file.ts Read file contents\n list-files.ts List directory contents\n edit-file.ts Search and replace in files\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Chat interface\n```\n\nThis starter is not production-ready.\n",
|
|
37
37
|
"tools/edit-file.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { readTextFile, writeTextFile, resolve, cwd } from \"veryfront/fs\";\n\nexport default tool({\n id: \"edit-file\",\n description: \"Edit a file by replacing a specific string with new content\",\n inputSchema: defineSchema((v) => v.object({\n path: v.string().describe(\"File path relative to the project root\"),\n search: v.string().describe(\"Exact string to find in the file\"),\n replace: v.string().describe(\"String to replace it with\"),\n }))(),\n execute: async ({ path, search, replace }) => {\n const absolute = resolve(cwd(), path);\n\n let content: string;\n try {\n content = await readTextFile(absolute);\n } catch {\n return { error: `File not found: ${path}` };\n }\n\n if (!content.includes(search)) {\n return { error: \"Search string not found in file\" };\n }\n\n const updated = content.replace(search, replace);\n await writeTextFile(absolute, updated);\n return { path, success: true };\n },\n});\n",
|
|
38
38
|
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { readDir, resolve, cwd } from \"veryfront/fs\";\n\nexport default tool({\n id: \"list-files\",\n description: \"List files in a project directory\",\n inputSchema: defineSchema((v) => v.object({\n directory: v\n .string()\n .default(\".\")\n .describe(\"Directory path relative to project root\"),\n extensions: v\n .array(v.string())\n .optional()\n .describe(\"Filter by file extensions (e.g. ['.ts', '.tsx'])\"),\n }))(),\n execute: async ({ directory, extensions }) => {\n const absolute = resolve(cwd(), directory);\n const entries = await readDir(absolute);\n\n let files = entries\n .filter((e) => e.isFile)\n .map((e) => e.name);\n\n if (extensions?.length) {\n files = files.filter((f) =>\n extensions.some((ext) => f.endsWith(ext))\n );\n }\n\n return { directory, files, count: files.length };\n },\n});\n",
|
|
39
39
|
"tools/read-file.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { readTextFile, resolve, cwd } from \"veryfront/fs\";\n\nexport default tool({\n id: \"read-file\",\n description: \"Read the contents of a file in the project\",\n inputSchema: defineSchema((v) => v.object({\n path: v.string().describe(\"File path relative to the project root\"),\n }))(),\n execute: async ({ path }) => {\n try {\n const absolute = resolve(cwd(), path);\n const content = await readTextFile(absolute);\n return { path, content };\n } catch {\n return { error: `File not found: ${path}` };\n }\n },\n});\n",
|
|
@@ -43,15 +43,15 @@ export default {
|
|
|
43
43
|
"docs-agent": {
|
|
44
44
|
"files": {
|
|
45
45
|
"agents/rag.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"rag\",\n system:\n `You answer questions using the provided documents. ` +\n `Always cite your sources by referencing the document title. ` +\n `If the search results don't contain a clear answer, say so honestly.`,\n});\n",
|
|
46
|
-
"app/api/
|
|
46
|
+
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\nimport { store } from \"../../../store.ts\";\n\nexport const POST = createAgUiHandler(\"rag\", {\n beforeStream: async ({ lastUserText }) => {\n const query = lastUserText.trim();\n if (!query) return;\n\n try {\n await store.indexContentDir();\n const results = await store.search(query, { topK: 5 });\n if (results.length === 0) return;\n\n const contextBlock = results\n .map(\n (result) =>\n `[${result.title}] (score: ${\n result.score.toFixed(2)\n })\\n${result.text}`,\n )\n .join(\"\\n\\n---\\n\\n\");\n\n return {\n prepend: [\n {\n role: \"system\",\n parts: [\n {\n type: \"text\",\n text:\n `Here are relevant documents retrieved for your question:\\n\\n${contextBlock}\\n\\n` +\n \"Use these documents to answer. Cite the document title when referencing information.\",\n },\n ],\n },\n ],\n };\n } catch (e) {\n console.error(\"[RAG] Retrieval failed:\", e);\n return;\n }\n },\n});\n",
|
|
47
47
|
"app/api/uploads/[id]/route.ts": "import { createUploadHandler } from \"veryfront/embedding\";\nimport { store } from \"../../../../store.ts\";\n\nexport const { DELETE } = createUploadHandler(store);\n",
|
|
48
48
|
"app/api/uploads/route.ts": "import { createUploadHandler } from \"veryfront/embedding\";\nimport { store } from \"../../../store.ts\";\n\nexport const { POST, GET } = createUploadHandler(store);\n",
|
|
49
49
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }): React.ReactNode {\n return (\n <>\n <Head><title>Docs Agent</title></Head>\n <div className=\"flex flex-col h-screen\">\n {children}\n </div>\n </>\n );\n}\n",
|
|
50
|
-
"app/page.tsx": "'use client'\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport { ChatWithSidebar, useChat, type QuickAction } from 'veryfront/chat'\n\nconst UPLOAD_API = '/api/uploads'\nconst ACCEPT = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.csv,.txt,.md,.mdx,.html,.rtf,.epub,.json,.xml'\n\nconst QUICK_ACTIONS: QuickAction[] = [\n { id: 'ask-question', label: 'Ask Question', prompt: 'I have a question about this document: ' },\n { id: 'extract-insights', label: 'Extract Insights', prompt: 'Extract the key insights from the uploaded documents.' },\n { id: 'find-sources', label: 'Find Sources', prompt: 'Find relevant sources and references in the documents for: ' },\n]\n\ninterface Doc { id: string; title: string; source: string; url?: string }\n\nfunction useUploads(api: string) {\n const [docs, setDocs] = useState<Doc[]>([])\n const [uploading, setUploading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n const res = await fetch(api)\n if (res.ok) {\n const data = await res.json()\n setDocs(Array.isArray(data) ? data : data.uploads ?? [])\n }\n } catch { /* ignore */ }\n }, [api])\n\n useEffect(() => { refresh() }, [refresh])\n\n const upload = useCallback(async (file: File) => {\n setUploading(true)\n setError(null)\n try {\n const form = new FormData()\n form.append('file', file)\n const res = await fetch(api, { method: 'POST', body: form })\n if (!res.ok) throw new Error(await res.text())\n await refresh()\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Upload failed')\n } finally {\n setUploading(false)\n }\n }, [api, refresh])\n\n const remove = useCallback(async (id: string) => {\n await fetch(`${api}/${id}`, { method: 'DELETE' })\n await refresh()\n }, [api, refresh])\n\n const uploads = useMemo(\n () => docs.filter((d) => d.source.startsWith('upload:')),\n [docs],\n )\n\n return { uploads, uploading, error, upload, remove }\n}\n\nexport default function DocsChat() {\n const chat = useChat({ api: '/api/
|
|
50
|
+
"app/page.tsx": "'use client'\n\nimport { useState, useEffect, useCallback, useMemo } from 'react'\nimport { ChatWithSidebar, useChat, type QuickAction } from 'veryfront/chat'\n\nconst UPLOAD_API = '/api/uploads'\nconst ACCEPT = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.csv,.txt,.md,.mdx,.html,.rtf,.epub,.json,.xml'\n\nconst QUICK_ACTIONS: QuickAction[] = [\n { id: 'ask-question', label: 'Ask Question', prompt: 'I have a question about this document: ' },\n { id: 'extract-insights', label: 'Extract Insights', prompt: 'Extract the key insights from the uploaded documents.' },\n { id: 'find-sources', label: 'Find Sources', prompt: 'Find relevant sources and references in the documents for: ' },\n]\n\ninterface Doc { id: string; title: string; source: string; url?: string }\n\nfunction useUploads(api: string) {\n const [docs, setDocs] = useState<Doc[]>([])\n const [uploading, setUploading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const refresh = useCallback(async () => {\n try {\n const res = await fetch(api)\n if (res.ok) {\n const data = await res.json()\n setDocs(Array.isArray(data) ? data : data.uploads ?? [])\n }\n } catch { /* ignore */ }\n }, [api])\n\n useEffect(() => { refresh() }, [refresh])\n\n const upload = useCallback(async (file: File) => {\n setUploading(true)\n setError(null)\n try {\n const form = new FormData()\n form.append('file', file)\n const res = await fetch(api, { method: 'POST', body: form })\n if (!res.ok) throw new Error(await res.text())\n await refresh()\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Upload failed')\n } finally {\n setUploading(false)\n }\n }, [api, refresh])\n\n const remove = useCallback(async (id: string) => {\n await fetch(`${api}/${id}`, { method: 'DELETE' })\n await refresh()\n }, [api, refresh])\n\n const uploads = useMemo(\n () => docs.filter((d) => d.source.startsWith('upload:')),\n [docs],\n )\n\n return { uploads, uploading, error, upload, remove }\n}\n\nexport default function DocsChat() {\n const chat = useChat({ api: '/api/ag-ui' })\n const docs = useUploads(UPLOAD_API)\n\n const attachmentItems = useMemo(() => {\n const items = docs.uploads.map((d) => ({\n id: d.id,\n name: d.title,\n status: 'ready' as const,\n }))\n if (docs.uploading) {\n items.push({ id: '__uploading', name: 'Uploading...', status: 'uploading' as const })\n }\n return items\n }, [docs.uploads, docs.uploading])\n\n const uploadFiles = useMemo(\n () => docs.uploads.map((d) => ({ id: d.id, name: d.title, url: d.url })),\n [docs.uploads],\n )\n\n const handleAttach = useCallback((files: FileList) => {\n for (const file of Array.from(files)) {\n docs.upload(file)\n }\n }, [docs.upload])\n\n const handleQuickAction = useCallback((action: QuickAction) => {\n if (action.prompt) chat.setInput(action.prompt)\n }, [chat.setInput])\n\n return (\n <ChatWithSidebar\n chat={chat}\n sidebar={{ storageKey: 'rag-threads' }}\n features={{ steps: true, tabs: true, sources: true, export: true }}\n models={{\n options: [\n {\n value: 'anthropic/claude-sonnet-4-6',\n label: 'Claude Sonnet',\n },\n {\n value: 'openai/gpt-4.1-mini',\n label: 'GPT-4.1 Mini',\n },\n {\n value: 'google/gemini-2.5-flash',\n label: 'Gemini 2.5 Flash',\n badge: 'Fast',\n },\n ],\n }}\n attachments={{\n uploads: uploadFiles,\n onRemoveUpload: docs.remove,\n onAttach: handleAttach,\n accept: ACCEPT,\n items: attachmentItems,\n onRemoveItem: docs.remove,\n }}\n quickActions={{\n actions: QUICK_ACTIONS,\n onAction: handleQuickAction,\n }}\n message={{\n renderTool: () => null,\n }}\n className=\"flex-1 min-h-0\"\n placeholder=\"Ask anything about your documents...\"\n emptyState={{ title: 'Docs Agent', description: 'Upload files and ask questions' }}\n />\n )\n}\n",
|
|
51
51
|
"content/architecture.md": "# Architecture\n\nAcme Platform uses a modular, event-driven architecture.\n\n## Core Components\n\n### API Gateway\nRoutes incoming requests to the appropriate microservice. Handles authentication, rate limiting, and request validation.\n\n### Event Bus\nAsynchronous message broker connecting all services. Supports pub/sub and point-to-point messaging patterns.\n\n### Data Layer\nMulti-tenant data storage with automatic sharding. Supports PostgreSQL for relational data and Redis for caching.\n\n## Request Flow\n\n1. Client sends request to API Gateway\n2. Gateway validates authentication token\n3. Request is routed to the target service\n4. Service processes request and publishes events\n5. Response is returned through the gateway\n\n## Scaling\n\nEach component scales independently. The API Gateway uses horizontal scaling with load balancing. Services auto-scale based on queue depth and CPU utilization.\n\n## Security\n\n- All inter-service communication uses mTLS\n- API tokens are rotated every 24 hours\n- Data at rest is encrypted with AES-256\n- Audit logs are retained for 90 days\n",
|
|
52
|
-
"content/getting-started.md": "# Getting Started\n\nWelcome to Acme Platform. This guide covers initial setup and core concepts.\n\n## Installation\n\nInstall the CLI globally:\n\n```bash\nnpm install -g @acme/cli\n```\n\n## Creating a Project\n\nRun the init command to scaffold a new project:\n\n```bash\nacme init my-project\ncd my-project\n```\n\n## Project Structure\n\n- `src
|
|
52
|
+
"content/getting-started.md": "# Getting Started\n\nWelcome to Acme Platform. This guide covers initial setup and core concepts.\n\n## Installation\n\nInstall the CLI globally:\n\n```bash\nnpm install -g @acme/cli\n```\n\n## Creating a Project\n\nRun the init command to scaffold a new project:\n\n```bash\nacme init my-project\ncd my-project\n```\n\n## Project Structure\n\n- `src/`: Application source code\n- `config/`: Configuration files\n- `tests/`: Test suite\n- `docs/`: Documentation\n\n## Configuration\n\nCreate an `acme.config.ts` file in your project root:\n\n```ts\nexport default {\n name: \"my-project\",\n region: \"us-east-1\",\n features: [\"auth\", \"storage\"],\n};\n```\n\n## Next Steps\n\n- Read the [Architecture Guide](./architecture) to understand the system design\n- Check [API Reference](./api-reference) for available endpoints\n- Join our Discord community for support\n",
|
|
53
53
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
54
|
-
"README.md": "# Docs Agent\n\nA chatbot that answers questions from your own documents using Retrieval-Augmented Generation (RAG).\n\n## What's included\n\n- Q&A agent with source citation\n- Embedding-based semantic search with convention-based model selection\n- Document upload supporting PDF, DOCX, XLSX, PPTX, CSV, HTML, RTF, EPUB, TXT, and Markdown\n- `ragStore()` with local JSON storage by default, and Veryfront Cloud RAG when bootstrap is present\n- Original uploaded files stored in Veryfront Cloud project uploads when cloud bootstrap is present\n- Sample content in `/content` directory auto-indexed on first search\n\n## Getting started\n\n1. Set your Veryfront Cloud bootstrap vars:\n\n ```bash\n export VERYFRONT_API_TOKEN=vf_...\n export VERYFRONT_PROJECT_SLUG=my-project\n ```\n\n2. Start the dev server:\n\n ```bash\n npx veryfront dev\n ```\n\n3. Open the app and upload a document or ask a question. The sample docs in `content/` are indexed automatically.\n\nIf you are using a self-hosted Veryfront API, also set `VERYFRONT_API_URL`.\n\n## Architecture\n\nRAG grounds LLM responses in your documents through three pipelines: **Ingestion**, **Query**, and **RAG**. These pipelines are orchestrated around a shared vector store.\n\n```mermaid\nflowchart LR\n ChatUI_L[\"Chat UI\"]\n\n subgraph IngestionFlow[\"Ingestion Pipeline\"]\n D[\"Documents\"] --> EXT[\"Extraction\"] --> DC[\"Chunking\"] --> DE[\"Document\\nEmbedding\"] --> ING[\"Storage\"]\n end\n\n subgraph QueryFlow[\"Query Pipeline\"]\n Q[\"Query\"] --> QE[\"Query\\nEmbedding\"] --> SS[\"Similarity\\nSearch\"]\n end\n\n subgraph RAGFlow[\"RAG Pipeline\"]\n BF[\"beforeStream\\nHook\"] --> RET[\"Retrieval\"] --> AUG[\"Augmentation\"] --> AG[\"Agent\"] --> GEN[\"Generation\"]\n end\n\n EMB((\"Embedding\\nModel\"))\n GEN_LLM((\"Generative\\nModel\"))\n VS[(\"Vector\\nStore\")]\n ChatUI_R[\"Chat UI\"]\n\n ChatUI_L --> D\n ChatUI_L --> Q\n\n QE -.- EMB\n DE -.- EMB\n\n SS --> VS\n ING --> VS\n\n Q --> BF\n VS --> RET\n AG -.- GEN_LLM\n GEN -.- GEN_LLM\n GEN --> ChatUI_R\n```\n\n### Pipelines\n\n**Ingestion**: Documents are parsed into plain text via the built-in kreuzberg extraction engine (supporting PDF, DOCX, XLSX, PPTX, HTML, RTF, EPUB, and 76+ formats), split into overlapping chunks (~1000 chars, 200 char overlap), and stored in the default `ragStore()`. In local mode that means `data/index.json`; with Veryfront Cloud bootstrap it upgrades to the cloud RAG backend automatically. The original uploaded binary is also stored in the project's Veryfront Cloud uploads store so remote projects retain the source file, not just the extracted text. Embeddings are generated lazily on first search to keep uploads fast.\n\n**Query**: The user's query is embedded into the same vector space as the documents, then compared against all stored chunks using cosine similarity to find the top-*k* most relevant results.\n\n**RAG**: The `beforeStream` hook in the
|
|
54
|
+
"README.md": "# Docs Agent\n\nA chatbot that answers questions from your own documents using Retrieval-Augmented Generation (RAG).\n\n## What's included\n\n- Q&A agent with source citation\n- Embedding-based semantic search with convention-based model selection\n- Document upload supporting PDF, DOCX, XLSX, PPTX, CSV, HTML, RTF, EPUB, TXT, and Markdown\n- `ragStore()` with local JSON storage by default, and Veryfront Cloud RAG when bootstrap is present\n- Original uploaded files stored in Veryfront Cloud project uploads when cloud bootstrap is present\n- Sample content in `/content` directory auto-indexed on first search\n\n## Getting started\n\n1. Set your Veryfront Cloud bootstrap vars:\n\n ```bash\n export VERYFRONT_API_TOKEN=vf_...\n export VERYFRONT_PROJECT_SLUG=my-project\n ```\n\n2. Start the dev server:\n\n ```bash\n npx veryfront dev\n ```\n\n3. Open the app and upload a document or ask a question. The sample docs in `content/` are indexed automatically.\n\nIf you are using a self-hosted Veryfront API, also set `VERYFRONT_API_URL`.\n\n## Architecture\n\nRAG grounds LLM responses in your documents through three pipelines: **Ingestion**, **Query**, and **RAG**. These pipelines are orchestrated around a shared vector store.\n\n```mermaid\nflowchart LR\n ChatUI_L[\"Chat UI\"]\n\n subgraph IngestionFlow[\"Ingestion Pipeline\"]\n D[\"Documents\"] --> EXT[\"Extraction\"] --> DC[\"Chunking\"] --> DE[\"Document\\nEmbedding\"] --> ING[\"Storage\"]\n end\n\n subgraph QueryFlow[\"Query Pipeline\"]\n Q[\"Query\"] --> QE[\"Query\\nEmbedding\"] --> SS[\"Similarity\\nSearch\"]\n end\n\n subgraph RAGFlow[\"RAG Pipeline\"]\n BF[\"beforeStream\\nHook\"] --> RET[\"Retrieval\"] --> AUG[\"Augmentation\"] --> AG[\"Agent\"] --> GEN[\"Generation\"]\n end\n\n EMB((\"Embedding\\nModel\"))\n GEN_LLM((\"Generative\\nModel\"))\n VS[(\"Vector\\nStore\")]\n ChatUI_R[\"Chat UI\"]\n\n ChatUI_L --> D\n ChatUI_L --> Q\n\n QE -.- EMB\n DE -.- EMB\n\n SS --> VS\n ING --> VS\n\n Q --> BF\n VS --> RET\n AG -.- GEN_LLM\n GEN -.- GEN_LLM\n GEN --> ChatUI_R\n```\n\n### Pipelines\n\n**Ingestion**: Documents are parsed into plain text via the built-in kreuzberg extraction engine (supporting PDF, DOCX, XLSX, PPTX, HTML, RTF, EPUB, and 76+ formats), split into overlapping chunks (~1000 chars, 200 char overlap), and stored in the default `ragStore()`. In local mode that means `data/index.json`; with Veryfront Cloud bootstrap it upgrades to the cloud RAG backend automatically. The original uploaded binary is also stored in the project's Veryfront Cloud uploads store so remote projects retain the source file, not just the extracted text. Embeddings are generated lazily on first search to keep uploads fast.\n\n**Query**: The user's query is embedded into the same vector space as the documents, then compared against all stored chunks using cosine similarity to find the top-*k* most relevant results.\n\n**RAG**: The `beforeStream` hook in the AG-UI route intercepts each message before it reaches the agent. It searches the document store for relevant chunks, assembles them into context, and prepends them as retrieved reference data. The agent then generates a cited response streamed back to the user.\n\n## Structure\n\n```\nstore.ts RAG store config\nagents/rag.ts Q&A agent with citation instructions\ncontent/\n getting-started.md Sample document\n architecture.md Sample document\napp/\n api/ag-ui/route.ts AG-UI endpoint\n api/uploads/route.ts Upload (POST) and list (GET) uploads\n api/uploads/[id]/route.ts Delete upload\n page.tsx Chat UI with document upload panel\n layout.tsx Root layout with header\n```\n\n## Framework usage\n\n| What | Framework | Template code |\n|------|-----------|---------------|\n| Chat UI + streaming | `Chat`, `useChat` | `page.tsx` |\n| Upload management | `useUploads` hook | `page.tsx` |\n| Source display | `showSources` prop on `Chat` | `page.tsx` |\n| Upload API routes | `createUploadHandler` | 1-line per route file |\n| AG-UI route | `createAgUiHandler` | 1 line in `route.ts` |\n| Agent definition | `agent()` | Config object in `agents/rag.ts` |\n| RAG retrieval | `beforeStream` hook | Context injection in `api/ag-ui/route.ts` |\n| Vector store | `ragStore()` | Config in `store.ts` |\n\n## Adding documents\n\n- Drop files into `content/`. They are indexed automatically on first search\n- Or use the upload panel in the UI for PDF, DOCX, XLSX, PPTX, CSV, HTML, RTF, EPUB, TXT, and MD files\n\n## Production notes\n\nThis is a starter template, not a production-ready setup. For production, consider:\n\n- **Vector store**: Replace the default store with pgvector, Pinecone, or Qdrant for datasets beyond ~10k chunks\n- **Reranking**: Add a cross-encoder reranker (e.g. Cohere Rerank) after retrieval to improve precision\n- **Hybrid search**: Combine dense vectors with BM25 keyword matching for better recall\n",
|
|
55
55
|
"store.ts": "import { ragStore } from \"veryfront/embedding\";\n\nexport const store = ragStore({\n storagePath: \"data/index.json\",\n contentDir: \"content\",\n});\n",
|
|
56
56
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
57
57
|
}
|
|
@@ -61,7 +61,7 @@ export default {
|
|
|
61
61
|
"app/about/page.mdx": "<div className=\"prose dark:prose-invert\">\n\n# About\n\nThis is a minimal starter template.\n\n## Features\n\n- HMR for local development\n- MDX support\n- Tailwind CSS\n- Minimal defaults\n\n## Getting Started\n\n1. Edit pages in the `app` directory\n2. Add components in `components`\n3. Customize with `veryfront.config.ts` when needed\n\nHappy coding!\n\n</div>\n",
|
|
62
62
|
"app/layout.tsx": "export default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <div className=\"min-h-screen bg-white text-neutral-900 dark:bg-neutral-900 dark:text-neutral-100\">\n <main className=\"mx-auto max-w-2xl px-6 py-16\">{children}</main>\n </div>\n );\n}\n",
|
|
63
63
|
"app/page.tsx": "export default function HomePage(): JSX.Element {\n return (\n <div>\n <h1 className=\"mb-4 text-4xl font-bold text-neutral-900 dark:text-white\">\n Welcome to Veryfront\n </h1>\n <p className=\"mb-8 text-neutral-600 dark:text-neutral-400\">\n Edit{\" \"}\n <code className=\"rounded bg-neutral-100 px-1.5 py-0.5 text-sm dark:bg-neutral-800\">\n app/page.tsx\n </code>{\" \"}\n to get started.\n </p>\n <div className=\"flex gap-3\">\n <a\n href=\"/about\"\n className=\"rounded-full bg-blue-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600\"\n >\n About\n </a>\n <a\n href=\"https://veryfront.com/docs\"\n className=\"rounded-full bg-neutral-100 px-4 py-2 text-sm font-medium text-neutral-900 transition-colors hover:bg-neutral-200 dark:bg-neutral-800 dark:text-white dark:hover:bg-neutral-700\"\n >\n Documentation\n </a>\n </div>\n </div>\n );\n}\n",
|
|
64
|
-
"README.md": "# Minimal\n\nA barebones starter with just pages and routing
|
|
64
|
+
"README.md": "# Minimal\n\nA barebones starter with just pages and routing, no agents, no tools.\n\n## What's included\n\n- Home page with welcome message\n- About page using MDX\n- Tailwind CSS styling with dark mode\n\n## Structure\n\n```\napp/\n layout.tsx Root layout\n page.tsx Home page\n about/page.mdx Markdown content page\n```\n\nThis starter is not production-ready.\n"
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"multi-agent-system": {
|
|
@@ -69,11 +69,11 @@ export default {
|
|
|
69
69
|
"agents/orchestrator.ts": "import { agent, getAgentsAsTools } from \"veryfront/agent\";\n\nexport default agent({\n id: \"orchestrator\",\n system:\n \"You coordinate a team of AI agents. \" +\n \"Delegate research tasks to the researcher and writing tasks to the writer. \" +\n \"Combine their outputs into a polished response.\",\n tools: getAgentsAsTools([\"researcher\", \"writer\"]),\n maxSteps: 10,\n});\n",
|
|
70
70
|
"agents/researcher.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"researcher\",\n system:\n \"You are a research specialist. \" +\n \"Gather comprehensive information on the given topic. \" +\n \"Present findings as structured bullet points with key facts and data.\",\n tools: true,\n maxSteps: 5,\n});\n",
|
|
71
71
|
"agents/writer.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"writer\",\n system:\n \"You are a writing specialist. \" +\n \"Take research notes and transform them into clear, engaging prose. \" +\n \"Use a professional but approachable tone.\",\n maxSteps: 3,\n});\n",
|
|
72
|
-
"app/api/
|
|
72
|
+
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"orchestrator\");\n",
|
|
73
73
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>Multi-Agent System</title>\n </Head>\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-950\">\n <header className=\"flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800\">\n <div className=\"max-w-3xl mx-auto flex items-center gap-3 px-4 py-3\">\n <div className=\"flex -space-x-2\">\n <div className=\"w-7 h-7 rounded-full bg-blue-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n O\n </div>\n <div className=\"w-7 h-7 rounded-full bg-amber-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n R\n </div>\n <div className=\"w-7 h-7 rounded-full bg-violet-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n W\n </div>\n </div>\n <div>\n <h1 className=\"font-medium text-neutral-900 dark:text-white text-sm\">\n Agent Team\n </h1>\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n Orchestrator, Researcher, Writer\n </p>\n </div>\n </div>\n </header>\n {children}\n </div>\n </>\n );\n}\n",
|
|
74
|
-
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function MultiAgentChat(): JSX.Element {\n const chat = useChat({ api: '/api/
|
|
74
|
+
"app/page.tsx": "'use client'\n\nimport { Chat, useChat } from 'veryfront/chat'\n\nexport default function MultiAgentChat(): JSX.Element {\n const chat = useChat({ api: '/api/ag-ui' })\n\n return <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Give the team a task...\" />\n}\n",
|
|
75
75
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
76
|
-
"README.md": "# Multi-Agent System\n\nA team of specialized agents that collaborate on tasks.\n\n## What's included\n\n- Orchestrator that delegates to researcher and writer agents\n- Agent-as-tool composition via `getAgentsAsTools()`\n- Web search tool (placeholder
|
|
76
|
+
"README.md": "# Multi-Agent System\n\nA team of specialized agents that collaborate on tasks.\n\n## What's included\n\n- Orchestrator that delegates to researcher and writer agents\n- Agent-as-tool composition via `getAgentsAsTools()`\n- Web search tool (placeholder, configure your own API)\n\n## Structure\n\n```\nagents/\n orchestrator.ts Coordinates the team\n researcher.ts Gathers information\n writer.ts Produces polished content\ntools/web-search.ts Placeholder search tool\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Chat interface\n```\n\nThis starter is not production-ready.\n",
|
|
77
77
|
"tools/web-search.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"web-search\",\n description: \"Search the web for information on a topic\",\n inputSchema: defineSchema((v) => v.object({\n query: v.string().describe(\"Search query\"),\n }))(),\n execute: async ({ query: _query }) => {\n // Connect a real search API to use this tool.\n // Popular options: Tavily, SerpAPI, Brave Search\n throw new Error(\n \"No search API configured. \" +\n \"See https://veryfront.com/code/guides/tools for setup instructions.\",\n );\n },\n});\n",
|
|
78
78
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
79
79
|
}
|
|
@@ -81,13 +81,13 @@ export default {
|
|
|
81
81
|
"saas-starter": {
|
|
82
82
|
"files": {
|
|
83
83
|
"agents/assistant.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"assistant\",\n system: \"You are a helpful AI assistant. Be concise and direct.\",\n tools: true,\n memory: { type: \"conversation\", maxMessages: 50 },\n maxSteps: 10,\n});\n",
|
|
84
|
-
"app/api/
|
|
85
|
-
"app/dashboard/page.tsx": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Chat, useChat } from \"veryfront/chat\";\n\ninterface Conversation {\n id: string;\n title: string;\n updatedAt: string;\n}\n\nconst INITIAL_CONVERSATIONS: Conversation[] = [\n { id: \"1\", title: \"Getting started\", updatedAt: \"Just now\" },\n];\n\nexport default function Dashboard(): JSX.Element {\n const [conversations] = useState<Conversation[]>(INITIAL_CONVERSATIONS);\n const [activeId, setActiveId] = useState(\"1\");\n const chat = useChat({ api: \"/api/
|
|
84
|
+
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"assistant\");\n",
|
|
85
|
+
"app/dashboard/page.tsx": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Chat, useChat } from \"veryfront/chat\";\n\ninterface Conversation {\n id: string;\n title: string;\n updatedAt: string;\n}\n\nconst INITIAL_CONVERSATIONS: Conversation[] = [\n { id: \"1\", title: \"Getting started\", updatedAt: \"Just now\" },\n];\n\nexport default function Dashboard(): JSX.Element {\n const [conversations] = useState<Conversation[]>(INITIAL_CONVERSATIONS);\n const [activeId, setActiveId] = useState(\"1\");\n const chat = useChat({ api: \"/api/ag-ui\" });\n\n return (\n <div className=\"flex h-screen bg-white dark:bg-neutral-950\">\n {/* Sidebar */}\n <aside className=\"w-64 border-r border-neutral-200 dark:border-neutral-800 flex flex-col bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"p-4 border-b border-neutral-200 dark:border-neutral-800\">\n <button className=\"w-full flex items-center gap-2 px-3 py-2 text-sm font-medium text-neutral-700 dark:text-neutral-300 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-colors\">\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M12 4.5v15m7.5-7.5h-15\"\n />\n </svg>\n New chat\n </button>\n </div>\n\n <nav className=\"flex-1 overflow-y-auto p-2 space-y-0.5\">\n {conversations.map((conv) => (\n <button\n key={conv.id}\n onClick={() => setActiveId(conv.id)}\n className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${\n activeId === conv.id\n ? \"bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-white\"\n : \"text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-800/50\"\n }`}\n >\n <p className=\"truncate\">{conv.title}</p>\n <p className=\"text-xs text-neutral-400 mt-0.5\">\n {conv.updatedAt}\n </p>\n </button>\n ))}\n </nav>\n\n <div className=\"p-4 border-t border-neutral-200 dark:border-neutral-800\">\n <div className=\"flex items-center gap-2\">\n <div className=\"w-8 h-8 rounded-full bg-neutral-200 dark:bg-neutral-700 flex items-center justify-center text-xs font-medium text-neutral-600 dark:text-neutral-300\">\n U\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-neutral-900 dark:text-white truncate\">\n User\n </p>\n <p className=\"text-xs text-neutral-500 truncate\">\n user@example.com\n </p>\n </div>\n </div>\n </div>\n </aside>\n\n {/* Chat */}\n <main className=\"flex-1 flex flex-col\">\n <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message...\" />\n </main>\n </div>\n );\n}\n",
|
|
86
86
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>AI SaaS</title>\n </Head>\n <div className=\"antialiased\">\n {children}\n </div>\n </>\n );\n}\n",
|
|
87
87
|
"app/login/page.tsx": "\"use client\";\n\nexport default function LoginPage(): JSX.Element {\n return (\n <div className=\"min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950 px-4\">\n <div className=\"w-full max-w-sm\">\n <div className=\"text-center mb-8\">\n <h1 className=\"text-xl font-bold text-neutral-900 dark:text-white\">\n Welcome back\n </h1>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400 mt-1\">\n Sign in to continue\n </p>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-900 rounded-2xl border border-neutral-200 dark:border-neutral-800 p-6 space-y-3\">\n <a\n href=\"/api/auth/google\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl text-sm font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors\"\n >\n <svg className=\"w-4 h-4\" viewBox=\"0 0 24 24\">\n <path\n fill=\"#4285F4\"\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.76h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M5.84 14.09a7.12 7.12 0 010-4.18V7.07H2.18A11.99 11.99 0 001 12c0 1.94.46 3.77 1.18 5.43l3.66-3.34z\"\n />\n <path\n fill=\"#EA4335\"\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n />\n </svg>\n Continue with Google\n </a>\n <a\n href=\"/api/auth/github\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl text-sm font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.51 11.51 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z\" />\n </svg>\n Continue with GitHub\n </a>\n </div>\n\n <p className=\"mt-6 text-center text-xs text-neutral-400\">\n <a\n href=\"/\"\n className=\"hover:text-neutral-600 dark:hover:text-neutral-300\"\n >\n ← Back to home\n </a>\n </p>\n </div>\n </div>\n );\n}\n",
|
|
88
|
-
"app/page.tsx": "export default function LandingPage(): JSX.Element {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-950\">\n {/* Nav */}\n <nav className=\"border-b border-neutral-100 dark:border-neutral-900\">\n <div className=\"max-w-5xl mx-auto flex items-center justify-between px-6 h-14\">\n <span className=\"font-semibold text-neutral-900 dark:text-white\">\n AI SaaS\n </span>\n <div className=\"flex items-center gap-4\">\n <a\n href=\"/login\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Sign in\n </a>\n <a\n href=\"/login\"\n className=\"text-sm px-4 py-1.5 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-full font-medium hover:opacity-90 transition-opacity\"\n >\n Get started\n </a>\n </div>\n </div>\n </nav>\n\n {/* Hero */}\n <main className=\"max-w-5xl mx-auto px-6\">\n <div className=\"pt-24 pb-16 text-center\">\n <h1 className=\"text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white\">\n Your AI-powered platform\n </h1>\n <p className=\"mt-4 text-lg text-neutral-500 dark:text-neutral-400 max-w-lg mx-auto\">\n Built with Veryfront. Agents, tools, and memory
|
|
88
|
+
"app/page.tsx": "export default function LandingPage(): JSX.Element {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-950\">\n {/* Nav */}\n <nav className=\"border-b border-neutral-100 dark:border-neutral-900\">\n <div className=\"max-w-5xl mx-auto flex items-center justify-between px-6 h-14\">\n <span className=\"font-semibold text-neutral-900 dark:text-white\">\n AI SaaS\n </span>\n <div className=\"flex items-center gap-4\">\n <a\n href=\"/login\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Sign in\n </a>\n <a\n href=\"/login\"\n className=\"text-sm px-4 py-1.5 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-full font-medium hover:opacity-90 transition-opacity\"\n >\n Get started\n </a>\n </div>\n </div>\n </nav>\n\n {/* Hero */}\n <main className=\"max-w-5xl mx-auto px-6\">\n <div className=\"pt-24 pb-16 text-center\">\n <h1 className=\"text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white\">\n Your AI-powered platform\n </h1>\n <p className=\"mt-4 text-lg text-neutral-500 dark:text-neutral-400 max-w-lg mx-auto\">\n Built with Veryfront. Agents, tools, and memory are ready for\n production.\n </p>\n <div className=\"mt-8 flex gap-3 justify-center\">\n <a\n href=\"/login\"\n className=\"px-6 py-2.5 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-full font-medium hover:opacity-90 transition-opacity\"\n >\n Start free\n </a>\n <a\n href=\"https://veryfront.com/code/guides\"\n className=\"px-6 py-2.5 border border-neutral-200 dark:border-neutral-800 text-neutral-700 dark:text-neutral-300 rounded-full font-medium hover:bg-neutral-50 dark:hover:bg-neutral-900 transition-colors\"\n >\n Documentation\n </a>\n </div>\n </div>\n\n {/* Features */}\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6 py-16 border-t border-neutral-100 dark:border-neutral-900\">\n {[\n {\n title: \"AI Agents\",\n desc: \"Define agents with tools, memory, and streaming. Veryfront auto-discovers them from your project.\",\n },\n {\n title: \"Per-User Memory\",\n desc: \"Each user gets their own conversation history, persisted across sessions.\",\n },\n {\n title: \"Production Ready\",\n desc: \"Use auth, rate limiting, and deployment to ship to production with one command.\",\n },\n ].map(({ title, desc }) => (\n <div key={title}>\n <h3 className=\"font-medium text-neutral-900 dark:text-white\">\n {title}\n </h3>\n <p className=\"mt-1 text-sm text-neutral-500 dark:text-neutral-400\">\n {desc}\n </p>\n </div>\n ))}\n </div>\n </main>\n </div>\n );\n}\n",
|
|
89
89
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
90
|
-
"README.md": "# SaaS Starter\n\nA production-ready app with authentication, conversation memory, and a full UI.\n\n## What's included\n\n- Landing page with feature highlights\n- OAuth login (Google and GitHub)\n- Dashboard with conversation sidebar\n- Per-user conversation memory persisted across sessions\n\n## Structure\n\n```\nagents/assistant.ts Agent with conversation memory\ntools/search.ts Placeholder domain search\napp/\n api/
|
|
90
|
+
"README.md": "# SaaS Starter\n\nA production-ready app with authentication, conversation memory, and a full UI.\n\n## What's included\n\n- Landing page with feature highlights\n- OAuth login (Google and GitHub)\n- Dashboard with conversation sidebar\n- Per-user conversation memory persisted across sessions\n\n## Structure\n\n```\nagents/assistant.ts Agent with conversation memory\ntools/search.ts Placeholder domain search\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Landing page\n login/page.tsx OAuth login\n dashboard/page.tsx Chat with sidebar\n```\n\nThis starter is not production-ready.\n",
|
|
91
91
|
"tools/search.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"search\",\n description: \"Search your knowledge base\",\n inputSchema: defineSchema((v) => v.object({\n query: v.string().describe(\"Search query\"),\n }))(),\n execute: async ({ query }) => {\n // Replace with your domain-specific search logic\n return {\n results: [],\n query,\n message: \"Connect your data source for real results.\",\n };\n },\n});\n",
|
|
92
92
|
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
93
93
|
}
|
|
@@ -97,7 +97,7 @@ export default {
|
|
|
97
97
|
"app/api/integrations/status/route.ts": "import { tokenStore } from \"../../../../lib/token-store.ts\";\nimport { requireUserIdFromRequest } from \"../../../../lib/user-id.ts\";\n\nconst INTEGRATIONS = [\n { id: \"gmail\", name: \"Gmail\", icon: \"mail\" },\n { id: \"slack\", name: \"Slack\", icon: \"slack\" },\n { id: \"calendar\", name: \"Calendar\", icon: \"calendar\" },\n { id: \"github\", name: \"GitHub\", icon: \"github\" },\n { id: \"jira\", name: \"Jira\", icon: \"jira\" },\n { id: \"notion\", name: \"Notion\", icon: \"notion\" },\n];\n\nexport async function GET(req: Request): Promise<Response> {\n const userId = requireUserIdFromRequest(req);\n\n const integrations = await Promise.all(\n INTEGRATIONS.map(async (integration) => {\n const { id, name, icon } = integration;\n\n return {\n id,\n name,\n icon,\n connected: await tokenStore.isConnected(userId, id),\n connectUrl: `/api/auth/${id}`,\n };\n }),\n );\n\n return Response.json({ integrations });\n}\n",
|
|
98
98
|
"app/api/integrations/token-storage/route.ts": "/**\n * Token Storage Status API\n *\n * Returns the current token storage mode and encryption status.\n * This endpoint is self-contained to work with any version of token-store.\n */\nexport async function GET(): Promise<Response> {\n const env = process.env;\n\n let mode: \"memory\" | \"database\" | \"kv\" | \"redis\" = \"memory\";\n if (env.DATABASE_URL) {\n mode = \"database\";\n } else if (env.KV_REST_API_URL) {\n mode = \"kv\";\n } else if (env.REDIS_URL) {\n mode = \"redis\";\n }\n\n const hasExplicitKey = env.TOKEN_ENCRYPTION_KEY?.length === 64;\n\n return Response.json({\n mode,\n encrypted: true,\n autoGenerated: !hasExplicitKey,\n });\n}\n",
|
|
99
99
|
"app/components/ServiceConnections.tsx": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\ninterface Service {\n id: string;\n name: string;\n connected: boolean;\n authUrl: string;\n}\n\ninterface ServiceConnectionsProps {\n services: Array<{\n id: string;\n name: string;\n authUrl: string;\n }>;\n className?: string;\n}\n\nfunction useIntegrationStatus(): { status: Record<string, boolean>; loading: boolean } {\n const [status, setStatus] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState<boolean>(true);\n\n useEffect(() => {\n async function checkStatus(): Promise<void> {\n try {\n const res = await fetch(\"/api/integrations/status\");\n if (!res.ok) return;\n\n const data = await res.json();\n const integrations = data?.integrations ?? [];\n\n const statusMap: Record<string, boolean> = {};\n for (const integration of integrations) {\n statusMap[integration.id] = integration.connected;\n }\n\n setStatus(statusMap);\n } catch (error) {\n console.error(\"Failed to check service status:\", error);\n } finally {\n setLoading(false);\n }\n }\n\n checkStatus();\n }, []);\n\n return { status, loading };\n}\n\nfunction withStatus(\n services: ServiceConnectionsProps[\"services\"],\n status: Record<string, boolean>,\n): Service[] {\n return services.map((service) => ({\n ...service,\n connected: status[service.id] ?? false,\n }));\n}\n\nexport function ServiceConnections({\n services,\n className = \"\",\n}: ServiceConnectionsProps): React.ReactElement {\n const { status, loading } = useIntegrationStatus();\n\n if (loading) {\n return (\n <div className={`flex items-center gap-2 ${className}`}>\n <div className=\"animate-pulse h-6 w-32 bg-neutral-200 dark:bg-neutral-700 rounded\" />\n </div>\n );\n }\n\n const servicesWithStatus = withStatus(services, status);\n const connectedCount = servicesWithStatus.reduce(\n (count, service) => count + (service.connected ? 1 : 0),\n 0,\n );\n\n return (\n <div className={`flex items-center gap-2 ${className}`}>\n {servicesWithStatus.map((service) => (\n <ServiceBadge key={service.id} service={service} />\n ))}\n {connectedCount < services.length && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400 ml-1\">\n {connectedCount}/{services.length} connected\n </span>\n )}\n </div>\n );\n}\n\nfunction ServiceBadge({ service }: { service: Service }): React.ReactElement {\n if (service.connected) {\n return (\n <span\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400\"\n title={`${service.name} connected`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-green-500\" />\n {service.name}\n </span>\n );\n }\n\n const handleConnect = (): void => {\n globalThis.location.href = service.authUrl;\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleConnect}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 transition-colors\"\n title={`Connect ${service.name}`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-neutral-400\" />\n {service.name}\n </button>\n );\n}\n\nexport function ServiceConnectionsCard({\n services,\n className = \"\",\n}: ServiceConnectionsProps): React.ReactElement | null {\n const { status, loading } = useIntegrationStatus();\n\n if (loading) return null;\n\n const disconnectedServices = withStatus(services, status).filter((service) => !service.connected);\n if (disconnectedServices.length === 0) return null;\n\n return (\n <div\n className={`rounded-lg border border-amber-200 dark:border-amber-900/50 bg-amber-50 dark:bg-amber-900/20 p-4 ${className}`}\n >\n <h3 className=\"font-medium text-amber-900 dark:text-amber-200 mb-2\">\n Connect your services\n </h3>\n <p className=\"text-sm text-amber-700 dark:text-amber-300/80 mb-3\">\n Connect the following services to unlock all features:\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {disconnectedServices.map((service) => (\n <a\n key={service.id}\n href={service.authUrl}\n className=\"inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60 transition-colors\"\n >\n Connect {service.name}\n </a>\n ))}\n </div>\n </div>\n );\n}\n",
|
|
100
|
-
"app/page.tsx": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { Chat, useChat } from 'veryfront/chat'\n\ninterface Integration {\n id: string\n name: string\n connected: boolean\n connectUrl: string\n}\n\nexport default function ChatPage(): React.ReactElement {\n const chat = useChat({ api: '/api/
|
|
100
|
+
"app/page.tsx": "'use client'\n\nimport { useEffect, useState } from 'react'\nimport { Chat, useChat } from 'veryfront/chat'\n\ninterface Integration {\n id: string\n name: string\n connected: boolean\n connectUrl: string\n}\n\nexport default function ChatPage(): React.ReactElement {\n const chat = useChat({ api: '/api/ag-ui' })\n\n return (\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-900\">\n <header className=\"sticky top-0 z-10 flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900\">\n <div className=\"px-4 py-3 flex items-center justify-between\">\n <h1 className=\"font-medium text-neutral-900 dark:text-white\">AI Agent</h1>\n <div className=\"flex items-center gap-4\">\n <ServiceStatusFromAPI />\n <a\n href=\"/setup\"\n className=\"text-sm text-neutral-500 hover:text-neutral-700 dark:text-neutral-400 dark:hover:text-neutral-200\"\n >\n Setup\n </a>\n </div>\n </div>\n </header>\n\n <Chat {...chat} className=\"flex-1 min-h-0\" placeholder=\"Message\" />\n </div>\n )\n}\n\nfunction ServiceStatusFromAPI(): React.ReactElement | null {\n const [integrations, setIntegrations] = useState<Integration[]>([])\n const [loading, setLoading] = useState<boolean>(true)\n\n useEffect((): void => {\n async function fetchStatus(): Promise<void> {\n try {\n const res = await fetch('/api/integrations/status')\n if (!res.ok) return\n\n const data = await res.json()\n setIntegrations(data.integrations ?? [])\n } catch (error) {\n console.error('Failed to fetch integration status:', error)\n } finally {\n setLoading(false)\n }\n }\n\n void fetchStatus()\n }, [])\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2\">\n <div className=\"animate-pulse h-6 w-24 bg-neutral-200 dark:bg-neutral-700 rounded-full\" />\n </div>\n )\n }\n\n if (integrations.length === 0) return null\n\n const connected: Integration[] = []\n const disconnected: Integration[] = []\n\n for (const integration of integrations) {\n if (integration.connected) connected.push(integration)\n else disconnected.push(integration)\n }\n\n return (\n <div className=\"flex items-center gap-2\">\n {connected.map(service => (\n <span\n key={service.id}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400\"\n title={`${service.name} connected`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-green-500\" />\n {service.name}\n </span>\n ))}\n\n {disconnected.map(service => (\n <a\n key={service.id}\n href={service.connectUrl}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-neutral-100 text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-700 transition-colors\"\n title={`Connect ${service.name}`}\n >\n <span className=\"w-1.5 h-1.5 rounded-full bg-neutral-400\" />\n {service.name}\n </a>\n ))}\n\n {disconnected.length > 0 && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {connected.length}/{integrations.length}\n </span>\n )}\n </div>\n )\n}\n",
|
|
101
101
|
"app/setup/page-helpers.tsx": "import type { JSX } from \"react\";\n\nexport interface Integration {\n id: string;\n name: string;\n icon: string;\n connected: boolean;\n connectUrl: string;\n}\n\nexport interface SetupStep {\n id: string;\n title: string;\n description: string;\n completed: boolean;\n action?: () => void;\n link?: string;\n}\n\ninterface SetupGuide {\n title: string;\n steps: string[];\n link: string;\n envVars: string[];\n category: string;\n}\n\nexport interface TokenStorageStatus {\n mode: \"memory\" | \"database\" | \"kv\" | \"redis\" | \"custom\";\n encrypted: boolean;\n autoGenerated?: boolean;\n}\n\nexport type TokenStorageStyles = {\n container: string;\n iconWrapper: string;\n title: string;\n text: string;\n isMemory: boolean;\n};\n\nexport const CATEGORIES = [\n { id: \"google\", name: \"Google Services\", icon: \"google\" },\n { id: \"microsoft\", name: \"Microsoft Services\", icon: \"microsoft\" },\n { id: \"atlassian\", name: \"Atlassian\", icon: \"atlassian\" },\n { id: \"communication\", name: \"Communication\", icon: \"chat\" },\n { id: \"development\", name: \"Development\", icon: \"code\" },\n { id: \"productivity\", name: \"Productivity\", icon: \"tasks\" },\n { id: \"storage\", name: \"Storage\", icon: \"folder\" },\n { id: \"infrastructure\", name: \"Infrastructure\", icon: \"server\" },\n { id: \"sales\", name: \"Sales & CRM\", icon: \"users\" },\n { id: \"support\", name: \"Support\", icon: \"headset\" },\n { id: \"finance\", name: \"Finance\", icon: \"dollar\" },\n { id: \"marketing\", name: \"Marketing\", icon: \"megaphone\" },\n { id: \"design\", name: \"Design\", icon: \"palette\" },\n { id: \"ai\", name: \"AI Providers\", icon: \"brain\" },\n] as const;\n\nexport const OAUTH_SETUP_GUIDES: Record<string, SetupGuide> = {\n gmail: {\n title: \"Google OAuth Setup (Gmail)\",\n category: \"google\",\n steps: [\n \"Go to Google Cloud Console\",\n \"Create a new project or select existing one\",\n \"Enable Gmail API in APIs & Services > Library\",\n \"Go to APIs & Services > Credentials\",\n \"Create OAuth 2.0 credentials (Web application)\",\n \"Add redirect URI: http://localhost:3000/api/auth/gmail/callback\",\n \"Copy Client ID and Secret to your .env file\",\n ],\n link: \"https://console.cloud.google.com/apis/credentials\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n calendar: {\n title: \"Google Calendar Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials as Gmail\",\n \"Enable Calendar API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/calendar/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/calendar-json.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n drive: {\n title: \"Google Drive Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Drive API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/drive/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/drive.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n sheets: {\n title: \"Google Sheets Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Sheets API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/sheets/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/sheets.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n \"docs-google\": {\n title: \"Google Docs Setup\",\n category: \"google\",\n steps: [\n \"Uses same Google OAuth credentials\",\n \"Enable Docs API in Google Cloud Console\",\n \"Add redirect URI: http://localhost:3000/api/auth/docs-google/callback\",\n ],\n link: \"https://console.cloud.google.com/apis/library/docs.googleapis.com\",\n envVars: [\"GOOGLE_CLIENT_ID\", \"GOOGLE_CLIENT_SECRET\"],\n },\n outlook: {\n title: \"Microsoft Outlook Setup\",\n category: \"microsoft\",\n steps: [\n \"Go to Azure Portal > Azure Active Directory\",\n \"Click App registrations > New registration\",\n \"Set redirect URI: http://localhost:3000/api/auth/outlook/callback\",\n \"Go to API permissions > Add Microsoft Graph permissions\",\n \"Add: Mail.Read, Mail.Send, Mail.ReadWrite\",\n \"Go to Certificates & secrets > New client secret\",\n \"Copy Application ID and Secret to .env\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n teams: {\n title: \"Microsoft Teams Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials as Outlook\",\n \"Add Teams permissions: Chat.Read, Chat.ReadWrite, Channel.ReadBasic.All\",\n \"Add redirect URI: http://localhost:3000/api/auth/teams/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n onedrive: {\n title: \"Microsoft OneDrive Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials\",\n \"Add permissions: Files.Read, Files.ReadWrite\",\n \"Add redirect URI: http://localhost:3000/api/auth/onedrive/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n sharepoint: {\n title: \"Microsoft SharePoint Setup\",\n category: \"microsoft\",\n steps: [\n \"Uses same Microsoft OAuth credentials\",\n \"Add permissions: Sites.Read.All, Sites.ReadWrite.All\",\n \"Add redirect URI: http://localhost:3000/api/auth/sharepoint/callback\",\n ],\n link: \"https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\",\n envVars: [\"MICROSOFT_CLIENT_ID\", \"MICROSOFT_CLIENT_SECRET\"],\n },\n jira: {\n title: \"Atlassian Jira Setup\",\n category: \"atlassian\",\n steps: [\n \"Go to Atlassian Developer Console\",\n \"Click Create > OAuth 2.0 integration\",\n \"Add Jira API scopes: read:jira-work, write:jira-work\",\n \"Set callback URL: http://localhost:3000/api/auth/jira/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.atlassian.com/console/myapps/\",\n envVars: [\"ATLASSIAN_CLIENT_ID\", \"ATLASSIAN_CLIENT_SECRET\"],\n },\n confluence: {\n title: \"Atlassian Confluence Setup\",\n category: \"atlassian\",\n steps: [\n \"Uses same Atlassian OAuth credentials as Jira\",\n \"Add Confluence scopes: read:confluence-content.all, write:confluence-content\",\n \"Add callback URL: http://localhost:3000/api/auth/confluence/callback\",\n ],\n link: \"https://developer.atlassian.com/console/myapps/\",\n envVars: [\"ATLASSIAN_CLIENT_ID\", \"ATLASSIAN_CLIENT_SECRET\"],\n },\n bitbucket: {\n title: \"Atlassian Bitbucket Setup\",\n category: \"atlassian\",\n steps: [\n \"Go to Bitbucket Settings > OAuth consumers\",\n \"Click Add consumer\",\n \"Set callback URL: http://localhost:3000/api/auth/bitbucket/callback\",\n \"Add permissions: repository:read, repository:write\",\n \"Copy Key and Secret to .env\",\n ],\n link: \"https://bitbucket.org/account/settings/app-passwords/\",\n envVars: [\"BITBUCKET_CLIENT_ID\", \"BITBUCKET_CLIENT_SECRET\"],\n },\n slack: {\n title: \"Slack App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Slack API Apps page\",\n \"Click Create New App > From scratch\",\n \"Go to OAuth & Permissions\",\n \"Add scopes: channels:read, chat:write, users:read, channels:history\",\n \"Add redirect URL: http://localhost:3000/api/auth/slack/callback\",\n \"Install to Workspace\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://api.slack.com/apps\",\n envVars: [\"SLACK_CLIENT_ID\", \"SLACK_CLIENT_SECRET\"],\n },\n discord: {\n title: \"Discord App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Discord Developer Portal\",\n \"Click New Application\",\n \"Go to OAuth2 section\",\n \"Add redirect: http://localhost:3000/api/auth/discord/callback\",\n \"Copy Client ID and Secret to .env\",\n \"Add bot permissions as needed\",\n ],\n link: \"https://discord.com/developers/applications\",\n envVars: [\"DISCORD_CLIENT_ID\", \"DISCORD_CLIENT_SECRET\"],\n },\n zoom: {\n title: \"Zoom App Setup\",\n category: \"communication\",\n steps: [\n \"Go to Zoom App Marketplace\",\n \"Click Develop > Build App\",\n \"Choose OAuth app type\",\n \"Add redirect URL: http://localhost:3000/api/auth/zoom/callback\",\n \"Add scopes: meeting:read, meeting:write, user:read\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://marketplace.zoom.us/develop/create\",\n envVars: [\"ZOOM_CLIENT_ID\", \"ZOOM_CLIENT_SECRET\"],\n },\n webex: {\n title: \"Webex Integration Setup\",\n category: \"communication\",\n steps: [\n \"Go to Webex Developer Portal\",\n \"Create a new integration\",\n \"Add redirect URI: http://localhost:3000/api/auth/webex/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.webex.com/my-apps\",\n envVars: [\"WEBEX_CLIENT_ID\", \"WEBEX_CLIENT_SECRET\"],\n },\n twilio: {\n title: \"Twilio Setup\",\n category: \"communication\",\n steps: [\n \"Go to Twilio Console\",\n \"Copy Account SID and Auth Token\",\n \"Get a phone number for SMS\",\n \"Add credentials to .env\",\n ],\n link: \"https://console.twilio.com/\",\n envVars: [\"TWILIO_ACCOUNT_SID\", \"TWILIO_AUTH_TOKEN\", \"TWILIO_PHONE_NUMBER\"],\n },\n github: {\n title: \"GitHub OAuth App Setup\",\n category: \"development\",\n steps: [\n \"Go to GitHub Developer Settings\",\n \"Click OAuth Apps > New OAuth App\",\n \"Set Homepage URL: http://localhost:3000\",\n \"Set callback URL: http://localhost:3000/api/auth/github/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://github.com/settings/developers\",\n envVars: [\"GITHUB_CLIENT_ID\", \"GITHUB_CLIENT_SECRET\"],\n },\n gitlab: {\n title: \"GitLab OAuth Setup\",\n category: \"development\",\n steps: [\n \"Go to GitLab User Settings > Applications\",\n \"Create new application\",\n \"Add redirect URI: http://localhost:3000/api/auth/gitlab/callback\",\n \"Select scopes: api, read_user, read_repository\",\n \"Copy Application ID and Secret to .env\",\n ],\n link: \"https://gitlab.com/-/profile/applications\",\n envVars: [\"GITLAB_CLIENT_ID\", \"GITLAB_CLIENT_SECRET\"],\n },\n sentry: {\n title: \"Sentry Setup\",\n category: \"development\",\n steps: [\n \"Go to Sentry Settings > Developer Settings\",\n \"Create new integration\",\n \"Add redirect URL: http://localhost:3000/api/auth/sentry/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://sentry.io/settings/developer-settings/\",\n envVars: [\"SENTRY_CLIENT_ID\", \"SENTRY_CLIENT_SECRET\"],\n },\n posthog: {\n title: \"PostHog Setup\",\n category: \"development\",\n steps: [\"Go to PostHog Project Settings\", \"Copy your Project API Key\", \"Add to .env file\"],\n link: \"https://app.posthog.com/project/settings\",\n envVars: [\"POSTHOG_API_KEY\", \"POSTHOG_HOST\"],\n },\n mixpanel: {\n title: \"Mixpanel Setup\",\n category: \"development\",\n steps: [\n \"Go to Mixpanel Project Settings\",\n \"Copy your Project Token\",\n \"For API access, create a Service Account\",\n \"Add credentials to .env\",\n ],\n link: \"https://mixpanel.com/settings/project\",\n envVars: [\"MIXPANEL_TOKEN\", \"MIXPANEL_API_SECRET\"],\n },\n notion: {\n title: \"Notion Integration Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Notion Integrations page\",\n \"Click New integration\",\n \"Name your integration and select workspace\",\n \"Copy the Internal Integration Token\",\n \"Share desired pages/databases with your integration\",\n \"Add token to .env\",\n ],\n link: \"https://www.notion.so/my-integrations\",\n envVars: [\"NOTION_API_KEY\"],\n },\n linear: {\n title: \"Linear OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Linear Settings > API\",\n \"Create new OAuth application\",\n \"Add redirect URI: http://localhost:3000/api/auth/linear/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://linear.app/settings/api\",\n envVars: [\"LINEAR_CLIENT_ID\", \"LINEAR_CLIENT_SECRET\"],\n },\n asana: {\n title: \"Asana OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Asana Developer Console\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/asana/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.asana.com/0/developer-console\",\n envVars: [\"ASANA_CLIENT_ID\", \"ASANA_CLIENT_SECRET\"],\n },\n trello: {\n title: \"Trello Power-Up Setup\",\n category: \"productivity\",\n steps: [\n \"Go to Trello Power-Ups Admin\",\n \"Create new Power-Up\",\n \"Add redirect URI: http://localhost:3000/api/auth/trello/callback\",\n \"Copy API Key and Secret to .env\",\n ],\n link: \"https://trello.com/power-ups/admin\",\n envVars: [\"TRELLO_API_KEY\", \"TRELLO_API_SECRET\"],\n },\n monday: {\n title: \"Monday.com App Setup\",\n category: \"productivity\",\n steps: [\n \"Go to monday.com Developers\",\n \"Create new app\",\n \"Add OAuth redirect: http://localhost:3000/api/auth/monday/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://monday.com/developers/apps\",\n envVars: [\"MONDAY_CLIENT_ID\", \"MONDAY_CLIENT_SECRET\"],\n },\n clickup: {\n title: \"ClickUp OAuth Setup\",\n category: \"productivity\",\n steps: [\n \"Go to ClickUp Settings > Apps\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/clickup/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.clickup.com/settings/apps\",\n envVars: [\"CLICKUP_CLIENT_ID\", \"CLICKUP_CLIENT_SECRET\"],\n },\n dropbox: {\n title: \"Dropbox App Setup\",\n category: \"storage\",\n steps: [\n \"Go to Dropbox App Console\",\n \"Create new app\",\n \"Choose Scoped access and Full Dropbox\",\n \"Add redirect URI: http://localhost:3000/api/auth/dropbox/callback\",\n \"Copy App Key and Secret to .env\",\n ],\n link: \"https://www.dropbox.com/developers/apps\",\n envVars: [\"DROPBOX_CLIENT_ID\", \"DROPBOX_CLIENT_SECRET\"],\n },\n box: {\n title: \"Box App Setup\",\n category: \"storage\",\n steps: [\n \"Go to Box Developer Console\",\n \"Create new app with OAuth 2.0\",\n \"Add redirect URI: http://localhost:3000/api/auth/box/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://app.box.com/developers/console\",\n envVars: [\"BOX_CLIENT_ID\", \"BOX_CLIENT_SECRET\"],\n },\n airtable: {\n title: \"Airtable OAuth Setup\",\n category: \"storage\",\n steps: [\n \"Go to Airtable Developer Hub\",\n \"Create new OAuth integration\",\n \"Add redirect URI: http://localhost:3000/api/auth/airtable/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://airtable.com/create/oauth\",\n envVars: [\"AIRTABLE_CLIENT_ID\", \"AIRTABLE_CLIENT_SECRET\"],\n },\n supabase: {\n title: \"Supabase Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Supabase Dashboard\",\n \"Create new project or select existing\",\n \"Go to Settings > API\",\n \"Copy Project URL and anon/service_role keys\",\n \"Add to .env file\",\n ],\n link: \"https://supabase.com/dashboard\",\n envVars: [\"SUPABASE_URL\", \"SUPABASE_ANON_KEY\", \"SUPABASE_SERVICE_ROLE_KEY\"],\n },\n neon: {\n title: \"Neon Database Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Neon Console\",\n \"Create new project\",\n \"Copy connection string from Dashboard\",\n \"Add to .env file\",\n ],\n link: \"https://console.neon.tech/\",\n envVars: [\"DATABASE_URL\"],\n },\n snowflake: {\n title: \"Snowflake Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to Snowflake Console\",\n \"Create a service account or use existing credentials\",\n \"Note your account identifier, warehouse, database\",\n \"Add credentials to .env\",\n ],\n link: \"https://app.snowflake.com/\",\n envVars: [\"SNOWFLAKE_ACCOUNT\", \"SNOWFLAKE_USER\", \"SNOWFLAKE_PASSWORD\", \"SNOWFLAKE_WAREHOUSE\"],\n },\n aws: {\n title: \"AWS Setup\",\n category: \"infrastructure\",\n steps: [\n \"Go to AWS IAM Console\",\n \"Create new IAM user with programmatic access\",\n \"Attach required policies (S3, Lambda, DynamoDB)\",\n \"Copy Access Key ID and Secret\",\n \"Add to .env file\",\n ],\n link: \"https://console.aws.amazon.com/iam/\",\n envVars: [\"AWS_ACCESS_KEY_ID\", \"AWS_SECRET_ACCESS_KEY\", \"AWS_REGION\"],\n },\n salesforce: {\n title: \"Salesforce Connected App Setup\",\n category: \"sales\",\n steps: [\n \"Go to Salesforce Setup > App Manager\",\n \"Create new Connected App\",\n \"Enable OAuth Settings\",\n \"Add callback URL: http://localhost:3000/api/auth/salesforce/callback\",\n \"Select OAuth scopes: api, refresh_token\",\n \"Copy Consumer Key and Secret to .env\",\n ],\n link: \"https://login.salesforce.com/\",\n envVars: [\"SALESFORCE_CLIENT_ID\", \"SALESFORCE_CLIENT_SECRET\"],\n },\n hubspot: {\n title: \"HubSpot App Setup\",\n category: \"sales\",\n steps: [\n \"Go to HubSpot Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/hubspot/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.hubspot.com/\",\n envVars: [\"HUBSPOT_CLIENT_ID\", \"HUBSPOT_CLIENT_SECRET\"],\n },\n pipedrive: {\n title: \"Pipedrive OAuth Setup\",\n category: \"sales\",\n steps: [\n \"Go to Pipedrive Developer Hub\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/pipedrive/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.pipedrive.com/\",\n envVars: [\"PIPEDRIVE_CLIENT_ID\", \"PIPEDRIVE_CLIENT_SECRET\"],\n },\n zendesk: {\n title: \"Zendesk OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Zendesk Admin > API > OAuth Clients\",\n \"Add new OAuth client\",\n \"Set redirect URI: http://localhost:3000/api/auth/zendesk/callback\",\n \"Copy Client ID and Secret to .env\",\n \"Add your Zendesk subdomain\",\n ],\n link: \"https://support.zendesk.com/hc/en-us/articles/4408845965210\",\n envVars: [\"ZENDESK_CLIENT_ID\", \"ZENDESK_CLIENT_SECRET\", \"ZENDESK_SUBDOMAIN\"],\n },\n intercom: {\n title: \"Intercom OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Intercom Developer Hub\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/intercom/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.intercom.com/\",\n envVars: [\"INTERCOM_CLIENT_ID\", \"INTERCOM_CLIENT_SECRET\"],\n },\n freshdesk: {\n title: \"Freshdesk OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to Freshdesk Admin > Apps > Custom Apps\",\n \"Create new OAuth application\",\n \"Add redirect URI: http://localhost:3000/api/auth/freshdesk/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developers.freshdesk.com/\",\n envVars: [\"FRESHDESK_CLIENT_ID\", \"FRESHDESK_CLIENT_SECRET\", \"FRESHDESK_DOMAIN\"],\n },\n servicenow: {\n title: \"ServiceNow OAuth Setup\",\n category: \"support\",\n steps: [\n \"Go to ServiceNow System OAuth > Application Registry\",\n \"Create OAuth API endpoint for external clients\",\n \"Add redirect URL: http://localhost:3000/api/auth/servicenow/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://docs.servicenow.com/\",\n envVars: [\"SERVICENOW_CLIENT_ID\", \"SERVICENOW_CLIENT_SECRET\", \"SERVICENOW_INSTANCE\"],\n },\n stripe: {\n title: \"Stripe Setup\",\n category: \"finance\",\n steps: [\n \"Go to Stripe Dashboard\",\n \"Go to Developers > API keys\",\n \"Copy Publishable and Secret keys\",\n \"For Connect, set up OAuth in Connect settings\",\n \"Add to .env file\",\n ],\n link: \"https://dashboard.stripe.com/apikeys\",\n envVars: [\"STRIPE_SECRET_KEY\", \"STRIPE_PUBLISHABLE_KEY\"],\n },\n quickbooks: {\n title: \"QuickBooks OAuth Setup\",\n category: \"finance\",\n steps: [\n \"Go to Intuit Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/quickbooks/callback\",\n \"Select Accounting scope\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.intuit.com/app/developer/dashboard\",\n envVars: [\"QUICKBOOKS_CLIENT_ID\", \"QUICKBOOKS_CLIENT_SECRET\"],\n },\n xero: {\n title: \"Xero OAuth Setup\",\n category: \"finance\",\n steps: [\n \"Go to Xero Developer Portal\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/xero/callback\",\n \"Select required scopes\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.xero.com/app/manage\",\n envVars: [\"XERO_CLIENT_ID\", \"XERO_CLIENT_SECRET\"],\n },\n mailchimp: {\n title: \"Mailchimp OAuth Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Mailchimp Developer Portal\",\n \"Register new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/mailchimp/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://admin.mailchimp.com/account/oauth2/\",\n envVars: [\"MAILCHIMP_CLIENT_ID\", \"MAILCHIMP_CLIENT_SECRET\"],\n },\n shopify: {\n title: \"Shopify App Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Shopify Partners Dashboard\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/shopify/callback\",\n \"Copy API Key and Secret to .env\",\n ],\n link: \"https://partners.shopify.com/\",\n envVars: [\"SHOPIFY_API_KEY\", \"SHOPIFY_API_SECRET\"],\n },\n twitter: {\n title: \"Twitter/X OAuth Setup\",\n category: \"marketing\",\n steps: [\n \"Go to Twitter Developer Portal\",\n \"Create new project and app\",\n \"Enable OAuth 2.0\",\n \"Add redirect URI: http://localhost:3000/api/auth/twitter/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://developer.twitter.com/en/portal/dashboard\",\n envVars: [\"TWITTER_CLIENT_ID\", \"TWITTER_CLIENT_SECRET\"],\n },\n figma: {\n title: \"Figma OAuth Setup\",\n category: \"design\",\n steps: [\n \"Go to Figma Developer Settings\",\n \"Create new app\",\n \"Add redirect URI: http://localhost:3000/api/auth/figma/callback\",\n \"Copy Client ID and Secret to .env\",\n ],\n link: \"https://www.figma.com/developers/apps\",\n envVars: [\"FIGMA_CLIENT_ID\", \"FIGMA_CLIENT_SECRET\"],\n },\n anthropic: {\n title: \"Anthropic API Setup\",\n category: \"ai\",\n steps: [\"Go to Anthropic Console\", \"Create new API key\", \"Copy API key to .env\"],\n link: \"https://console.anthropic.com/\",\n envVars: [\"ANTHROPIC_API_KEY\"],\n },\n};\n\nexport function filterIntegrations(\n integrations: Integration[],\n searchQuery: string,\n selectedCategory: string | null,\n): Integration[] {\n const query = searchQuery.toLowerCase();\n\n return integrations.filter((integration) => {\n const guide = OAUTH_SETUP_GUIDES[integration.id];\n\n const matchesSearch =\n query === \"\" ||\n integration.name.toLowerCase().includes(query) ||\n integration.id.toLowerCase().includes(query);\n\n const matchesCategory = selectedCategory === null || guide?.category === selectedCategory;\n\n return matchesSearch && matchesCategory;\n });\n}\n\nexport function groupIntegrationsByCategory(\n integrations: Integration[],\n): Record<string, Integration[]> {\n const groups: Record<string, Integration[]> = {};\n\n for (const integration of integrations) {\n const category = OAUTH_SETUP_GUIDES[integration.id]?.category ?? \"other\";\n (groups[category] ??= []).push(integration);\n }\n\n return groups;\n}\n\nexport function buildSetupSteps(\n envChecked: boolean,\n allConnected: boolean,\n markEnvChecked: () => void,\n): SetupStep[] {\n return [\n {\n id: \"env\",\n title: \"Configure Environment Variables\",\n description: \"Add your OAuth credentials to the .env file\",\n completed: envChecked,\n action: markEnvChecked,\n },\n {\n id: \"oauth\",\n title: \"Create OAuth Apps\",\n description: \"Set up OAuth applications for each service\",\n completed: false,\n },\n {\n id: \"connect\",\n title: \"Connect Services\",\n description: \"Authorize your app to access each service\",\n completed: allConnected,\n },\n ];\n}\n\nexport function getTokenStorageStyles(\n tokenStorage: TokenStorageStatus | null,\n): TokenStorageStyles | null {\n if (!tokenStorage) return null;\n\n const isMemory = tokenStorage.mode === \"memory\";\n\n return {\n container: `rounded-2xl p-6 shadow-sm border mb-8 ${\n isMemory\n ? \"bg-amber-50 dark:bg-amber-900/20 border-amber-200 dark:border-amber-800\"\n : \"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800\"\n }`,\n iconWrapper: `w-10 h-10 rounded-full flex items-center justify-center ${\n isMemory ? \"bg-amber-100 dark:bg-amber-900\" : \"bg-green-100 dark:bg-green-900\"\n }`,\n title: `font-semibold ${\n isMemory ? \"text-amber-800 dark:text-amber-200\" : \"text-green-800 dark:text-green-200\"\n }`,\n text: `text-sm mt-1 ${\n isMemory ? \"text-amber-700 dark:text-amber-300\" : \"text-green-700 dark:text-green-300\"\n }`,\n isMemory,\n };\n}\n\nexport function ServiceIcon({ name }: { name: string }): JSX.Element {\n const iconMap: Record<string, JSX.Element> = {\n mail: (\n <svg className=\"w-6 h-6 text-red-500\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n fill=\"none\"\n />\n </svg>\n ),\n slack: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313z\"\n fill=\"#E01E5A\"\n />\n <path\n d=\"M8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312z\"\n fill=\"#36C5F0\"\n />\n <path\n d=\"M18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312z\"\n fill=\"#2EB67D\"\n />\n <path\n d=\"M15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z\"\n fill=\"#ECB22E\"\n />\n </svg>\n ),\n calendar: (\n <svg\n className=\"w-6 h-6 text-blue-500\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n ),\n github: (\n <svg className=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n fillRule=\"evenodd\"\n d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"\n clipRule=\"evenodd\"\n />\n </svg>\n ),\n jira: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\">\n <defs>\n <linearGradient id=\"jira-gradient\" x1=\"98.031%\" x2=\"58.888%\" y1=\".161%\" y2=\"40.766%\">\n <stop offset=\"0%\" stopColor=\"#0052CC\" />\n <stop offset=\"100%\" stopColor=\"#2684FF\" />\n </linearGradient>\n </defs>\n <path\n fill=\"url(#jira-gradient)\"\n d=\"M11.571 11.513H0a5.218 5.218 0 005.232 5.215h2.13v2.057A5.215 5.215 0 0012.575 24V12.518a1.005 1.005 0 00-1.005-1.005z\"\n />\n <path\n fill=\"#2684FF\"\n d=\"M17.151 5.97H5.58a5.215 5.215 0 005.215 5.214h2.129v2.058a5.218 5.218 0 005.232 5.215V6.975a1.005 1.005 0 00-1.005-1.005z\"\n />\n <path\n fill=\"#2684FF\"\n d=\"M22.723.426H11.152a5.215 5.215 0 005.215 5.215h2.129v2.057a5.218 5.218 0 005.232 5.215V1.431a1.005 1.005 0 00-1.005-1.005z\"\n />\n </svg>\n ),\n notion: (\n <svg className=\"w-6 h-6\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M4.459 4.208c.746.606 1.026.56 2.428.466l13.215-.793c.28 0 .047-.28-.046-.326L17.86 1.968c-.42-.326-.98-.7-2.055-.607L3.01 2.295c-.466.046-.56.28-.374.466l1.823 1.447zm.793 3.08v13.904c0 .747.373 1.027 1.214.98l14.523-.84c.84-.046.933-.56.933-1.167V6.354c0-.606-.233-.933-.746-.886l-15.177.887c-.56.046-.747.326-.747.933zm14.337.745c.093.42 0 .84-.42.888l-.7.14v10.264c-.608.327-1.168.514-1.635.514-.746 0-.933-.234-1.495-.933l-4.577-7.186v6.952L12.21 19s0 .84-1.168.84l-3.222.186c-.093-.186 0-.653.327-.746l.84-.233V9.854L7.822 9.76c-.094-.42.14-1.026.793-1.073l3.456-.233 4.764 7.279v-6.44l-1.215-.14c-.093-.514.28-.886.747-.933l3.222-.186zM1.936 1.035l13.31-.98c1.634-.14 2.055-.047 3.082.7l4.249 2.986c.7.513.933.653.933 1.213v16.378c0 1.026-.373 1.634-1.68 1.726l-15.458.934c-.98.047-1.448-.093-1.962-.747l-3.129-4.06c-.56-.747-.793-1.306-.793-1.96V2.667c0-.839.374-1.54 1.448-1.632z\" />\n </svg>\n ),\n default: (\n <svg\n className=\"w-6 h-6 text-neutral-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 10V3L4 14h7v7l9-11h-7z\"\n />\n </svg>\n ),\n };\n\n return iconMap[name] ?? iconMap.default;\n}\n",
|
|
102
102
|
"app/setup/page.tsx": "\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport {\n buildSetupSteps,\n CATEGORIES,\n filterIntegrations,\n getTokenStorageStyles,\n groupIntegrationsByCategory,\n type Integration,\n OAUTH_SETUP_GUIDES,\n ServiceIcon,\n type TokenStorageStatus,\n} from \"./page-helpers\";\n\nexport default function SetupPage(): React.JSX.Element {\n const [integrations, setIntegrations] = useState<Integration[]>([]);\n const [loading, setLoading] = useState(true);\n const [expandedGuide, setExpandedGuide] = useState<string | null>(null);\n const [envChecked, setEnvChecked] = useState(false);\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [selectedCategory, setSelectedCategory] = useState<string | null>(null);\n const [tokenStorage, setTokenStorage] = useState<TokenStorageStatus | null>(null);\n\n useEffect(() => {\n void fetchStatus();\n void fetchTokenStorage();\n }, []);\n\n async function fetchStatus(): Promise<void> {\n try {\n const res = await fetch(\"/api/integrations/status\");\n if (!res.ok) {\n console.error(\"Failed to fetch integration status:\", res.status);\n setIntegrations([]);\n return;\n }\n\n const data = await res.json();\n setIntegrations(data.integrations ?? []);\n } catch (error) {\n console.error(\"Failed to fetch integration status:\", error);\n setIntegrations([]);\n } finally {\n setLoading(false);\n }\n }\n\n async function fetchTokenStorage(): Promise<void> {\n const fallback: TokenStorageStatus = { mode: \"memory\", encrypted: false };\n\n try {\n const res = await fetch(\"/api/integrations/token-storage\");\n if (!res.ok) {\n setTokenStorage(fallback);\n return;\n }\n const data = await res.json();\n setTokenStorage(data);\n } catch {\n setTokenStorage(fallback);\n }\n }\n\n const filteredIntegrations = useMemo(\n () => filterIntegrations(integrations, searchQuery, selectedCategory),\n [integrations, searchQuery, selectedCategory],\n );\n\n const groupedIntegrations = useMemo(\n () => groupIntegrationsByCategory(filteredIntegrations),\n [filteredIntegrations],\n );\n\n const connectedCount = integrations.filter((i) => i.connected).length;\n const totalCount = integrations.length;\n const progress = totalCount > 0 ? (connectedCount / totalCount) * 100 : 0;\n\n const allConnected = connectedCount === totalCount && totalCount > 0;\n\n const setupSteps = useMemo(\n () => buildSetupSteps(envChecked, allConnected, () => setEnvChecked(true)),\n [allConnected, envChecked],\n );\n\n const tokenStorageStyles = useMemo(() => getTokenStorageStyles(tokenStorage), [tokenStorage]);\n\n return (\n <div className=\"min-h-screen bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"max-w-4xl mx-auto px-4 py-12\">\n <div className=\"text-center mb-12\">\n <h1 className=\"text-4xl font-bold text-neutral-900 dark:text-white mb-4\">\n Setup Your AI Agent\n </h1>\n <p className=\"text-lg text-neutral-600 dark:text-neutral-400\">\n Connect your services to enable AI-powered automation\n </p>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl p-6 shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium text-neutral-600 dark:text-neutral-400\">\n Setup Progress\n </span>\n <span className=\"text-sm font-medium text-neutral-900 dark:text-white\">\n {connectedCount} / {totalCount} services connected\n </span>\n </div>\n <div className=\"w-full bg-neutral-200 dark:bg-neutral-700 rounded-full h-3\">\n <div\n className=\"bg-gradient-to-r from-green-500 to-emerald-500 h-3 rounded-full transition-all duration-500\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n\n {tokenStorage && tokenStorageStyles && (\n <div className={tokenStorageStyles.container}>\n <div className=\"flex items-start gap-4\">\n <div className={tokenStorageStyles.iconWrapper}>\n {tokenStorageStyles.isMemory ? (\n <svg\n className=\"w-5 h-5 text-amber-600 dark:text-amber-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n ) : (\n <svg\n className=\"w-5 h-5 text-green-600 dark:text-green-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z\"\n />\n </svg>\n )}\n </div>\n\n <div className=\"flex-1\">\n <h3 className={tokenStorageStyles.title}>\n Token Storage:{\" \"}\n {tokenStorageStyles.isMemory\n ? \"Development Mode\"\n : `${tokenStorage.mode.charAt(0).toUpperCase()}${tokenStorage.mode.slice(\n 1,\n )} Storage`}\n </h3>\n\n <p className={tokenStorageStyles.text}>\n {tokenStorageStyles.isMemory ? (\n <>Tokens are stored in memory and will be lost on restart.</>\n ) : (\n <>Tokens are persisted to {tokenStorage.mode} storage.</>\n )}\n </p>\n\n <div className=\"mt-2 flex items-center gap-1.5 text-sm text-green-600 dark:text-green-400\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n <span>Encryption enabled {tokenStorage.autoGenerated && \"(auto-generated key)\"}</span>\n </div>\n\n {tokenStorageStyles.isMemory && (\n <div className=\"mt-4 pt-4 border-t border-amber-200 dark:border-amber-800\">\n <p className=\"text-sm font-medium text-amber-800 dark:text-amber-200 mb-3\">\n For production, add one of these to your{\" \"}\n <code className=\"px-1 py-0.5 bg-amber-100 dark:bg-amber-900 rounded text-xs\">\n .env\n </code>\n :\n </p>\n <div className=\"grid gap-2\">\n <a\n href=\"https://upstash.com/docs/redis/overall/getstarted\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-green-200 dark:border-green-700 hover:border-green-400 dark:hover:border-green-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Upstash\n </span>\n <span className=\"text-green-600 dark:text-green-400 text-xs ml-2 font-medium\">\n Recommended\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Serverless Redis, scales horizontally\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n REDIS_URL\n </code>\n </a>\n\n <a\n href=\"https://docs.turso.tech/quickstart\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Turso / libSQL\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Edge SQLite, fast reads globally\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL\n </code>\n </a>\n\n <a\n href=\"https://vercel.com/docs/storage/vercel-kv/quickstart\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n Vercel KV\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Built-in if using Vercel\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n KV_REST_API_URL\n </code>\n </a>\n\n <a\n href=\"https://neon.tech/docs/get-started-with-neon/connect-neon\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">Neon</span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Serverless Postgres\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL\n </code>\n </a>\n\n <a\n href=\"https://www.sqlite.org/index.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center justify-between p-3 bg-white dark:bg-neutral-800 rounded-lg border border-amber-200 dark:border-amber-700 hover:border-amber-400 dark:hover:border-amber-500 transition-colors group\"\n >\n <div>\n <span className=\"font-medium text-neutral-900 dark:text-white\">\n SQLite\n </span>\n <span className=\"text-neutral-500 dark:text-neutral-400 text-sm ml-2\">\n Local file, single instance only\n </span>\n </div>\n <code className=\"text-xs bg-neutral-100 dark:bg-neutral-700 px-2 py-1 rounded text-neutral-600 dark:text-neutral-300\">\n DATABASE_URL=file:./data.db\n </code>\n </a>\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 mb-8 overflow-hidden\">\n <div className=\"p-6 border-b border-neutral-200 dark:border-neutral-700\">\n <h2 className=\"text-xl font-semibold text-neutral-900 dark:text-white\">\n Quick Start Guide\n </h2>\n </div>\n <div className=\"divide-y divide-neutral-200 dark:divide-neutral-700\">\n {setupSteps.map((step, index) => (\n <div key={step.id} className=\"p-6 flex items-start gap-4\">\n <div\n className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${\n step.completed\n ? \"bg-green-500 text-white\"\n : \"bg-neutral-200 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-400\"\n }`}\n >\n {step.completed ? (\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n ) : (\n <span className=\"font-semibold\">{index + 1}</span>\n )}\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-neutral-900 dark:text-white\">{step.title}</h3>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-400 mt-1\">\n {step.description}\n </p>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-800 rounded-2xl shadow-sm border border-neutral-200 dark:border-neutral-700 overflow-hidden\">\n <div className=\"p-6 border-b border-neutral-200 dark:border-neutral-700\">\n <h2 className=\"text-xl font-semibold text-neutral-900 dark:text-white\">\n Service Connections\n </h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-400 mt-1\">\n Click on a service to see setup instructions or connect\n </p>\n\n <div className=\"mt-4\">\n <input\n type=\"text\"\n placeholder=\"Search services...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full px-4 py-2 bg-neutral-100 dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 rounded-xl text-neutral-900 dark:text-white placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setSelectedCategory(null)}\n className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${\n selectedCategory === null\n ? \"bg-neutral-900 dark:bg-white text-white dark:text-neutral-900\"\n : \"bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600\"\n }`}\n >\n All\n </button>\n\n {CATEGORIES.map((category) => (\n <button\n key={category.id}\n type=\"button\"\n onClick={() =>\n setSelectedCategory(selectedCategory === category.id ? null : category.id)\n }\n className={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors ${\n selectedCategory === category.id\n ? \"bg-neutral-900 dark:bg-white text-white dark:text-neutral-900\"\n : \"bg-neutral-100 dark:bg-neutral-700 text-neutral-600 dark:text-neutral-300 hover:bg-neutral-200 dark:hover:bg-neutral-600\"\n }`}\n >\n {category.name}\n </button>\n ))}\n </div>\n </div>\n\n {loading ? (\n <div className=\"p-12 text-center text-neutral-500\">Loading...</div>\n ) : filteredIntegrations.length === 0 ? (\n <div className=\"p-12 text-center text-neutral-500\">\n No services found matching your search\n </div>\n ) : (\n <div>\n {CATEGORIES.filter((cat) => groupedIntegrations[cat.id]?.length > 0).map(\n (category) => (\n <div key={category.id}>\n <div className=\"px-6 py-3 bg-neutral-50 dark:bg-neutral-900 border-b border-neutral-200 dark:border-neutral-700\">\n <h3 className=\"text-sm font-semibold text-neutral-500 dark:text-neutral-400 uppercase tracking-wider\">\n {category.name}\n </h3>\n </div>\n\n <div className=\"divide-y divide-neutral-200 dark:divide-neutral-700\">\n {groupedIntegrations[category.id]?.map((integration) => {\n const guide = OAUTH_SETUP_GUIDES[integration.id];\n const isExpanded = expandedGuide === integration.id;\n\n return (\n <div key={integration.id}>\n <div className=\"p-6 flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <div className=\"w-12 h-12 bg-neutral-100 dark:bg-neutral-700 rounded-xl flex items-center justify-center\">\n <ServiceIcon name={integration.icon} />\n </div>\n <div>\n <h3 className=\"font-semibold text-neutral-900 dark:text-white\">\n {integration.name}\n </h3>\n <p\n className={`text-sm ${\n integration.connected\n ? \"text-green-600 dark:text-green-400\"\n : \"text-neutral-500\"\n }`}\n >\n {integration.connected ? \"Connected\" : \"Not connected\"}\n </p>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3\">\n {guide && (\n <button\n type=\"button\"\n onClick={() =>\n setExpandedGuide(isExpanded ? null : integration.id)\n }\n className=\"px-4 py-2 text-sm font-medium text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white\"\n >\n {isExpanded ? \"Hide Guide\" : \"Setup Guide\"}\n </button>\n )}\n\n {integration.connected ? (\n <span className=\"inline-flex items-center gap-1.5 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 rounded-xl text-sm font-medium\">\n <span className=\"w-2 h-2 bg-green-500 rounded-full\" />\n Connected\n </span>\n ) : (\n <a\n href={integration.connectUrl}\n className=\"px-4 py-2 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-xl text-sm font-medium hover:opacity-90 transition-opacity\"\n >\n Connect\n </a>\n )}\n </div>\n </div>\n\n {isExpanded && guide && (\n <div className=\"px-6 pb-6\">\n <div className=\"bg-neutral-50 dark:bg-neutral-900 rounded-xl p-6 border border-neutral-200 dark:border-neutral-700\">\n <h4 className=\"font-semibold text-neutral-900 dark:text-white mb-4\">\n {guide.title}\n </h4>\n\n <ol className=\"space-y-3 mb-6\">\n {guide.steps.map((step, i) => (\n <li key={i} className=\"flex items-start gap-3\">\n <span className=\"w-6 h-6 bg-neutral-200 dark:bg-neutral-700 rounded-full flex items-center justify-center text-sm font-medium text-neutral-600 dark:text-neutral-400 flex-shrink-0\">\n {i + 1}\n </span>\n <span className=\"text-neutral-700 dark:text-neutral-300\">\n {step}\n </span>\n </li>\n ))}\n </ol>\n\n <div className=\"mb-4 p-4 bg-neutral-100 dark:bg-neutral-800 rounded-lg\">\n <h5 className=\"text-sm font-semibold text-neutral-700 dark:text-neutral-300 mb-2\">\n Required Environment Variables:\n </h5>\n <pre className=\"text-sm text-neutral-600 dark:text-neutral-400 font-mono whitespace-pre-wrap\">\n {guide.envVars.map((v) => `${v}=your_value`).join(\"\\n\")}\n </pre>\n </div>\n\n <a\n href={guide.link}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 text-sm font-medium hover:underline\"\n >\n Open Developer Console\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14\"\n />\n </svg>\n </a>\n </div>\n </div>\n )}\n </div>\n );\n })}\n </div>\n </div>\n ),\n )}\n </div>\n )}\n </div>\n\n {allConnected && (\n <div className=\"mt-8 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200 dark:border-green-800 text-center\">\n <div className=\"text-4xl mb-4\">🎉</div>\n <h3 className=\"text-xl font-semibold text-green-800 dark:text-green-200 mb-2\">\n All Services Connected!\n </h3>\n <p className=\"text-green-700 dark:text-green-300 mb-4\">\n Your AI agent is ready to use. Start chatting to automate your workflows.\n </p>\n <a\n href=\"/\"\n className=\"inline-flex items-center gap-2 px-6 py-3 bg-green-600 text-white rounded-xl font-medium hover:bg-green-700 transition-colors\"\n >\n Start Using Your Agent\n <svg className=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 7l5 5m0 0l-5 5m5-5H6\"\n />\n </svg>\n </a>\n </div>\n )}\n </div>\n </div>\n );\n}\n",
|
|
103
103
|
"lib/oauth-memory-store.ts": "import { MemoryTokenStore } from \"veryfront/oauth\";\n\nexport const oauthMemoryTokenStore = new MemoryTokenStore();\n",
|
|
@@ -570,7 +570,7 @@ export default {
|
|
|
570
570
|
".env.example": "# Trello OAuth Configuration\n# Get your credentials from https://trello.com/app-key\nTRELLO_CLIENT_ID=your-api-key\nTRELLO_CLIENT_SECRET=your-oauth-secret\n",
|
|
571
571
|
"app/api/auth/trello/callback/route.ts": "import { createOAuthCallbackHandler, trelloConfig } from \"veryfront/oauth\";\nimport { tokenStore } from \"../../../../../lib/token-store.ts\";\nimport { oauthMemoryTokenStore } from \"../../../../../lib/oauth-memory-store.ts\";\n\nconst hybridTokenStore = {\n getTokens(serviceId: string, userId: string) {\n return tokenStore.getToken(userId, serviceId);\n },\n async setTokens(\n serviceId: string,\n userId: string,\n tokens: { accessToken: string; refreshToken?: string; expiresAt?: number },\n ) {\n await tokenStore.setToken(userId, serviceId, tokens);\n },\n async clearTokens(serviceId: string, userId: string) {\n await tokenStore.revokeToken(userId, serviceId);\n },\n setState(\n state: string,\n meta: {\n userId: string;\n serviceId: string;\n codeVerifier?: string;\n redirectUri?: string;\n scopes?: string[];\n createdAt: number;\n },\n ) {\n return oauthMemoryTokenStore.setState(state, meta);\n },\n consumeState(state: string) {\n return oauthMemoryTokenStore.consumeState(state);\n },\n};\n\nexport const GET = createOAuthCallbackHandler(trelloConfig, { tokenStore: hybridTokenStore });\n",
|
|
572
572
|
"app/api/auth/trello/route.ts": "import { createOAuthInitHandler, trelloConfig } from \"veryfront/oauth\";\nimport { oauthMemoryTokenStore } from \"../../../../../lib/oauth-memory-store.ts\";\nimport { requireUserIdFromRequest } from \"../../../../../lib/user-id.ts\";\n\nfunction getUserId(request: Request): string {\n return requireUserIdFromRequest(request);\n}\n\nexport const GET = createOAuthInitHandler(trelloConfig, {\n tokenStore: oauthMemoryTokenStore,\n getUserId,\n});",
|
|
573
|
-
"lib/trello-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst TRELLO_BASE_URL = \"https://api.trello.com/1\";\n\ninterface TrelloBoard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n url: string;\n prefs: {\n background: string;\n backgroundColor: string;\n };\n dateLastActivity: string;\n}\n\ninterface TrelloList {\n id: string;\n name: string;\n closed: boolean;\n idBoard: string;\n pos: number;\n}\n\ninterface TrelloCard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n idBoard: string;\n idList: string;\n idMembers: string[];\n labels: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n due: string | null;\n dueComplete: boolean;\n url: string;\n dateLastActivity: string;\n}\n\ninterface TrelloMember {\n id: string;\n fullName: string;\n username: string;\n avatarUrl: string;\n}\n\nasync function trelloFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Trello. Please connect your account.\");\n }\n\n const clientId = process.env.TRELLO_CLIENT_ID;\n if (!clientId) {\n throw new Error(\"TRELLO_CLIENT_ID environment variable is not set.\");\n }\n\n const url = new URL(`${TRELLO_BASE_URL}${endpoint}`);\n // SECURITY: Trello's REST API requires key and token as query parameters.\n // Tokens in query params may be recorded in browser history, server/proxy\n // access logs, and leaked via the Referer header. The Referrer-Policy\n // header (set by Veryfront's security middleware) mitigates the Referer leak.\n // This is an API design limitation
|
|
573
|
+
"lib/trello-client.ts": "import { getAccessToken } from \"./token-store.ts\";\n\nconst TRELLO_BASE_URL = \"https://api.trello.com/1\";\n\ninterface TrelloBoard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n url: string;\n prefs: {\n background: string;\n backgroundColor: string;\n };\n dateLastActivity: string;\n}\n\ninterface TrelloList {\n id: string;\n name: string;\n closed: boolean;\n idBoard: string;\n pos: number;\n}\n\ninterface TrelloCard {\n id: string;\n name: string;\n desc: string;\n closed: boolean;\n idBoard: string;\n idList: string;\n idMembers: string[];\n labels: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n due: string | null;\n dueComplete: boolean;\n url: string;\n dateLastActivity: string;\n}\n\ninterface TrelloMember {\n id: string;\n fullName: string;\n username: string;\n avatarUrl: string;\n}\n\nasync function trelloFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const token = await getAccessToken();\n if (!token) {\n throw new Error(\"Not authenticated with Trello. Please connect your account.\");\n }\n\n const clientId = process.env.TRELLO_CLIENT_ID;\n if (!clientId) {\n throw new Error(\"TRELLO_CLIENT_ID environment variable is not set.\");\n }\n\n const url = new URL(`${TRELLO_BASE_URL}${endpoint}`);\n // SECURITY: Trello's REST API requires key and token as query parameters.\n // Tokens in query params may be recorded in browser history, server/proxy\n // access logs, and leaked via the Referer header. The Referrer-Policy\n // header (set by Veryfront's security middleware) mitigates the Referer leak.\n // This is an API design limitation. There is no Authorization header alternative.\n url.searchParams.set(\"key\", clientId);\n url.searchParams.set(\"token\", token);\n\n const response = await fetch(url.toString(), {\n ...options,\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"\");\n const message = errorText || response.statusText;\n throw new Error(`Trello API error: ${response.status} ${message}`);\n }\n\n return response.json();\n}\n\nexport async function listBoards(): Promise<TrelloBoard[]> {\n return trelloFetch<TrelloBoard[]>(\n \"/members/me/boards?fields=name,desc,closed,url,prefs,dateLastActivity\",\n );\n}\n\nexport async function getBoard(boardId: string): Promise<TrelloBoard> {\n return trelloFetch<TrelloBoard>(\n `/boards/${boardId}?fields=name,desc,closed,url,prefs,dateLastActivity`,\n );\n}\n\nexport async function listLists(boardId: string): Promise<TrelloList[]> {\n return trelloFetch<TrelloList[]>(\n `/boards/${boardId}/lists?fields=name,closed,idBoard,pos`,\n );\n}\n\nexport async function listCards(options: {\n boardId?: string;\n listId?: string;\n limit?: number;\n}): Promise<TrelloCard[]> {\n const { boardId, listId, limit = 50 } = options;\n\n const fields =\n \"name,desc,closed,idBoard,idList,idMembers,labels,due,dueComplete,url,dateLastActivity\";\n\n if (listId) {\n return trelloFetch<TrelloCard[]>(\n `/lists/${listId}/cards?fields=${fields}&limit=${limit}`,\n );\n }\n\n if (boardId) {\n return trelloFetch<TrelloCard[]>(\n `/boards/${boardId}/cards?fields=${fields}&limit=${limit}`,\n );\n }\n\n throw new Error(\"Either boardId or listId must be provided\");\n}\n\nexport async function getCard(cardId: string): Promise<TrelloCard> {\n return trelloFetch<TrelloCard>(\n \"/cards/\" +\n `${cardId}?fields=name,desc,closed,idBoard,idList,idMembers,labels,due,dueComplete,url,dateLastActivity`,\n );\n}\n\nexport async function createCard(options: {\n listId: string;\n name: string;\n desc?: string;\n due?: string;\n pos?: string | number;\n idMembers?: string[];\n idLabels?: string[];\n}): Promise<TrelloCard> {\n const params = new URLSearchParams({\n idList: options.listId,\n name: options.name,\n });\n\n if (options.desc) params.set(\"desc\", options.desc);\n if (options.due) params.set(\"due\", options.due);\n if (options.pos !== undefined) params.set(\"pos\", String(options.pos));\n if (options.idMembers) params.set(\"idMembers\", options.idMembers.join(\",\"));\n if (options.idLabels) params.set(\"idLabels\", options.idLabels.join(\",\"));\n\n return trelloFetch<TrelloCard>(`/cards?${params}`, { method: \"POST\" });\n}\n\nexport async function updateCard(\n cardId: string,\n updates: {\n name?: string;\n desc?: string;\n closed?: boolean;\n idList?: string;\n due?: string | null;\n dueComplete?: boolean;\n idMembers?: string[];\n idLabels?: string[];\n pos?: string | number;\n },\n): Promise<TrelloCard> {\n const params = new URLSearchParams();\n\n if (updates.name !== undefined) params.set(\"name\", updates.name);\n if (updates.desc !== undefined) params.set(\"desc\", updates.desc);\n if (updates.closed !== undefined) params.set(\"closed\", String(updates.closed));\n if (updates.idList !== undefined) params.set(\"idList\", updates.idList);\n if (updates.due !== undefined) params.set(\"due\", updates.due ?? \"\");\n if (updates.dueComplete !== undefined) params.set(\"dueComplete\", String(updates.dueComplete));\n if (updates.idMembers !== undefined) params.set(\"idMembers\", updates.idMembers.join(\",\"));\n if (updates.idLabels !== undefined) params.set(\"idLabels\", updates.idLabels.join(\",\"));\n if (updates.pos !== undefined) params.set(\"pos\", String(updates.pos));\n\n return trelloFetch<TrelloCard>(`/cards/${cardId}?${params}`, { method: \"PUT\" });\n}\n\nexport async function getMe(): Promise<TrelloMember> {\n return trelloFetch<TrelloMember>(\"/members/me?fields=fullName,username,avatarUrl\");\n}\n",
|
|
574
574
|
"tools/create-card.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { createCard } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"create-card\",\n description: \"Create a new card in a Trello list.\",\n inputSchema: defineSchema((v) => v.object({\n listId: v.string().describe(\"The ID of the list to create the card in\"),\n name: v.string().describe(\"The name/title of the card\"),\n desc: v.string().optional().describe(\"Description or details for the card\"),\n due: v\n .string()\n .optional()\n .describe(\"Due date in ISO 8601 format (e.g., 2024-12-31T23:59:59.000Z)\"),\n pos: v\n .union([v.string(), v.number()])\n .optional()\n .describe('Position of the card: \"top\", \"bottom\", or a positive number'),\n idMembers: v\n .array(v.string())\n .optional()\n .describe(\"Array of member IDs to assign to the card\"),\n idLabels: v\n .array(v.string())\n .optional()\n .describe(\"Array of label IDs to add to the card\"),\n }))(),\n async execute({ listId, name, desc, due, pos, idMembers, idLabels }) {\n const card = await createCard({\n listId,\n name,\n desc,\n due,\n pos,\n idMembers,\n idLabels,\n });\n\n return {\n success: true,\n card: {\n id: card.id,\n name: card.name,\n desc: card.desc,\n url: card.url,\n idList: card.idList,\n due: card.due,\n labels: card.labels.map((label) => ({\n id: label.id,\n name: label.name,\n color: label.color,\n })),\n },\n };\n },\n});\n",
|
|
575
575
|
"tools/get-card.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { getCard } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"get-card\",\n description: \"Get details of a specific Trello card by its ID.\",\n inputSchema: defineSchema((v) => v.object({\n cardId: v.string().describe(\"The ID of the card to retrieve\"),\n }))(),\n async execute({ cardId }) {\n const card = await getCard(cardId);\n\n return {\n id: card.id,\n name: card.name,\n desc: card.desc,\n url: card.url,\n closed: card.closed,\n idList: card.idList,\n idBoard: card.idBoard,\n due: card.due,\n dueComplete: card.dueComplete,\n labels: card.labels.map(({ id, name, color }) => ({ id, name, color })),\n memberIds: card.idMembers,\n lastActivity: card.dateLastActivity,\n };\n },\n});\n",
|
|
576
576
|
"tools/list-boards.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { listBoards } from \"../../lib/trello-client.ts\";\n\nexport default tool({\n id: \"list-boards\",\n description: \"List all Trello boards accessible to the current user.\",\n inputSchema: defineSchema((v) => v.object({\n includeArchived: v\n .boolean()\n .default(false)\n .describe(\"Include archived/closed boards\"),\n limit: v\n .number()\n .min(1)\n .max(100)\n .default(20)\n .describe(\"Maximum number of boards to return\"),\n }))(),\n async execute({ includeArchived, limit }) {\n const boards = await listBoards();\n\n const visibleBoards = includeArchived\n ? boards\n : boards.filter((board) => !board.closed);\n\n return visibleBoards.slice(0, limit).map((board) => ({\n id: board.id,\n name: board.name,\n desc: board.desc,\n url: board.url,\n closed: board.closed,\n backgroundColor: board.prefs?.backgroundColor,\n lastActivity: board.dateLastActivity,\n }));\n },\n});\n",
|
package/esm/deno.js
CHANGED
|
@@ -137,8 +137,8 @@ export declare function publishInvokeAgentChildRunProgress(input: {
|
|
|
137
137
|
childMessageId: string;
|
|
138
138
|
description?: string;
|
|
139
139
|
status: "pending" | "running" | "waiting_for_tool" | "completed" | "failed" | "cancelled";
|
|
140
|
-
sourceTargetKind?: "project" | "
|
|
141
|
-
runtimeTargetKind?: "
|
|
140
|
+
sourceTargetKind?: "project" | "main_branch" | "environment" | "preview_branch" | null;
|
|
141
|
+
runtimeTargetKind?: "main_branch" | "environment" | "preview_branch" | null;
|
|
142
142
|
targetEnvironmentId?: string | null;
|
|
143
143
|
targetBranchId?: string | null;
|
|
144
144
|
publishParentRunEvents?: (events: InvokeAgentChildRunProgressEvent[]) => Promise<void> | void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke-agent-child-runs.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/child-run/invoke-agent-child-runs.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAQpE,eAAO,MAAM,0CAA0C;;;;;;;;;;;;GAgBtD,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,uCAAuC;;;;;;;;;;;;GAEnD,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG,WAAW,CACzD,UAAU,CAAC,OAAO,0CAA0C,CAAC,CAC9D,CAAC;AAEF,eAAO,MAAM,sCAAsC;;;;;;;;;;;;;;;;;;;GAWlD,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;GAE/C,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,WAAW,CACrD,UAAU,CAAC,OAAO,sCAAsC,CAAC,CAC1D,CAAC;AAEF,eAAO,MAAM,gDAAgD;;;;;;;;;;;;;;;;GAM5D,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,6CAA6C;;;;;;;;;;;;;;;;GAEzD,CAAC;AAEF,MAAM,MAAM,uCAAuC,GAAG,WAAW,CAC/D,UAAU,CAAC,OAAO,gDAAgD,CAAC,CACpE,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC1F,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gCAAgC,GACxC,6BAA6B,GAC7B,uCAAuC,CAAC;AAwB5C,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,gCAAgC,GACtC,6BAA6B,CAY/B;AAED,wBAAgB,4CAA4C,CAC1D,KAAK,EAAE,gCAAgC,GACtC,uCAAuC,CAMzC;AAED,wBAAgB,sCAAsC,CACpD,KAAK,EAAE,gCAAgC,GACtC,SAAS,CAAC,6BAA6B,EAAE,uCAAuC,CAAC,CAKnF;AAED,wBAAsB,kCAAkC,CAAC,KAAK,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qCAAqC,CAAC,EAAE,MAAM,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC1F,gBAAgB,CAAC,EAAE,SAAS,GAAG,
|
|
1
|
+
{"version":3,"file":"invoke-agent-child-runs.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/child-run/invoke-agent-child-runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,9 +10,9 @@ export const getInvokeAgentChildRunLifecycleValueSchema = defineSchema((v) => v.
|
|
|
10
10
|
childAgentId: v.string().min(1),
|
|
11
11
|
description: v.string().min(1).optional(),
|
|
12
12
|
status: v.enum(["pending", "running", "waiting_for_tool", "completed", "failed", "cancelled"]),
|
|
13
|
-
sourceTargetKind: v.enum(["project", "
|
|
13
|
+
sourceTargetKind: v.enum(["project", "main_branch", "environment", "preview_branch"]).nullable()
|
|
14
14
|
.optional(),
|
|
15
|
-
runtimeTargetKind: v.enum(["
|
|
15
|
+
runtimeTargetKind: v.enum(["main_branch", "environment", "preview_branch"]).nullable()
|
|
16
16
|
.optional(),
|
|
17
17
|
targetEnvironmentId: v.string().uuid().nullable().optional(),
|
|
18
18
|
targetBranchId: v.string().uuid().nullable().optional(),
|
|
@@ -29,7 +29,7 @@ function createTimedAbortSignal(timeoutMs, abortSignal) {
|
|
|
29
29
|
}
|
|
30
30
|
export const getConversationRunTargetsSchema = defineSchema((v) => v.object({
|
|
31
31
|
sourceTargetKind: v.enum(["project", "preview_branch"]).nullable(),
|
|
32
|
-
runtimeTargetKind: v.enum(["
|
|
32
|
+
runtimeTargetKind: v.enum(["main_branch", "preview_branch"]).nullable(),
|
|
33
33
|
targetBranchId: v.string().uuid().nullable(),
|
|
34
34
|
}));
|
|
35
35
|
/** @deprecated Use getConversationRunTargetsSchema() */
|
|
@@ -49,7 +49,7 @@ export function resolveConversationRunTargets(input) {
|
|
|
49
49
|
}
|
|
50
50
|
: {
|
|
51
51
|
sourceTargetKind: "project",
|
|
52
|
-
runtimeTargetKind: "
|
|
52
|
+
runtimeTargetKind: "main_branch",
|
|
53
53
|
targetBranchId: null,
|
|
54
54
|
});
|
|
55
55
|
}
|
package/esm/src/agent/index.d.ts
CHANGED
package/esm/src/agent/index.js
CHANGED
|
@@ -78,16 +78,16 @@ export const getRuntimeAgentSourceContextSchema = defineSchema((v) => v.discrimi
|
|
|
78
78
|
]));
|
|
79
79
|
/** @deprecated Use getRuntimeAgentSourceContextSchema() */
|
|
80
80
|
export const RuntimeAgentSourceContextSchema = lazySchema(getRuntimeAgentSourceContextSchema);
|
|
81
|
-
export const getRuntimeAgentTargetKindSchema = defineSchema((v) => v.enum(["
|
|
81
|
+
export const getRuntimeAgentTargetKindSchema = defineSchema((v) => v.enum(["main_branch", "environment", "preview_branch"]));
|
|
82
82
|
/** @deprecated Use getRuntimeAgentTargetKindSchema() */
|
|
83
83
|
export const RuntimeAgentTargetKindSchema = lazySchema(getRuntimeAgentTargetKindSchema);
|
|
84
84
|
export function validateRuntimeAgentTargetSelection(input, ctx) {
|
|
85
85
|
const kind = input.runtimeTargetKind;
|
|
86
|
-
if (!kind || kind === "
|
|
86
|
+
if (!kind || kind === "main_branch") {
|
|
87
87
|
if (input.runtimeTargetEnvironmentId || input.runtimeTargetBranchId) {
|
|
88
88
|
ctx.addIssue({
|
|
89
89
|
code: "custom",
|
|
90
|
-
message: "
|
|
90
|
+
message: "main_branch target does not accept environment or branch identifiers",
|
|
91
91
|
path: ["runtimeTargetKind"],
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -69,6 +69,7 @@ export type HostedAgentServiceRouteSet<TExecution extends object> = {
|
|
|
69
69
|
handleRuntimeAgentRunInvocationExecuteRequest: (input: {
|
|
70
70
|
request: Request;
|
|
71
71
|
requestOrCtx?: unknown;
|
|
72
|
+
runId?: string;
|
|
72
73
|
}) => Promise<Response>;
|
|
73
74
|
handleDurableChatRunCancelRequest: (input: {
|
|
74
75
|
request: Request;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/service/routes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../../src/src/agent/service/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseProviderError } from "../../chat/provider-errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { CONTROL_PLANE_RUN_STREAM_PATH } from "../../channels/control-plane.js";
|
|
3
3
|
import { createAgUiRunErrorEvent, createAgUiSseErrorResponse } from "../ag-ui/host-support.js";
|
|
4
4
|
import { createAgUiRuntimeHandler } from "../ag-ui/runtime-handler.js";
|
|
5
5
|
import { createAgUiCancelHandler } from "../ag-ui/run-control.js";
|
|
@@ -137,6 +137,9 @@ export function createHostedAgentServiceRouteSet(options) {
|
|
|
137
137
|
if (req instanceof Response) {
|
|
138
138
|
return req;
|
|
139
139
|
}
|
|
140
|
+
if (input.runId && req.durableRootRun?.runId !== input.runId) {
|
|
141
|
+
return Response.json({ errorCode: "CONTROL_PLANE_RUN_ID_MISMATCH" }, { status: 400 });
|
|
142
|
+
}
|
|
140
143
|
return executeParsedDurableChatRun({
|
|
141
144
|
req,
|
|
142
145
|
request: input.request,
|
|
@@ -161,11 +164,6 @@ export function createHostedAgentServiceRouteSet(options) {
|
|
|
161
164
|
});
|
|
162
165
|
}
|
|
163
166
|
const routes = [
|
|
164
|
-
{
|
|
165
|
-
method: "POST",
|
|
166
|
-
path: "/api/ag-ui/messages/stream",
|
|
167
|
-
handler: (request) => handleAgUiRequest(request),
|
|
168
|
-
},
|
|
169
167
|
{
|
|
170
168
|
method: "POST",
|
|
171
169
|
path: "/api/ag-ui",
|
|
@@ -186,8 +184,8 @@ export function createHostedAgentServiceRouteSet(options) {
|
|
|
186
184
|
},
|
|
187
185
|
{
|
|
188
186
|
method: "POST",
|
|
189
|
-
path:
|
|
190
|
-
handler: (request) => handleRuntimeAgentRunInvocationExecuteRequest({ request }),
|
|
187
|
+
path: CONTROL_PLANE_RUN_STREAM_PATH,
|
|
188
|
+
handler: (request, params) => handleRuntimeAgentRunInvocationExecuteRequest({ request, runId: params.runId }),
|
|
191
189
|
},
|
|
192
190
|
];
|
|
193
191
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../../../src/src/agent/testing/durable-run-canaries/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAIvE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,kCAAkC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,6BAA8B,SAAQ,kCAAkC;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,gCAAgC;;;;;;;;4BAO5C,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAC/C,UAAU,CAAC,OAAO,gCAAgC,CAAC,CACpD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA4CD,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAoC1F;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../../../src/src/agent/testing/durable-run-canaries/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAIvE,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,kCAAkC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oCAAoC;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,6BAA8B,SAAQ,kCAAkC;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,gCAAgC;;;;;;;;4BAO5C,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,WAAW,CAC/C,UAAU,CAAC,OAAO,gCAAgC,CAAC,CACpD,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA4CD,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAoC1F;AAkGD,MAAM,WAAW,yBAAyB;IACxC,oBAAoB,EAAE,CAAC,KAAK,EAAE,kCAAkC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE,kCAAkC,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAClG,qBAAqB,EAAE,CAAC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAC;IACjG,wBAAwB,EAAE,CACxB,KAAK,EAAE,oCAAoC,KACxC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACtC,eAAe,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,yBAAyB,GAChC,yBAAyB,CAgF3B;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE;QAChB,QAAQ,EAAE,uBAAuB,EAAE,CAAC;QACpC,GAAG,EAAE,0BAA0B,CAAC;KACjC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,OAAO,CAAC,4BAA4B,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,4BAA6B,SAAQ,yBAAyB;IAC7E,sBAAsB,EAAE,OAAO,CAAC;CACjC;AA0DD,iBAAS,qCAAqC,CAAC,QAAQ,EAAE,uBAAuB,EAAE,GAAG,MAAM,EAAE,CAc5F;AAQD,iBAAS,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEpD;AA2CD,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,4BAA4B,EACpC,SAAS,GAAE,yBAAmE;wBAkB7C,oBAAoB,KAAG,OAAO,CAAC,sBAAsB,CAAC;EAqFxF;AAED,eAAO,MAAM,+BAA+B;;;CAG3C,CAAC"}
|
|
@@ -93,6 +93,24 @@ function createApiUrl(config, path) {
|
|
|
93
93
|
const relativePath = path.startsWith("/") ? path.slice(1) : path;
|
|
94
94
|
return new URL(relativePath, baseHref);
|
|
95
95
|
}
|
|
96
|
+
function buildCreateRootRunTargetFields(config) {
|
|
97
|
+
if (!config.projectId) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
if (config.branchId) {
|
|
101
|
+
return {
|
|
102
|
+
source_target_kind: "preview_branch",
|
|
103
|
+
runtime_target_kind: "preview_branch",
|
|
104
|
+
source_target_branch_id: config.branchId,
|
|
105
|
+
runtime_target_branch_id: config.branchId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
source_target_kind: "project",
|
|
110
|
+
runtime_target_kind: "main_branch",
|
|
111
|
+
runtime_target_branch_id: null,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
96
114
|
function buildCreateRootRunBody(config, input) {
|
|
97
115
|
return {
|
|
98
116
|
kind: "agent",
|
|
@@ -105,13 +123,7 @@ function buildCreateRootRunBody(config, input) {
|
|
|
105
123
|
mode: "default_chat",
|
|
106
124
|
agent_id: config.agentId,
|
|
107
125
|
initial_status: "pending",
|
|
108
|
-
...(config
|
|
109
|
-
? {
|
|
110
|
-
source_target_kind: "project",
|
|
111
|
-
runtime_target_kind: "production",
|
|
112
|
-
runtime_target_branch_id: config.branchId ?? null,
|
|
113
|
-
}
|
|
114
|
-
: {}),
|
|
126
|
+
...buildCreateRootRunTargetFields(config),
|
|
115
127
|
},
|
|
116
128
|
};
|
|
117
129
|
}
|
|
@@ -3,8 +3,8 @@ import type { Agent } from "../agent/types.js";
|
|
|
3
3
|
import type { HandlerContext } from "../types/server.js";
|
|
4
4
|
import type { InferSchema, Schema } from "../extensions/schema/index.js";
|
|
5
5
|
export declare const CONTROL_PLANE_AGENTS_LIST_PATH = "/api/control-plane/agents/list";
|
|
6
|
-
export declare const
|
|
7
|
-
export declare const
|
|
6
|
+
export declare const CONTROL_PLANE_RUNS_PATH_PREFIX = "/api/control-plane/runs/";
|
|
7
|
+
export declare const CONTROL_PLANE_RUN_STREAM_PATH = "/api/control-plane/runs/:runId/stream";
|
|
8
8
|
export declare const getControlPlaneSurfaceSchema: () => Schema<string>;
|
|
9
9
|
export declare const ControlPlaneSurfaceSchema: Schema<string>;
|
|
10
10
|
export declare const getControlPlaneAgentsListRequestSchema: () => Schema<import("../extensions/schema/schema-validator.js").InferShape<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../../src/src/channels/control-plane.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAIzE,eAAO,MAAM,8BAA8B,mCAAmC,CAAC;AAC/E,eAAO,MAAM
|
|
1
|
+
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../../src/src/channels/control-plane.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { base64urlEncodeBytes } from "../utils/base64url.js";
|
|
|
5
5
|
import { defineSchema, lazySchema } from "../schemas/index.js";
|
|
6
6
|
const SIGNATURE_SKEW_SECONDS = 5;
|
|
7
7
|
export const CONTROL_PLANE_AGENTS_LIST_PATH = "/api/control-plane/agents/list";
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
8
|
+
export const CONTROL_PLANE_RUNS_PATH_PREFIX = "/api/control-plane/runs/";
|
|
9
|
+
export const CONTROL_PLANE_RUN_STREAM_PATH = "/api/control-plane/runs/:runId/stream";
|
|
10
10
|
const getCompactJwsHeaderSchema = defineSchema((v) => v.object({
|
|
11
11
|
alg: v.literal("EdDSA"),
|
|
12
12
|
typ: v.string().optional(),
|
package/esm/src/chat/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* import { Chat, useChat } from "veryfront/chat";
|
|
9
9
|
*
|
|
10
10
|
* export default function Page() {
|
|
11
|
-
* const chat = useChat({ api: "/api/
|
|
11
|
+
* const chat = useChat({ api: "/api/ag-ui" });
|
|
12
12
|
* return (
|
|
13
13
|
* <Chat
|
|
14
14
|
* messages={chat.messages}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* import { Chat, useChat } from "veryfront/chat";
|
|
26
26
|
*
|
|
27
27
|
* export default function Page() {
|
|
28
|
-
* const chat = useChat({ api: "/api/
|
|
28
|
+
* const chat = useChat({ api: "/api/ag-ui" });
|
|
29
29
|
* return (
|
|
30
30
|
* <Chat.Root messages={chat.messages} input={chat.input}>
|
|
31
31
|
* <Chat.Empty title="Ask me anything" />
|
package/esm/src/chat/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* import { Chat, useChat } from "veryfront/chat";
|
|
9
9
|
*
|
|
10
10
|
* export default function Page() {
|
|
11
|
-
* const chat = useChat({ api: "/api/
|
|
11
|
+
* const chat = useChat({ api: "/api/ag-ui" });
|
|
12
12
|
* return (
|
|
13
13
|
* <Chat
|
|
14
14
|
* messages={chat.messages}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* import { Chat, useChat } from "veryfront/chat";
|
|
26
26
|
*
|
|
27
27
|
* export default function Page() {
|
|
28
|
-
* const chat = useChat({ api: "/api/
|
|
28
|
+
* const chat = useChat({ api: "/api/ag-ui" });
|
|
29
29
|
* return (
|
|
30
30
|
* <Chat.Root messages={chat.messages} input={chat.input}>
|
|
31
31
|
* <Chat.Empty title="Ask me anything" />
|
package/esm/src/proxy/handler.js
CHANGED
|
@@ -60,7 +60,7 @@ const INTERNAL_CONTROL_PLANE_SIGNATURE_HEADERS = [
|
|
|
60
60
|
];
|
|
61
61
|
function isInternalControlPlanePath(pathname) {
|
|
62
62
|
return pathname === "/channels/invoke" ||
|
|
63
|
-
pathname.startsWith("/api/control-plane/
|
|
63
|
+
pathname.startsWith("/api/control-plane/") ||
|
|
64
64
|
pathname.startsWith("/internal/tasks/") ||
|
|
65
65
|
pathname.startsWith("/internal/workflows/");
|
|
66
66
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CONTROL_PLANE_RUNS_PATH_PREFIX } from "../../../channels/control-plane.js";
|
|
2
2
|
import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
|
|
3
3
|
import { agentRunSessionManager, } from "../../../internal-agents/session-manager.js";
|
|
4
4
|
import { INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES, InternalAgentRequestBodyTooLargeError, readInternalAgentRequestBody, } from "../../../internal-agents/request-body.js";
|
|
5
5
|
import { BaseHandler } from "../response/base.js";
|
|
6
6
|
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
7
|
-
const CANCEL_PATH_REGEX = /^\/api\/control-plane\/
|
|
7
|
+
const CANCEL_PATH_REGEX = /^\/api\/control-plane\/runs\/([^/]+)$/;
|
|
8
8
|
function getRunId(pathname) {
|
|
9
9
|
return CANCEL_PATH_REGEX.exec(pathname)?.[1] ?? null;
|
|
10
10
|
}
|
|
@@ -14,7 +14,7 @@ export class AgentRunCancelHandler extends BaseHandler {
|
|
|
14
14
|
name: "AgentRunCancelHandler",
|
|
15
15
|
priority: PRIORITY_MEDIUM_API,
|
|
16
16
|
patterns: [
|
|
17
|
-
{ pattern:
|
|
17
|
+
{ pattern: CONTROL_PLANE_RUNS_PATH_PREFIX, prefix: true, method: "DELETE" },
|
|
18
18
|
],
|
|
19
19
|
};
|
|
20
20
|
constructor(sessionManager = agentRunSessionManager) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CONTROL_PLANE_RUNS_PATH_PREFIX } from "../../../channels/control-plane.js";
|
|
2
2
|
import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
|
|
3
3
|
import { agentRunSessionManager, RunNotActiveError, ToolResultConflictError, ToolResultNotWaitingError, } from "../../../internal-agents/session-manager.js";
|
|
4
4
|
import { INTERNAL_AGENT_CONTROL_PLANE_MAX_BODY_BYTES, InternalAgentRequestBodyTooLargeError, readInternalAgentRequestBody, } from "../../../internal-agents/request-body.js";
|
|
5
5
|
import { getResumeSignalSchema } from "../../../internal-agents/schema.js";
|
|
6
6
|
import { BaseHandler } from "../response/base.js";
|
|
7
7
|
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
8
|
-
const RESUME_PATH_REGEX = /^\/api\/control-plane\/
|
|
8
|
+
const RESUME_PATH_REGEX = /^\/api\/control-plane\/runs\/([^/]+)\/resume$/;
|
|
9
9
|
function getRunId(pathname) {
|
|
10
10
|
return RESUME_PATH_REGEX.exec(pathname)?.[1] ?? null;
|
|
11
11
|
}
|
|
@@ -15,7 +15,7 @@ export class AgentRunResumeHandler extends BaseHandler {
|
|
|
15
15
|
name: "AgentRunResumeHandler",
|
|
16
16
|
priority: PRIORITY_MEDIUM_API,
|
|
17
17
|
patterns: [
|
|
18
|
-
{ pattern:
|
|
18
|
+
{ pattern: CONTROL_PLANE_RUNS_PATH_PREFIX, prefix: true, method: "POST" },
|
|
19
19
|
],
|
|
20
20
|
};
|
|
21
21
|
constructor(sessionManager = agentRunSessionManager) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,4BAA4B,EAE7B,MAAM,2CAA2C,CAAC;AAwBnD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnG,MAAM,WAAW,sBACf,SAAQ,yBAAyB,EAAE,+BAA+B;IAClE,4BAA4B,CAAC,EAAE,OAAO,4BAA4B,CAAC;CACpE;AA8FD,qBAAa,kBAAmB,SAAQ,WAAW;IASrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,QAAQ,EAAE,eAAe,CAMvB;gBAE2B,IAAI,GAAE,sBAAoC;IAIvE,OAAO,CAAC,sBAAsB;IAwBxB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAoHxE"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { defaultChannelInvokeDeps } from "../../../channels/invoke.js";
|
|
2
|
-
import { CONTROL_PLANE_AGENT_STREAM_PATH, } from "../../../channels/control-plane.js";
|
|
3
2
|
import { createRuntimeAgentStreamResponse, } from "../../../internal-agents/run-stream.js";
|
|
4
3
|
import { resolveRuntimeOwnerInvokeUrl, RUNTIME_OWNER_INVOKE_URL_HEADER, } from "../../../internal-agents/runtime-owner.js";
|
|
5
4
|
import { ControlPlaneRequestError, verifyControlPlaneRequest, } from "../../../internal-agents/control-plane-auth.js";
|
|
@@ -17,6 +16,7 @@ const defaultDeps = {
|
|
|
17
16
|
resolveRuntimeOwnerInvokeUrl,
|
|
18
17
|
};
|
|
19
18
|
const logger = serverLogger.component("agent-stream-handler");
|
|
19
|
+
const RUN_STREAM_PATH_REGEX = /^\/api\/control-plane\/runs\/([^/]+)\/stream$/;
|
|
20
20
|
function buildAgentSourceRunOptions(sourceContext) {
|
|
21
21
|
switch (sourceContext.type) {
|
|
22
22
|
case "branch":
|
|
@@ -67,13 +67,17 @@ function parseAgentStreamPayload(rawPayload) {
|
|
|
67
67
|
}
|
|
68
68
|
return internalAgentStreamRequestSchema.parse(rawPayload);
|
|
69
69
|
}
|
|
70
|
+
function getPathRunId(pathname) {
|
|
71
|
+
const match = RUN_STREAM_PATH_REGEX.exec(pathname);
|
|
72
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
73
|
+
}
|
|
70
74
|
export class AgentStreamHandler extends BaseHandler {
|
|
71
75
|
deps;
|
|
72
76
|
metadata = {
|
|
73
77
|
name: "AgentStreamHandler",
|
|
74
78
|
priority: PRIORITY_MEDIUM_API,
|
|
75
79
|
patterns: [
|
|
76
|
-
{ pattern:
|
|
80
|
+
{ pattern: RUN_STREAM_PATH_REGEX, method: "POST" },
|
|
77
81
|
],
|
|
78
82
|
};
|
|
79
83
|
constructor(deps = defaultDeps) {
|
|
@@ -100,8 +104,12 @@ export class AgentStreamHandler extends BaseHandler {
|
|
|
100
104
|
.withCORS(req, ctx.securityConfig?.cors)
|
|
101
105
|
.withSecurity(ctx.securityConfig ?? undefined, req);
|
|
102
106
|
try {
|
|
107
|
+
const pathRunId = getPathRunId(new URL(req.url).pathname);
|
|
103
108
|
const rawBody = await readInternalAgentRequestBody(req, INTERNAL_AGENT_STREAM_MAX_BODY_BYTES);
|
|
104
109
|
const payload = parseAgentStreamPayload(JSON.parse(rawBody));
|
|
110
|
+
if (!pathRunId || pathRunId !== payload.runId) {
|
|
111
|
+
return this.respond(builder.json({ error: "CONTROL_PLANE_RUN_ID_MISMATCH" }, 400));
|
|
112
|
+
}
|
|
105
113
|
await verifyControlPlaneRequest(req, ctx, rawBody, {
|
|
106
114
|
expectedSubject: payload.runId,
|
|
107
115
|
expectedSurface: "studio",
|
|
@@ -20,12 +20,12 @@ export function resolveEnvironment(opts) {
|
|
|
20
20
|
let releaseId = opts.releaseId;
|
|
21
21
|
// Some framework control-plane surfaces are routed directly to a runtime owner pod and
|
|
22
22
|
// rely on signed control-plane auth instead of a user-facing release address.
|
|
23
|
-
const
|
|
23
|
+
const isControlPlanePath = opts.pathname.startsWith("/api/control-plane/");
|
|
24
24
|
// Skip releaseId validation for development assets and signed control-plane
|
|
25
25
|
// requests because they do not require a user-facing release context.
|
|
26
26
|
const canSkipReleaseIdValidation = opts.pathname === "/_ws" ||
|
|
27
27
|
opts.pathname.startsWith("/_veryfront/") ||
|
|
28
|
-
|
|
28
|
+
isControlPlanePath;
|
|
29
29
|
// Validate releaseId in proxy mode production
|
|
30
30
|
if (opts.isProxyMode &&
|
|
31
31
|
resolvedEnvironment === "production" &&
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.549";
|
|
2
2
|
//# sourceMappingURL=version-constant.d.ts.map
|