diff --git a/.gitignore b/.gitignore index eb5c4a5d9..327e91768 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ **/.claude/settings.local.json .vscode/ .idea/ +mastra.db* .DS_Store +test-results/ diff --git a/typescript-sdk/apps/dojo/e2e/tests/adkMiddlewareTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/adkMiddlewareTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..84b97564a --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/adkMiddlewareTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[ADK Middleware] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/adk-middleware/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..9d6307433 --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[Agno] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/agno/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..2eef4d06d --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[LanggraphFastAPI] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/langgraph-fastapi/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphPythonTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphPythonTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..5ab1695b3 --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphPythonTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[LanggraphPython] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/langgraph/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..1bbdec63d --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[LlamaIndex] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/llama-index/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..b7e4b732e --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[MastraAgentLocal] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/mastra-agent-local/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/mastraTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/mastraTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..c99d3ba8a --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/mastraTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[Mastra] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/mastra/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..c1e00f3d2 --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/backendToolRenderingPage.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from "@playwright/test"; + +test("[PydanticAI] Backend Tool Rendering displays weather cards", async ({ page }) => { + // Set shorter default timeout for this test + test.setTimeout(30000); // 30 seconds total + + await page.goto("/pydantic-ai/feature/backend_tool_rendering"); + + // Verify suggestion buttons are visible + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 5000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait for either test ID or fallback to "Current Weather" text + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first, fallback to text + try { + await expect(weatherCard).toBeVisible({ timeout: 10000 }); + } catch (e) { + // Fallback to checking for "Current Weather" text + await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 }); + } + + // Verify weather content is present (use flexible selectors) + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(2000); + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/backendToolRenderingPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/backendToolRenderingPage.spec.ts new file mode 100644 index 000000000..f6806654b --- /dev/null +++ b/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/backendToolRenderingPage.spec.ts @@ -0,0 +1,72 @@ +import { test, expect } from "@playwright/test"; + +test("[ServerStarterAllFeatures] Backend Tool Rendering displays weather cards", async ({ + page, +}) => { + // Set longer timeout for this test since server-starter-all-features can be slower + test.setTimeout(60000); // 60 seconds total + + await page.goto("/server-starter-all-features/feature/backend_tool_rendering"); + + // Wait for page to load - be more lenient with timeout + await page.waitForLoadState("networkidle", { timeout: 15000 }).catch(() => {}); + + // Verify suggestion buttons are visible with longer timeout + await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({ + timeout: 15000, + }); + + // Click first suggestion and verify weather card appears + await page.getByRole("button", { name: "Weather in San Francisco" }).click(); + + // Wait longer for weather card to appear (backend processing time) + const weatherCard = page.getByTestId("weather-card"); + const currentWeatherText = page.getByText("Current Weather"); + + // Try test ID first with longer timeout, fallback to text + let weatherVisible = false; + try { + await expect(weatherCard).toBeVisible({ timeout: 20000 }); + weatherVisible = true; + } catch (e) { + // Fallback to checking for "Current Weather" text + try { + await expect(currentWeatherText.first()).toBeVisible({ timeout: 20000 }); + weatherVisible = true; + } catch (e2) { + // Last resort - check for any weather-related content + const weatherContent = await page.getByText(/Humidity|Wind|Temperature/i).count(); + weatherVisible = weatherContent > 0; + } + } + + expect(weatherVisible).toBeTruthy(); + + // Verify weather content is present (use flexible selectors) + await page.waitForTimeout(1000); // Give elements time to render + + const hasHumidity = await page + .getByText("Humidity") + .isVisible() + .catch(() => false); + const hasWind = await page + .getByText("Wind") + .isVisible() + .catch(() => false); + const hasCityName = await page + .locator("h3") + .filter({ hasText: /San Francisco/i }) + .isVisible() + .catch(() => false); + + // At least one of these should be true + expect(hasHumidity || hasWind || hasCityName).toBeTruthy(); + + // Click second suggestion + await page.getByRole("button", { name: "Weather in New York" }).click(); + await page.waitForTimeout(3000); // Longer wait for backend to process + + // Verify at least one weather-related element is still visible + const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count(); + expect(weatherElements).toBeGreaterThan(0); +}); diff --git a/typescript-sdk/apps/dojo/scripts/generate-content-json.ts b/typescript-sdk/apps/dojo/scripts/generate-content-json.ts index c224e5945..4f2171202 100644 --- a/typescript-sdk/apps/dojo/scripts/generate-content-json.ts +++ b/typescript-sdk/apps/dojo/scripts/generate-content-json.ts @@ -2,11 +2,11 @@ import fs from "fs"; import path from "path"; // Function to parse agents.ts file and extract agent keys without executing -function parseAgentsFile(): Array<{id: string, agentKeys: string[]}> { - const agentsFilePath = path.join(__dirname, '../src/agents.ts'); - const agentsContent = fs.readFileSync(agentsFilePath, 'utf8'); +function parseAgentsFile(): Array<{ id: string; agentKeys: string[] }> { + const agentsFilePath = path.join(__dirname, "../src/agents.ts"); + const agentsContent = fs.readFileSync(agentsFilePath, "utf8"); - const agentConfigs: Array<{id: string, agentKeys: string[]}> = []; + const agentConfigs: Array<{ id: string; agentKeys: string[] }> = []; // Split the content to process each agent configuration individually const agentBlocks = agentsContent.split(/(?=\s*{\s*id:\s*["'])/); @@ -25,7 +25,6 @@ function parseAgentsFile(): Array<{id: string, agentKeys: string[]}> { const startIndex = returnMatch.index! + returnMatch[0].length; const returnObjectContent = extractBalancedBraces(block, startIndex); - // Extract keys from the return object - only capture keys that are followed by a colon and then 'new' // This ensures we only get the top-level keys like "agentic_chat: new ..." not nested keys like "url: ..." const keyRegex = /^\s*(\w+):\s*new\s+\w+/gm; @@ -47,9 +46,9 @@ function extractBalancedBraces(text: string, startIndex: number): string { let i = startIndex; while (i < text.length) { - if (text[i] === '{') { + if (text[i] === "{") { braceCount++; - } else if (text[i] === '}') { + } else if (text[i] === "}") { if (braceCount === 0) { // Found the closing brace for the return object return text.substring(startIndex, i); @@ -59,24 +58,24 @@ function extractBalancedBraces(text: string, startIndex: number): string { i++; } - return ''; + return ""; } const agentConfigs = parseAgentsFile(); -const featureFiles = ["page.tsx", "style.css", "README.mdx"] +const featureFiles = ["page.tsx", "style.css", "README.mdx"]; async function getFile(_filePath: string | undefined, _fileName?: string) { if (!_filePath) { console.warn(`File path is undefined, skipping.`); - return {} + return {}; } - const fileName = _fileName ?? _filePath.split('/').pop() ?? '' + const fileName = _fileName ?? _filePath.split("/").pop() ?? ""; const filePath = _fileName ? path.join(_filePath, fileName) : _filePath; // Check if it's a remote URL - const isRemoteUrl = _filePath.startsWith('http://') || _filePath.startsWith('https://'); + const isRemoteUrl = _filePath.startsWith("http://") || _filePath.startsWith("https://"); let content: string; @@ -84,8 +83,10 @@ async function getFile(_filePath: string | undefined, _fileName?: string) { if (isRemoteUrl) { // Convert GitHub URLs to raw URLs for direct file access let fetchUrl = _filePath; - if (_filePath.includes('github.com') && _filePath.includes('/blob/')) { - fetchUrl = _filePath.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/'); + if (_filePath.includes("github.com") && _filePath.includes("/blob/")) { + fetchUrl = _filePath + .replace("github.com", "raw.githubusercontent.com") + .replace("/blob/", "/"); } // Fetch remote file content @@ -93,14 +94,14 @@ async function getFile(_filePath: string | undefined, _fileName?: string) { const response = await fetch(fetchUrl); if (!response.ok) { console.warn(`Failed to fetch remote file: ${fetchUrl}, status: ${response.status}`); - return {} + return {}; } content = await response.text(); } else { // Handle local file if (!fs.existsSync(filePath)) { console.warn(`File not found: ${filePath}, skipping.`); - return {} + return {}; } content = fs.readFileSync(filePath, "utf8"); } @@ -120,129 +121,253 @@ async function getFile(_filePath: string | undefined, _fileName?: string) { name: fileName, content, language, - type: 'file' - } + type: "file", + }; } catch (error) { console.error(`Error reading file ${filePath}:`, error); - return {} + return {}; } } async function getFeatureFrontendFiles(featureId: string) { - const featurePath = path.join(__dirname, `../src/app/[integrationId]/feature/${featureId as string}`); - const retrievedFiles = [] + const featurePath = path.join( + __dirname, + `../src/app/[integrationId]/feature/${featureId as string}`, + ); + const retrievedFiles = []; for (const fileName of featureFiles) { - retrievedFiles.push(await getFile(featurePath, fileName)) + retrievedFiles.push(await getFile(featurePath, fileName)); } return retrievedFiles; } -const integrationsFolderPath = '../../../integrations' +const integrationsFolderPath = "../../../integrations"; const agentFilesMapper: Record Record> = { - 'middleware-starter': () => ({ - agentic_chat: [path.join(__dirname, integrationsFolderPath, `/middleware-starter/src/index.ts`)] + "middleware-starter": () => ({ + agentic_chat: [ + path.join(__dirname, integrationsFolderPath, `/middleware-starter/src/index.ts`), + ], }), - 'pydantic-ai': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/pydantic-ai/examples/server/api/${agentId}.py`)] - }), {}) + "pydantic-ai": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/pydantic-ai/examples/server/api/${agentId}.py`, + ), + ], + }), + {}, + ); }, - 'server-starter': () => ({ - agentic_chat: [path.join(__dirname, integrationsFolderPath, `/server-starter/server/python/example_server/__init__.py`)] + "server-starter": () => ({ + agentic_chat: [ + path.join( + __dirname, + integrationsFolderPath, + `/server-starter/server/python/example_server/__init__.py`, + ), + ], }), - 'server-starter-all-features': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/server-starter-all-features/server/python/example_server/${agentId}.py`)] - }), {}) + "server-starter-all-features": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/server-starter-all-features/server/python/example_server/${agentId}.py`, + ), + ], + }), + {}, + ); }, - 'mastra': () => ({ - agentic_chat: [path.join(__dirname, integrationsFolderPath, `/mastra/example/src/mastra/agents/weather-agent.ts`)] + mastra: () => ({ + agentic_chat: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/weather-agent.ts`, + ), + ], + backend_tool_rendering: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/backend-tool-rendering.ts`, + ), + ], + tool_based_generative_ui: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/haiku-agent.ts`, + ), + ], }), - 'mastra-agent-lock': () => ({ - agentic_chat: [path.join(__dirname, integrationsFolderPath, `/mastra/example/src/mastra/agents/weather-agent.ts`)] + "mastra-agent-lock": () => ({ + agentic_chat: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/weather-agent.ts`, + ), + ], + backend_tool_rendering: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/backend-tool-rendering.ts`, + ), + ], + tool_based_generative_ui: [ + path.join( + __dirname, + integrationsFolderPath, + `/mastra/example/src/mastra/agents/haiku-agent.ts`, + ), + ], }), - 'vercel-ai-sdk': () => ({ - agentic_chat: [path.join(__dirname, integrationsFolderPath, `/vercel-ai-sdk/src/index.ts`)] + "vercel-ai-sdk": () => ({ + agentic_chat: [path.join(__dirname, integrationsFolderPath, `/vercel-ai-sdk/src/index.ts`)], }), - 'langgraph': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [ - path.join(__dirname, integrationsFolderPath, `/langgraph/examples/python/agents/${agentId}/agent.py`), - path.join(__dirname, integrationsFolderPath, `/langgraph/examples/typescript/src/agents/${agentId}/agent.ts`) - ] - }), {}) + langgraph: (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/langgraph/examples/python/agents/${agentId}/agent.py`, + ), + path.join( + __dirname, + integrationsFolderPath, + `/langgraph/examples/typescript/src/agents/${agentId}/agent.ts`, + ), + ], + }), + {}, + ); }, - 'langgraph-typescript': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [ - path.join(__dirname, integrationsFolderPath, `/langgraph/examples/python/agents/${agentId}/agent.py`), - path.join(__dirname, integrationsFolderPath, `/langgraph/examples/typescript/src/agents/${agentId}/agent.ts`) - ] - }), {}) + "langgraph-typescript": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/langgraph/examples/python/agents/${agentId}/agent.py`, + ), + path.join( + __dirname, + integrationsFolderPath, + `/langgraph/examples/typescript/src/agents/${agentId}/agent.ts`, + ), + ], + }), + {}, + ); }, - 'langgraph-fastapi': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/langgraph/examples/python/agents/${agentId}/agent.py`)] - }), {}) + "langgraph-fastapi": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/langgraph/examples/python/agents/${agentId}/agent.py`, + ), + ], + }), + {}, + ); }, - 'agno': () => ({}), - 'llama-index': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/llamaindex/server-py/server/routers/${agentId}.py`)] - }), {}) + agno: () => ({}), + "llama-index": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/llamaindex/server-py/server/routers/${agentId}.py`, + ), + ], + }), + {}, + ); }, - 'crewai': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/crewai/python/ag_ui_crewai/examples/${agentId}.py`)] - }), {}) + crewai: (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/crewai/python/ag_ui_crewai/examples/${agentId}.py`, + ), + ], + }), + {}, + ); }, - 'adk-middleware': (agentKeys: string[]) => { - return agentKeys.reduce((acc, agentId) => ({ - ...acc, - [agentId]: [path.join(__dirname, integrationsFolderPath, `/adk-middleware/python/examples/server/api/${agentId}.py`)] - }), {}) - } -} + "adk-middleware": (agentKeys: string[]) => { + return agentKeys.reduce( + (acc, agentId) => ({ + ...acc, + [agentId]: [ + path.join( + __dirname, + integrationsFolderPath, + `/adk-middleware/python/examples/server/api/${agentId}.py`, + ), + ], + }), + {}, + ); + }, +}; async function runGenerateContent() { - const result = {} + const result = {}; for (const agentConfig of agentConfigs) { // Use the parsed agent keys instead of executing the agents function - const agentsPerFeatures = agentConfig.agentKeys + const agentsPerFeatures = agentConfig.agentKeys; - const agentFilePaths = agentFilesMapper[agentConfig.id](agentConfig.agentKeys) + const agentFilePaths = agentFilesMapper[agentConfig.id](agentConfig.agentKeys); // Per feature, assign all the frontend files like page.tsx as well as all agent files for (const featureId of agentsPerFeatures) { - const agentFilePathsForFeature = agentFilePaths[featureId] ?? [] + const agentFilePathsForFeature = agentFilePaths[featureId] ?? []; // @ts-expect-error -- redundant error about indexing of a new object. result[`${agentConfig.id}::${featureId}`] = [ // Get all frontend files for the feature ...(await getFeatureFrontendFiles(featureId)), // Get the agent (python/TS) file - ...(await Promise.all(agentFilePathsForFeature.map(async f => await getFile(f)))) - ] + ...(await Promise.all(agentFilePathsForFeature.map(async (f) => await getFile(f)))), + ]; } } - return result + return result; } (async () => { const result = await runGenerateContent(); - fs.writeFileSync( - path.join(__dirname, "../src/files.json"), - JSON.stringify(result, null, 2) - ); + fs.writeFileSync(path.join(__dirname, "../src/files.json"), JSON.stringify(result, null, 2)); console.log("Successfully generated src/files.json"); -})(); \ No newline at end of file +})(); diff --git a/typescript-sdk/apps/dojo/src/agents.ts b/typescript-sdk/apps/dojo/src/agents.ts index 3c2dc3192..02409e827 100644 --- a/typescript-sdk/apps/dojo/src/agents.ts +++ b/typescript-sdk/apps/dojo/src/agents.ts @@ -52,6 +52,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ tool_based_generative_ui: new PydanticAIAgent({ url: `${envVars.pydanticAIUrl}/tool_based_generative_ui/`, }), + backend_tool_rendering: new PydanticAIAgent({ + url: `${envVars.pydanticAIUrl}/backend_tool_rendering`, + }), }; }, }, @@ -68,8 +71,15 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ agents: async () => { return { agentic_chat: new ADKAgent({ url: `${envVars.adkMiddlewareUrl}/chat` }), - 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` }), + 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` }), }; @@ -82,6 +92,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ 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`, }), @@ -135,6 +148,10 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ 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", @@ -172,6 +189,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ 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`, }), @@ -204,6 +224,10 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ 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", @@ -241,6 +265,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ 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`, + }), }; }, }, @@ -260,6 +287,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ shared_state: new LlamaIndexAgent({ url: `${envVars.llamaIndexUrl}/shared_state/run`, }), + backend_tool_rendering: new LlamaIndexAgent({ + url: `${envVars.llamaIndexUrl}/backend_tool_rendering/run`, + }), }; }, }, @@ -292,7 +322,11 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [ id: "a2a", agents: async () => { // A2A agents: building management, finance, it agents - const agentUrls = [envVars.a2aMiddlewareBuildingsManagementUrl, envVars.a2aMiddlewareFinanceUrl, envVars.a2aMiddlewareItUrl]; + const agentUrls = [ + envVars.a2aMiddlewareBuildingsManagementUrl, + envVars.a2aMiddlewareFinanceUrl, + envVars.a2aMiddlewareItUrl, + ]; // AGUI orchestration/routing agent const orchestrationAgent = new HttpAgent({ url: envVars.a2aMiddlewareOrchestratorUrl, diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx index d82259e9d..1c7c29c07 100644 --- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx @@ -57,7 +57,7 @@ const Chat = () => { >
{
; +} + +const AgenticChat: React.FC = ({ params }) => { + const { integrationId } = React.use(params); + + return ( + + + + ); +}; + +const Chat = () => { + useCopilotAction({ + name: "get_weather", + available: "disabled", + parameters: [{ name: "location", type: "string", required: true }], + render: ({ args, result, status }) => { + if (status !== "complete") { + return ( +
+ ⚙️ Retrieving weather... +
+ ); + } + + const weatherResult: WeatherToolResult = { + temperature: result?.temperature || 0, + conditions: result?.conditions || "clear", + humidity: result?.humidity || 0, + windSpeed: result?.wind_speed || 0, + feelsLike: result?.feels_like || result?.temperature || 0, + }; + + const themeColor = getThemeColor(weatherResult.conditions); + + return ( + + ); + }, + }); + + return ( +
+
+ +
+
+ ); +}; + +interface WeatherToolResult { + temperature: number; + conditions: string; + humidity: number; + windSpeed: number; + feelsLike: number; +} + +function getThemeColor(conditions: string): string { + const conditionLower = conditions.toLowerCase(); + if (conditionLower.includes("clear") || conditionLower.includes("sunny")) { + return "#667eea"; + } + if (conditionLower.includes("rain") || conditionLower.includes("storm")) { + return "#4A5568"; + } + if (conditionLower.includes("cloud")) { + return "#718096"; + } + if (conditionLower.includes("snow")) { + return "#63B3ED"; + } + return "#764ba2"; +} + +function WeatherCard({ + location, + themeColor, + result, + status, +}: { + location?: string; + themeColor: string; + result: WeatherToolResult; + status: "inProgress" | "executing" | "complete"; +}) { + return ( +
+
+
+
+

+ {location} +

+

Current Weather

+
+ +
+ +
+
+ {result.temperature}° C + + {" / "} + {((result.temperature * 9) / 5 + 32).toFixed(1)}° F + +
+
{result.conditions}
+
+ +
+
+
+

Humidity

+

{result.humidity}%

+
+
+

Wind

+

{result.windSpeed} mph

+
+
+

Feels Like

+

{result.feelsLike}°

+
+
+
+
+
+ ); +} + +function WeatherIcon({ conditions }: { conditions: string }) { + if (!conditions) return null; + + if (conditions.toLowerCase().includes("clear") || conditions.toLowerCase().includes("sunny")) { + return ; + } + + if ( + conditions.toLowerCase().includes("rain") || + conditions.toLowerCase().includes("drizzle") || + conditions.toLowerCase().includes("snow") || + conditions.toLowerCase().includes("thunderstorm") + ) { + return ; + } + + if ( + conditions.toLowerCase().includes("fog") || + conditions.toLowerCase().includes("cloud") || + conditions.toLowerCase().includes("overcast") + ) { + return ; + } + + return ; +} + +// Simple sun icon for the weather card +function SunIcon() { + return ( + + + + + ); +} + +function RainIcon() { + return ( + + {/* Cloud */} + + {/* Rain drops */} + + + ); +} + +function CloudIcon() { + return ( + + + + ); +} + +export default AgenticChat; diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/backend_tool_rendering/style.css b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/backend_tool_rendering/style.css new file mode 100644 index 000000000..ce75e3a58 --- /dev/null +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/backend_tool_rendering/style.css @@ -0,0 +1,11 @@ +.copilotKitInput { + border-bottom-left-radius: 0.75rem; + border-bottom-right-radius: 0.75rem; + border-top-left-radius: 0.75rem; + border-top-right-radius: 0.75rem; + border: 1px solid var(--copilot-kit-separator-color) !important; +} + +.copilotKitChat { + background-color: #fff !important; +} diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx index 860cd6393..cb22d3fca 100644 --- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/human_in_the_loop/page.tsx @@ -367,7 +367,7 @@ const Chat = ({ integrationId }: { integrationId: string }) => { { title: "Simple plan", message: "Please plan a trip to mars in 5 steps." }, { title: "Complex plan", message: "Please plan a pastra dish in 10 steps." }, ]} - className="h-full rounded-2xl" + className="h-full rounded-2xl max-w-6xl mx-auto" labels={{ initial: "Hi, I'm an agent specialized in helping you with your tasks. How can I help you?", diff --git a/typescript-sdk/apps/dojo/src/config.ts b/typescript-sdk/apps/dojo/src/config.ts index ec79b91ca..e27f3a4b8 100644 --- a/typescript-sdk/apps/dojo/src/config.ts +++ b/typescript-sdk/apps/dojo/src/config.ts @@ -23,6 +23,12 @@ export const featureConfig: FeatureConfig[] = [ description: "Chat with your Copilot and call frontend tools", tags: ["Chat", "Tools", "Streaming"], }), + createFeatureConfig({ + id: "backend_tool_rendering", + name: "Backend Tool Rendering", + description: "Render and stream your backend tools to the frontend.", + tags: ["Agent State", "Collaborating"], + }), createFeatureConfig({ id: "human_in_the_loop", name: "Human in the loop", diff --git a/typescript-sdk/apps/dojo/src/files.json b/typescript-sdk/apps/dojo/src/files.json index eb9e93247..59ab64da0 100644 --- a/typescript-sdk/apps/dojo/src/files.json +++ b/typescript-sdk/apps/dojo/src/files.json @@ -2,7 +2,7 @@ "middleware-starter::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n
\n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n
\n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -28,7 +28,7 @@ "pydantic-ai::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n
\n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -54,7 +54,7 @@ "pydantic-ai::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -80,7 +80,7 @@ "pydantic-ai::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -155,10 +155,36 @@ "type": "file" } ], + "pydantic-ai::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "backend_tool_rendering.py", + "content": "\"\"\"Backend Tool Rendering feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nfrom textwrap import dedent\nfrom zoneinfo import ZoneInfo\n\nimport httpx\nfrom pydantic_ai import Agent\n\nagent = Agent(\n \"openai:gpt-4o-mini\",\n instructions=dedent(\n \"\"\"\n You are a helpful weather assistant that provides accurate weather information.\n\n Your primary function is to help users get weather details for specific locations. When responding:\n - Always ask for a location if none is provided\n - If the location name isn’t in English, please translate it\n - If giving a location with multiple parts (e.g. \"New York, NY\"), use the most relevant part (e.g. \"New York\")\n - Include relevant details like humidity, wind conditions, and precipitation\n - Keep responses concise but informative\n\n Use the get_weather tool to fetch current weather data.\n \"\"\"\n ),\n)\napp = agent.to_ag_ui()\n\n\ndef get_weather_condition(code: int) -> str:\n \"\"\"Map weather code to human-readable condition.\n\n Args:\n code: WMO weather code.\n\n Returns:\n Human-readable weather condition string.\n \"\"\"\n conditions = {\n 0: \"Clear sky\",\n 1: \"Mainly clear\",\n 2: \"Partly cloudy\",\n 3: \"Overcast\",\n 45: \"Foggy\",\n 48: \"Depositing rime fog\",\n 51: \"Light drizzle\",\n 53: \"Moderate drizzle\",\n 55: \"Dense drizzle\",\n 56: \"Light freezing drizzle\",\n 57: \"Dense freezing drizzle\",\n 61: \"Slight rain\",\n 63: \"Moderate rain\",\n 65: \"Heavy rain\",\n 66: \"Light freezing rain\",\n 67: \"Heavy freezing rain\",\n 71: \"Slight snow fall\",\n 73: \"Moderate snow fall\",\n 75: \"Heavy snow fall\",\n 77: \"Snow grains\",\n 80: \"Slight rain showers\",\n 81: \"Moderate rain showers\",\n 82: \"Violent rain showers\",\n 85: \"Slight snow showers\",\n 86: \"Heavy snow showers\",\n 95: \"Thunderstorm\",\n 96: \"Thunderstorm with slight hail\",\n 99: \"Thunderstorm with heavy hail\",\n }\n return conditions.get(code, \"Unknown\")\n\n\n@agent.tool_plain\nasync def get_weather(location: str) -> dict[str, str | float]:\n \"\"\"Get current weather for a location.\n\n Args:\n location: City name.\n\n Returns:\n Dictionary with weather information including temperature, feels like,\n humidity, wind speed, wind gust, conditions, and location name.\n \"\"\"\n async with httpx.AsyncClient() as client:\n # Geocode the location\n geocoding_url = (\n f\"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1\"\n )\n geocoding_response = await client.get(geocoding_url)\n geocoding_data = geocoding_response.json()\n\n if not geocoding_data.get(\"results\"):\n raise ValueError(f\"Location '{location}' not found\")\n\n result = geocoding_data[\"results\"][0]\n latitude = result[\"latitude\"]\n longitude = result[\"longitude\"]\n name = result[\"name\"]\n\n # Get weather data\n weather_url = (\n f\"https://api.open-meteo.com/v1/forecast?\"\n f\"latitude={latitude}&longitude={longitude}\"\n f\"¤t=temperature_2m,apparent_temperature,relative_humidity_2m,\"\n f\"wind_speed_10m,wind_gusts_10m,weather_code\"\n )\n weather_response = await client.get(weather_url)\n weather_data = weather_response.json()\n\n current = weather_data[\"current\"]\n\n return {\n \"temperature\": current[\"temperature_2m\"],\n \"feelsLike\": current[\"apparent_temperature\"],\n \"humidity\": current[\"relative_humidity_2m\"],\n \"windSpeed\": current[\"wind_speed_10m\"],\n \"windGust\": current[\"wind_gusts_10m\"],\n \"conditions\": get_weather_condition(current[\"weather_code\"]),\n \"location\": name,\n }\n", + "language": "python", + "type": "file" + } + ], "server-starter::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -184,7 +210,7 @@ "adk-middleware::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -236,7 +262,7 @@ "adk-middleware::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -259,6 +285,32 @@ "type": "file" } ], + "adk-middleware::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "backend_tool_rendering.py", + "content": "\"\"\"Basic Chat feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom fastapi import FastAPI\nfrom ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint\nfrom google.adk.agents import LlmAgent\nfrom google.adk import tools as adk_tools\nimport httpx\nimport json\n\n\ndef get_weather_condition(code: int) -> str:\n \"\"\"Map weather code to human-readable condition.\n\n Args:\n code: WMO weather code.\n\n Returns:\n Human-readable weather condition string.\n \"\"\"\n conditions = {\n 0: \"Clear sky\",\n 1: \"Mainly clear\",\n 2: \"Partly cloudy\",\n 3: \"Overcast\",\n 45: \"Foggy\",\n 48: \"Depositing rime fog\",\n 51: \"Light drizzle\",\n 53: \"Moderate drizzle\",\n 55: \"Dense drizzle\",\n 56: \"Light freezing drizzle\",\n 57: \"Dense freezing drizzle\",\n 61: \"Slight rain\",\n 63: \"Moderate rain\",\n 65: \"Heavy rain\",\n 66: \"Light freezing rain\",\n 67: \"Heavy freezing rain\",\n 71: \"Slight snow fall\",\n 73: \"Moderate snow fall\",\n 75: \"Heavy snow fall\",\n 77: \"Snow grains\",\n 80: \"Slight rain showers\",\n 81: \"Moderate rain showers\",\n 82: \"Violent rain showers\",\n 85: \"Slight snow showers\",\n 86: \"Heavy snow showers\",\n 95: \"Thunderstorm\",\n 96: \"Thunderstorm with slight hail\",\n 99: \"Thunderstorm with heavy hail\",\n }\n return conditions.get(code, \"Unknown\")\n\n\nasync def get_weather(location: str) -> dict[str, str | float]:\n \"\"\"Get current weather for a location.\n\n Args:\n location: City name.\n\n Returns:\n Dictionary with weather information including temperature, feels like,\n humidity, wind speed, wind gust, conditions, and location name.\n \"\"\"\n async with httpx.AsyncClient() as client:\n # Geocode the location\n geocoding_url = (\n f\"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1\"\n )\n geocoding_response = await client.get(geocoding_url)\n geocoding_data = geocoding_response.json()\n\n if not geocoding_data.get(\"results\"):\n raise ValueError(f\"Location '{location}' not found\")\n\n result = geocoding_data[\"results\"][0]\n latitude = result[\"latitude\"]\n longitude = result[\"longitude\"]\n name = result[\"name\"]\n\n # Get weather data\n weather_url = (\n f\"https://api.open-meteo.com/v1/forecast?\"\n f\"latitude={latitude}&longitude={longitude}\"\n f\"¤t=temperature_2m,apparent_temperature,relative_humidity_2m,\"\n f\"wind_speed_10m,wind_gusts_10m,weather_code\"\n )\n weather_response = await client.get(weather_url)\n weather_data = weather_response.json()\n\n current = weather_data[\"current\"]\n\n return {\n \"temperature\": current[\"temperature_2m\"],\n \"feelsLike\": current[\"apparent_temperature\"],\n \"humidity\": current[\"relative_humidity_2m\"],\n \"windSpeed\": current[\"wind_speed_10m\"],\n \"windGust\": current[\"wind_gusts_10m\"],\n \"conditions\": get_weather_condition(current[\"weather_code\"]),\n \"location\": name,\n }\n\n\n# Create a sample ADK agent (this would be your actual agent)\nsample_agent = LlmAgent(\n name=\"assistant\",\n model=\"gemini-2.0-flash\",\n instruction=\"\"\"\n You are a helpful weather assistant that provides accurate weather information.\n\n Your primary function is to help users get weather details for specific locations. When responding:\n - Always ask for a location if none is provided\n - If the location name isn’t in English, please translate it\n - If giving a location with multiple parts (e.g. \"New York, NY\"), use the most relevant part (e.g. \"New York\")\n - Include relevant details like humidity, wind conditions, and precipitation\n - Keep responses concise but informative\n\n Use the get_weather tool to fetch current weather data.\n \"\"\",\n tools=[adk_tools.preload_memory_tool.PreloadMemoryTool(), get_weather],\n)\n\n# Create ADK middleware agent instance\nchat_agent = ADKAgent(\n adk_agent=sample_agent,\n app_name=\"demo_app\",\n user_id=\"demo_user\",\n session_timeout_seconds=3600,\n use_in_memory_services=True,\n)\n\n# Create FastAPI app\napp = FastAPI(title=\"ADK Middleware Weather Agent\")\n\n# Add the ADK endpoint\nadd_adk_fastapi_endpoint(app, chat_agent, path=\"/\")\n", + "language": "python", + "type": "file" + } + ], "adk-middleware::shared_state": [ { "name": "page.tsx", @@ -288,7 +340,7 @@ "server-starter-all-features::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -311,10 +363,36 @@ "type": "file" } ], + "server-starter-all-features::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "backend_tool_rendering.py", + "content": "\"\"\"\nAgentic chat endpoint for the AG-UI protocol.\n\"\"\"\n\nimport uuid\nimport json\nfrom fastapi import Request\nfrom fastapi.responses import StreamingResponse\nfrom ag_ui.core import (\n RunAgentInput,\n EventType,\n RunStartedEvent,\n RunFinishedEvent,\n TextMessageStartEvent,\n TextMessageContentEvent,\n TextMessageEndEvent,\n MessagesSnapshotEvent,\n ToolMessage,\n ToolCall,\n AssistantMessage,\n)\nfrom ag_ui.encoder import EventEncoder\n\n\nasync def backend_tool_rendering_endpoint(input_data: RunAgentInput, request: Request):\n \"\"\"Agentic chat endpoint\"\"\"\n # Get the accept header from the request\n accept_header = request.headers.get(\"accept\")\n\n # Create an event encoder to properly format SSE events\n encoder = EventEncoder(accept=accept_header)\n\n async def event_generator():\n # Get the last message content for conditional logic\n last_message_role = None\n if input_data.messages and len(input_data.messages) > 0:\n last_message = input_data.messages[-1]\n last_message_role = getattr(last_message, \"role\", None)\n\n # Send run started event\n yield encoder.encode(\n RunStartedEvent(\n type=EventType.RUN_STARTED,\n thread_id=input_data.thread_id,\n run_id=input_data.run_id,\n ),\n )\n\n # Conditional logic based on last message\n if last_message_role == \"tool\":\n async for event in send_tool_result_message_events():\n yield encoder.encode(event)\n else:\n async for event in send_backend_tool_call_events(input_data.messages):\n yield encoder.encode(event)\n\n # Send run finished event\n yield encoder.encode(\n RunFinishedEvent(\n type=EventType.RUN_FINISHED,\n thread_id=input_data.thread_id,\n run_id=input_data.run_id,\n ),\n )\n\n return StreamingResponse(event_generator(), media_type=encoder.get_content_type())\n\n\nasync def send_tool_result_message_events():\n \"\"\"Send message for tool result\"\"\"\n message_id = str(uuid.uuid4())\n\n # Start of message\n yield TextMessageStartEvent(\n type=EventType.TEXT_MESSAGE_START, message_id=message_id, role=\"assistant\"\n )\n\n # Content\n yield TextMessageContentEvent(\n type=EventType.TEXT_MESSAGE_CONTENT,\n message_id=message_id,\n delta=\"Retrieved weather information!\",\n )\n\n # End of message\n yield TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)\n\n\nasync def send_backend_tool_call_events(messages: list):\n \"\"\"Send backend tool call events\"\"\"\n tool_call_id = str(uuid.uuid4())\n\n new_message = AssistantMessage(\n id=str(uuid.uuid4()),\n role=\"assistant\",\n tool_calls=[\n ToolCall(\n id=tool_call_id,\n type=\"function\",\n function={\n \"name\": \"get_weather\",\n \"arguments\": json.dumps({\"city\": \"San Francisco\"}),\n },\n )\n ],\n )\n\n result_message = ToolMessage(\n id=str(uuid.uuid4()),\n role=\"tool\",\n content=json.dumps(\n {\n \"city\": \"San Francisco\",\n \"conditions\": \"sunny\",\n \"wind_speed\": \"10\",\n \"temperature\": \"20\",\n \"humidity\": \"60\",\n }\n ),\n tool_call_id=tool_call_id,\n )\n\n all_messages = list(messages) + [new_message, result_message]\n\n # Send messages snapshot event\n yield MessagesSnapshotEvent(type=EventType.MESSAGES_SNAPSHOT, messages=all_messages)\n", + "language": "python", + "type": "file" + } + ], "server-starter-all-features::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -340,7 +418,7 @@ "server-starter-all-features::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -444,7 +522,7 @@ "langgraph::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -473,10 +551,42 @@ "type": "file" } ], + "langgraph::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "agent.py", + "content": "\"\"\"\nA simple agentic chat flow using LangGraph instead of CrewAI.\n\"\"\"\n\nfrom typing import List, Any, Optional\nimport os\n\n# Updated imports for LangGraph\nfrom langchain_core.runnables import RunnableConfig\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.graph import MessagesState\nfrom langgraph.types import Command\nfrom requests.api import get\nfrom langgraph.prebuilt import create_react_agent\n\n\n@tool\ndef get_weather(location: str):\n \"\"\"\n Get the weather for a given location.\n \"\"\"\n return {\n \"temperature\": 20,\n \"conditions\": \"sunny\",\n \"humidity\": 50,\n \"wind_speed\": 10,\n \"feelsLike\": 25,\n }\n\n\n# Conditionally use a checkpointer based on the environment\n# Check for multiple indicators that we're running in LangGraph dev/API mode\nis_fast_api = os.environ.get(\"LANGGRAPH_FAST_API\", \"false\").lower() == \"true\"\n\n# Compile the graph\nif is_fast_api:\n # For CopilotKit and other contexts, use MemorySaver\n from langgraph.checkpoint.memory import MemorySaver\n\n graph = create_react_agent(\n model=\"openai:gpt-4.1-mini\",\n tools=[get_weather],\n prompt=\"You are a helpful assistant\",\n checkpointer=MemorySaver(),\n )\nelse:\n # When running in LangGraph API/dev, don't use a custom checkpointer\n graph = create_react_agent(\n model=\"openai:gpt-4.1-mini\",\n tools=[get_weather],\n prompt=\"You are a helpful assistant\",\n )\n", + "language": "python", + "type": "file" + }, + { + "name": "agent.ts", + "content": "/**\n * A simple agentic chat flow using LangGraph instead of CrewAI.\n */\n\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { SystemMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig } from \"@langchain/core/runnables\";\nimport { Annotation, MessagesAnnotation, StateGraph, Command, START, END } from \"@langchain/langgraph\";\n\nconst AgentStateAnnotation = Annotation.Root({\n tools: Annotation({\n reducer: (x, y) => y ?? x,\n default: () => []\n }),\n ...MessagesAnnotation.spec,\n});\n\ntype AgentState = typeof AgentStateAnnotation.State;\n\nasync function chatNode(state: AgentState, config?: RunnableConfig) {\n /**\n * Standard chat node based on the ReAct design pattern. It handles:\n * - The model to use (and binds in CopilotKit actions and the tools defined above)\n * - The system prompt\n * - Getting a response from the model\n * - Handling tool calls\n *\n * For more about the ReAct design pattern, see: \n * https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n */\n \n // 1. Define the model\n const model = new ChatOpenAI({ model: \"gpt-4o\" });\n \n // Define config for the model\n if (!config) {\n config = { recursionLimit: 25 };\n }\n\n // 2. Bind the tools to the model\n const modelWithTools = model.bindTools(\n [\n ...state.tools,\n // your_tool_here\n ],\n {\n // 2.1 Disable parallel tool calls to avoid race conditions,\n // enable this for faster performance if you want to manage\n // the complexity of running tool calls in parallel.\n parallel_tool_calls: false,\n }\n );\n\n // 3. Define the system message by which the chat model will be run\n const systemMessage = new SystemMessage({\n content: \"You are a helpful assistant.\"\n });\n\n // 4. Run the model to generate a response\n const response = await modelWithTools.invoke([\n systemMessage,\n ...state.messages,\n ], config);\n\n // 6. We've handled all tool calls, so we can end the graph.\n return new Command({\n goto: END,\n update: {\n messages: [response]\n }\n })\n}\n\n// Define a new graph \nconst workflow = new StateGraph(AgentStateAnnotation)\n .addNode(\"chat_node\", chatNode)\n .addEdge(START, \"chat_node\");\n\n// Compile the graph\nexport const agenticChatGraph = workflow.compile();", + "language": "ts", + "type": "file" + } + ], "langgraph::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -508,7 +618,7 @@ "langgraph::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -695,7 +805,7 @@ "langgraph-fastapi::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -718,10 +828,36 @@ "type": "file" } ], + "langgraph-fastapi::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "agent.py", + "content": "\"\"\"\nA simple agentic chat flow using LangGraph instead of CrewAI.\n\"\"\"\n\nfrom typing import List, Any, Optional\nimport os\n\n# Updated imports for LangGraph\nfrom langchain_core.runnables import RunnableConfig\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI\nfrom langgraph.graph import StateGraph, END, START\nfrom langgraph.graph import MessagesState\nfrom langgraph.types import Command\nfrom requests.api import get\nfrom langgraph.prebuilt import create_react_agent\n\n\n@tool\ndef get_weather(location: str):\n \"\"\"\n Get the weather for a given location.\n \"\"\"\n return {\n \"temperature\": 20,\n \"conditions\": \"sunny\",\n \"humidity\": 50,\n \"wind_speed\": 10,\n \"feelsLike\": 25,\n }\n\n\n# Conditionally use a checkpointer based on the environment\n# Check for multiple indicators that we're running in LangGraph dev/API mode\nis_fast_api = os.environ.get(\"LANGGRAPH_FAST_API\", \"false\").lower() == \"true\"\n\n# Compile the graph\nif is_fast_api:\n # For CopilotKit and other contexts, use MemorySaver\n from langgraph.checkpoint.memory import MemorySaver\n\n graph = create_react_agent(\n model=\"openai:gpt-4.1-mini\",\n tools=[get_weather],\n prompt=\"You are a helpful assistant\",\n checkpointer=MemorySaver(),\n )\nelse:\n # When running in LangGraph API/dev, don't use a custom checkpointer\n graph = create_react_agent(\n model=\"openai:gpt-4.1-mini\",\n tools=[get_weather],\n prompt=\"You are a helpful assistant\",\n )\n", + "language": "python", + "type": "file" + } + ], "langgraph-fastapi::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -747,7 +883,7 @@ "langgraph-fastapi::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -903,7 +1039,7 @@ "langgraph-typescript::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -935,7 +1071,7 @@ "langgraph-typescript::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -967,7 +1103,7 @@ "langgraph-typescript::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -1127,7 +1263,7 @@ "agno::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -1164,10 +1300,30 @@ "type": "file" } ], + "agno::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + } + ], "llama-index::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -1193,7 +1349,7 @@ "llama-index::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -1219,7 +1375,7 @@ "llama-index::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, @@ -1268,10 +1424,36 @@ "type": "file" } ], + "llama-index::backend_tool_rendering": [ + { + "name": "page.tsx", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n useCopilotAction({\n name: \"get_weather\",\n available: \"disabled\",\n parameters: [{ name: \"location\", type: \"string\", required: true }],\n render: ({ args, result, status }) => {\n if (status !== \"complete\") {\n return (\n
\n ⚙️ Retrieving weather...\n
\n );\n }\n\n const weatherResult: WeatherToolResult = {\n temperature: result?.temperature || 0,\n conditions: result?.conditions || \"clear\",\n humidity: result?.humidity || 0,\n windSpeed: result?.wind_speed || 0,\n feelsLike: result?.feels_like || result?.temperature || 0,\n };\n\n const themeColor = getThemeColor(weatherResult.conditions);\n\n return (\n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\ninterface WeatherToolResult {\n temperature: number;\n conditions: string;\n humidity: number;\n windSpeed: number;\n feelsLike: number;\n}\n\nfunction getThemeColor(conditions: string): string {\n const conditionLower = conditions.toLowerCase();\n if (conditionLower.includes(\"clear\") || conditionLower.includes(\"sunny\")) {\n return \"#667eea\";\n }\n if (conditionLower.includes(\"rain\") || conditionLower.includes(\"storm\")) {\n return \"#4A5568\";\n }\n if (conditionLower.includes(\"cloud\")) {\n return \"#718096\";\n }\n if (conditionLower.includes(\"snow\")) {\n return \"#63B3ED\";\n }\n return \"#764ba2\";\n}\n\nfunction WeatherCard({\n location,\n themeColor,\n result,\n status,\n}: {\n location?: string;\n themeColor: string;\n result: WeatherToolResult;\n status: \"inProgress\" | \"executing\" | \"complete\";\n}) {\n return (\n \n
\n
\n
\n

\n {location}\n

\n

Current Weather

\n
\n \n
\n\n
\n
\n {result.temperature}° C\n \n {\" / \"}\n {((result.temperature * 9) / 5 + 32).toFixed(1)}° F\n \n
\n
{result.conditions}
\n
\n\n
\n
\n
\n

Humidity

\n

{result.humidity}%

\n
\n
\n

Wind

\n

{result.windSpeed} mph

\n
\n
\n

Feels Like

\n

{result.feelsLike}°

\n
\n
\n
\n
\n \n );\n}\n\nfunction WeatherIcon({ conditions }: { conditions: string }) {\n if (!conditions) return null;\n\n if (conditions.toLowerCase().includes(\"clear\") || conditions.toLowerCase().includes(\"sunny\")) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"rain\") ||\n conditions.toLowerCase().includes(\"drizzle\") ||\n conditions.toLowerCase().includes(\"snow\") ||\n conditions.toLowerCase().includes(\"thunderstorm\")\n ) {\n return ;\n }\n\n if (\n conditions.toLowerCase().includes(\"fog\") ||\n conditions.toLowerCase().includes(\"cloud\") ||\n conditions.toLowerCase().includes(\"overcast\")\n ) {\n return ;\n }\n\n return ;\n}\n\n// Simple sun icon for the weather card\nfunction SunIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction RainIcon() {\n return (\n \n {/* Cloud */}\n \n {/* Rain drops */}\n \n \n );\n}\n\nfunction CloudIcon() {\n return (\n \n \n \n );\n}\n\nexport default AgenticChat;\n", + "language": "typescript", + "type": "file" + }, + { + "name": "style.css", + "content": ".copilotKitInput {\n border-bottom-left-radius: 0.75rem;\n border-bottom-right-radius: 0.75rem;\n border-top-left-radius: 0.75rem;\n border-top-right-radius: 0.75rem;\n border: 1px solid var(--copilot-kit-separator-color) !important;\n}\n\n.copilotKitChat {\n background-color: #fff !important;\n}\n", + "language": "css", + "type": "file" + }, + { + "name": "README.mdx", + "content": "# 🤖 Agentic Chat with Frontend Tools\n\n## What This Demo Shows\n\nThis demo showcases CopilotKit's **agentic chat** capabilities with **frontend\ntool integration**:\n\n1. **Natural Conversation**: Chat with your Copilot in a familiar chat interface\n2. **Frontend Tool Execution**: The Copilot can directly interacts with your UI\n by calling frontend functions\n3. **Seamless Integration**: Tools defined in the frontend and automatically\n discovered and made available to the agent\n\n## How to Interact\n\nTry asking your Copilot to:\n\n- \"Can you change the background color to something more vibrant?\"\n- \"Make the background a blue to purple gradient\"\n- \"Set the background to a sunset-themed gradient\"\n- \"Change it back to a simple light color\"\n\nYou can also chat about other topics - the agent will respond conversationally\nwhile having the ability to use your UI tools when appropriate.\n\n## ✨ Frontend Tool Integration in Action\n\n**What's happening technically:**\n\n- The React component defines a frontend function using `useCopilotAction`\n- CopilotKit automatically exposes this function to the agent\n- When you make a request, the agent determines whether to use the tool\n- The agent calls the function with the appropriate parameters\n- The UI immediately updates in response\n\n**What you'll see in this demo:**\n\n- The Copilot understands requests to change the background\n- It generates CSS values for colors and gradients\n- When it calls the tool, the background changes instantly\n- The agent provides a conversational response about the changes it made\n\nThis technique of exposing frontend functions to your Copilot can be extended to\nany UI manipulation you want to enable, from theme changes to data filtering,\nnavigation, or complex UI state management!\n", + "language": "markdown", + "type": "file" + }, + { + "name": "backend_tool_rendering.py", + "content": "\"\"\"Backend Tool Rendering feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom datetime import datetime\nimport json\nfrom textwrap import dedent\nfrom zoneinfo import ZoneInfo\n\nimport httpx\nfrom llama_index.llms.openai import OpenAI\nfrom llama_index.protocols.ag_ui.router import get_ag_ui_workflow_router\n\n\ndef get_weather_condition(code: int) -> str:\n \"\"\"Map weather code to human-readable condition.\n\n Args:\n code: WMO weather code.\n\n Returns:\n Human-readable weather condition string.\n \"\"\"\n conditions = {\n 0: \"Clear sky\",\n 1: \"Mainly clear\",\n 2: \"Partly cloudy\",\n 3: \"Overcast\",\n 45: \"Foggy\",\n 48: \"Depositing rime fog\",\n 51: \"Light drizzle\",\n 53: \"Moderate drizzle\",\n 55: \"Dense drizzle\",\n 56: \"Light freezing drizzle\",\n 57: \"Dense freezing drizzle\",\n 61: \"Slight rain\",\n 63: \"Moderate rain\",\n 65: \"Heavy rain\",\n 66: \"Light freezing rain\",\n 67: \"Heavy freezing rain\",\n 71: \"Slight snow fall\",\n 73: \"Moderate snow fall\",\n 75: \"Heavy snow fall\",\n 77: \"Snow grains\",\n 80: \"Slight rain showers\",\n 81: \"Moderate rain showers\",\n 82: \"Violent rain showers\",\n 85: \"Slight snow showers\",\n 86: \"Heavy snow showers\",\n 95: \"Thunderstorm\",\n 96: \"Thunderstorm with slight hail\",\n 99: \"Thunderstorm with heavy hail\",\n }\n return conditions.get(code, \"Unknown\")\n\n\nasync def get_weather(location: str) -> str:\n \"\"\"Get current weather for a location.\n\n Args:\n location: City name.\n\n Returns:\n Dictionary with weather information including temperature, feels like,\n humidity, wind speed, wind gust, conditions, and location name.\n \"\"\"\n async with httpx.AsyncClient() as client:\n # Geocode the location\n geocoding_url = (\n f\"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1\"\n )\n geocoding_response = await client.get(geocoding_url)\n geocoding_data = geocoding_response.json()\n\n if not geocoding_data.get(\"results\"):\n raise ValueError(f\"Location '{location}' not found\")\n\n result = geocoding_data[\"results\"][0]\n latitude = result[\"latitude\"]\n longitude = result[\"longitude\"]\n name = result[\"name\"]\n\n # Get weather data\n weather_url = (\n f\"https://api.open-meteo.com/v1/forecast?\"\n f\"latitude={latitude}&longitude={longitude}\"\n f\"¤t=temperature_2m,apparent_temperature,relative_humidity_2m,\"\n f\"wind_speed_10m,wind_gusts_10m,weather_code\"\n )\n weather_response = await client.get(weather_url)\n weather_data = weather_response.json()\n\n current = weather_data[\"current\"]\n\n return json.dumps({\n \"temperature\": current[\"temperature_2m\"],\n \"feelsLike\": current[\"apparent_temperature\"],\n \"humidity\": current[\"relative_humidity_2m\"],\n \"windSpeed\": current[\"wind_speed_10m\"],\n \"windGust\": current[\"wind_gusts_10m\"],\n \"conditions\": get_weather_condition(current[\"weather_code\"]),\n \"location\": name,\n })\n\n\n# Create the router with weather tools\nbackend_tool_rendering_router = get_ag_ui_workflow_router(\n llm=OpenAI(model=\"gpt-4o-mini\"),\n backend_tools=[get_weather],\n system_prompt=dedent(\n \"\"\"\n You are a helpful weather assistant that provides accurate weather information.\n\n Your primary function is to help users get weather details for specific locations. When responding:\n - Always ask for a location if none is provided\n - If the location name isn’t in English, please translate it\n - If giving a location with multiple parts (e.g. \"New York, NY\"), use the most relevant part (e.g. \"New York\")\n - Include relevant details like humidity, wind conditions, and precipitation\n - Keep responses concise but informative\n\n Use the get_weather tool to fetch current weather data.\n \"\"\"\n ),\n)\n", + "language": "python", + "type": "file" + } + ], "crewai::agentic_chat": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", + "content": "\"use client\";\nimport React, { useState } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgent, useCopilotAction, useCopilotChat } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\n\ninterface AgenticChatProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticChat: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\nconst Chat = () => {\n const [background, setBackground] = useState(\"--copilot-kit-background-color\");\n\n useCopilotAction({\n name: \"change_background\",\n description:\n \"Change the background color of the chat. Can be anything that the CSS background attribute accepts. Regular colors, linear of radial gradients etc.\",\n parameters: [\n {\n name: \"background\",\n type: \"string\",\n description: \"The background. Prefer gradients. Only use when asked.\",\n },\n ],\n handler: ({ background }) => {\n setBackground(background);\n return {\n status: \"success\",\n message: `Background changed to ${background}`,\n };\n },\n });\n\n return (\n \n
\n \n
\n \n );\n};\n\nexport default AgenticChat;\n", "language": "typescript", "type": "file" }, @@ -1297,7 +1479,7 @@ "crewai::human_in_the_loop": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", + "content": "\"use client\";\nimport React, { useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCopilotAction, useLangGraphInterrupt } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface HumanInTheLoopProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst HumanInTheLoop: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n\n return (\n \n \n \n );\n};\n\ninterface Step {\n description: string;\n status: \"disabled\" | \"enabled\" | \"executing\";\n}\n\n// Shared UI Components\nconst StepContainer = ({ theme, children }: { theme?: string; children: React.ReactNode }) => (\n
\n \n {children}\n
\n \n);\n\nconst StepHeader = ({\n theme,\n enabledCount,\n totalCount,\n status,\n showStatus = false,\n}: {\n theme?: string;\n enabledCount: number;\n totalCount: number;\n status?: string;\n showStatus?: boolean;\n}) => (\n
\n
\n

\n Select Steps\n

\n
\n
\n {enabledCount}/{totalCount} Selected\n
\n {showStatus && (\n \n {status === \"executing\" ? \"Ready\" : \"Waiting\"}\n
\n )}\n
\n
\n\n \n 0 ? (enabledCount / totalCount) * 100 : 0}%` }}\n />\n \n \n);\n\nconst StepItem = ({\n step,\n theme,\n status,\n onToggle,\n disabled = false,\n}: {\n step: { description: string; status: string };\n theme?: string;\n status?: string;\n onToggle: () => void;\n disabled?: boolean;\n}) => (\n \n \n \n);\n\nconst ActionButton = ({\n variant,\n theme,\n disabled,\n onClick,\n children,\n}: {\n variant: \"primary\" | \"secondary\" | \"success\" | \"danger\";\n theme?: string;\n disabled?: boolean;\n onClick: () => void;\n children: React.ReactNode;\n}) => {\n const baseClasses = \"px-6 py-3 rounded-lg font-semibold transition-all duration-200\";\n const enabledClasses = \"hover:scale-105 shadow-md hover:shadow-lg\";\n const disabledClasses = \"opacity-50 cursor-not-allowed\";\n\n const variantClasses = {\n primary:\n \"bg-gradient-to-r from-purple-500 to-purple-700 hover:from-purple-600 hover:to-purple-800 text-white shadow-lg hover:shadow-xl\",\n secondary:\n theme === \"dark\"\n ? \"bg-slate-700 hover:bg-slate-600 text-white border border-slate-600 hover:border-slate-500\"\n : \"bg-gray-100 hover:bg-gray-200 text-gray-800 border border-gray-300 hover:border-gray-400\",\n success:\n \"bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white shadow-lg hover:shadow-xl\",\n danger:\n \"bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white shadow-lg hover:shadow-xl\",\n };\n\n return (\n \n {children}\n \n );\n};\n\nconst DecorativeElements = ({\n theme,\n variant = \"default\",\n}: {\n theme?: string;\n variant?: \"default\" | \"success\" | \"danger\";\n}) => (\n <>\n \n \n \n);\nconst InterruptHumanInTheLoop: React.FC<{\n event: { value: { steps: Step[] } };\n resolve: (value: string) => void;\n}> = ({ event, resolve }) => {\n const { theme } = useTheme();\n\n // Parse and initialize steps data\n let initialSteps: Step[] = [];\n if (event.value && event.value.steps && Array.isArray(event.value.steps)) {\n initialSteps = event.value.steps.map((step: any) => ({\n description: typeof step === \"string\" ? step : step.description || \"\",\n status: typeof step === \"object\" && step.status ? step.status : \"enabled\",\n }));\n }\n\n const [localSteps, setLocalSteps] = useState(initialSteps);\n const enabledCount = localSteps.filter((step) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handlePerformSteps = () => {\n const selectedSteps = localSteps\n .filter((step) => step.status === \"enabled\")\n .map((step) => step.description);\n resolve(\"The user selected the following steps: \" + selectedSteps.join(\", \"));\n };\n\n return (\n \n \n\n
\n {localSteps.map((step, index) => (\n handleStepToggle(index)}\n />\n ))}\n
\n\n
\n \n ✨\n Perform Steps\n \n {enabledCount}\n \n \n
\n\n \n
\n );\n};\n\nconst Chat = ({ integrationId }: { integrationId: string }) => {\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // This hook won't do anything for other integrations.\n useLangGraphInterrupt({\n render: ({ event, resolve }) => ,\n });\n useCopilotAction({\n name: \"generate_task_steps\",\n description: \"Generates a list of steps for the user to perform\",\n parameters: [\n {\n name: \"steps\",\n type: \"object[]\",\n attributes: [\n {\n name: \"description\",\n type: \"string\",\n },\n {\n name: \"status\",\n type: \"string\",\n enum: [\"enabled\", \"disabled\", \"executing\"],\n },\n ],\n },\n ],\n // Langgraph uses it's own hook to handle human-in-the-loop interactions via langgraph interrupts,\n // so don't use this action for langgraph integration.\n available: [\"langgraph\", \"langgraph-fastapi\", \"langgraph-typescript\"].includes(integrationId)\n ? \"disabled\"\n : \"enabled\",\n renderAndWaitForResponse: ({ args, respond, status }) => {\n return ;\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\nconst StepsFeedback = ({ args, respond, status }: { args: any; respond: any; status: any }) => {\n const { theme } = useTheme();\n const [localSteps, setLocalSteps] = useState([]);\n const [accepted, setAccepted] = useState(null);\n\n useEffect(() => {\n if (status === \"executing\" && localSteps.length === 0) {\n setLocalSteps(args.steps);\n }\n }, [status, args.steps, localSteps]);\n\n if (args.steps === undefined || args.steps.length === 0) {\n return <>;\n }\n\n const steps = localSteps.length > 0 ? localSteps : args.steps;\n const enabledCount = steps.filter((step: any) => step.status === \"enabled\").length;\n\n const handleStepToggle = (index: number) => {\n setLocalSteps((prevSteps) =>\n prevSteps.map((step, i) =>\n i === index\n ? { ...step, status: step.status === \"enabled\" ? \"disabled\" : \"enabled\" }\n : step,\n ),\n );\n };\n\n const handleReject = () => {\n if (respond) {\n setAccepted(false);\n respond({ accepted: false });\n }\n };\n\n const handleConfirm = () => {\n if (respond) {\n setAccepted(true);\n respond({ accepted: true, steps: localSteps.filter((step) => step.status === \"enabled\") });\n }\n };\n\n return (\n \n \n\n
\n {steps.map((step: any, index: any) => (\n handleStepToggle(index)}\n disabled={status !== \"executing\"}\n />\n ))}\n
\n\n {/* Action Buttons - Different logic from InterruptHumanInTheLoop */}\n {accepted === null && (\n
\n \n âś—\n Reject\n \n \n âś“\n Confirm\n \n {enabledCount}\n \n \n
\n )}\n\n {/* Result State - Unique to StepsFeedback */}\n {accepted !== null && (\n
\n \n {accepted ? \"âś“\" : \"âś—\"}\n {accepted ? \"Accepted\" : \"Rejected\"}\n
\n \n )}\n\n \n
\n );\n};\n\nexport default HumanInTheLoop;\n", "language": "typescript", "type": "file" }, @@ -1349,7 +1531,7 @@ "crewai::agentic_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", + "content": "\"use client\";\nimport React from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport { CopilotKit, useCoAgentStateRender } from \"@copilotkit/react-core\";\nimport { CopilotChat } from \"@copilotkit/react-ui\";\nimport { useTheme } from \"next-themes\";\n\ninterface AgenticGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\nconst AgenticGenerativeUI: React.FC = ({ params }) => {\n const { integrationId } = React.use(params);\n return (\n \n \n \n );\n};\n\ninterface AgentState {\n steps: {\n description: string;\n status: \"pending\" | \"completed\";\n }[];\n}\n\nconst Chat = () => {\n const { theme } = useTheme();\n useCoAgentStateRender({\n name: \"agentic_generative_ui\",\n render: ({ state }) => {\n if (!state.steps || state.steps.length === 0) {\n return null;\n }\n\n const completedCount = state.steps.filter((step) => step.status === \"completed\").length;\n const progressPercentage = (completedCount / state.steps.length) * 100;\n\n return (\n
\n \n {/* Header */}\n
\n
\n

\n Task Progress\n

\n
\n {completedCount}/{state.steps.length} Complete\n
\n
\n\n {/* Progress Bar */}\n \n \n \n
\n
\n\n {/* Steps */}\n
\n {state.steps.map((step, index) => {\n const isCompleted = step.status === \"completed\";\n const isCurrentPending =\n step.status === \"pending\" &&\n index === state.steps.findIndex((s) => s.status === \"pending\");\n const isFuturePending = step.status === \"pending\" && !isCurrentPending;\n\n return (\n \n {/* Connector Line */}\n {index < state.steps.length - 1 && (\n \n )}\n\n {/* Status Icon */}\n \n {isCompleted ? (\n \n ) : isCurrentPending ? (\n \n ) : (\n \n )}\n
\n\n {/* Step Content */}\n
\n \n {step.description}\n
\n {isCurrentPending && (\n \n Processing...\n \n )}\n \n\n {/* Animated Background for Current Step */}\n {isCurrentPending && (\n \n )}\n \n );\n })}\n \n\n {/* Decorative Elements */}\n \n \n \n \n );\n },\n });\n\n return (\n
\n
\n \n
\n
\n );\n};\n\n// Enhanced Icons\nfunction CheckIcon() {\n return (\n \n \n \n );\n}\n\nfunction SpinnerIcon() {\n return (\n \n \n \n \n );\n}\n\nfunction ClockIcon({ theme }: { theme?: string }) {\n return (\n \n \n \n \n );\n}\n\nexport default AgenticGenerativeUI;\n", "language": "typescript", "type": "file" }, diff --git a/typescript-sdk/apps/dojo/src/mastra/index.ts b/typescript-sdk/apps/dojo/src/mastra/index.ts index 569b43849..9943b0ddd 100644 --- a/typescript-sdk/apps/dojo/src/mastra/index.ts +++ b/typescript-sdk/apps/dojo/src/mastra/index.ts @@ -7,15 +7,14 @@ import { DynamoDBStore } from "@mastra/dynamodb"; import { createStep, createWorkflow, Mastra } from "@mastra/core"; import { createTool } from "@mastra/core"; import { z } from "zod"; - - +import { weatherTool } from "./tools"; function getStorage(): LibSQLStore | DynamoDBStore { if (process.env.DYNAMODB_TABLE_NAME) { return new DynamoDBStore({ name: "dynamodb", config: { - tableName: process.env.DYNAMODB_TABLE_NAME + tableName: process.env.DYNAMODB_TABLE_NAME, }, }); } else { @@ -23,54 +22,6 @@ function getStorage(): LibSQLStore | DynamoDBStore { } } -export const weatherInfo = createTool({ - id: "Get Weather Information", - description: `Fetches the current weather information for a given city`, - inputSchema: z.object({ - city: z.string(), - }), - outputSchema: z.object({ - text: z.string(), - }), - execute: async ({ writer }) => { - const step = createStep({ - id: "weather-info", - inputSchema: z.object({}), - outputSchema: z.object({ - text: z.string(), - }), - execute: async () => { - return { - text: "70 degrees" - } - } - }) - - const workflow = createWorkflow({ - id: "weather-info", - inputSchema: z.object({}), - outputSchema: z.object({}), - steps: [step], - }).then(step).commit(); - - const run = await workflow.createRunAsync() - - const stream = run.streamVNext() - - for await (const chunk of stream) { - console.log('WORKFLOW CHUNK', chunk) - if ('payload' in chunk) { - await writer!.write({ - type: `${chunk.from}-${chunk.type}`, - payload: chunk.payload, - }) - } - } - - return await stream.result as any; - }, -}); - export const mastra = new Mastra({ agents: { agentic_chat: new Agent({ @@ -86,9 +37,7 @@ export const mastra = new Mastra({ - Keep responses concise but informative `, model: openai("gpt-4o"), - tools: { - weatherInfo - }, + tools: { get_weather: weatherTool }, memory: new Memory({ storage: getStorage(), options: { @@ -101,6 +50,28 @@ export const mastra = new Mastra({ }, }), }), + backend_tool_rendering: new Agent({ + name: "Weather Agent", + instructions: ` + You are a helpful weather assistant that provides accurate weather information. + + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the weatherTool to fetch current weather data. + `, + model: openai("gpt-4o-mini"), + tools: { get_weather: weatherTool }, + memory: new Memory({ + storage: new LibSQLStore({ + url: "file:../mastra.db", // path is relative to the .mastra/output directory + }), + }), + }), shared_state: new Agent({ name: "shared_state", instructions: ` diff --git a/typescript-sdk/apps/dojo/src/mastra/tools.ts b/typescript-sdk/apps/dojo/src/mastra/tools.ts new file mode 100644 index 000000000..5eabe6c50 --- /dev/null +++ b/typescript-sdk/apps/dojo/src/mastra/tools.ts @@ -0,0 +1,34 @@ +import { createTool } from "@mastra/core/tools"; +import { z } from "zod"; + +export const weatherTool = createTool({ + id: "get-weather", + description: "Get current weather for a location", + inputSchema: z.object({ + location: z.string().describe("City name"), + }), + outputSchema: z.object({ + temperature: z.number(), + feelsLike: z.number(), + humidity: z.number(), + windSpeed: z.number(), + windGust: z.number(), + conditions: z.string(), + city: z.string(), + }), + execute: async ({ context }) => { + return await getWeather(context.location); + }, +}); + +const getWeather = async (location: string) => { + return { + temperature: 20, + feelsLike: 22, + humidity: 60, + windSpeed: 10, + windGust: 15, + conditions: "Sunny", + city: location, + }; +}; diff --git a/typescript-sdk/apps/dojo/src/menu.ts b/typescript-sdk/apps/dojo/src/menu.ts index fb22b6ece..5b03a20a4 100644 --- a/typescript-sdk/apps/dojo/src/menu.ts +++ b/typescript-sdk/apps/dojo/src/menu.ts @@ -6,6 +6,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "LangGraph (Python)", features: [ "agentic_chat", + "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -19,6 +20,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "LangGraph (FastAPI)", features: [ "agentic_chat", + "backend_tool_rendering", "human_in_the_loop", "agentic_chat_reasoning", "agentic_generative_ui", @@ -33,6 +35,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "LangGraph (Typescript)", features: [ "agentic_chat", + "backend_tool_rendering", "human_in_the_loop", "agentic_generative_ui", "predictive_state_updates", @@ -44,18 +47,24 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ { id: "mastra", name: "Mastra", - features: ["agentic_chat", "tool_based_generative_ui"], + features: ["agentic_chat", "backend_tool_rendering", "tool_based_generative_ui"], }, { id: "mastra-agent-local", name: "Mastra Agent (Local)", - features: ["agentic_chat", "shared_state", "tool_based_generative_ui"], + features: [ + "agentic_chat", + "backend_tool_rendering", + "shared_state", + "tool_based_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 @@ -69,6 +78,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "Google ADK", features: [ "agentic_chat", + "backend_tool_rendering", "human_in_the_loop", "shared_state", "tool_based_generative_ui", @@ -78,19 +88,25 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ { id: "agno", name: "Agno", - features: ["agentic_chat", "tool_based_generative_ui"], + features: ["agentic_chat", "backend_tool_rendering", "tool_based_generative_ui"], }, { id: "llama-index", name: "LlamaIndex", - features: ["agentic_chat", "human_in_the_loop", "agentic_generative_ui", "shared_state"], - + 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", @@ -119,6 +135,7 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "Server Starter (All Features)", features: [ "agentic_chat", + "backend_tool_rendering", "human_in_the_loop", "agentic_chat_reasoning", "agentic_generative_ui", @@ -133,4 +150,3 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ features: ["a2a_chat"], }, ]; - diff --git a/typescript-sdk/apps/dojo/src/types/integration.ts b/typescript-sdk/apps/dojo/src/types/integration.ts index 4c171a4c6..59d9b3adb 100644 --- a/typescript-sdk/apps/dojo/src/types/integration.ts +++ b/typescript-sdk/apps/dojo/src/types/integration.ts @@ -7,6 +7,7 @@ export type Feature = | "predictive_state_updates" | "shared_state" | "tool_based_generative_ui" + | "backend_tool_rendering" | "agentic_chat_reasoning" | "subgraphs" | "a2a_chat"; diff --git a/typescript-sdk/integrations/adk-middleware/python/examples/pyproject.toml b/typescript-sdk/integrations/adk-middleware/python/examples/pyproject.toml index 7a52827c8..3c83dfc56 100644 --- a/typescript-sdk/integrations/adk-middleware/python/examples/pyproject.toml +++ b/typescript-sdk/integrations/adk-middleware/python/examples/pyproject.toml @@ -10,6 +10,7 @@ readme = "README.md" requires-python = ">=3.12" dependencies = [ "fastapi>=0.104.0", + "httpx>=0.27.0", "uvicorn[standard]>=0.24.0", "python-dotenv>=1.0.0", "pydantic>=2.0.0", diff --git a/typescript-sdk/integrations/adk-middleware/python/examples/server/__init__.py b/typescript-sdk/integrations/adk-middleware/python/examples/server/__init__.py index 37da9b1fd..cdec42694 100644 --- a/typescript-sdk/integrations/adk-middleware/python/examples/server/__init__.py +++ b/typescript-sdk/integrations/adk-middleware/python/examples/server/__init__.py @@ -22,6 +22,7 @@ tool_based_generative_ui_app, human_in_the_loop_app, shared_state_app, + backend_tool_rendering_app, # predictive_state_updates_app, ) @@ -32,6 +33,7 @@ app.include_router(tool_based_generative_ui_app.router, prefix='/adk-tool-based-generative-ui', tags=['Tool Based Generative UI']) app.include_router(human_in_the_loop_app.router, prefix='/adk-human-in-loop-agent', tags=['Human in the Loop']) app.include_router(shared_state_app.router, prefix='/adk-shared-state-agent', tags=['Shared State']) +app.include_router(backend_tool_rendering_app.router, prefix='/backend_tool_rendering', tags=['Backend Tool Rendering']) # app.include_router(predictive_state_updates_app.router, prefix='/adk-predictive-state-agent', tags=['Predictive State Updates']) @@ -44,6 +46,7 @@ async def root(): "tool_based_generative_ui": "/adk-tool-based-generative-ui", "human_in_the_loop": "/adk-human-in-loop-agent", "shared_state": "/adk-shared-state-agent", + "backend_tool_rendering": "/backend_tool_rendering", # "predictive_state_updates": "/adk-predictive-state-agent", "docs": "/docs" } diff --git a/typescript-sdk/integrations/adk-middleware/python/examples/server/api/__init__.py b/typescript-sdk/integrations/adk-middleware/python/examples/server/api/__init__.py index d78ba9614..1a7ca5ae8 100644 --- a/typescript-sdk/integrations/adk-middleware/python/examples/server/api/__init__.py +++ b/typescript-sdk/integrations/adk-middleware/python/examples/server/api/__init__.py @@ -5,6 +5,7 @@ from .human_in_the_loop import app as human_in_the_loop_app from .shared_state import app as shared_state_app from .predictive_state_updates import app as predictive_state_updates_app +from .backend_tool_rendering import app as backend_tool_rendering_app __all__ = [ "agentic_chat_app", @@ -12,4 +13,5 @@ "human_in_the_loop_app", "shared_state_app", "predictive_state_updates_app", + "backend_tool_rendering_app", ] diff --git a/typescript-sdk/integrations/adk-middleware/python/examples/server/api/backend_tool_rendering.py b/typescript-sdk/integrations/adk-middleware/python/examples/server/api/backend_tool_rendering.py new file mode 100644 index 000000000..684429013 --- /dev/null +++ b/typescript-sdk/integrations/adk-middleware/python/examples/server/api/backend_tool_rendering.py @@ -0,0 +1,136 @@ +"""Basic Chat feature.""" + +from __future__ import annotations + +from fastapi import FastAPI +from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint +from google.adk.agents import LlmAgent +from google.adk import tools as adk_tools +import httpx +import json + + +def get_weather_condition(code: int) -> str: + """Map weather code to human-readable condition. + + Args: + code: WMO weather code. + + Returns: + Human-readable weather condition string. + """ + conditions = { + 0: "Clear sky", + 1: "Mainly clear", + 2: "Partly cloudy", + 3: "Overcast", + 45: "Foggy", + 48: "Depositing rime fog", + 51: "Light drizzle", + 53: "Moderate drizzle", + 55: "Dense drizzle", + 56: "Light freezing drizzle", + 57: "Dense freezing drizzle", + 61: "Slight rain", + 63: "Moderate rain", + 65: "Heavy rain", + 66: "Light freezing rain", + 67: "Heavy freezing rain", + 71: "Slight snow fall", + 73: "Moderate snow fall", + 75: "Heavy snow fall", + 77: "Snow grains", + 80: "Slight rain showers", + 81: "Moderate rain showers", + 82: "Violent rain showers", + 85: "Slight snow showers", + 86: "Heavy snow showers", + 95: "Thunderstorm", + 96: "Thunderstorm with slight hail", + 99: "Thunderstorm with heavy hail", + } + return conditions.get(code, "Unknown") + + +async def get_weather(location: str) -> dict[str, str | float]: + """Get current weather for a location. + + Args: + location: City name. + + Returns: + Dictionary with weather information including temperature, feels like, + humidity, wind speed, wind gust, conditions, and location name. + """ + async with httpx.AsyncClient() as client: + # Geocode the location + geocoding_url = ( + f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1" + ) + geocoding_response = await client.get(geocoding_url) + geocoding_data = geocoding_response.json() + + if not geocoding_data.get("results"): + raise ValueError(f"Location '{location}' not found") + + result = geocoding_data["results"][0] + latitude = result["latitude"] + longitude = result["longitude"] + name = result["name"] + + # Get weather data + weather_url = ( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={latitude}&longitude={longitude}" + f"¤t=temperature_2m,apparent_temperature,relative_humidity_2m," + f"wind_speed_10m,wind_gusts_10m,weather_code" + ) + weather_response = await client.get(weather_url) + weather_data = weather_response.json() + + current = weather_data["current"] + + return { + "temperature": current["temperature_2m"], + "feelsLike": current["apparent_temperature"], + "humidity": current["relative_humidity_2m"], + "windSpeed": current["wind_speed_10m"], + "windGust": current["wind_gusts_10m"], + "conditions": get_weather_condition(current["weather_code"]), + "location": name, + } + + +# Create a sample ADK agent (this would be your actual agent) +sample_agent = LlmAgent( + name="assistant", + model="gemini-2.0-flash", + instruction=""" + You are a helpful weather assistant that provides accurate weather information. + + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the get_weather tool to fetch current weather data. + """, + tools=[adk_tools.preload_memory_tool.PreloadMemoryTool(), get_weather], +) + +# Create ADK middleware agent instance +chat_agent = ADKAgent( + adk_agent=sample_agent, + app_name="demo_app", + user_id="demo_user", + session_timeout_seconds=3600, + use_in_memory_services=True, +) + +# Create FastAPI app +app = FastAPI(title="ADK Middleware Weather Agent") + +# Add the ADK endpoint +add_adk_fastapi_endpoint(app, chat_agent, path="/") diff --git a/typescript-sdk/integrations/adk-middleware/python/examples/uv.lock b/typescript-sdk/integrations/adk-middleware/python/examples/uv.lock index 8971b7169..a15f16554 100644 --- a/typescript-sdk/integrations/adk-middleware/python/examples/uv.lock +++ b/typescript-sdk/integrations/adk-middleware/python/examples/uv.lock @@ -22,6 +22,7 @@ source = { editable = "." } dependencies = [ { name = "ag-ui-adk" }, { name = "fastapi" }, + { name = "httpx" }, { name = "pydantic" }, { name = "python-dotenv" }, { name = "uvicorn", extra = ["standard"] }, @@ -31,6 +32,7 @@ dependencies = [ requires-dist = [ { name = "ag-ui-adk", directory = "../" }, { name = "fastapi", specifier = ">=0.104.0" }, + { name = "httpx", specifier = ">=0.27.0" }, { name = "pydantic", specifier = ">=2.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.24.0" }, diff --git a/typescript-sdk/integrations/agno/examples/pyproject.toml b/typescript-sdk/integrations/agno/examples/pyproject.toml index a7e9d282b..fa2c89c20 100644 --- a/typescript-sdk/integrations/agno/examples/pyproject.toml +++ b/typescript-sdk/integrations/agno/examples/pyproject.toml @@ -10,6 +10,7 @@ readme = "README.md" requires-python = ">=3.12,<4.0" dependencies = [ "agno>=1.7.7", + "httpx>=0.27.0", "openai>=1.99.1", "yfinance>=0.2.63", "fastapi>=0.116.1", @@ -23,4 +24,4 @@ authors = [ [project.scripts] -dev = "server:main" \ No newline at end of file +dev = "server:main" diff --git a/typescript-sdk/integrations/agno/examples/server/__init__.py b/typescript-sdk/integrations/agno/examples/server/__init__.py index 592c22fd0..e23307a09 100644 --- a/typescript-sdk/integrations/agno/examples/server/__init__.py +++ b/typescript-sdk/integrations/agno/examples/server/__init__.py @@ -16,11 +16,13 @@ from .api import ( agentic_chat_app, tool_based_generative_ui_app, + backend_tool_rendering_app, ) app = FastAPI(title='Agno AG-UI server') app.mount('/agentic_chat', agentic_chat_app, 'Agentic Chat') app.mount('/tool_based_generative_ui', tool_based_generative_ui_app, 'Tool-based Generative UI') +app.mount('/backend_tool_rendering', backend_tool_rendering_app, 'Backend Tool Rendering') def main(): """Main function to start the FastAPI server.""" diff --git a/typescript-sdk/integrations/agno/examples/server/api/__init__.py b/typescript-sdk/integrations/agno/examples/server/api/__init__.py index 83be232bc..c8697730b 100644 --- a/typescript-sdk/integrations/agno/examples/server/api/__init__.py +++ b/typescript-sdk/integrations/agno/examples/server/api/__init__.py @@ -4,8 +4,10 @@ from .agentic_chat import app as agentic_chat_app from .tool_based_generative_ui import app as tool_based_generative_ui_app +from .backend_tool_rendering import app as backend_tool_rendering_app __all__ = [ 'agentic_chat_app', 'tool_based_generative_ui_app', -] \ No newline at end of file + 'backend_tool_rendering_app', +] diff --git a/typescript-sdk/integrations/agno/examples/server/api/backend_tool_rendering.py b/typescript-sdk/integrations/agno/examples/server/api/backend_tool_rendering.py new file mode 100644 index 000000000..4dd7d831e --- /dev/null +++ b/typescript-sdk/integrations/agno/examples/server/api/backend_tool_rendering.py @@ -0,0 +1,134 @@ +"""Example: Agno Agent with Finance tools + +This example shows how to create an Agno Agent with tools (YFinanceTools) and expose it in an AG-UI compatible way. +""" + +from agno.agent.agent import Agent +from agno.app.agui.app import AGUIApp +from agno.models.openai import OpenAIChat +from agno.tools.yfinance import YFinanceTools +from agno.tools import tool +import httpx +import json + + +def get_weather_condition(code: int) -> str: + """Map weather code to human-readable condition. + + Args: + code: WMO weather code. + + Returns: + Human-readable weather condition string. + """ + conditions = { + 0: "Clear sky", + 1: "Mainly clear", + 2: "Partly cloudy", + 3: "Overcast", + 45: "Foggy", + 48: "Depositing rime fog", + 51: "Light drizzle", + 53: "Moderate drizzle", + 55: "Dense drizzle", + 56: "Light freezing drizzle", + 57: "Dense freezing drizzle", + 61: "Slight rain", + 63: "Moderate rain", + 65: "Heavy rain", + 66: "Light freezing rain", + 67: "Heavy freezing rain", + 71: "Slight snow fall", + 73: "Moderate snow fall", + 75: "Heavy snow fall", + 77: "Snow grains", + 80: "Slight rain showers", + 81: "Moderate rain showers", + 82: "Violent rain showers", + 85: "Slight snow showers", + 86: "Heavy snow showers", + 95: "Thunderstorm", + 96: "Thunderstorm with slight hail", + 99: "Thunderstorm with heavy hail", + } + return conditions.get(code, "Unknown") + + +@tool(external_execution=False) +async def get_weather(location: str) -> str: + """Get current weather for a location. + + Args: + location: City name. + + Returns: + A json string with weather information including temperature, feels like, + humidity, wind speed, wind gust, conditions, and location name. + """ + async with httpx.AsyncClient() as client: + # Geocode the location + geocoding_url = ( + f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1" + ) + geocoding_response = await client.get(geocoding_url) + geocoding_data = geocoding_response.json() + + if not geocoding_data.get("results"): + raise ValueError(f"Location '{location}' not found") + + result = geocoding_data["results"][0] + latitude = result["latitude"] + longitude = result["longitude"] + name = result["name"] + + # Get weather data + weather_url = ( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={latitude}&longitude={longitude}" + f"¤t=temperature_2m,apparent_temperature,relative_humidity_2m," + f"wind_speed_10m,wind_gusts_10m,weather_code" + ) + weather_response = await client.get(weather_url) + weather_data = weather_response.json() + + current = weather_data["current"] + + return json.dumps( + { + "temperature": current["temperature_2m"], + "feelsLike": current["apparent_temperature"], + "humidity": current["relative_humidity_2m"], + "windSpeed": current["wind_speed_10m"], + "windGust": current["wind_gusts_10m"], + "conditions": get_weather_condition(current["weather_code"]), + "location": name, + } + ) + + +agent = Agent( + model=OpenAIChat(id="gpt-4o"), + tools=[ + get_weather, + ], + description="You are a helpful weather assistant that provides accurate weather information.", + instructions=""" + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the get_weather tool to fetch current weather data. + """, +) + +agui_app = AGUIApp( + agent=agent, + name="Weather Agent", + app_id="backend_tool_rendering", + description="A helpful weather assistant that provides accurate weather information.", +) + +app = agui_app.get_app() diff --git a/typescript-sdk/integrations/agno/examples/uv.lock b/typescript-sdk/integrations/agno/examples/uv.lock index a0c73c20a..6a450c1f2 100644 --- a/typescript-sdk/integrations/agno/examples/uv.lock +++ b/typescript-sdk/integrations/agno/examples/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 2 -requires-python = ">=3.12" +requires-python = ">=3.12, <4.0" [[package]] name = "ag-ui-protocol" @@ -210,6 +210,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + [[package]] name = "fastapi" version = "0.116.1" @@ -714,7 +725,9 @@ source = { editable = "." } dependencies = [ { name = "ag-ui-protocol" }, { name = "agno" }, + { name = "dotenv" }, { name = "fastapi" }, + { name = "httpx" }, { name = "openai" }, { name = "uvicorn" }, { name = "yfinance" }, @@ -724,7 +737,9 @@ dependencies = [ requires-dist = [ { name = "ag-ui-protocol", specifier = ">=0.1.8" }, { name = "agno", specifier = ">=1.7.7" }, + { name = "dotenv", specifier = ">=0.9.9,<0.10.0" }, { name = "fastapi", specifier = ">=0.116.1" }, + { name = "httpx", specifier = ">=0.27.0" }, { name = "openai", specifier = ">=1.99.1" }, { name = "uvicorn", specifier = ">=0.35.0" }, { name = "yfinance", specifier = ">=0.2.63" }, diff --git a/typescript-sdk/integrations/langgraph/examples/python/agents/backend_tool_rendering/__init__.py b/typescript-sdk/integrations/langgraph/examples/python/agents/backend_tool_rendering/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/typescript-sdk/integrations/langgraph/examples/python/agents/backend_tool_rendering/agent.py b/typescript-sdk/integrations/langgraph/examples/python/agents/backend_tool_rendering/agent.py new file mode 100644 index 000000000..f60cc2836 --- /dev/null +++ b/typescript-sdk/integrations/langgraph/examples/python/agents/backend_tool_rendering/agent.py @@ -0,0 +1,55 @@ +""" +A simple agentic chat flow using LangGraph instead of CrewAI. +""" + +from typing import List, Any, Optional +import os + +# Updated imports for LangGraph +from langchain_core.runnables import RunnableConfig +from langchain_core.messages import SystemMessage +from langchain_core.tools import tool +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, END, START +from langgraph.graph import MessagesState +from langgraph.types import Command +from requests.api import get +from langgraph.prebuilt import create_react_agent + + +@tool +def get_weather(location: str): + """ + Get the weather for a given location. + """ + return { + "temperature": 20, + "conditions": "sunny", + "humidity": 50, + "wind_speed": 10, + "feelsLike": 25, + } + + +# Conditionally use a checkpointer based on the environment +# Check for multiple indicators that we're running in LangGraph dev/API mode +is_fast_api = os.environ.get("LANGGRAPH_FAST_API", "false").lower() == "true" + +# Compile the graph +if is_fast_api: + # For CopilotKit and other contexts, use MemorySaver + from langgraph.checkpoint.memory import MemorySaver + + graph = create_react_agent( + model="openai:gpt-4.1-mini", + tools=[get_weather], + prompt="You are a helpful assistant", + checkpointer=MemorySaver(), + ) +else: + # When running in LangGraph API/dev, don't use a custom checkpointer + graph = create_react_agent( + model="openai:gpt-4.1-mini", + tools=[get_weather], + prompt="You are a helpful assistant", + ) diff --git a/typescript-sdk/integrations/langgraph/examples/python/agents/dojo.py b/typescript-sdk/integrations/langgraph/examples/python/agents/dojo.py index 10af2cd58..8dc083050 100644 --- a/typescript-sdk/integrations/langgraph/examples/python/agents/dojo.py +++ b/typescript-sdk/integrations/langgraph/examples/python/agents/dojo.py @@ -3,6 +3,7 @@ from fastapi import FastAPI from dotenv import load_dotenv + load_dotenv() os.environ["LANGGRAPH_FAST_API"] = "true" @@ -15,6 +16,7 @@ from .agentic_chat.agent import graph as agentic_chat_graph from .agentic_generative_ui.agent import graph as agentic_generative_ui_graph from .agentic_chat_reasoning.agent import graph as agentic_chat_reasoning_graph +from .backend_tool_rendering.agent import graph as backend_tool_rendering_graph from .subgraphs.agent import graph as subgraphs_graph app = FastAPI(title="LangGraph Dojo Example Server") @@ -24,7 +26,12 @@ "agentic_chat": LangGraphAgent( name="agentic_chat", description="An example for an agentic chat flow using LangGraph.", - graph=agentic_chat_graph + graph=agentic_chat_graph, + ), + "backend_tool_rendering": LangGraphAgent( + name="backend_tool_rendering", + description="An example for a backend tool rendering flow.", + graph=backend_tool_rendering_graph, ), "tool_based_generative_ui": LangGraphAgent( name="tool_based_generative_ui", @@ -63,60 +70,53 @@ ), } +add_langgraph_fastapi_endpoint( + app=app, agent=agents["agentic_chat"], path="/agent/agentic_chat" +) + add_langgraph_fastapi_endpoint( app=app, - agent=agents["agentic_chat"], - path="/agent/agentic_chat" + agent=agents["backend_tool_rendering"], + path="/agent/backend_tool_rendering", ) + add_langgraph_fastapi_endpoint( app=app, agent=agents["tool_based_generative_ui"], - path="/agent/tool_based_generative_ui" + path="/agent/tool_based_generative_ui", ) add_langgraph_fastapi_endpoint( - app=app, - agent=agents["agentic_generative_ui"], - path="/agent/agentic_generative_ui" + app=app, agent=agents["agentic_generative_ui"], path="/agent/agentic_generative_ui" ) add_langgraph_fastapi_endpoint( - app=app, - agent=agents["human_in_the_loop"], - path="/agent/human_in_the_loop" + app=app, agent=agents["human_in_the_loop"], path="/agent/human_in_the_loop" ) add_langgraph_fastapi_endpoint( - app=app, - agent=agents["shared_state"], - path="/agent/shared_state" + app=app, agent=agents["shared_state"], path="/agent/shared_state" ) add_langgraph_fastapi_endpoint( app=app, agent=agents["predictive_state_updates"], - path="/agent/predictive_state_updates" + path="/agent/predictive_state_updates", ) add_langgraph_fastapi_endpoint( app=app, agent=agents["agentic_chat_reasoning"], - path="/agent/agentic_chat_reasoning" + path="/agent/agentic_chat_reasoning", ) add_langgraph_fastapi_endpoint( - app=app, - agent=agents["subgraphs"], - path="/agent/subgraphs" + app=app, agent=agents["subgraphs"], path="/agent/subgraphs" ) + def main(): """Run the uvicorn server.""" port = int(os.getenv("PORT", "8000")) - uvicorn.run( - "agents.dojo:app", - host="0.0.0.0", - port=port, - reload=True - ) + uvicorn.run("agents.dojo:app", host="0.0.0.0", port=port, reload=True) diff --git a/typescript-sdk/integrations/langgraph/examples/python/langgraph.json b/typescript-sdk/integrations/langgraph/examples/python/langgraph.json index bea65ef3f..021bc0645 100644 --- a/typescript-sdk/integrations/langgraph/examples/python/langgraph.json +++ b/typescript-sdk/integrations/langgraph/examples/python/langgraph.json @@ -4,6 +4,7 @@ "dependencies": ["."], "graphs": { "agentic_chat": "./agents/agentic_chat/agent.py:graph", + "backend_tool_rendering": "./agents/backend_tool_rendering/agent.py:graph", "agentic_generative_ui": "./agents/agentic_generative_ui/agent.py:graph", "human_in_the_loop": "./agents/human_in_the_loop/agent.py:graph", "predictive_state_updates": "./agents/predictive_state_updates/agent.py:graph", diff --git a/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/backend_tool_rendering/agent.ts b/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/backend_tool_rendering/agent.ts new file mode 100644 index 000000000..cdb972499 --- /dev/null +++ b/typescript-sdk/integrations/langgraph/examples/typescript/src/agents/backend_tool_rendering/agent.ts @@ -0,0 +1,80 @@ +/** + * A simple agentic chat flow using LangGraph instead of CrewAI. + */ + +import { ChatOpenAI } from "@langchain/openai"; +import { SystemMessage } from "@langchain/core/messages"; +import { RunnableConfig } from "@langchain/core/runnables"; +import { Annotation, MessagesAnnotation, StateGraph, Command, START, END } from "@langchain/langgraph"; + +const AgentStateAnnotation = Annotation.Root({ + tools: Annotation({ + reducer: (x, y) => y ?? x, + default: () => [] + }), + ...MessagesAnnotation.spec, +}); + +type AgentState = typeof AgentStateAnnotation.State; + +async function chatNode(state: AgentState, config?: RunnableConfig) { + /** + * Standard chat node based on the ReAct design pattern. It handles: + * - The model to use (and binds in CopilotKit actions and the tools defined above) + * - The system prompt + * - Getting a response from the model + * - Handling tool calls + * + * For more about the ReAct design pattern, see: + * https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg + */ + + // 1. Define the model + const model = new ChatOpenAI({ model: "gpt-4o" }); + + // Define config for the model + if (!config) { + config = { recursionLimit: 25 }; + } + + // 2. Bind the tools to the model + const modelWithTools = model.bindTools( + [ + ...state.tools, + // your_tool_here + ], + { + // 2.1 Disable parallel tool calls to avoid race conditions, + // enable this for faster performance if you want to manage + // the complexity of running tool calls in parallel. + parallel_tool_calls: false, + } + ); + + // 3. Define the system message by which the chat model will be run + const systemMessage = new SystemMessage({ + content: "You are a helpful assistant." + }); + + // 4. Run the model to generate a response + const response = await modelWithTools.invoke([ + systemMessage, + ...state.messages, + ], config); + + // 6. We've handled all tool calls, so we can end the graph. + return new Command({ + goto: END, + update: { + messages: [response] + } + }) +} + +// Define a new graph +const workflow = new StateGraph(AgentStateAnnotation) + .addNode("chat_node", chatNode) + .addEdge(START, "chat_node"); + +// Compile the graph +export const agenticChatGraph = workflow.compile(); \ No newline at end of file diff --git a/typescript-sdk/integrations/llamaindex/server-py/pyproject.toml b/typescript-sdk/integrations/llamaindex/server-py/pyproject.toml index 6c323b06a..9cb063e3b 100644 --- a/typescript-sdk/integrations/llamaindex/server-py/pyproject.toml +++ b/typescript-sdk/integrations/llamaindex/server-py/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "jsonpatch>=1.33", "uvicorn>=0.27.0", "fastapi>=0.100.0", + "httpx>=0.24.0", ] authors = [ { name = "Logan Markewich", email = "logan@runllama.ai" }, diff --git a/typescript-sdk/integrations/llamaindex/server-py/server/__init__.py b/typescript-sdk/integrations/llamaindex/server-py/server/__init__.py index d81e2c840..9f5f24a34 100644 --- a/typescript-sdk/integrations/llamaindex/server-py/server/__init__.py +++ b/typescript-sdk/integrations/llamaindex/server-py/server/__init__.py @@ -6,14 +6,16 @@ from .routers.human_in_the_loop import human_in_the_loop_router from .routers.agentic_generative_ui import agentic_generative_ui_router from .routers.shared_state import shared_state_router +from .routers.backend_tool_rendering import backend_tool_rendering_router app = FastAPI(title="AG-UI Llama-Index Endpoint") app.include_router(agentic_chat_router, prefix="/agentic_chat") app.include_router(human_in_the_loop_router, prefix="/human_in_the_loop") app.include_router(agentic_generative_ui_router, prefix="/agentic_generative_ui") -app.include_router(shared_state_router, prefix="/shared_state") +app.include_router(shared_state_router, prefix="/shared_state") +app.include_router(backend_tool_rendering_router, prefix="/backend_tool_rendering") def main(): """Main function to start the FastAPI server.""" diff --git a/typescript-sdk/integrations/llamaindex/server-py/server/routers/backend_tool_rendering.py b/typescript-sdk/integrations/llamaindex/server-py/server/routers/backend_tool_rendering.py new file mode 100644 index 000000000..c24b8d198 --- /dev/null +++ b/typescript-sdk/integrations/llamaindex/server-py/server/routers/backend_tool_rendering.py @@ -0,0 +1,124 @@ +"""Backend Tool Rendering feature.""" + +from __future__ import annotations + +from datetime import datetime +import json +from textwrap import dedent +from zoneinfo import ZoneInfo + +import httpx +from llama_index.llms.openai import OpenAI +from llama_index.protocols.ag_ui.router import get_ag_ui_workflow_router + + +def get_weather_condition(code: int) -> str: + """Map weather code to human-readable condition. + + Args: + code: WMO weather code. + + Returns: + Human-readable weather condition string. + """ + conditions = { + 0: "Clear sky", + 1: "Mainly clear", + 2: "Partly cloudy", + 3: "Overcast", + 45: "Foggy", + 48: "Depositing rime fog", + 51: "Light drizzle", + 53: "Moderate drizzle", + 55: "Dense drizzle", + 56: "Light freezing drizzle", + 57: "Dense freezing drizzle", + 61: "Slight rain", + 63: "Moderate rain", + 65: "Heavy rain", + 66: "Light freezing rain", + 67: "Heavy freezing rain", + 71: "Slight snow fall", + 73: "Moderate snow fall", + 75: "Heavy snow fall", + 77: "Snow grains", + 80: "Slight rain showers", + 81: "Moderate rain showers", + 82: "Violent rain showers", + 85: "Slight snow showers", + 86: "Heavy snow showers", + 95: "Thunderstorm", + 96: "Thunderstorm with slight hail", + 99: "Thunderstorm with heavy hail", + } + return conditions.get(code, "Unknown") + + +async def get_weather(location: str) -> str: + """Get current weather for a location. + + Args: + location: City name. + + Returns: + Dictionary with weather information including temperature, feels like, + humidity, wind speed, wind gust, conditions, and location name. + """ + async with httpx.AsyncClient() as client: + # Geocode the location + geocoding_url = ( + f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1" + ) + geocoding_response = await client.get(geocoding_url) + geocoding_data = geocoding_response.json() + + if not geocoding_data.get("results"): + raise ValueError(f"Location '{location}' not found") + + result = geocoding_data["results"][0] + latitude = result["latitude"] + longitude = result["longitude"] + name = result["name"] + + # Get weather data + weather_url = ( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={latitude}&longitude={longitude}" + f"¤t=temperature_2m,apparent_temperature,relative_humidity_2m," + f"wind_speed_10m,wind_gusts_10m,weather_code" + ) + weather_response = await client.get(weather_url) + weather_data = weather_response.json() + + current = weather_data["current"] + + return json.dumps({ + "temperature": current["temperature_2m"], + "feelsLike": current["apparent_temperature"], + "humidity": current["relative_humidity_2m"], + "windSpeed": current["wind_speed_10m"], + "windGust": current["wind_gusts_10m"], + "conditions": get_weather_condition(current["weather_code"]), + "location": name, + }) + + +# Create the router with weather tools +backend_tool_rendering_router = get_ag_ui_workflow_router( + llm=OpenAI(model="gpt-4o-mini"), + backend_tools=[get_weather], + system_prompt=dedent( + """ + You are a helpful weather assistant that provides accurate weather information. + + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the get_weather tool to fetch current weather data. + """ + ), +) diff --git a/typescript-sdk/integrations/llamaindex/server-py/uv.lock b/typescript-sdk/integrations/llamaindex/server-py/uv.lock index 72fbbd7fb..548b748cc 100644 --- a/typescript-sdk/integrations/llamaindex/server-py/uv.lock +++ b/typescript-sdk/integrations/llamaindex/server-py/uv.lock @@ -1825,6 +1825,7 @@ version = "0.1.0" source = { editable = "." } dependencies = [ { name = "fastapi" }, + { name = "httpx" }, { name = "jsonpatch" }, { name = "llama-index-core" }, { name = "llama-index-llms-openai" }, @@ -1835,6 +1836,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "fastapi", specifier = ">=0.100.0" }, + { name = "httpx", specifier = ">=0.24.0" }, { name = "jsonpatch", specifier = ">=1.33" }, { name = "llama-index-core", specifier = ">=0.14.0,<0.15" }, { name = "llama-index-llms-openai", specifier = ">=0.5.0,<0.6.0" }, diff --git a/typescript-sdk/integrations/mastra/example/src/mastra/agents/backend-tool-rendering.ts b/typescript-sdk/integrations/mastra/example/src/mastra/agents/backend-tool-rendering.ts new file mode 100644 index 000000000..7db10a2c2 --- /dev/null +++ b/typescript-sdk/integrations/mastra/example/src/mastra/agents/backend-tool-rendering.ts @@ -0,0 +1,28 @@ +import { openai } from "@ai-sdk/openai"; +import { Agent } from "@mastra/core/agent"; +import { Memory } from "@mastra/memory"; +import { LibSQLStore } from "@mastra/libsql"; +import { weatherTool } from "../tools/weather-tool"; + +export const backendToolRenderingAgent = new Agent({ + name: "Weather Agent", + instructions: ` + You are a helpful weather assistant that provides accurate weather information. + + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the get_weather tool to fetch current weather data. +`, + model: openai("gpt-4o-mini"), + tools: { get_weather: weatherTool }, + memory: new Memory({ + storage: new LibSQLStore({ + url: "file:../mastra.db", // path is relative to the .mastra/output directory + }), + }), +}); diff --git a/typescript-sdk/integrations/mastra/example/src/mastra/index.ts b/typescript-sdk/integrations/mastra/example/src/mastra/index.ts index 1980c338c..f70f9fbd3 100644 --- a/typescript-sdk/integrations/mastra/example/src/mastra/index.ts +++ b/typescript-sdk/integrations/mastra/example/src/mastra/index.ts @@ -4,13 +4,18 @@ import { LibSQLStore } from "@mastra/libsql"; import { agenticChatAgent } from "./agents/agentic-chat"; import { toolBasedGenerativeUIAgent } from "./agents/tool-based-generative-ui"; +import { backendToolRenderingAgent } from "./agents/backend-tool-rendering"; export const mastra = new Mastra({ server: { port: process.env.PORT ? parseInt(process.env.PORT) : 4111, host: "0.0.0.0", }, - agents: { agentic_chat: agenticChatAgent, tool_based_generative_ui: toolBasedGenerativeUIAgent }, + agents: { + agentic_chat: agenticChatAgent, + tool_based_generative_ui: toolBasedGenerativeUIAgent, + backend_tool_rendering: backendToolRenderingAgent, + }, storage: new LibSQLStore({ // stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db url: ":memory:", diff --git a/typescript-sdk/integrations/pydantic-ai/examples/server/__init__.py b/typescript-sdk/integrations/pydantic-ai/examples/server/__init__.py index 3c38ea0c2..d197be533 100644 --- a/typescript-sdk/integrations/pydantic-ai/examples/server/__init__.py +++ b/typescript-sdk/integrations/pydantic-ai/examples/server/__init__.py @@ -21,6 +21,7 @@ from .api import ( agentic_chat_app, agentic_generative_ui_app, + backend_tool_rendering_app, human_in_the_loop_app, predictive_state_updates_app, shared_state_app, @@ -30,6 +31,7 @@ app = FastAPI(title='Pydantic AI AG-UI server') app.mount('/agentic_chat', agentic_chat_app, 'Agentic Chat') app.mount('/agentic_generative_ui', agentic_generative_ui_app, 'Agentic Generative UI') +app.mount('/backend_tool_rendering', backend_tool_rendering_app, 'Backend Tool Rendering') app.mount('/human_in_the_loop', human_in_the_loop_app, 'Human in the Loop') app.mount( '/predictive_state_updates', diff --git a/typescript-sdk/integrations/pydantic-ai/examples/server/api/__init__.py b/typescript-sdk/integrations/pydantic-ai/examples/server/api/__init__.py index 2f8954317..bdc7144eb 100644 --- a/typescript-sdk/integrations/pydantic-ai/examples/server/api/__init__.py +++ b/typescript-sdk/integrations/pydantic-ai/examples/server/api/__init__.py @@ -4,6 +4,7 @@ from .agentic_chat import app as agentic_chat_app from .agentic_generative_ui import app as agentic_generative_ui_app +from .backend_tool_rendering import app as backend_tool_rendering_app from .human_in_the_loop import app as human_in_the_loop_app from .predictive_state_updates import app as predictive_state_updates_app from .shared_state import app as shared_state_app @@ -12,6 +13,7 @@ __all__ = [ 'agentic_chat_app', 'agentic_generative_ui_app', + 'backend_tool_rendering_app', 'human_in_the_loop_app', 'predictive_state_updates_app', 'shared_state_app', diff --git a/typescript-sdk/integrations/pydantic-ai/examples/server/api/backend_tool_rendering.py b/typescript-sdk/integrations/pydantic-ai/examples/server/api/backend_tool_rendering.py new file mode 100644 index 000000000..d35cd41e1 --- /dev/null +++ b/typescript-sdk/integrations/pydantic-ai/examples/server/api/backend_tool_rendering.py @@ -0,0 +1,121 @@ +"""Backend Tool Rendering feature.""" + +from __future__ import annotations + +from datetime import datetime +from textwrap import dedent +from zoneinfo import ZoneInfo + +import httpx +from pydantic_ai import Agent + +agent = Agent( + "openai:gpt-4o-mini", + instructions=dedent( + """ + You are a helpful weather assistant that provides accurate weather information. + + Your primary function is to help users get weather details for specific locations. When responding: + - Always ask for a location if none is provided + - If the location name isn’t in English, please translate it + - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") + - Include relevant details like humidity, wind conditions, and precipitation + - Keep responses concise but informative + + Use the get_weather tool to fetch current weather data. + """ + ), +) +app = agent.to_ag_ui() + + +def get_weather_condition(code: int) -> str: + """Map weather code to human-readable condition. + + Args: + code: WMO weather code. + + Returns: + Human-readable weather condition string. + """ + conditions = { + 0: "Clear sky", + 1: "Mainly clear", + 2: "Partly cloudy", + 3: "Overcast", + 45: "Foggy", + 48: "Depositing rime fog", + 51: "Light drizzle", + 53: "Moderate drizzle", + 55: "Dense drizzle", + 56: "Light freezing drizzle", + 57: "Dense freezing drizzle", + 61: "Slight rain", + 63: "Moderate rain", + 65: "Heavy rain", + 66: "Light freezing rain", + 67: "Heavy freezing rain", + 71: "Slight snow fall", + 73: "Moderate snow fall", + 75: "Heavy snow fall", + 77: "Snow grains", + 80: "Slight rain showers", + 81: "Moderate rain showers", + 82: "Violent rain showers", + 85: "Slight snow showers", + 86: "Heavy snow showers", + 95: "Thunderstorm", + 96: "Thunderstorm with slight hail", + 99: "Thunderstorm with heavy hail", + } + return conditions.get(code, "Unknown") + + +@agent.tool_plain +async def get_weather(location: str) -> dict[str, str | float]: + """Get current weather for a location. + + Args: + location: City name. + + Returns: + Dictionary with weather information including temperature, feels like, + humidity, wind speed, wind gust, conditions, and location name. + """ + async with httpx.AsyncClient() as client: + # Geocode the location + geocoding_url = ( + f"https://geocoding-api.open-meteo.com/v1/search?name={location}&count=1" + ) + geocoding_response = await client.get(geocoding_url) + geocoding_data = geocoding_response.json() + + if not geocoding_data.get("results"): + raise ValueError(f"Location '{location}' not found") + + result = geocoding_data["results"][0] + latitude = result["latitude"] + longitude = result["longitude"] + name = result["name"] + + # Get weather data + weather_url = ( + f"https://api.open-meteo.com/v1/forecast?" + f"latitude={latitude}&longitude={longitude}" + f"¤t=temperature_2m,apparent_temperature,relative_humidity_2m," + f"wind_speed_10m,wind_gusts_10m,weather_code" + ) + weather_response = await client.get(weather_url) + weather_data = weather_response.json() + + current = weather_data["current"] + + return { + "temperature": current["temperature_2m"], + "feelsLike": current["apparent_temperature"], + "humidity": current["relative_humidity_2m"], + "windSpeed": current["wind_speed_10m"], + "windGust": current["wind_gusts_10m"], + "conditions": get_weather_condition(current["weather_code"]), + "location": name, + } diff --git a/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/__init__.py b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/__init__.py index 177cd49c8..39134d0d6 100644 --- a/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/__init__.py +++ b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/__init__.py @@ -11,12 +11,16 @@ from .tool_based_generative_ui import tool_based_generative_ui_endpoint from .shared_state import shared_state_endpoint from .predictive_state_updates import predictive_state_updates_endpoint +from .backend_tool_rendering import backend_tool_rendering_endpoint app = FastAPI(title="AG-UI Endpoint") # Register the agentic chat endpoint app.post("/agentic_chat")(agentic_chat_endpoint) +# Register the backend_tool_rendering endpoint +app.post("/backend_tool_rendering")(backend_tool_rendering_endpoint) + # Register the human in the loop endpoint app.post("/human_in_the_loop")(human_in_the_loop_endpoint) @@ -36,9 +40,4 @@ def main(): """Run the uvicorn server.""" port = int(os.getenv("PORT", "8000")) - uvicorn.run( - "example_server:app", - host="0.0.0.0", - port=port, - reload=True - ) + uvicorn.run("example_server:app", host="0.0.0.0", port=port, reload=True) diff --git a/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/backend_tool_rendering.py b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/backend_tool_rendering.py new file mode 100644 index 000000000..1ef20bc24 --- /dev/null +++ b/typescript-sdk/integrations/server-starter-all-features/server/python/example_server/backend_tool_rendering.py @@ -0,0 +1,126 @@ +""" +Agentic chat endpoint for the AG-UI protocol. +""" + +import uuid +import json +from fastapi import Request +from fastapi.responses import StreamingResponse +from ag_ui.core import ( + RunAgentInput, + EventType, + RunStartedEvent, + RunFinishedEvent, + TextMessageStartEvent, + TextMessageContentEvent, + TextMessageEndEvent, + MessagesSnapshotEvent, + ToolMessage, + ToolCall, + AssistantMessage, +) +from ag_ui.encoder import EventEncoder + + +async def backend_tool_rendering_endpoint(input_data: RunAgentInput, request: Request): + """Agentic chat endpoint""" + # Get the accept header from the request + accept_header = request.headers.get("accept") + + # Create an event encoder to properly format SSE events + encoder = EventEncoder(accept=accept_header) + + async def event_generator(): + # Get the last message content for conditional logic + last_message_role = None + if input_data.messages and len(input_data.messages) > 0: + last_message = input_data.messages[-1] + last_message_role = getattr(last_message, "role", None) + + # Send run started event + yield encoder.encode( + RunStartedEvent( + type=EventType.RUN_STARTED, + thread_id=input_data.thread_id, + run_id=input_data.run_id, + ), + ) + + # Conditional logic based on last message + if last_message_role == "tool": + async for event in send_tool_result_message_events(): + yield encoder.encode(event) + else: + async for event in send_backend_tool_call_events(input_data.messages): + yield encoder.encode(event) + + # Send run finished event + yield encoder.encode( + RunFinishedEvent( + type=EventType.RUN_FINISHED, + thread_id=input_data.thread_id, + run_id=input_data.run_id, + ), + ) + + return StreamingResponse(event_generator(), media_type=encoder.get_content_type()) + + +async def send_tool_result_message_events(): + """Send message for tool result""" + message_id = str(uuid.uuid4()) + + # Start of message + yield TextMessageStartEvent( + type=EventType.TEXT_MESSAGE_START, message_id=message_id, role="assistant" + ) + + # Content + yield TextMessageContentEvent( + type=EventType.TEXT_MESSAGE_CONTENT, + message_id=message_id, + delta="Retrieved weather information!", + ) + + # End of message + yield TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id) + + +async def send_backend_tool_call_events(messages: list): + """Send backend tool call events""" + tool_call_id = str(uuid.uuid4()) + + new_message = AssistantMessage( + id=str(uuid.uuid4()), + role="assistant", + tool_calls=[ + ToolCall( + id=tool_call_id, + type="function", + function={ + "name": "get_weather", + "arguments": json.dumps({"city": "San Francisco"}), + }, + ) + ], + ) + + result_message = ToolMessage( + id=str(uuid.uuid4()), + role="tool", + content=json.dumps( + { + "city": "San Francisco", + "conditions": "sunny", + "wind_speed": "10", + "temperature": "20", + "humidity": "60", + } + ), + tool_call_id=tool_call_id, + ) + + all_messages = list(messages) + [new_message, result_message] + + # Send messages snapshot event + yield MessagesSnapshotEvent(type=EventType.MESSAGES_SNAPSHOT, messages=all_messages)