From abdeb865abb4b97b82752112b46050cb54b526dc Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 13:59:45 -0500 Subject: [PATCH 01/20] feat: fix feature ID matching and adds 404 handling Addresses issues where feature IDs were not correctly matched, leading to incorrect page rendering. Implements middleware to validate integration and feature IDs, returning a custom 404 page when either is invalid. Adds a new client component for feature layouts to handle rendering the content or code/readme viewers based on the URL parameters. Also removes trailing slashes from agent URLs. --- apps/dojo/src/agents.ts | 16 +- .../[integrationId]/feature/layout-client.tsx | 68 ++++++++ .../app/[integrationId]/feature/layout.tsx | 77 ++------- .../app/[integrationId]/feature/not-found.tsx | 19 +++ apps/dojo/src/integration-features.ts | 150 ++++++++++++++++++ apps/dojo/src/middleware.ts | 125 +++++++++++++++ 6 files changed, 386 insertions(+), 69 deletions(-) create mode 100644 apps/dojo/src/app/[integrationId]/feature/layout-client.tsx create mode 100644 apps/dojo/src/app/[integrationId]/feature/not-found.tsx create mode 100644 apps/dojo/src/integration-features.ts create mode 100644 apps/dojo/src/middleware.ts diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 55e36428f..c7929cb11 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -39,23 +39,23 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ agents: async () => { return { agentic_chat: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_chat/`, + url: `${envVars.pydanticAIUrl}/agentic_chat`, }), agentic_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_generative_ui/`, + url: `${envVars.pydanticAIUrl}/agentic_generative_ui`, }), human_in_the_loop: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/human_in_the_loop/`, + url: `${envVars.pydanticAIUrl}/human_in_the_loop`, }), // Disabled until we can figure out why production builds break // predictive_state_updates: new PydanticAIAgent({ - // url: `${envVars.pydanticAIUrl}/predictive_state_updates/`, + // url: `${envVars.pydanticAIUrl}/predictive_state_updates`, // }), shared_state: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/shared_state/`, + url: `${envVars.pydanticAIUrl}/shared_state`, }), tool_based_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/tool_based_generative_ui/`, + url: `${envVars.pydanticAIUrl}/tool_based_generative_ui`, }), backend_tool_rendering: new PydanticAIAgent({ url: `${envVars.pydanticAIUrl}/backend_tool_rendering`, @@ -77,7 +77,7 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ return { agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }), agentic_generative_ui: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui/`, + url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui`, }), tool_based_generative_ui: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`, @@ -493,7 +493,7 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ backend_tool_rendering: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/backend-tool-rendering/` }), agentic_generative_ui: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-generative-ui/` }), shared_state: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/shared-state/` }), - human_in_the_loop: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/human-in-the-loop/`, debug: true }), + human_in_the_loop: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/human-in-the-loop`, debug: true }), }; }, }, diff --git a/apps/dojo/src/app/[integrationId]/feature/layout-client.tsx b/apps/dojo/src/app/[integrationId]/feature/layout-client.tsx new file mode 100644 index 000000000..653e1b93d --- /dev/null +++ b/apps/dojo/src/app/[integrationId]/feature/layout-client.tsx @@ -0,0 +1,68 @@ +'use client'; + +import React, { useMemo } from "react"; +import filesJSON from '../../../files.json' +import Readme from "@/components/readme/readme"; +import CodeViewer from "@/components/code-viewer/code-viewer"; +import { useURLParams } from "@/contexts/url-params-context"; +import { cn } from "@/lib/utils"; +import { Feature } from "@/types/integration"; + +type FileItem = { + name: string; + content: string; + language: string; + type: string; +}; + +type FilesJsonType = Record; + +interface Props { + integrationId: string; + featureId: Feature; + children: React.ReactNode; +} + +export default function FeatureLayoutClient({ children, integrationId, featureId }: Props) { + const { sidebarHidden } = useURLParams(); + const { view } = useURLParams(); + + const files = (filesJSON as FilesJsonType)[`${integrationId}::${featureId}`] || []; + + const readme = files.find((file) => file?.name?.includes(".mdx")) || null; + const codeFiles = files.filter( + (file) => file && Object.keys(file).length > 0 && !file.name?.includes(".mdx"), + ); + + const content = useMemo(() => { + switch (view) { + case "code": + return ( + + ) + case "readme": + return ( + + ) + default: + return ( +
{children}
+ ) + } + }, [children, codeFiles, readme, view]) + + return ( +
+
+ {content} +
+
+ ); +} + diff --git a/apps/dojo/src/app/[integrationId]/feature/layout.tsx b/apps/dojo/src/app/[integrationId]/feature/layout.tsx index 9a1000099..b42f05043 100644 --- a/apps/dojo/src/app/[integrationId]/feature/layout.tsx +++ b/apps/dojo/src/app/[integrationId]/feature/layout.tsx @@ -1,75 +1,30 @@ -'use client'; - -import React, { useMemo } from "react"; -import { usePathname } from "next/navigation"; -import filesJSON from '../../../files.json' -import Readme from "@/components/readme/readme"; -import CodeViewer from "@/components/code-viewer/code-viewer"; -import { useURLParams } from "@/contexts/url-params-context"; -import { cn } from "@/lib/utils"; - -type FileItem = { - name: string; - content: string; - language: string; - type: string; -}; - -type FilesJsonType = Record; +import { headers } from "next/headers"; +import { Feature } from "@/types/integration"; +import FeatureLayoutClient from "./layout-client"; interface Props { params: Promise<{ integrationId: string; }>; - children: React.ReactNode + children: React.ReactNode; } -export default function FeatureLayout({ children, params }: Props) { - const { sidebarHidden } = useURLParams(); - const { integrationId } = React.use(params); - const pathname = usePathname(); - const { view } = useURLParams(); - - // Extract featureId from pathname: /[integrationId]/feature/[featureId] - const pathParts = pathname.split('/'); - const featureId = pathParts[pathParts.length - 1]; // Last segment is the featureId - - const files = (filesJSON as FilesJsonType)[`${integrationId}::${featureId}`] || []; +export default async function FeatureLayout({ children, params }: Props) { + const { integrationId } = await params; - const readme = files.find((file) => file?.name?.includes(".mdx")) || null; - const codeFiles = files.filter( - (file) => file && Object.keys(file).length > 0 && !file.name?.includes(".mdx"), - ); + // Get pathname from header (set by middleware) + const headersList = await headers(); + const pathname = headersList.get("x-pathname") || ""; + // Extract featureId from pathname: /[integrationId]/feature/[featureId] + const pathParts = pathname.split("/"); + const featureId = pathParts[pathParts.length - 1] as Feature; - const content = useMemo(() => { - switch (view) { - case "code": - return ( - - ) - case "readme": - return ( - - ) - default: - return ( -
{children}
- ) - } - }, [children, codeFiles, readme, view]) + // Note: 404 checks are handled by middleware which returns proper HTTP 404 status return ( -
-
- {content} -
-
+ + {children} + ); } diff --git a/apps/dojo/src/app/[integrationId]/feature/not-found.tsx b/apps/dojo/src/app/[integrationId]/feature/not-found.tsx new file mode 100644 index 000000000..16bac6d5e --- /dev/null +++ b/apps/dojo/src/app/[integrationId]/feature/not-found.tsx @@ -0,0 +1,19 @@ +import Link from "next/link"; + +export default function FeatureNotFound() { + return ( +
+

Feature Not Found

+

+ This feature is not available for the selected integration. +

+ + Back to Home + +
+ ); +} + diff --git a/apps/dojo/src/integration-features.ts b/apps/dojo/src/integration-features.ts new file mode 100644 index 000000000..b256166a8 --- /dev/null +++ b/apps/dojo/src/integration-features.ts @@ -0,0 +1,150 @@ +/** + * Integration features data - used by both middleware and menu.ts + * This is a plain data file without imports so it can be used in Edge runtime (middleware) + */ + +export const INTEGRATION_FEATURES: Record = { + "langgraph": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + "langgraph-fastapi": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + "langgraph-typescript": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + "mastra": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + "mastra-agent-local": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + ], + "spring-ai": [ + "agentic_chat", + "shared_state", + "tool_based_generative_ui", + "human_in_the_loop", + "agentic_generative_ui", + ], + "pydantic-ai": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "shared_state", + "tool_based_generative_ui", + ], + "adk-middleware": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + ], + "microsoft-agent-framework-dotnet": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + "microsoft-agent-framework-python": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + "agno": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + "llama-index": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "shared_state", + ], + "crewai": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + "a2a-basic": ["vnext_chat"], + "middleware-starter": ["agentic_chat"], + "server-starter": ["agentic_chat"], + "server-starter-all-features": [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + "a2a": ["a2a_chat"], + "aws-strands": [ + "agentic_chat", + "backend_tool_rendering", + "agentic_generative_ui", + "shared_state", + "human_in_the_loop", + ], +}; + +/** + * Check if a feature is available for a given integration + */ +export function isFeatureAvailable(integrationId: string, featureId: string): boolean { + const features = INTEGRATION_FEATURES[integrationId]; + return features ? features.includes(featureId) : false; +} + +/** + * Check if an integration exists + */ +export function isIntegrationValid(integrationId: string): boolean { + return integrationId in INTEGRATION_FEATURES; +} + diff --git a/apps/dojo/src/middleware.ts b/apps/dojo/src/middleware.ts new file mode 100644 index 000000000..1a29b2068 --- /dev/null +++ b/apps/dojo/src/middleware.ts @@ -0,0 +1,125 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { isFeatureAvailable, isIntegrationValid } from "./integration-features"; + +function create404Response(title: string, message: string): NextResponse { + const html = ` + + + ${title} + + + +
+

${title}

+

${message}

+ Back to Home +
+ +`; + + return new NextResponse(html, { + status: 404, + headers: { + "Content-Type": "text/html", + }, + }); +} + +export function middleware(request: NextRequest) { + const pathname = request.nextUrl.pathname; + + // Check for feature routes: /[integrationId]/feature/[featureId] + const featureMatch = pathname.match(/^\/([^/]+)\/feature\/([^/]+)\/?$/); + + if (featureMatch) { + const [, integrationId, featureId] = featureMatch; + + // Check if integration exists + if (!isIntegrationValid(integrationId)) { + return create404Response( + "Integration Not Found", + "The integration you're looking for doesn't exist." + ); + } + + // Check if feature is available for this integration + if (!isFeatureAvailable(integrationId, featureId)) { + return create404Response( + "Feature Not Found", + "This feature is not available for the selected integration." + ); + } + } + + // Check for integration routes: /[integrationId] (but not /[integrationId]/feature/...) + const integrationMatch = pathname.match(/^\/([^/]+)\/?$/); + + if (integrationMatch) { + const [, integrationId] = integrationMatch; + + // Skip the root path + if (integrationId && integrationId !== "") { + if (!isIntegrationValid(integrationId)) { + return create404Response( + "Integration Not Found", + "The integration you're looking for doesn't exist." + ); + } + } + } + + // Clone the request headers and set the pathname for downstream use + const requestHeaders = new Headers(request.headers); + requestHeaders.set("x-pathname", pathname); + + return NextResponse.next({ + request: { + headers: requestHeaders, + }, + }); +} + +export const config = { + matcher: [ + // Match all paths except static files and api routes + "/((?!api|_next/static|_next/image|favicon.ico|images).*)", + ], +}; + From 721697b3ffcd45541081ab09d5ade7aba96fc422 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 14:09:33 -0500 Subject: [PATCH 02/20] feat: improve 404 handling with middleware Refactors middleware to set headers indicating 404 status, allowing the `FeatureLayout` component to trigger a `notFound()` call. This centralizes 404 handling and removes redundant checks in the component. Also sets `dynamicParams = false` to return 404 for invalid integration IDs. --- .../app/[integrationId]/feature/layout.tsx | 14 +++- apps/dojo/src/app/[integrationId]/page.tsx | 3 + apps/dojo/src/app/not-found-page/page.tsx | 1 + apps/dojo/src/middleware.ts | 84 ++----------------- 4 files changed, 21 insertions(+), 81 deletions(-) create mode 100644 apps/dojo/src/app/not-found-page/page.tsx diff --git a/apps/dojo/src/app/[integrationId]/feature/layout.tsx b/apps/dojo/src/app/[integrationId]/feature/layout.tsx index b42f05043..6070a86ad 100644 --- a/apps/dojo/src/app/[integrationId]/feature/layout.tsx +++ b/apps/dojo/src/app/[integrationId]/feature/layout.tsx @@ -1,7 +1,11 @@ import { headers } from "next/headers"; +import { notFound } from "next/navigation"; import { Feature } from "@/types/integration"; import FeatureLayoutClient from "./layout-client"; +// Force dynamic rendering to ensure proper 404 handling +export const dynamic = "force-dynamic"; + interface Props { params: Promise<{ integrationId: string; @@ -12,16 +16,20 @@ interface Props { export default async function FeatureLayout({ children, params }: Props) { const { integrationId } = await params; - // Get pathname from header (set by middleware) + // Get headers set by middleware const headersList = await headers(); const pathname = headersList.get("x-pathname") || ""; + const notFoundType = headersList.get("x-not-found"); + + // If middleware flagged this as not found, trigger 404 + if (notFoundType) { + notFound(); + } // Extract featureId from pathname: /[integrationId]/feature/[featureId] const pathParts = pathname.split("/"); const featureId = pathParts[pathParts.length - 1] as Feature; - // Note: 404 checks are handled by middleware which returns proper HTTP 404 status - return ( {children} diff --git a/apps/dojo/src/app/[integrationId]/page.tsx b/apps/dojo/src/app/[integrationId]/page.tsx index e216e5a63..634a10d4f 100644 --- a/apps/dojo/src/app/[integrationId]/page.tsx +++ b/apps/dojo/src/app/[integrationId]/page.tsx @@ -11,6 +11,9 @@ export async function generateStaticParams() { })); } +// Return 404 for any params not in generateStaticParams +export const dynamicParams = false; + interface IntegrationPageProps { params: Promise<{ integrationId: string; diff --git a/apps/dojo/src/app/not-found-page/page.tsx b/apps/dojo/src/app/not-found-page/page.tsx new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/apps/dojo/src/app/not-found-page/page.tsx @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/dojo/src/middleware.ts b/apps/dojo/src/middleware.ts index 1a29b2068..bdb659bcb 100644 --- a/apps/dojo/src/middleware.ts +++ b/apps/dojo/src/middleware.ts @@ -2,68 +2,10 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { isFeatureAvailable, isIntegrationValid } from "./integration-features"; -function create404Response(title: string, message: string): NextResponse { - const html = ` - - - ${title} - - - -
-

${title}

-

${message}

- Back to Home -
- -`; - - return new NextResponse(html, { - status: 404, - headers: { - "Content-Type": "text/html", - }, - }); -} - export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; + const requestHeaders = new Headers(request.headers); + requestHeaders.set("x-pathname", pathname); // Check for feature routes: /[integrationId]/feature/[featureId] const featureMatch = pathname.match(/^\/([^/]+)\/feature\/([^/]+)\/?$/); @@ -73,18 +15,11 @@ export function middleware(request: NextRequest) { // Check if integration exists if (!isIntegrationValid(integrationId)) { - return create404Response( - "Integration Not Found", - "The integration you're looking for doesn't exist." - ); + requestHeaders.set("x-not-found", "integration"); } - // Check if feature is available for this integration - if (!isFeatureAvailable(integrationId, featureId)) { - return create404Response( - "Feature Not Found", - "This feature is not available for the selected integration." - ); + else if (!isFeatureAvailable(integrationId, featureId)) { + requestHeaders.set("x-not-found", "feature"); } } @@ -97,18 +32,11 @@ export function middleware(request: NextRequest) { // Skip the root path if (integrationId && integrationId !== "") { if (!isIntegrationValid(integrationId)) { - return create404Response( - "Integration Not Found", - "The integration you're looking for doesn't exist." - ); + requestHeaders.set("x-not-found", "integration"); } } } - // Clone the request headers and set the pathname for downstream use - const requestHeaders = new Headers(request.headers); - requestHeaders.set("x-pathname", pathname); - return NextResponse.next({ request: { headers: requestHeaders, From b029f89091ea21bfbd7f76afd17dacb38d9282a1 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 14:11:39 -0500 Subject: [PATCH 03/20] feat: remove integration features data file Removes the dedicated file for integration features data. This data is now sourced directly from the `menuIntegrations` array, streamlining feature availability checks and eliminating data duplication. --- apps/dojo/src/integration-features.ts | 150 -------------------------- apps/dojo/src/middleware.ts | 11 +- 2 files changed, 10 insertions(+), 151 deletions(-) delete mode 100644 apps/dojo/src/integration-features.ts diff --git a/apps/dojo/src/integration-features.ts b/apps/dojo/src/integration-features.ts deleted file mode 100644 index b256166a8..000000000 --- a/apps/dojo/src/integration-features.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Integration features data - used by both middleware and menu.ts - * This is a plain data file without imports so it can be used in Edge runtime (middleware) - */ - -export const INTEGRATION_FEATURES: Record = { - "langgraph": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - "langgraph-fastapi": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - "langgraph-typescript": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - "mastra": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - "mastra-agent-local": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - ], - "spring-ai": [ - "agentic_chat", - "shared_state", - "tool_based_generative_ui", - "human_in_the_loop", - "agentic_generative_ui", - ], - "pydantic-ai": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "shared_state", - "tool_based_generative_ui", - ], - "adk-middleware": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - ], - "microsoft-agent-framework-dotnet": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - "microsoft-agent-framework-python": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - "agno": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - "llama-index": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "shared_state", - ], - "crewai": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - "a2a-basic": ["vnext_chat"], - "middleware-starter": ["agentic_chat"], - "server-starter": ["agentic_chat"], - "server-starter-all-features": [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - "a2a": ["a2a_chat"], - "aws-strands": [ - "agentic_chat", - "backend_tool_rendering", - "agentic_generative_ui", - "shared_state", - "human_in_the_loop", - ], -}; - -/** - * Check if a feature is available for a given integration - */ -export function isFeatureAvailable(integrationId: string, featureId: string): boolean { - const features = INTEGRATION_FEATURES[integrationId]; - return features ? features.includes(featureId) : false; -} - -/** - * Check if an integration exists - */ -export function isIntegrationValid(integrationId: string): boolean { - return integrationId in INTEGRATION_FEATURES; -} - diff --git a/apps/dojo/src/middleware.ts b/apps/dojo/src/middleware.ts index bdb659bcb..6448e308d 100644 --- a/apps/dojo/src/middleware.ts +++ b/apps/dojo/src/middleware.ts @@ -1,6 +1,15 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { isFeatureAvailable, isIntegrationValid } from "./integration-features"; +import { menuIntegrations } from "./menu"; + +function isIntegrationValid(integrationId: string): boolean { + return menuIntegrations.some((i) => i.id === integrationId); +} + +function isFeatureAvailable(integrationId: string, featureId: string): boolean { + const integration = menuIntegrations.find((i) => i.id === integrationId); + return integration?.features.includes(featureId as typeof integration.features[number]) ?? false; +} export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; From 9e2d19c171dfa549f08a6ec8e671c8936c685371 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 15:03:15 -0500 Subject: [PATCH 04/20] feat: consolidate integration configurations Centralizes integration configurations into a single source of truth within `integrations.ts`. Removes redundant integration definitions from `menu.ts` and utilizes the centralized configuration. Updates middleware to leverage the single source of truth for validation. --- apps/dojo/src/integrations.ts | 247 ++++++++++++++++++++++++++++++++++ apps/dojo/src/menu.ts | 227 +------------------------------ apps/dojo/src/middleware.ts | 11 +- 3 files changed, 253 insertions(+), 232 deletions(-) create mode 100644 apps/dojo/src/integrations.ts diff --git a/apps/dojo/src/integrations.ts b/apps/dojo/src/integrations.ts new file mode 100644 index 000000000..ba6b1d787 --- /dev/null +++ b/apps/dojo/src/integrations.ts @@ -0,0 +1,247 @@ +import { Feature } from "./types/integration"; + +/** + * Integration configuration - SINGLE SOURCE OF TRUTH + * + * This file defines all integrations and their available features. + * Used by: + * - menu.ts (for UI menu) + * - middleware.ts (for route validation) + * - agents.ts references these IDs for consistency + */ + +export interface IntegrationConfig { + id: string; + name: string; + features: Feature[]; +} + +export const integrations: IntegrationConfig[] = [ + { + id: "langgraph", + name: "LangGraph (Python)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "langgraph-fastapi", + name: "LangGraph (FastAPI)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "langgraph-typescript", + name: "LangGraph (Typescript)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "mastra", + name: "Mastra", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + }, + { + id: "mastra-agent-local", + name: "Mastra Agent (Local)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "spring-ai", + name: "Spring AI", + features: [ + "agentic_chat", + "shared_state", + "tool_based_generative_ui", + "human_in_the_loop", + "agentic_generative_ui", + ], + }, + { + id: "pydantic-ai", + name: "Pydantic AI", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + // Disabled until we can figure out why production builds break + // "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "adk-middleware", + name: "Google ADK", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + // "predictive_state_updates" + ], + }, + { + id: "microsoft-agent-framework-dotnet", + name: "Microsoft Agent Framework (.NET)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "microsoft-agent-framework-python", + name: "Microsoft Agent Framework (Python)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "agno", + name: "Agno", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + }, + { + id: "llama-index", + name: "LlamaIndex", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "shared_state", + ], + }, + { + id: "crewai", + name: "CrewAI", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "a2a-basic", + name: "A2A (Direct)", + features: ["vnext_chat"], + }, + // Disabled until we can support Vercel AI SDK v5 + // { + // id: "vercel-ai-sdk", + // name: "Vercel AI SDK", + // features: ["agentic_chat"], + // }, + { + id: "middleware-starter", + name: "Middleware Starter", + features: ["agentic_chat"], + }, + { + id: "server-starter", + name: "Server Starter", + features: ["agentic_chat"], + }, + { + id: "server-starter-all-features", + name: "Server Starter (All Features)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "a2a", + name: "A2A", + features: ["a2a_chat"], + }, + { + id: "aws-strands", + name: "AWS Strands", + features: [ + "agentic_chat", + "backend_tool_rendering", + "agentic_generative_ui", + "shared_state", + "human_in_the_loop", + ], + }, +]; + +// Helper functions +export function isIntegrationValid(integrationId: string): boolean { + return integrations.some((i) => i.id === integrationId); +} + +export function isFeatureAvailable(integrationId: string, featureId: string): boolean { + const integration = integrations.find((i) => i.id === integrationId); + return integration?.features.includes(featureId as Feature) ?? false; +} + +export function getIntegration(integrationId: string): IntegrationConfig | undefined { + return integrations.find((i) => i.id === integrationId); +} + diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 7ae22c92b..3b95cc866 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -1,224 +1,7 @@ import { MenuIntegrationConfig } from "./types/integration"; +import { integrations } from "./integrations"; -export const menuIntegrations: MenuIntegrationConfig[] = [ - { - id: "langgraph", - name: "LangGraph (Python)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "langgraph-fastapi", - name: "LangGraph (FastAPI)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "langgraph-typescript", - name: "LangGraph (Typescript)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "langchain", - name: "LangChain", - features: [ - "agentic_chat", - "tool_based_generative_ui", - ], - }, - { - id: "mastra", - name: "Mastra", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - }, - { - id: "mastra-agent-local", - name: "Mastra Agent (Local)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "spring-ai", - name: "Spring AI", - features: [ - "agentic_chat", - "shared_state", - "tool_based_generative_ui", - "human_in_the_loop", - "agentic_generative_ui", - ], - }, - { - id: "pydantic-ai", - name: "Pydantic AI", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - // Disabled until we can figure out why production builds break - // "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "adk-middleware", - name: "Google ADK", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - // "predictive_state_updates" - ], - }, - { - id: "microsoft-agent-framework-dotnet", - name: "Microsoft Agent Framework (.NET)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "microsoft-agent-framework-python", - name: "Microsoft Agent Framework (Python)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "agno", - name: "Agno", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - }, - { - id: "llama-index", - name: "LlamaIndex", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "shared_state", - ], - }, - { - id: "crewai", - name: "CrewAI", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "a2a-basic", - name: "A2A (Direct)", - features: ["vnext_chat"], - }, - // Disabled until we can support Vercel AI SDK v5 - // { - // id: "vercel-ai-sdk", - // name: "Vercel AI SDK", - // features: ["agentic_chat"], - // }, - { - id: "middleware-starter", - name: "Middleware Starter", - features: ["agentic_chat"], - }, - { - id: "server-starter", - name: "Server Starter", - features: ["agentic_chat"], - }, - { - id: "server-starter-all-features", - name: "Server Starter (All Features)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "a2a", - name: "A2A", - features: ["a2a_chat"], - }, - { - id: "aws-strands", - name: "AWS Strands", - features: [ - "agentic_chat", - "backend_tool_rendering", - "agentic_generative_ui", - "shared_state", - "human_in_the_loop", - ], - }, -]; +/** + * Menu integrations - derived from the single source of truth in integrations.ts + */ +export const menuIntegrations: MenuIntegrationConfig[] = integrations; diff --git a/apps/dojo/src/middleware.ts b/apps/dojo/src/middleware.ts index 6448e308d..34ea96e9e 100644 --- a/apps/dojo/src/middleware.ts +++ b/apps/dojo/src/middleware.ts @@ -1,15 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { menuIntegrations } from "./menu"; - -function isIntegrationValid(integrationId: string): boolean { - return menuIntegrations.some((i) => i.id === integrationId); -} - -function isFeatureAvailable(integrationId: string, featureId: string): boolean { - const integration = menuIntegrations.find((i) => i.id === integrationId); - return integration?.features.includes(featureId as typeof integration.features[number]) ?? false; -} +import { isIntegrationValid, isFeatureAvailable } from "./integrations"; export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; From 6accb6da237ae3ce1660e78022ddbaaf2cf9dbec Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 15:28:05 -0500 Subject: [PATCH 05/20] feat: consolidate integration configurations Removes the separate integrations.ts file and merges its content into menu.ts, which serves as the single source of truth for integration configurations. This change streamlines the codebase and ensures consistency in integration definitions used by the UI menu, middleware, and agents. --- apps/dojo/src/integrations.ts | 247 ---------------------------------- apps/dojo/src/menu.ts | 241 ++++++++++++++++++++++++++++++++- apps/dojo/src/middleware.ts | 2 +- 3 files changed, 238 insertions(+), 252 deletions(-) delete mode 100644 apps/dojo/src/integrations.ts diff --git a/apps/dojo/src/integrations.ts b/apps/dojo/src/integrations.ts deleted file mode 100644 index ba6b1d787..000000000 --- a/apps/dojo/src/integrations.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { Feature } from "./types/integration"; - -/** - * Integration configuration - SINGLE SOURCE OF TRUTH - * - * This file defines all integrations and their available features. - * Used by: - * - menu.ts (for UI menu) - * - middleware.ts (for route validation) - * - agents.ts references these IDs for consistency - */ - -export interface IntegrationConfig { - id: string; - name: string; - features: Feature[]; -} - -export const integrations: IntegrationConfig[] = [ - { - id: "langgraph", - name: "LangGraph (Python)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "langgraph-fastapi", - name: "LangGraph (FastAPI)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "langgraph-typescript", - name: "LangGraph (Typescript)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - "subgraphs", - ], - }, - { - id: "mastra", - name: "Mastra", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - }, - { - id: "mastra-agent-local", - name: "Mastra Agent (Local)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "spring-ai", - name: "Spring AI", - features: [ - "agentic_chat", - "shared_state", - "tool_based_generative_ui", - "human_in_the_loop", - "agentic_generative_ui", - ], - }, - { - id: "pydantic-ai", - name: "Pydantic AI", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - // Disabled until we can figure out why production builds break - // "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "adk-middleware", - name: "Google ADK", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "shared_state", - "tool_based_generative_ui", - // "predictive_state_updates" - ], - }, - { - id: "microsoft-agent-framework-dotnet", - name: "Microsoft Agent Framework (.NET)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "microsoft-agent-framework-python", - name: "Microsoft Agent Framework (Python)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "agno", - name: "Agno", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "tool_based_generative_ui", - ], - }, - { - id: "llama-index", - name: "LlamaIndex", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "shared_state", - ], - }, - { - id: "crewai", - name: "CrewAI", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "a2a-basic", - name: "A2A (Direct)", - features: ["vnext_chat"], - }, - // Disabled until we can support Vercel AI SDK v5 - // { - // id: "vercel-ai-sdk", - // name: "Vercel AI SDK", - // features: ["agentic_chat"], - // }, - { - id: "middleware-starter", - name: "Middleware Starter", - features: ["agentic_chat"], - }, - { - id: "server-starter", - name: "Server Starter", - features: ["agentic_chat"], - }, - { - id: "server-starter-all-features", - name: "Server Starter (All Features)", - features: [ - "agentic_chat", - "backend_tool_rendering", - "human_in_the_loop", - "agentic_chat_reasoning", - "agentic_generative_ui", - "predictive_state_updates", - "shared_state", - "tool_based_generative_ui", - ], - }, - { - id: "a2a", - name: "A2A", - features: ["a2a_chat"], - }, - { - id: "aws-strands", - name: "AWS Strands", - features: [ - "agentic_chat", - "backend_tool_rendering", - "agentic_generative_ui", - "shared_state", - "human_in_the_loop", - ], - }, -]; - -// Helper functions -export function isIntegrationValid(integrationId: string): boolean { - return integrations.some((i) => i.id === integrationId); -} - -export function isFeatureAvailable(integrationId: string, featureId: string): boolean { - const integration = integrations.find((i) => i.id === integrationId); - return integration?.features.includes(featureId as Feature) ?? false; -} - -export function getIntegration(integrationId: string): IntegrationConfig | undefined { - return integrations.find((i) => i.id === integrationId); -} - diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 3b95cc866..884783c1b 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -1,7 +1,240 @@ -import { MenuIntegrationConfig } from "./types/integration"; -import { integrations } from "./integrations"; +import { MenuIntegrationConfig, Feature } from "./types/integration"; /** - * Menu integrations - derived from the single source of truth in integrations.ts + * Integration configuration - SINGLE SOURCE OF TRUTH + * + * This file defines all integrations and their available features. + * Used by: + * - UI menu components + * - middleware.ts (for route validation) + * - agents.ts references these IDs for consistency */ -export const menuIntegrations: MenuIntegrationConfig[] = integrations; + +export const menuIntegrations: MenuIntegrationConfig[] = [ + { + id: "langgraph", + name: "LangGraph (Python)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "langgraph-fastapi", + name: "LangGraph (FastAPI)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "langgraph-typescript", + name: "LangGraph (Typescript)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + "subgraphs", + ], + }, + { + id: "mastra", + name: "Mastra", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + }, + { + id: "mastra-agent-local", + name: "Mastra Agent (Local)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "spring-ai", + name: "Spring AI", + features: [ + "agentic_chat", + "shared_state", + "tool_based_generative_ui", + "human_in_the_loop", + "agentic_generative_ui", + ], + }, + { + id: "pydantic-ai", + name: "Pydantic AI", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + // Disabled until we can figure out why production builds break + // "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "adk-middleware", + name: "Google ADK", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "shared_state", + "tool_based_generative_ui", + // "predictive_state_updates" + ], + }, + { + id: "microsoft-agent-framework-dotnet", + name: "Microsoft Agent Framework (.NET)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "microsoft-agent-framework-python", + name: "Microsoft Agent Framework (Python)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "agno", + name: "Agno", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "tool_based_generative_ui", + ], + }, + { + id: "llama-index", + name: "LlamaIndex", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "shared_state", + ], + }, + { + id: "crewai", + name: "CrewAI", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "a2a-basic", + name: "A2A (Direct)", + features: ["vnext_chat"], + }, + // Disabled until we can support Vercel AI SDK v5 + // { + // id: "vercel-ai-sdk", + // name: "Vercel AI SDK", + // features: ["agentic_chat"], + // }, + { + id: "middleware-starter", + name: "Middleware Starter", + features: ["agentic_chat"], + }, + { + id: "server-starter", + name: "Server Starter", + features: ["agentic_chat"], + }, + { + id: "server-starter-all-features", + name: "Server Starter (All Features)", + features: [ + "agentic_chat", + "backend_tool_rendering", + "human_in_the_loop", + "agentic_chat_reasoning", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], + }, + { + id: "a2a", + name: "A2A", + features: ["a2a_chat"], + }, + { + id: "aws-strands", + name: "AWS Strands", + features: [ + "agentic_chat", + "backend_tool_rendering", + "agentic_generative_ui", + "shared_state", + "human_in_the_loop", + ], + }, +]; + +// Helper functions for route validation +export function isIntegrationValid(integrationId: string): boolean { + return menuIntegrations.some((i) => i.id === integrationId); +} + +export function isFeatureAvailable(integrationId: string, featureId: string): boolean { + const integration = menuIntegrations.find((i) => i.id === integrationId); + return integration?.features.includes(featureId as Feature) ?? false; +} + +export function getIntegration(integrationId: string): MenuIntegrationConfig | undefined { + return menuIntegrations.find((i) => i.id === integrationId); +} diff --git a/apps/dojo/src/middleware.ts b/apps/dojo/src/middleware.ts index 34ea96e9e..59b668609 100644 --- a/apps/dojo/src/middleware.ts +++ b/apps/dojo/src/middleware.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; -import { isIntegrationValid, isFeatureAvailable } from "./integrations"; +import { isIntegrationValid, isFeatureAvailable } from "./menu"; export function middleware(request: NextRequest) { const pathname = request.nextUrl.pathname; From e1b8d0a26f1ef251ae447dd77494286fe17df8b4 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 15:44:11 -0500 Subject: [PATCH 06/20] feat: enforce type-safe agent configurations Introduces a `defineAgents` helper function to ensure type safety when defining agent configurations. This ensures that the integration ID matches one defined in `menu.ts` and that the agent keys are a subset of the features defined for that integration. This prevents runtime errors and improves code maintainability by providing compile-time validation of agent configurations. --- apps/dojo/src/agents.ts | 816 +++++++++++++---------------- apps/dojo/src/menu.ts | 19 +- apps/dojo/src/types/integration.ts | 33 +- 3 files changed, 400 insertions(+), 468 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index c7929cb11..8601b560d 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -1,6 +1,8 @@ import "server-only"; -import { AgentIntegrationConfig } from "./types/integration"; +import { AbstractAgent } from "@ag-ui/client"; +import { AgentIntegrationConfig, Feature } from "./types/integration"; +import { FeaturesFor, IntegrationId } from "./menu"; import { MiddlewareStarterAgent } from "@ag-ui/middleware-starter"; import { ServerStarterAgent } from "@ag-ui/server-starter"; import { ServerStarterAllFeaturesAgent } from "@ag-ui/server-starter-all-features"; @@ -25,451 +27,351 @@ import { A2AClient } from "@a2a-js/sdk/client"; import { LangChainAgent } from "@ag-ui/langchain"; const envVars = getEnvVars(); -export const agentsIntegrations: AgentIntegrationConfig[] = [ - { - id: "middleware-starter", - agents: async () => { - return { - agentic_chat: new MiddlewareStarterAgent(), - }; - }, - }, - { - id: "pydantic-ai", - agents: async () => { - return { - agentic_chat: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_chat`, - }), - agentic_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_generative_ui`, - }), - human_in_the_loop: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/human_in_the_loop`, - }), - // Disabled until we can figure out why production builds break - // predictive_state_updates: new PydanticAIAgent({ - // url: `${envVars.pydanticAIUrl}/predictive_state_updates`, - // }), - shared_state: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/shared_state`, - }), - tool_based_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/tool_based_generative_ui`, - }), - backend_tool_rendering: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/backend_tool_rendering`, - }), - }; - }, - }, - { - id: "server-starter", - agents: async () => { - return { - agentic_chat: new ServerStarterAgent({ url: envVars.serverStarterUrl }), - }; - }, - }, - { - id: "adk-middleware", - agents: async () => { - return { - agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }), - agentic_generative_ui: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui`, - }), - tool_based_generative_ui: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`, - }), - human_in_the_loop: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-human-in-loop-agent`, - }), - backend_tool_rendering: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/backend_tool_rendering`, - }), - shared_state: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-shared-state-agent`, - }), - // predictive_state_updates: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-predictive-state-agent` }), - }; - }, - }, - { - id: "server-starter-all-features", - agents: async () => { - return { - agentic_chat: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/agentic_chat`, - }), - backend_tool_rendering: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/agentic_generative_ui`, - }), - tool_based_generative_ui: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/tool_based_generative_ui`, - }), - shared_state: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/shared_state`, - }), - predictive_state_updates: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/predictive_state_updates`, - }), - }; - }, - }, - { - id: "mastra", - agents: async () => { - const mastraClient = new MastraClient({ - baseUrl: envVars.mastraUrl, - }); - return MastraAgent.getRemoteAgents({ - mastraClient, - }); - }, - }, - { - id: "mastra-agent-local", - agents: async () => { - return MastraAgent.getLocalAgents({ mastra }); - }, - }, +/** + * Helper to create type-safe agent configs. + * Validates that: + * 1. The integration ID matches one defined in menu.ts + * 2. The agent keys are a subset of features defined for that integration + */ +function defineAgents( + id: Id, + agents: () => Promise, AbstractAgent>>> +): AgentIntegrationConfig> { + return { id, agents }; +} + +export const agentsIntegrations: AgentIntegrationConfig[] = [ + defineAgents("middleware-starter", async () => ({ + agentic_chat: new MiddlewareStarterAgent(), + })), + defineAgents("pydantic-ai", async () => ({ + agentic_chat: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/agentic_chat`, + }), + agentic_generative_ui: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/agentic_generative_ui`, + }), + human_in_the_loop: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/human_in_the_loop`, + }), + // Disabled until we can figure out why production builds break + // predictive_state_updates: new PydanticAIAgent({ + // url: `${envVars.pydanticAIUrl}/predictive_state_updates`, + // }), + shared_state: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/shared_state`, + }), + tool_based_generative_ui: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/tool_based_generative_ui`, + }), + backend_tool_rendering: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/backend_tool_rendering`, + }), + })), + defineAgents("server-starter", async () => ({ + agentic_chat: new ServerStarterAgent({ url: envVars.serverStarterUrl }), + })), + defineAgents("adk-middleware", async () => ({ + agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }), + agentic_generative_ui: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui`, + }), + tool_based_generative_ui: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`, + }), + human_in_the_loop: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/adk-human-in-loop-agent`, + }), + backend_tool_rendering: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/backend_tool_rendering`, + }), + shared_state: new ADKAgent({ + url: `${envVars.adkMiddlewareUrl}/adk-shared-state-agent`, + }), + // predictive_state_updates: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-predictive-state-agent` }), + })), + defineAgents("server-starter-all-features", async () => ({ + agentic_chat: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/agentic_chat`, + }), + backend_tool_rendering: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/backend_tool_rendering`, + }), + human_in_the_loop: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/human_in_the_loop`, + }), + agentic_generative_ui: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/agentic_generative_ui`, + }), + tool_based_generative_ui: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/tool_based_generative_ui`, + }), + shared_state: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/shared_state`, + }), + predictive_state_updates: new ServerStarterAllFeaturesAgent({ + url: `${envVars.serverStarterAllFeaturesUrl}/predictive_state_updates`, + }), + })), + defineAgents("mastra", async () => { + const mastraClient = new MastraClient({ + baseUrl: envVars.mastraUrl, + }); + + return MastraAgent.getRemoteAgents({ + mastraClient, + }); + }), + defineAgents("mastra-agent-local", async () => { + return MastraAgent.getLocalAgents({ mastra }); + }), // Disabled until we can support Vercel AI SDK v5 - // { - // id: "vercel-ai-sdk", - // agents: async () => { - // return { - // agentic_chat: new VercelAISDKAgent({ model: openai("gpt-4o") }), - // }; - // }, - // }, - { - id: "langgraph", - agents: async () => { - return { - agentic_chat: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "agentic_chat", - }), - backend_tool_rendering: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "backend_tool_rendering", - }), - agentic_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "agentic_generative_ui", - }), - human_in_the_loop: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "human_in_the_loop", - }), - predictive_state_updates: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "predictive_state_updates", - }), - shared_state: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "shared_state", - }), - tool_based_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "tool_based_generative_ui", - }), - agentic_chat_reasoning: new LangGraphHttpAgent({ - url: `${envVars.langgraphPythonUrl}/agent/agentic_chat_reasoning`, - }), - subgraphs: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "subgraphs", - }), - }; - }, - }, - { - id: "langgraph-fastapi", - agents: async () => { - return { - agentic_chat: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat`, - }), - backend_tool_rendering: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/backend_tool_rendering`, - }), - agentic_generative_ui: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_generative_ui`, - }), - human_in_the_loop: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/human_in_the_loop`, - }), - predictive_state_updates: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/predictive_state_updates`, - }), - shared_state: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/shared_state`, - }), - tool_based_generative_ui: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/tool_based_generative_ui`, - }), - agentic_chat_reasoning: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat_reasoning`, - }), - subgraphs: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/subgraphs`, - }), - }; - }, - }, - { - id: "langgraph-typescript", - agents: async () => { - return { - agentic_chat: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "agentic_chat", - }), - // agentic_chat_reasoning: new LangGraphAgent({ - // deploymentUrl: envVars.langgraphTypescriptUrl, - // graphId: "agentic_chat_reasoning", - // }), - agentic_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "agentic_generative_ui", - }), - human_in_the_loop: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "human_in_the_loop", - }), - predictive_state_updates: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "predictive_state_updates", - }), - shared_state: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "shared_state", - }), - tool_based_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "tool_based_generative_ui", - }), - subgraphs: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "subgraphs", - }), - }; - }, - }, - { - id: "langchain", - agents: async () => { - return { - agentic_chat: new LangChainAgent({ - chainFn: async ({ messages, tools, threadId }) => { - // @ts-ignore - const { ChatOpenAI } = await import("@langchain/openai"); - const chatOpenAI = new ChatOpenAI({ model: "gpt-4o" }); - const model = chatOpenAI.bindTools(tools, { - strict: true, - }); - return model.stream(messages, { tools, metadata: { conversation_id: threadId } }); - }, - }), - tool_based_generative_ui: new LangChainAgent({ - chainFn: async ({ messages, tools, threadId }) => { - // @ts-ignore - const { ChatOpenAI } = await import("@langchain/openai"); - const chatOpenAI = new ChatOpenAI({ model: "gpt-4o" }); - const model = chatOpenAI.bindTools(tools, { - strict: true, - }); - return model.stream(messages, { tools, metadata: { conversation_id: threadId } }); - }, - }), - } - }, - }, - { - id: "agno", - agents: async () => { - return { - agentic_chat: new AgnoAgent({ - url: `${envVars.agnoUrl}/agentic_chat/agui`, - }), - tool_based_generative_ui: new AgnoAgent({ - url: `${envVars.agnoUrl}/tool_based_generative_ui/agui`, - }), - backend_tool_rendering: new AgnoAgent({ - url: `${envVars.agnoUrl}/backend_tool_rendering/agui`, - }), - human_in_the_loop: new AgnoAgent({ - url: `${envVars.agnoUrl}/human_in_the_loop/agui`, - }), - }; - }, - }, - { - id: "spring-ai", - agents: async () => { - return { - agentic_chat: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_chat/agui`, - }), - shared_state: new SpringAiAgent({ - url: `${envVars.springAiUrl}/shared_state/agui`, - }), - tool_based_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/tool_based_generative_ui/agui`, - }), - human_in_the_loop: new SpringAiAgent({ - url: `${envVars.springAiUrl}/human_in_the_loop/agui`, - }), - agentic_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_generative_ui/agui`, - }), - }; - }, - }, - { - id: "llama-index", - agents: async () => { - return { - agentic_chat: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/agentic_chat/run`, - }), - human_in_the_loop: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/human_in_the_loop/run`, - }), - agentic_generative_ui: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/agentic_generative_ui/run`, - }), - shared_state: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/shared_state/run`, - }), - backend_tool_rendering: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/backend_tool_rendering/run`, - }), - }; - }, - }, - { - id: "crewai", - agents: async () => { - return { - agentic_chat: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/agentic_chat`, - }), - human_in_the_loop: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/human_in_the_loop`, - }), - tool_based_generative_ui: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/tool_based_generative_ui`, - }), - agentic_generative_ui: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/agentic_generative_ui`, - }), - shared_state: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/shared_state`, - }), - predictive_state_updates: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/predictive_state_updates`, - }), - }; - }, - }, - { - id: "microsoft-agent-framework-python", - agents: async () => { - return { - agentic_chat: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/agentic_chat`, - }), - backend_tool_rendering: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/agentic_generative_ui`, - }), - shared_state: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/shared_state`, - }), - tool_based_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/tool_based_generative_ui`, - }), - predictive_state_updates: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/predictive_state_updates`, - }), - }; - }, - }, - { - id: "a2a-basic", - agents: async () => { - const a2aClient = new A2AClient(envVars.a2aUrl); - return { - agentic_chat: new A2AAgent({ - description: "Direct A2A agent", - a2aClient, - debug: process.env.NODE_ENV !== "production", - }), - }; - }, - }, - { - id: "microsoft-agent-framework-dotnet", - agents: async () => { - return { - agentic_chat: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/agentic_chat`, - }), - backend_tool_rendering: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/agentic_generative_ui`, - }), - shared_state: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/shared_state`, - }), - tool_based_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/tool_based_generative_ui`, - }), - predictive_state_updates: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/predictive_state_updates`, - }), - }; - }, - }, - { - id: "a2a", - agents: async () => { - // A2A agents: building management, finance, it agents - const agentUrls = [ - envVars.a2aMiddlewareBuildingsManagementUrl, - envVars.a2aMiddlewareFinanceUrl, - envVars.a2aMiddlewareItUrl, - ]; - // AGUI orchestration/routing agent - const orchestrationAgent = new HttpAgent({ - url: envVars.a2aMiddlewareOrchestratorUrl, - }); - return { - a2a_chat: new A2AMiddlewareAgent({ - description: "Middleware that connects to remote A2A agents", - agentUrls, - orchestrationAgent, - instructions: ` + })), + // defineAgents("vercel-ai-sdk", async () => ({ + // agentic_chat: new VercelAISDKAgent({ model: openai("gpt-4o") }), + // })), + defineAgents("langgraph", async () => ({ + agentic_chat: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "agentic_chat", + }), + backend_tool_rendering: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "backend_tool_rendering", + }), + agentic_generative_ui: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "agentic_generative_ui", + }), + human_in_the_loop: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "human_in_the_loop", + }), + predictive_state_updates: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "predictive_state_updates", + }), + shared_state: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "shared_state", + }), + tool_based_generative_ui: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "tool_based_generative_ui", + }), + agentic_chat_reasoning: new LangGraphHttpAgent({ + url: `${envVars.langgraphPythonUrl}/agent/agentic_chat_reasoning`, + }), + subgraphs: new LangGraphAgent({ + deploymentUrl: envVars.langgraphPythonUrl, + graphId: "subgraphs", + }), + })), + defineAgents("langgraph-fastapi", async () => ({ + agentic_chat: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat`, + }), + backend_tool_rendering: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/backend_tool_rendering`, + }), + agentic_generative_ui: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/agentic_generative_ui`, + }), + human_in_the_loop: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/human_in_the_loop`, + }), + predictive_state_updates: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/predictive_state_updates`, + }), + shared_state: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/shared_state`, + }), + tool_based_generative_ui: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/tool_based_generative_ui`, + }), + agentic_chat_reasoning: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat_reasoning`, + }), + subgraphs: new LangGraphHttpAgent({ + url: `${envVars.langgraphFastApiUrl}/agent/subgraphs`, + }), + })), + defineAgents("langgraph-typescript", async () => ({ + agentic_chat: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "agentic_chat", + }), + // agentic_chat_reasoning: new LangGraphAgent({ + // deploymentUrl: envVars.langgraphTypescriptUrl, + // graphId: "agentic_chat_reasoning", + // }), + agentic_generative_ui: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "agentic_generative_ui", + }), + human_in_the_loop: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "human_in_the_loop", + }), + predictive_state_updates: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "predictive_state_updates", + }), + shared_state: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "shared_state", + }), + tool_based_generative_ui: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "tool_based_generative_ui", + }), + subgraphs: new LangGraphAgent({ + deploymentUrl: envVars.langgraphTypescriptUrl, + graphId: "subgraphs", + }), + })), + defineAgents("agno", async () => ({ + agentic_chat: new AgnoAgent({ + url: `${envVars.agnoUrl}/agentic_chat/agui`, + }), + tool_based_generative_ui: new AgnoAgent({ + url: `${envVars.agnoUrl}/tool_based_generative_ui/agui`, + }), + backend_tool_rendering: new AgnoAgent({ + url: `${envVars.agnoUrl}/backend_tool_rendering/agui`, + }), + human_in_the_loop: new AgnoAgent({ + url: `${envVars.agnoUrl}/human_in_the_loop/agui`, + }), + })), + defineAgents("spring-ai", async () => ({ + agentic_chat: new SpringAiAgent({ + url: `${envVars.springAiUrl}/agentic_chat/agui`, + }), + shared_state: new SpringAiAgent({ + url: `${envVars.springAiUrl}/shared_state/agui`, + }), + tool_based_generative_ui: new SpringAiAgent({ + url: `${envVars.springAiUrl}/tool_based_generative_ui/agui`, + }), + human_in_the_loop: new SpringAiAgent({ + url: `${envVars.springAiUrl}/human_in_the_loop/agui`, + }), + agentic_generative_ui: new SpringAiAgent({ + url: `${envVars.springAiUrl}/agentic_generative_ui/agui`, + }), + })), + defineAgents("llama-index", async () => ({ + agentic_chat: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/agentic_chat/run`, + }), + human_in_the_loop: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/human_in_the_loop/run`, + }), + agentic_generative_ui: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/agentic_generative_ui/run`, + }), + shared_state: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/shared_state/run`, + }), + backend_tool_rendering: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/backend_tool_rendering/run`, + }), + })), + defineAgents("crewai", async () => ({ + agentic_chat: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/agentic_chat`, + }), + human_in_the_loop: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/human_in_the_loop`, + }), + tool_based_generative_ui: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/tool_based_generative_ui`, + }), + agentic_generative_ui: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/agentic_generative_ui`, + }), + shared_state: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/shared_state`, + }), + predictive_state_updates: new CrewAIAgent({ + url: `${envVars.crewAiUrl}/predictive_state_updates`, + }), + })), + defineAgents("microsoft-agent-framework-python", async () => ({ + agentic_chat: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/agentic_chat`, + }), + backend_tool_rendering: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/backend_tool_rendering`, + }), + human_in_the_loop: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/human_in_the_loop`, + }), + agentic_generative_ui: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/agentic_generative_ui`, + }), + shared_state: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/shared_state`, + }), + tool_based_generative_ui: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/tool_based_generative_ui`, + }), + predictive_state_updates: new HttpAgent({ + url: `${envVars.agentFrameworkPythonUrl}/predictive_state_updates`, + }), + })), + defineAgents("a2a-basic", async () => { + const a2aClient = new A2AClient(envVars.a2aUrl); + return { + vnext_chat: new A2AAgent({ + description: "Direct A2A agent", + a2aClient, + debug: process.env.NODE_ENV !== "production", + }), + }; + }), + defineAgents("microsoft-agent-framework-dotnet", async () => ({ + agentic_chat: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/agentic_chat`, + }), + backend_tool_rendering: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/backend_tool_rendering`, + }), + human_in_the_loop: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/human_in_the_loop`, + }), + agentic_generative_ui: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/agentic_generative_ui`, + }), + shared_state: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/shared_state`, + }), + tool_based_generative_ui: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/tool_based_generative_ui`, + }), + predictive_state_updates: new HttpAgent({ + url: `${envVars.agentFrameworkDotnetUrl}/predictive_state_updates`, + }), + })), + defineAgents("a2a", async () => { + // A2A agents: building management, finance, it agents + const agentUrls = [ + envVars.a2aMiddlewareBuildingsManagementUrl, + envVars.a2aMiddlewareFinanceUrl, + envVars.a2aMiddlewareItUrl, + ]; + // AGUI orchestration/routing agent + const orchestrationAgent = new HttpAgent({ + url: envVars.a2aMiddlewareOrchestratorUrl, + }); + return { + a2a_chat: new A2AMiddlewareAgent({ + description: "Middleware that connects to remote A2A agents", + agentUrls, + orchestrationAgent, + instructions: ` You are an HR agent. You are responsible for hiring employees and other typical HR tasks. It's very important to contact all the departments necessary to complete the task. @@ -481,20 +383,14 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ When choosing a seat with the buildings management agent, You MUST use the \`pickTable\` tool to have the user pick a seat. The buildings management agent will then use the \`pickSeat\` tool to pick a seat. `, - }), - }; - }, - }, - { - id: "aws-strands", - agents: async () => { - return { - agentic_chat: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-chat/` }), - backend_tool_rendering: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/backend-tool-rendering/` }), - agentic_generative_ui: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-generative-ui/` }), - shared_state: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/shared-state/` }), - human_in_the_loop: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/human-in-the-loop`, debug: true }), - }; - }, - }, + }), + }; + }), + defineAgents("aws-strands", async () => ({ + agentic_chat: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-chat/` }), + backend_tool_rendering: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/backend-tool-rendering/` }), + agentic_generative_ui: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-generative-ui/` }), + shared_state: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/shared-state/` }), + human_in_the_loop: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/human-in-the-loop`, debug: true }), + })), ]; diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 884783c1b..673d7d0dc 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -1,4 +1,4 @@ -import { MenuIntegrationConfig, Feature } from "./types/integration"; +import { MenuIntegrationConfig, Feature, IntegrationFeatures } from "./types/integration"; /** * Integration configuration - SINGLE SOURCE OF TRUTH @@ -7,10 +7,10 @@ import { MenuIntegrationConfig, Feature } from "./types/integration"; * Used by: * - UI menu components * - middleware.ts (for route validation) - * - agents.ts references these IDs for consistency + * - agents.ts validates agent keys against these features */ -export const menuIntegrations: MenuIntegrationConfig[] = [ +export const menuIntegrations = [ { id: "langgraph", name: "LangGraph (Python)", @@ -223,7 +223,16 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ "human_in_the_loop", ], }, -]; +] as const satisfies readonly MenuIntegrationConfig[]; + +/** Type representing all valid integration IDs */ +export type IntegrationId = (typeof menuIntegrations)[number]["id"]; + +/** Type to get features for a specific integration ID */ +export type FeaturesFor = IntegrationFeatures< + typeof menuIntegrations, + Id +>; // Helper functions for route validation export function isIntegrationValid(integrationId: string): boolean { @@ -232,7 +241,7 @@ export function isIntegrationValid(integrationId: string): boolean { export function isFeatureAvailable(integrationId: string, featureId: string): boolean { const integration = menuIntegrations.find((i) => i.id === integrationId); - return integration?.features.includes(featureId as Feature) ?? false; + return (integration?.features as readonly string[])?.includes(featureId) ?? false; } export function getIntegration(integrationId: string): MenuIntegrationConfig | undefined { diff --git a/apps/dojo/src/types/integration.ts b/apps/dojo/src/types/integration.ts index 182933a6f..341a331fd 100644 --- a/apps/dojo/src/types/integration.ts +++ b/apps/dojo/src/types/integration.ts @@ -19,7 +19,34 @@ export interface MenuIntegrationConfig { features: Feature[]; } -export interface AgentIntegrationConfig { - id: string; - agents: () => Promise>>; +/** + * Agent integration config + * @template Id - The integration ID + * @template F - The features available for this integration + */ +export interface AgentIntegrationConfig< + Id extends string = string, + F extends Feature = Feature +> { + id: Id; + agents: () => Promise>>; +} + +/** + * Helper type to extract features for a specific integration from menu config + */ +export type IntegrationFeatures< + T extends readonly MenuIntegrationConfig[], + Id extends string +> = Extract["features"][number]; + +/** + * Helper function to create a type-safe agent config + * Validates that agent keys match the features defined in menu.ts + */ +export function defineAgentConfig< + Id extends string, + F extends Feature +>(config: AgentIntegrationConfig): AgentIntegrationConfig { + return config; } From dfde3ee554ec98ead916f28b861bc78259405f4f Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 16:22:31 -0500 Subject: [PATCH 07/20] feat: refactor agents configuration for better type safety Improves type safety and reduces boilerplate in agent integrations by introducing a `mapAgents` utility function. This simplifies the configuration of agents that follow a consistent pattern, especially when multiple agents share the same base URL and only differ in their path. The change also updates the agent integrations to use a simple object with async functions rather than the previous `defineAgents` helper. Fixes #Implement Feature ID Matching and 404 --- apps/dojo/src/agents.ts | 529 ++++++++---------- .../api/copilotkit/[integrationId]/route.ts | 9 +- apps/dojo/src/types/integration.ts | 26 - 3 files changed, 224 insertions(+), 340 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 8601b560d..07f4021c5 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -1,7 +1,6 @@ import "server-only"; import { AbstractAgent } from "@ag-ui/client"; -import { AgentIntegrationConfig, Feature } from "./types/integration"; import { FeaturesFor, IntegrationId } from "./menu"; import { MiddlewareStarterAgent } from "@ag-ui/middleware-starter"; import { ServerStarterAgent } from "@ag-ui/server-starter"; @@ -29,92 +28,79 @@ import { LangChainAgent } from "@ag-ui/langchain"; const envVars = getEnvVars(); /** - * Helper to create type-safe agent configs. - * Validates that: - * 1. The integration ID matches one defined in menu.ts - * 2. The agent keys are a subset of features defined for that integration + * Helper to map feature keys to agent instances using a builder function. + * Reduces repetition when all agents follow the same pattern with different paths. */ -function defineAgents( - id: Id, - agents: () => Promise, AbstractAgent>>> -): AgentIntegrationConfig> { - return { id, agents }; +function mapAgents>( + builder: (path: string) => AbstractAgent, + mapping: T +): { [K in keyof T]: AbstractAgent } { + return Object.fromEntries( + Object.entries(mapping).map(([key, path]) => [key, builder(path)]) + ) as { [K in keyof T]: AbstractAgent }; } -export const agentsIntegrations: AgentIntegrationConfig[] = [ - defineAgents("middleware-starter", async () => ({ +/** + * Agent integrations map - keys are integration IDs from menu.ts + * TypeScript validates that agent keys match features defined for each integration + */ +type AgentsMap = { + [K in IntegrationId]?: () => Promise, AbstractAgent>>>; +}; + +export const agentsIntegrations = { + "middleware-starter": async () => ({ agentic_chat: new MiddlewareStarterAgent(), - })), - defineAgents("pydantic-ai", async () => ({ - agentic_chat: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_chat`, - }), - agentic_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/agentic_generative_ui`, - }), - human_in_the_loop: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/human_in_the_loop`, - }), - // Disabled until we can figure out why production builds break - // predictive_state_updates: new PydanticAIAgent({ - // url: `${envVars.pydanticAIUrl}/predictive_state_updates`, - // }), - shared_state: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/shared_state`, - }), - tool_based_generative_ui: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/tool_based_generative_ui`, - }), - backend_tool_rendering: new PydanticAIAgent({ - url: `${envVars.pydanticAIUrl}/backend_tool_rendering`, - }), - })), - defineAgents("server-starter", async () => ({ + }), + + "pydantic-ai": async () => + mapAgents( + (path) => new PydanticAIAgent({ url: `${envVars.pydanticAIUrl}/${path}` }), + { + agentic_chat: "agentic_chat", + agentic_generative_ui: "agentic_generative_ui", + human_in_the_loop: "human_in_the_loop", + // Disabled until we can figure out why production builds break + // predictive_state_updates: "predictive_state_updates", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + backend_tool_rendering: "backend_tool_rendering", + } + ), + + "server-starter": async () => ({ agentic_chat: new ServerStarterAgent({ url: envVars.serverStarterUrl }), - })), - defineAgents("adk-middleware", async () => ({ - agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }), - agentic_generative_ui: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-agentic-generative-ui`, - }), - tool_based_generative_ui: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-tool-based-generative-ui`, - }), - human_in_the_loop: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-human-in-loop-agent`, - }), - backend_tool_rendering: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/backend_tool_rendering`, - }), - shared_state: new ADKAgent({ - url: `${envVars.adkMiddlewareUrl}/adk-shared-state-agent`, - }), - // predictive_state_updates: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/adk-predictive-state-agent` }), - })), - defineAgents("server-starter-all-features", async () => ({ - agentic_chat: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/agentic_chat`, - }), - backend_tool_rendering: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/agentic_generative_ui`, - }), - tool_based_generative_ui: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/tool_based_generative_ui`, - }), - shared_state: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/shared_state`, - }), - predictive_state_updates: new ServerStarterAllFeaturesAgent({ - url: `${envVars.serverStarterAllFeaturesUrl}/predictive_state_updates`, - }), - })), - defineAgents("mastra", async () => { + }), + + "adk-middleware": async () => + mapAgents( + (path) => new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/${path}` }), + { + agentic_chat: "chat", + agentic_generative_ui: "adk-agentic-generative-ui", + tool_based_generative_ui: "adk-tool-based-generative-ui", + human_in_the_loop: "adk-human-in-loop-agent", + backend_tool_rendering: "backend_tool_rendering", + shared_state: "adk-shared-state-agent", + // predictive_state_updates: "adk-predictive-state-agent", + } + ), + + "server-starter-all-features": async () => + mapAgents( + (path) => new ServerStarterAllFeaturesAgent({ url: `${envVars.serverStarterAllFeaturesUrl}/${path}` }), + { + agentic_chat: "agentic_chat", + backend_tool_rendering: "backend_tool_rendering", + human_in_the_loop: "human_in_the_loop", + agentic_generative_ui: "agentic_generative_ui", + tool_based_generative_ui: "tool_based_generative_ui", + shared_state: "shared_state", + predictive_state_updates: "predictive_state_updates", + } + ), + + mastra: async () => { const mastraClient = new MastraClient({ baseUrl: envVars.mastraUrl, }); @@ -122,207 +108,131 @@ export const agentsIntegrations: AgentIntegrationConfig[ return MastraAgent.getRemoteAgents({ mastraClient, }); - }), - defineAgents("mastra-agent-local", async () => { + }, + + "mastra-agent-local": async () => { return MastraAgent.getLocalAgents({ mastra }); - }), + }, + // Disabled until we can support Vercel AI SDK v5 - })), - // defineAgents("vercel-ai-sdk", async () => ({ + // "vercel-ai-sdk": async () => ({ // agentic_chat: new VercelAISDKAgent({ model: openai("gpt-4o") }), - // })), - defineAgents("langgraph", async () => ({ - agentic_chat: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "agentic_chat", - }), - backend_tool_rendering: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "backend_tool_rendering", - }), - agentic_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "agentic_generative_ui", - }), - human_in_the_loop: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "human_in_the_loop", - }), - predictive_state_updates: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "predictive_state_updates", - }), - shared_state: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "shared_state", - }), - tool_based_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "tool_based_generative_ui", - }), + // }), + + langgraph: async () => ({ + ...mapAgents( + (graphId) => new LangGraphAgent({ deploymentUrl: envVars.langgraphPythonUrl, graphId }), + { + agentic_chat: "agentic_chat", + backend_tool_rendering: "backend_tool_rendering", + agentic_generative_ui: "agentic_generative_ui", + human_in_the_loop: "human_in_the_loop", + predictive_state_updates: "predictive_state_updates", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + subgraphs: "subgraphs", + } + ), + // Uses LangGraphHttpAgent instead of LangGraphAgent agentic_chat_reasoning: new LangGraphHttpAgent({ url: `${envVars.langgraphPythonUrl}/agent/agentic_chat_reasoning`, }), - subgraphs: new LangGraphAgent({ - deploymentUrl: envVars.langgraphPythonUrl, - graphId: "subgraphs", - }), - })), - defineAgents("langgraph-fastapi", async () => ({ - agentic_chat: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat`, - }), - backend_tool_rendering: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/backend_tool_rendering`, - }), - agentic_generative_ui: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_generative_ui`, - }), - human_in_the_loop: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/human_in_the_loop`, - }), - predictive_state_updates: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/predictive_state_updates`, - }), - shared_state: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/shared_state`, - }), - tool_based_generative_ui: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/tool_based_generative_ui`, - }), - agentic_chat_reasoning: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/agentic_chat_reasoning`, - }), - subgraphs: new LangGraphHttpAgent({ - url: `${envVars.langgraphFastApiUrl}/agent/subgraphs`, - }), - })), - defineAgents("langgraph-typescript", async () => ({ - agentic_chat: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "agentic_chat", - }), - // agentic_chat_reasoning: new LangGraphAgent({ - // deploymentUrl: envVars.langgraphTypescriptUrl, - // graphId: "agentic_chat_reasoning", - // }), - agentic_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "agentic_generative_ui", - }), - human_in_the_loop: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "human_in_the_loop", - }), - predictive_state_updates: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "predictive_state_updates", - }), - shared_state: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "shared_state", - }), - tool_based_generative_ui: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "tool_based_generative_ui", - }), - subgraphs: new LangGraphAgent({ - deploymentUrl: envVars.langgraphTypescriptUrl, - graphId: "subgraphs", - }), - })), - defineAgents("agno", async () => ({ - agentic_chat: new AgnoAgent({ - url: `${envVars.agnoUrl}/agentic_chat/agui`, - }), - tool_based_generative_ui: new AgnoAgent({ - url: `${envVars.agnoUrl}/tool_based_generative_ui/agui`, - }), - backend_tool_rendering: new AgnoAgent({ - url: `${envVars.agnoUrl}/backend_tool_rendering/agui`, - }), - human_in_the_loop: new AgnoAgent({ - url: `${envVars.agnoUrl}/human_in_the_loop/agui`, - }), - })), - defineAgents("spring-ai", async () => ({ - agentic_chat: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_chat/agui`, - }), - shared_state: new SpringAiAgent({ - url: `${envVars.springAiUrl}/shared_state/agui`, - }), - tool_based_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/tool_based_generative_ui/agui`, - }), - human_in_the_loop: new SpringAiAgent({ - url: `${envVars.springAiUrl}/human_in_the_loop/agui`, - }), - agentic_generative_ui: new SpringAiAgent({ - url: `${envVars.springAiUrl}/agentic_generative_ui/agui`, - }), - })), - defineAgents("llama-index", async () => ({ - agentic_chat: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/agentic_chat/run`, - }), - human_in_the_loop: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/human_in_the_loop/run`, - }), - agentic_generative_ui: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/agentic_generative_ui/run`, - }), - shared_state: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/shared_state/run`, - }), - backend_tool_rendering: new LlamaIndexAgent({ - url: `${envVars.llamaIndexUrl}/backend_tool_rendering/run`, - }), - })), - defineAgents("crewai", async () => ({ - agentic_chat: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/agentic_chat`, - }), - human_in_the_loop: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/human_in_the_loop`, - }), - tool_based_generative_ui: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/tool_based_generative_ui`, - }), - agentic_generative_ui: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/agentic_generative_ui`, - }), - shared_state: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/shared_state`, - }), - predictive_state_updates: new CrewAIAgent({ - url: `${envVars.crewAiUrl}/predictive_state_updates`, - }), - })), - defineAgents("microsoft-agent-framework-python", async () => ({ - agentic_chat: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/agentic_chat`, - }), - backend_tool_rendering: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/agentic_generative_ui`, - }), - shared_state: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/shared_state`, - }), - tool_based_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/tool_based_generative_ui`, - }), - predictive_state_updates: new HttpAgent({ - url: `${envVars.agentFrameworkPythonUrl}/predictive_state_updates`, - }), - })), - defineAgents("a2a-basic", async () => { + }), + + "langgraph-fastapi": async () => + mapAgents( + (path) => new LangGraphHttpAgent({ url: `${envVars.langgraphFastApiUrl}/agent/${path}` }), + { + agentic_chat: "agentic_chat", + backend_tool_rendering: "backend_tool_rendering", + agentic_generative_ui: "agentic_generative_ui", + human_in_the_loop: "human_in_the_loop", + predictive_state_updates: "predictive_state_updates", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + agentic_chat_reasoning: "agentic_chat_reasoning", + subgraphs: "subgraphs", + } + ), + + "langgraph-typescript": async () => + mapAgents( + (graphId) => new LangGraphAgent({ deploymentUrl: envVars.langgraphTypescriptUrl, graphId }), + { + agentic_chat: "agentic_chat", + // agentic_chat_reasoning: "agentic_chat_reasoning", + agentic_generative_ui: "agentic_generative_ui", + human_in_the_loop: "human_in_the_loop", + predictive_state_updates: "predictive_state_updates", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + subgraphs: "subgraphs", + } + ), + + agno: async () => + mapAgents( + (path) => new AgnoAgent({ url: `${envVars.agnoUrl}/${path}/agui` }), + { + agentic_chat: "agentic_chat", + tool_based_generative_ui: "tool_based_generative_ui", + backend_tool_rendering: "backend_tool_rendering", + human_in_the_loop: "human_in_the_loop", + } + ), + + "spring-ai": async () => + mapAgents( + (path) => new SpringAiAgent({ url: `${envVars.springAiUrl}/${path}/agui` }), + { + agentic_chat: "agentic_chat", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + human_in_the_loop: "human_in_the_loop", + agentic_generative_ui: "agentic_generative_ui", + } + ), + + "llama-index": async () => + mapAgents( + (path) => new LlamaIndexAgent({ url: `${envVars.llamaIndexUrl}/${path}/run` }), + { + agentic_chat: "agentic_chat", + human_in_the_loop: "human_in_the_loop", + agentic_generative_ui: "agentic_generative_ui", + shared_state: "shared_state", + backend_tool_rendering: "backend_tool_rendering", + } + ), + + crewai: async () => + mapAgents( + (path) => new CrewAIAgent({ url: `${envVars.crewAiUrl}/${path}` }), + { + agentic_chat: "agentic_chat", + human_in_the_loop: "human_in_the_loop", + tool_based_generative_ui: "tool_based_generative_ui", + agentic_generative_ui: "agentic_generative_ui", + shared_state: "shared_state", + predictive_state_updates: "predictive_state_updates", + } + ), + + "microsoft-agent-framework-python": async () => + mapAgents( + (path) => new HttpAgent({ url: `${envVars.agentFrameworkPythonUrl}/${path}` }), + { + agentic_chat: "agentic_chat", + backend_tool_rendering: "backend_tool_rendering", + human_in_the_loop: "human_in_the_loop", + agentic_generative_ui: "agentic_generative_ui", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + predictive_state_updates: "predictive_state_updates", + } + ), + + "a2a-basic": async () => { const a2aClient = new A2AClient(envVars.a2aUrl); return { vnext_chat: new A2AAgent({ @@ -331,31 +241,23 @@ export const agentsIntegrations: AgentIntegrationConfig[ debug: process.env.NODE_ENV !== "production", }), }; - }), - defineAgents("microsoft-agent-framework-dotnet", async () => ({ - agentic_chat: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/agentic_chat`, - }), - backend_tool_rendering: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/backend_tool_rendering`, - }), - human_in_the_loop: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/human_in_the_loop`, - }), - agentic_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/agentic_generative_ui`, - }), - shared_state: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/shared_state`, - }), - tool_based_generative_ui: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/tool_based_generative_ui`, - }), - predictive_state_updates: new HttpAgent({ - url: `${envVars.agentFrameworkDotnetUrl}/predictive_state_updates`, - }), - })), - defineAgents("a2a", async () => { + }, + + "microsoft-agent-framework-dotnet": async () => + mapAgents( + (path) => new HttpAgent({ url: `${envVars.agentFrameworkDotnetUrl}/${path}` }), + { + agentic_chat: "agentic_chat", + backend_tool_rendering: "backend_tool_rendering", + human_in_the_loop: "human_in_the_loop", + agentic_generative_ui: "agentic_generative_ui", + shared_state: "shared_state", + tool_based_generative_ui: "tool_based_generative_ui", + predictive_state_updates: "predictive_state_updates", + } + ), + + a2a: async () => { // A2A agents: building management, finance, it agents const agentUrls = [ envVars.a2aMiddlewareBuildingsManagementUrl, @@ -385,12 +287,19 @@ export const agentsIntegrations: AgentIntegrationConfig[ `, }), }; - }), - defineAgents("aws-strands", async () => ({ - agentic_chat: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-chat/` }), - backend_tool_rendering: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/backend-tool-rendering/` }), - agentic_generative_ui: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/agentic-generative-ui/` }), - shared_state: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/shared-state/` }), + }, + + "aws-strands": async () => ({ + // Different URL pattern (hyphens) and one has debug:true, so not using mapAgents + ...mapAgents( + (path) => new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/${path}/` }), + { + agentic_chat: "agentic-chat", + backend_tool_rendering: "backend-tool-rendering", + agentic_generative_ui: "agentic-generative-ui", + shared_state: "shared-state", + } + ), human_in_the_loop: new AWSStrandsAgent({ url: `${envVars.awsStrandsUrl}/human-in-the-loop`, debug: true }), - })), -]; + }), +} satisfies AgentsMap; diff --git a/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts b/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts index a1c04b856..bce11890b 100644 --- a/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts +++ b/apps/dojo/src/app/api/copilotkit/[integrationId]/route.ts @@ -6,16 +6,17 @@ import { import { NextRequest } from "next/server"; import { agentsIntegrations } from "@/agents"; +import { IntegrationId } from "@/menu"; export async function POST(request: NextRequest) { - const integrationId = request.url.split("/").pop(); + const integrationId = request.url.split("/").pop() as IntegrationId; - const integration = agentsIntegrations.find((i) => i.id === integrationId); - if (!integration) { + const getAgents = agentsIntegrations[integrationId]; + if (!getAgents) { return new Response("Integration not found", { status: 404 }); } - const agents = await integration.agents(); + const agents = await getAgents(); const runtime = new CopilotRuntime({ // @ts-ignore for now agents, diff --git a/apps/dojo/src/types/integration.ts b/apps/dojo/src/types/integration.ts index 341a331fd..bd708cda3 100644 --- a/apps/dojo/src/types/integration.ts +++ b/apps/dojo/src/types/integration.ts @@ -1,5 +1,3 @@ -import { AbstractAgent } from "@ag-ui/client"; - export type Feature = | "agentic_chat" | "agentic_generative_ui" @@ -19,19 +17,6 @@ export interface MenuIntegrationConfig { features: Feature[]; } -/** - * Agent integration config - * @template Id - The integration ID - * @template F - The features available for this integration - */ -export interface AgentIntegrationConfig< - Id extends string = string, - F extends Feature = Feature -> { - id: Id; - agents: () => Promise>>; -} - /** * Helper type to extract features for a specific integration from menu config */ @@ -39,14 +24,3 @@ export type IntegrationFeatures< T extends readonly MenuIntegrationConfig[], Id extends string > = Extract["features"][number]; - -/** - * Helper function to create a type-safe agent config - * Validates that agent keys match the features defined in menu.ts - */ -export function defineAgentConfig< - Id extends string, - F extends Feature ->(config: AgentIntegrationConfig): AgentIntegrationConfig { - return config; -} From d09e2cf7dc3bb685852e8ea1af74f174bae5d309 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 21:48:39 -0500 Subject: [PATCH 08/20] fix: enforce agent integration feature matching Ensures that the integration IDs defined in `menu.ts` have corresponding entries in the `agentsIntegrations` map. Also enforces that all features defined for each integration are present in the returned object, preventing runtime errors due to missing agent implementations. --- apps/dojo/src/agents.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 07f4021c5..9895384eb 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -30,22 +30,28 @@ const envVars = getEnvVars(); /** * Helper to map feature keys to agent instances using a builder function. * Reduces repetition when all agents follow the same pattern with different paths. + * + * NOTE: TypeScript can validate MISSING features but not EXTRA features due to + * structural typing limitations. Extra features won't break at runtime (they just + * won't be accessible), but they won't trigger type errors either. */ -function mapAgents>( +function mapAgents( builder: (path: string) => AbstractAgent, - mapping: T -): { [K in keyof T]: AbstractAgent } { + mapping: Record +): Record { return Object.fromEntries( - Object.entries(mapping).map(([key, path]) => [key, builder(path)]) - ) as { [K in keyof T]: AbstractAgent }; + (Object.entries(mapping) as [F, string][]).map(([key, path]) => [key, builder(path)]) + ) as Record; } /** * Agent integrations map - keys are integration IDs from menu.ts - * TypeScript validates that agent keys match features defined for each integration + * TypeScript enforces: + * - All integration IDs from menu.ts must have an entry + * - All features for each integration must be present in the returned object */ type AgentsMap = { - [K in IntegrationId]?: () => Promise, AbstractAgent>>>; + [K in IntegrationId]: () => Promise, AbstractAgent>>; }; export const agentsIntegrations = { From e5ce07dd7a8f17383f1436112147f88db71680b5 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 21:57:04 -0500 Subject: [PATCH 09/20] fix: enforce strict type checking for agent features Ensures that agent integrations maps have exactly the expected features, preventing missing or extra features. This change improves type safety by leveraging TypeScript's type system to validate the agent integrations configuration. It introduces an `ExactRecord` type that verifies that a given type has only the keys specified in another type, preventing unexpected or missing features in agent definitions. The "backend_tool_rendering" feature was temporarily disabled to ensure type correctness given current agent implementations. This feature may be re-enabled in future commits after corresponding agent implementations have been updated. Fixes #implement-feature-id-matching-and-404 --- apps/dojo/src/agents.ts | 33 +++++++++++++++++++++++---------- apps/dojo/src/menu.ts | 6 +++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 9895384eb..6b36236b9 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -31,27 +31,40 @@ const envVars = getEnvVars(); * Helper to map feature keys to agent instances using a builder function. * Reduces repetition when all agents follow the same pattern with different paths. * - * NOTE: TypeScript can validate MISSING features but not EXTRA features due to - * structural typing limitations. Extra features won't break at runtime (they just - * won't be accessible), but they won't trigger type errors either. + * Uses `const` type parameter to preserve exact literal keys from the mapping, + * which enables strict type checking via `satisfies AgentsMap`. */ -function mapAgents( +function mapAgents>( builder: (path: string) => AbstractAgent, - mapping: Record -): Record { + mapping: T +): { [K in keyof T]: AbstractAgent } { return Object.fromEntries( - (Object.entries(mapping) as [F, string][]).map(([key, path]) => [key, builder(path)]) - ) as Record; + Object.entries(mapping).map(([key, path]) => [key, builder(path)]) + ) as { [K in keyof T]: AbstractAgent }; } +/** + * Checks if Actual has exactly the keys in Expected (no more, no less) + */ +type ExactRecord> = + [keyof Actual] extends [keyof Expected] + ? [keyof Expected] extends [keyof Actual] + ? Actual + : never + : never; + /** * Agent integrations map - keys are integration IDs from menu.ts * TypeScript enforces: * - All integration IDs from menu.ts must have an entry - * - All features for each integration must be present in the returned object + * - All features for each integration must be present (no missing) + * - No extra features allowed (exact match via ExactRecord) */ type AgentsMap = { - [K in IntegrationId]: () => Promise, AbstractAgent>>; + [K in IntegrationId]: () => Promise, AbstractAgent>, + Record, AbstractAgent> + >>; }; export const agentsIntegrations = { diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 673d7d0dc..b540def42 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -45,7 +45,7 @@ export const menuIntegrations = [ name: "LangGraph (Typescript)", features: [ "agentic_chat", - "backend_tool_rendering", + // "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -164,7 +164,7 @@ export const menuIntegrations = [ name: "CrewAI", features: [ "agentic_chat", - "backend_tool_rendering", + // "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -200,7 +200,7 @@ export const menuIntegrations = [ "agentic_chat", "backend_tool_rendering", "human_in_the_loop", - "agentic_chat_reasoning", + // "agentic_chat_reasoning", "agentic_generative_ui", "predictive_state_updates", "shared_state", From 39b71a8a3657b1ba782d06b38a8f7748bdde100a Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 21:57:14 -0500 Subject: [PATCH 10/20] feat: refines agent mapping type for improved safety Updates the `mapAgents` function to correctly remove the readonly modifier from the mapped type, ensuring it matches the expected `AgentsMap` type. Removes the `ExactRecord` type as it's no longer needed due to the improved `mapAgents` type definition, leading to a simpler and more maintainable code. --- apps/dojo/src/agents.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 6b36236b9..0e1b1b4cc 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -31,40 +31,27 @@ const envVars = getEnvVars(); * Helper to map feature keys to agent instances using a builder function. * Reduces repetition when all agents follow the same pattern with different paths. * - * Uses `const` type parameter to preserve exact literal keys from the mapping, - * which enables strict type checking via `satisfies AgentsMap`. + * Uses `const` type parameter to preserve exact literal keys from the mapping. + * The return type `{ -readonly [K in keyof T]: AbstractAgent }` removes the readonly + * modifier added by `const T` to match the expected AgentsMap type. */ function mapAgents>( builder: (path: string) => AbstractAgent, mapping: T -): { [K in keyof T]: AbstractAgent } { +): { -readonly [K in keyof T]: AbstractAgent } { return Object.fromEntries( Object.entries(mapping).map(([key, path]) => [key, builder(path)]) - ) as { [K in keyof T]: AbstractAgent }; + ) as { -readonly [K in keyof T]: AbstractAgent }; } -/** - * Checks if Actual has exactly the keys in Expected (no more, no less) - */ -type ExactRecord> = - [keyof Actual] extends [keyof Expected] - ? [keyof Expected] extends [keyof Actual] - ? Actual - : never - : never; - /** * Agent integrations map - keys are integration IDs from menu.ts * TypeScript enforces: * - All integration IDs from menu.ts must have an entry - * - All features for each integration must be present (no missing) - * - No extra features allowed (exact match via ExactRecord) + * - All features for each integration must be present in the returned object */ type AgentsMap = { - [K in IntegrationId]: () => Promise, AbstractAgent>, - Record, AbstractAgent> - >>; + [K in IntegrationId]: () => Promise<{ [P in FeaturesFor]: AbstractAgent }>; }; export const agentsIntegrations = { From e8dcd46856d5b79ac350ea370f652a0ac1ddcb5b Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Thu, 4 Dec 2025 21:58:12 -0500 Subject: [PATCH 11/20] fix: revert change silencing errors re missing backend_tool_rendering Enables the 'backend_tool_rendering' feature for LangGraph and CrewAI, and 'agentic_chat_reasoning' for the Autonomous Tools integration by uncommenting them in the menu configuration. The context indicates that this change is related to implementing feature ID matching and addressing 404 errors. --- apps/dojo/src/menu.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index b540def42..673d7d0dc 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -45,7 +45,7 @@ export const menuIntegrations = [ name: "LangGraph (Typescript)", features: [ "agentic_chat", - // "backend_tool_rendering", + "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -164,7 +164,7 @@ export const menuIntegrations = [ name: "CrewAI", features: [ "agentic_chat", - // "backend_tool_rendering", + "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -200,7 +200,7 @@ export const menuIntegrations = [ "agentic_chat", "backend_tool_rendering", "human_in_the_loop", - // "agentic_chat_reasoning", + "agentic_chat_reasoning", "agentic_generative_ui", "predictive_state_updates", "shared_state", From ae5811efc9049dfd90d2d760cdffd19610dfaa70 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 15:53:03 -0500 Subject: [PATCH 12/20] fix: temporarily add missing agents to silence errors --- apps/dojo/src/agents.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 0e1b1b4cc..1e25e5084 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -97,6 +97,8 @@ export const agentsIntegrations = { (path) => new ServerStarterAllFeaturesAgent({ url: `${envVars.serverStarterAllFeaturesUrl}/${path}` }), { agentic_chat: "agentic_chat", + // TODO: Add agent for agentic_chat_reasoning + agentic_chat_reasoning: "agentic_chat_reasoning", backend_tool_rendering: "backend_tool_rendering", human_in_the_loop: "human_in_the_loop", agentic_generative_ui: "agentic_generative_ui", @@ -166,7 +168,8 @@ export const agentsIntegrations = { (graphId) => new LangGraphAgent({ deploymentUrl: envVars.langgraphTypescriptUrl, graphId }), { agentic_chat: "agentic_chat", - // agentic_chat_reasoning: "agentic_chat_reasoning", + // TODO: Add agent for backend_tool_rendering + backend_tool_rendering: "backend_tool_rendering", agentic_generative_ui: "agentic_generative_ui", human_in_the_loop: "human_in_the_loop", predictive_state_updates: "predictive_state_updates", @@ -216,6 +219,8 @@ export const agentsIntegrations = { (path) => new CrewAIAgent({ url: `${envVars.crewAiUrl}/${path}` }), { agentic_chat: "agentic_chat", + // TODO: Add agent for backend_tool_rendering + backend_tool_rendering: "backend_tool_rendering", human_in_the_loop: "human_in_the_loop", tool_based_generative_ui: "tool_based_generative_ui", agentic_generative_ui: "agentic_generative_ui", From e50676479b076ed4e5befa2f1cde73b7408a34ff Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 15:53:55 -0500 Subject: [PATCH 13/20] chore: re-add langchain (previously removed for reformatting) --- apps/dojo/src/agents.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 1e25e5084..7cc97b344 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -179,6 +179,25 @@ export const agentsIntegrations = { } ), + langchain: async () => + mapAgents( + new LangChainAgent({ + chainFn: async ({ messages, tools, threadId }) => { + // @ts-ignore + const { ChatOpenAI } = await import("@langchain/openai"); + const chatOpenAI = new ChatOpenAI({ model: "gpt-4o" }); + const model = chatOpenAI.bindTools(tools, { + strict: true, + }); + return model.stream(messages, { tools, metadata: { conversation_id: threadId } }); + }, + }), + { + agentic_chat: "", + tool_based_generative_ui: "", + } + ), + agno: async () => mapAgents( (path) => new AgnoAgent({ url: `${envVars.agnoUrl}/${path}/agui` }), From 32500d178cae9179b30383c3b84064239b69a9cb Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 15:55:40 -0500 Subject: [PATCH 14/20] fix: silence TS errors re missing types and assign to Ran via comment --- apps/dojo/src/agents.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 7cc97b344..8a6e5439f 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -182,6 +182,8 @@ export const agentsIntegrations = { langchain: async () => mapAgents( new LangChainAgent({ + // TODO: @ranst91 - can you add types here? + // @ts-expect-error - TODO: add types chainFn: async ({ messages, tools, threadId }) => { // @ts-ignore const { ChatOpenAI } = await import("@langchain/openai"); From 7fe84f65e19804da2c707c5906ebaef1f692dea9 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 17:59:49 -0500 Subject: [PATCH 15/20] fix: mastra agents type safety --- apps/dojo/src/agents.ts | 9 +++++--- integrations/mastra/typescript/src/mastra.ts | 12 +++++----- integrations/mastra/typescript/src/utils.ts | 24 ++++++++++++-------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 8a6e5439f..313ee0ff3 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -115,12 +115,15 @@ export const agentsIntegrations = { return MastraAgent.getRemoteAgents({ mastraClient, + keys: ["agentic_chat", "backend_tool_rendering", "human_in_the_loop", "tool_based_generative_ui"], }); }, - "mastra-agent-local": async () => { - return MastraAgent.getLocalAgents({ mastra }); - }, + "mastra-agent-local": async () => + MastraAgent.getLocalAgents({ + mastra, + keys: ["agentic_chat", "backend_tool_rendering", "human_in_the_loop", "shared_state", "tool_based_generative_ui"], + }), // Disabled until we can support Vercel AI SDK v5 // "vercel-ai-sdk": async () => ({ diff --git a/integrations/mastra/typescript/src/mastra.ts b/integrations/mastra/typescript/src/mastra.ts index 27cbe4425..c199e708d 100644 --- a/integrations/mastra/typescript/src/mastra.ts +++ b/integrations/mastra/typescript/src/mastra.ts @@ -381,15 +381,15 @@ export class MastraAgent extends AbstractAgent { } } - static async getRemoteAgents( - options: GetRemoteAgentsOptions, - ): Promise> { + static async getRemoteAgents( + options: GetRemoteAgentsOptions, + ): Promise> { return getRemoteAgents(options); } - static getLocalAgents( - options: GetLocalAgentsOptions, - ): Record { + static getLocalAgents( + options: GetLocalAgentsOptions, + ): Record { return getLocalAgents(options); } diff --git a/integrations/mastra/typescript/src/utils.ts b/integrations/mastra/typescript/src/utils.ts index e775d353f..3639e3e95 100644 --- a/integrations/mastra/typescript/src/utils.ts +++ b/integrations/mastra/typescript/src/utils.ts @@ -86,22 +86,24 @@ export function convertAGUIMessagesToMastra(messages: Message[]): CoreMessage[] return result; } -export interface GetRemoteAgentsOptions { +export interface GetRemoteAgentsOptions { mastraClient: MastraClient; resourceId?: string; + /** Expected agent keys - used for type inference only */ + keys?: readonly K[]; } -export async function getRemoteAgents({ +export async function getRemoteAgents({ mastraClient, resourceId, -}: GetRemoteAgentsOptions): Promise> { +}: GetRemoteAgentsOptions): Promise> { const agents = await mastraClient.getAgents(); return Object.entries(agents).reduce( (acc, [agentId]) => { const agent = mastraClient.getAgent(agentId); - acc[agentId] = new MastraAgent({ + acc[agentId as K] = new MastraAgent({ agentId, agent, resourceId, @@ -109,26 +111,28 @@ export async function getRemoteAgents({ return acc; }, - {} as Record, + {} as Record, ); } -export interface GetLocalAgentsOptions { +export interface GetLocalAgentsOptions { mastra: Mastra; resourceId?: string; runtimeContext?: RuntimeContext; + /** Expected agent keys - used for type inference only */ + keys?: readonly K[]; } -export function getLocalAgents({ +export function getLocalAgents({ mastra, resourceId, runtimeContext, -}: GetLocalAgentsOptions): Record { +}: GetLocalAgentsOptions): Record { const agents = mastra.getAgents() || {}; const agentAGUI = Object.entries(agents).reduce( (acc, [agentId, agent]) => { - acc[agentId] = new MastraAgent({ + acc[agentId as K] = new MastraAgent({ agentId, agent, resourceId, @@ -136,7 +140,7 @@ export function getLocalAgents({ }); return acc; }, - {} as Record, + {} as Record, ); return agentAGUI; From cce20cc1c74ca09ebf91d61a8a1035ac13ac1c53 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 18:02:31 -0500 Subject: [PATCH 16/20] chore: refine comment --- apps/dojo/src/agents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 313ee0ff3..fc8e1d595 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -185,7 +185,7 @@ export const agentsIntegrations = { langchain: async () => mapAgents( new LangChainAgent({ - // TODO: @ranst91 - can you add types here? + // TODO: @ranst91 - can you add param types here? // @ts-expect-error - TODO: add types chainFn: async ({ messages, tools, threadId }) => { // @ts-ignore From e3ef86f47001739e54be86bed0d89a05d7376bca Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 18:02:35 -0500 Subject: [PATCH 17/20] chore: clear unused @ts-ignore --- apps/dojo/src/agents.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index fc8e1d595..17c662a8f 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -188,7 +188,6 @@ export const agentsIntegrations = { // TODO: @ranst91 - can you add param types here? // @ts-expect-error - TODO: add types chainFn: async ({ messages, tools, threadId }) => { - // @ts-ignore const { ChatOpenAI } = await import("@langchain/openai"); const chatOpenAI = new ChatOpenAI({ model: "gpt-4o" }); const model = chatOpenAI.bindTools(tools, { From 187f245c085ade0f62a08adbedd2a6840ae2f23c Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 18:04:59 -0500 Subject: [PATCH 18/20] fix: add `langchain` to `menuIntegrations` --- apps/dojo/src/menu.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index 673d7d0dc..ae160d6ba 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -54,6 +54,14 @@ export const menuIntegrations = [ "subgraphs", ], }, + { + id: "langchain", + name: "LangChain", + features: [ + "agentic_chat", + "tool_based_generative_ui", + ], + }, { id: "mastra", name: "Mastra", From 488630202c1f03d317beafd83e1289375fafb8d7 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Fri, 5 Dec 2025 18:07:04 -0500 Subject: [PATCH 19/20] fix: remove temporarily added missing agents to un-silence errors --- apps/dojo/src/agents.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 17c662a8f..ca7afec3d 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -98,7 +98,7 @@ export const agentsIntegrations = { { agentic_chat: "agentic_chat", // TODO: Add agent for agentic_chat_reasoning - agentic_chat_reasoning: "agentic_chat_reasoning", + // agentic_chat_reasoning: "agentic_chat_reasoning", backend_tool_rendering: "backend_tool_rendering", human_in_the_loop: "human_in_the_loop", agentic_generative_ui: "agentic_generative_ui", @@ -172,7 +172,7 @@ export const agentsIntegrations = { { agentic_chat: "agentic_chat", // TODO: Add agent for backend_tool_rendering - backend_tool_rendering: "backend_tool_rendering", + // backend_tool_rendering: "backend_tool_rendering", agentic_generative_ui: "agentic_generative_ui", human_in_the_loop: "human_in_the_loop", predictive_state_updates: "predictive_state_updates", @@ -243,7 +243,7 @@ export const agentsIntegrations = { { agentic_chat: "agentic_chat", // TODO: Add agent for backend_tool_rendering - backend_tool_rendering: "backend_tool_rendering", + // backend_tool_rendering: "backend_tool_rendering", human_in_the_loop: "human_in_the_loop", tool_based_generative_ui: "tool_based_generative_ui", agentic_generative_ui: "agentic_generative_ui", From a2e08b77926575d3644c31df8b2bfa1820cc6b57 Mon Sep 17 00:00:00 2001 From: Brandon McConnell Date: Tue, 9 Dec 2025 16:53:07 -0500 Subject: [PATCH 20/20] chore: remove menu items for missing agents in dojo --- apps/dojo/src/agents.ts | 4 +--- apps/dojo/src/menu.ts | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/dojo/src/agents.ts b/apps/dojo/src/agents.ts index 3d46bbe8a..0fdda693c 100644 --- a/apps/dojo/src/agents.ts +++ b/apps/dojo/src/agents.ts @@ -1,7 +1,7 @@ import "server-only"; import type { AbstractAgent } from "@ag-ui/client"; -import type { FeaturesFor, IntegrationId } from "./menu"; +import { FeaturesFor, IntegrationId } from "./menu"; import { MiddlewareStarterAgent } from "@ag-ui/middleware-starter"; import { ServerStarterAgent } from "@ag-ui/server-starter"; import { ServerStarterAllFeaturesAgent } from "@ag-ui/server-starter-all-features"; @@ -98,7 +98,6 @@ export const agentsIntegrations = { { agentic_chat: "agentic_chat", // TODO: Add agent for agentic_chat_reasoning - // agentic_chat_reasoning: "agentic_chat_reasoning", backend_tool_rendering: "backend_tool_rendering", human_in_the_loop: "human_in_the_loop", agentic_generative_ui: "agentic_generative_ui", @@ -172,7 +171,6 @@ export const agentsIntegrations = { { agentic_chat: "agentic_chat", // TODO: Add agent for backend_tool_rendering - // backend_tool_rendering: "backend_tool_rendering", agentic_generative_ui: "agentic_generative_ui", human_in_the_loop: "human_in_the_loop", predictive_state_updates: "predictive_state_updates", diff --git a/apps/dojo/src/menu.ts b/apps/dojo/src/menu.ts index b8e769207..defb34ddc 100644 --- a/apps/dojo/src/menu.ts +++ b/apps/dojo/src/menu.ts @@ -45,7 +45,7 @@ export const menuIntegrations = [ name: "LangGraph (Typescript)", features: [ "agentic_chat", - "backend_tool_rendering", + // "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -172,7 +172,7 @@ export const menuIntegrations = [ name: "CrewAI", features: [ "agentic_chat", - "backend_tool_rendering", + // "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -208,7 +208,7 @@ export const menuIntegrations = [ "agentic_chat", "backend_tool_rendering", "human_in_the_loop", - "agentic_chat_reasoning", + // "agentic_chat_reasoning", "agentic_generative_ui", "predictive_state_updates", "shared_state",