diff --git a/.github/workflows/dojo-e2e.yml b/.github/workflows/dojo-e2e.yml index f0597e633..666eba6be 100644 --- a/.github/workflows/dojo-e2e.yml +++ b/.github/workflows/dojo-e2e.yml @@ -7,9 +7,142 @@ on: branches: [main] jobs: + changes: + name: Determine suites to run + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + should_run: ${{ steps.set-matrix.outputs.should_run }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed areas + id: filter + uses: dorny/paths-filter@v3 + with: + filters: | + core_ts: + - 'typescript-sdk/packages/**' + - 'typescript-sdk/package.json' + - 'typescript-sdk/pnpm-lock.yaml' + - 'typescript-sdk/pnpm-workspace.yaml' + - 'typescript-sdk/tsconfig.json' + - 'typescript-sdk/turbo.json' + core_py: + - 'python-sdk/**' + e2e_tests: + - 'typescript-sdk/apps/dojo/e2e/**' + - 'typescript-sdk/apps/dojo/scripts/**' + workflow_self: + - '.github/workflows/dojo-e2e.yml' + agno: + - 'typescript-sdk/integrations/agno/**' + crew_ai: + - 'typescript-sdk/integrations/crewai/**' + langgraph: + - 'typescript-sdk/integrations/langgraph/**' + llama_index: + - 'typescript-sdk/integrations/llamaindex/**' + mastra: + - 'typescript-sdk/integrations/mastra/**' + middleware_starter: + - 'typescript-sdk/integrations/middleware-starter/**' + pydantic_ai: + - 'typescript-sdk/integrations/pydantic-ai/**' + server_starter: + - 'typescript-sdk/integrations/server-starter/**' + server_starter_all: + - 'typescript-sdk/integrations/server-starter-all-features/**' + vercel_ai_sdk: + - 'typescript-sdk/integrations/vercel-ai-sdk/**' + + - name: Build dynamic matrix + id: set-matrix + env: + CORE_TS: ${{ steps.filter.outputs.core_ts }} + CORE_PY: ${{ steps.filter.outputs.core_py }} + E2E_TESTS: ${{ steps.filter.outputs.e2e_tests }} + WORKFLOW_SELF: ${{ steps.filter.outputs.workflow_self }} + AGNO: ${{ steps.filter.outputs.agno }} + CREW_AI: ${{ steps.filter.outputs.crew_ai }} + LANGGRAPH: ${{ steps.filter.outputs.langgraph }} + LLAMA_INDEX: ${{ steps.filter.outputs.llama_index }} + MASTRA: ${{ steps.filter.outputs.mastra }} + MIDDLEWARE_STARTER: ${{ steps.filter.outputs.middleware_starter }} + PYDANTIC_AI: ${{ steps.filter.outputs.pydantic_ai }} + SERVER_STARTER: ${{ steps.filter.outputs.server_starter }} + SERVER_STARTER_ALL: ${{ steps.filter.outputs.server_starter_all }} + VERCEL_AI_SDK: ${{ steps.filter.outputs.vercel_ai_sdk }} + run: | + python3 - << 'PY' + import os, json + + all_entries = [ + {"suite": "agno", "test_path": "tests/agnoTests", "services": ["dojo","agno"], "wait_on": "http://localhost:9999,tcp:localhost:8002"}, + {"suite": "crew-ai", "test_path": "tests/crewAITests", "services": ["dojo","crew-ai"], "wait_on": "http://localhost:9999,tcp:localhost:8003"}, + {"suite": "langgraph", "test_path": "tests/langgraphTests", "services": ["dojo","langgraph-platform-python","langgraph-platform-typescript"], "wait_on": "http://localhost:9999,tcp:localhost:8005,tcp:localhost:8006"}, + {"suite": "langgraph-fastapi", "test_path": "tests/langgraphFastAPITests", "services": ["dojo","langgraph-fastapi"], "wait_on": "http://localhost:9999,tcp:localhost:8004"}, + {"suite": "llama-index", "test_path": "tests/llamaIndexTests", "services": ["dojo","llama-index"], "wait_on": "http://localhost:9999,tcp:localhost:8007"}, + {"suite": "mastra", "test_path": "tests/mastraTests", "services": ["dojo","mastra"], "wait_on": "http://localhost:9999,tcp:localhost:8008"}, + {"suite": "mastra-agent-local", "test_path": "tests/mastraAgentLocalTests", "services": ["dojo"], "wait_on": "http://localhost:9999"}, + {"suite": "middleware-starter", "test_path": "tests/middlewareStarterTests", "services": ["dojo"], "wait_on": "http://localhost:9999"}, + {"suite": "pydantic-ai", "test_path": "tests/pydanticAITests", "services": ["dojo","pydantic-ai"], "wait_on": "http://localhost:9999,tcp:localhost:8009"}, + {"suite": "server-starter", "test_path": "tests/serverStarterTests", "services": ["dojo","server-starter"], "wait_on": "http://localhost:9999,tcp:localhost:8000"}, + {"suite": "server-starter-all", "test_path": "tests/serverStarterAllFeaturesTests", "services": ["dojo","server-starter-all"], "wait_on": "http://localhost:9999,tcp:localhost:8001"}, + {"suite": "vercel-ai-sdk", "test_path": "tests/vercelAISdkTests", "services": ["dojo"], "wait_on": "http://localhost:9999"}, + ] + + entry_by_suite = {e["suite"]: e for e in all_entries} + core_changed = ( + (os.environ.get('CORE_TS') == 'true') or + (os.environ.get('CORE_PY') == 'true') or + (os.environ.get('E2E_TESTS') == 'true') or + (os.environ.get('WORKFLOW_SELF') == 'true') + ) + + include = [] + if core_changed: + include = all_entries + else: + if os.environ.get('AGNO') == 'true': + include.append(entry_by_suite['agno']) + if os.environ.get('CREW_AI') == 'true': + include.append(entry_by_suite['crew-ai']) + if os.environ.get('LANGGRAPH') == 'true': + include.append(entry_by_suite['langgraph']) + include.append(entry_by_suite['langgraph-fastapi']) + if os.environ.get('LLAMA_INDEX') == 'true': + include.append(entry_by_suite['llama-index']) + if os.environ.get('MASTRA') == 'true': + include.append(entry_by_suite['mastra']) + include.append(entry_by_suite['mastra-agent-local']) + if os.environ.get('MIDDLEWARE_STARTER') == 'true': + include.append(entry_by_suite['middleware-starter']) + if os.environ.get('PYDANTIC_AI') == 'true': + include.append(entry_by_suite['pydantic-ai']) + if os.environ.get('SERVER_STARTER') == 'true': + include.append(entry_by_suite['server-starter']) + if os.environ.get('SERVER_STARTER_ALL') == 'true': + include.append(entry_by_suite['server-starter-all']) + if os.environ.get('VERCEL_AI_SDK') == 'true': + include.append(entry_by_suite['vercel-ai-sdk']) + + matrix = {"include": include} + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + fh.write(f"matrix={json.dumps(matrix)}\n") + fh.write(f"should_run={'true' if include else 'false'}\n") + PY e2e: - name: E2E Tests - runs-on: depot-ubuntu-latest-8 + needs: changes + if: ${{ needs.changes.outputs.should_run == 'true' }} + name: ${{ matrix.suite }} + runs-on: depot-ubuntu-24.04 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.changes.outputs.matrix) }} steps: - name: Checkout code @@ -25,6 +158,33 @@ jobs: with: version: 10.13.1 + # Now that pnpm is available, cache its store to speed installs + - name: Resolve pnpm store path + id: pnpm-store + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Cache pnpm store + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + # Cache Python tool caches and virtualenvs; restore only to avoid long saves + - name: Cache Python dependencies (restore-only) + id: cache-python + uses: actions/cache/restore@v4 + with: + path: | + ~/.cache/pip + ~/.cache/pypoetry + ~/.cache/uv + **/.venv + key: ${{ runner.os }}-pydeps-${{ hashFiles('**/poetry.lock', '**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pydeps- + - name: Install Poetry uses: snok/install-poetry@v1 with: @@ -35,21 +195,14 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v6 - - name: Setup pnpm cache - uses: actions/cache@v4 - with: - path: ~/.local/share/pnpm/store - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - name: Install dependencies working-directory: typescript-sdk run: pnpm install --frozen-lockfile - name: Prepare dojo for e2e working-directory: typescript-sdk/apps/dojo - run: node ./scripts/prep-dojo-everything.js -e2e + if: ${{ join(matrix.services, ',') != '' }} + run: node ./scripts/prep-dojo-everything.js --only ${{ join(matrix.services, ',') }} - name: Install e2e dependencies working-directory: typescript-sdk/apps/dojo/e2e @@ -61,6 +214,7 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} + if: ${{ contains(join(matrix.services, ','), 'langgraph-fastapi') || contains(join(matrix.services, ','), 'langgraph-platform-python') || contains(join(matrix.services, ','), 'langgraph-platform-typescript') }} run: | echo "OPENAI_API_KEY=${OPENAI_API_KEY}" > examples/python/.env echo "LANGSMITH_API_KEY=${LANGSMITH_API_KEY}" >> examples/python/.env @@ -74,33 +228,28 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} + if: ${{ join(matrix.services, ',') != '' && contains(join(matrix.services, ','), 'dojo') }} with: run: | - node ../scripts/run-dojo-everything.js + node ../scripts/run-dojo-everything.js --only ${{ join(matrix.services, ',') }} working-directory: typescript-sdk/apps/dojo/e2e - wait-on: | - http://localhost:9999 - tcp:localhost:8000 - tcp:localhost:8001 - tcp:localhost:8002 - tcp:localhost:8003 - tcp:localhost:8004 - tcp:localhost:8005 - tcp:localhost:8006 - tcp:localhost:8007 - tcp:localhost:8008 - tcp:localhost:8009 - - - name: Run tests + wait-on: ${{ matrix.wait_on }} + wait-for: 300000 + + - name: Run tests – ${{ matrix.suite }} working-directory: typescript-sdk/apps/dojo/e2e env: BASE_URL: http://localhost:9999 - run: pnpm test + PLAYWRIGHT_SUITE: ${{ matrix.suite }} + run: | + pnpm test -- ${{ matrix.test_path }} - - name: Upload traces + - name: Upload traces – ${{ matrix.suite }} if: always() # Uploads artifacts even if tests fail uses: actions/upload-artifact@v4 with: - name: playwright-traces - path: typescript-sdk/apps/dojo/e2e/test-results/ + name: ${{ matrix.suite }}-playwright-traces + path: | + typescript-sdk/apps/dojo/e2e/test-results/${{ matrix.suite }}/**/* + typescript-sdk/apps/dojo/e2e/playwright-report/**/* retention-days: 7 diff --git a/typescript-sdk/apps/dojo/e2e/playwright.config.ts b/typescript-sdk/apps/dojo/e2e/playwright.config.ts index 0ccf39dd1..7571ff1e2 100644 --- a/typescript-sdk/apps/dojo/e2e/playwright.config.ts +++ b/typescript-sdk/apps/dojo/e2e/playwright.config.ts @@ -46,7 +46,7 @@ function getBaseUrl(): string { export default defineConfig({ timeout: process.env.CI ? 300_000 : 120_000, // 5min in CI, 2min locally for AI tests testDir: "./tests", - retries: process.env.CI ? 1 : 0, // More retries for flaky AI tests in CI, 0 for local + retries: process.env.CI ? 3 : 0, // More retries for flaky AI tests in CI, 0 for local // Make this sequential for now to avoid race conditions workers: process.env.CI ? 1 : undefined, fullyParallel: process.env.CI ? false : true, @@ -66,7 +66,7 @@ export default defineConfig({ baseURL: getBaseUrl(), }, expect: { - timeout: 90_000, // 1.5 minutes for AI-generated content to appear + timeout: 120_000, // 2 minutes for AI-generated content to appear }, // Test isolation between each test projects: [ diff --git a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/agenticGenUI.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/agenticGenUI.spec.ts index cacdbf7d0..a10f14356 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/agenticGenUI.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/agenticGenUI.spec.ts @@ -3,7 +3,7 @@ import { AgenticGenUIPage } from "../../pages/pydanticAIPages/AgenticUIGenPage"; test.describe("Agent Generative UI Feature", () => { // Flaky. Sometimes the steps render but never process. - test.fixme("[PydanticAI] should interact with the chat to get a planner on prompt", async ({ + test("[PydanticAI] should interact with the chat to get a planner on prompt", async ({ page, }) => { const genUIAgent = new AgenticGenUIPage(page); diff --git a/typescript-sdk/apps/dojo/scripts/prep-dojo-everything.js b/typescript-sdk/apps/dojo/scripts/prep-dojo-everything.js index bf4faf463..e7c4f3c93 100755 --- a/typescript-sdk/apps/dojo/scripts/prep-dojo-everything.js +++ b/typescript-sdk/apps/dojo/scripts/prep-dojo-everything.js @@ -9,17 +9,36 @@ const args = process.argv.slice(2); const showHelp = args.includes('--help') || args.includes('-h'); const dryRun = args.includes('--dry-run'); +// selection controls +function parseList(flag) { + const idx = args.indexOf(flag); + if (idx !== -1 && args[idx + 1]) { + return args[idx + 1] + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + } + return null; +} + +const onlyList = parseList('--only') || parseList('--include'); +const excludeList = parseList('--exclude') || []; + if (showHelp) { console.log(` Usage: node prep-dojo-everything.js [options] Options: --dry-run Show what would be installed without actually running + --only list Comma-separated services to include (defaults to all) + --exclude list Comma-separated services to exclude --help, -h Show this help message Examples: node prep-dojo-everything.js node prep-dojo-everything.js --dry-run + node prep-dojo-everything.js --only dojo,agno + node prep-dojo-everything.js --exclude crew-ai,mastra `); process.exit(0); } @@ -29,78 +48,62 @@ const integrationsRoot = path.join(gitRoot, 'typescript-sdk', 'integrations'); -// Server Starter -const serverStarter = { - command: 'poetry install', - name: 'Server Starter', - cwd: path.join(integrationsRoot, 'server-starter/server/python'), -} - -// Server Starter All Features -const serverStarterAllFeatures = { - command: 'poetry install', - name: 'Server AF', - cwd: path.join(integrationsRoot, 'server-starter-all-features/server/python'), -} - -// Agno -const agno = { - command: 'uv sync', - name: 'Agno', - cwd: path.join(integrationsRoot, 'agno/examples'), -} - -// CrewAI -const crewai = { - command: 'poetry install', - name: 'CrewAI', - cwd: path.join(integrationsRoot, 'crewai/python'), -} - -// Langgraph (FastAPI) -const langgraphFastapi = { - command: 'poetry install', - name: 'LG FastAPI', - cwd: path.join(integrationsRoot, 'langgraph/examples/python'), - env: { - POETRY_VIRTUALENVS_IN_PROJECT: "false" - } -} - -// Langgraph (Platorm {typescript}) -const langgraphPlatformTypescript = { - command: 'pnpm install', - name: 'LG Platform TS', - cwd: path.join(integrationsRoot, 'langgraph/examples/typescript/'), -} - -// Llama Index -const llamaIndex = { - command: 'uv sync', - name: 'Llama Index', - cwd: path.join(integrationsRoot, 'llamaindex/server-py'), -} - -// Mastra -const mastra = { - command: 'npm install', - name: 'Mastra', - cwd: path.join(integrationsRoot, 'mastra/example'), -} - -// Pydantic AI -const pydanticAi = { - command: 'uv sync', - name: 'Pydantic AI', - cwd: path.join(integrationsRoot, 'pydantic-ai/examples'), -} - -// THE ACTUAL DOJO -const dojo = { - command: 'pnpm install --no-frozen-lockfile && pnpm build --filter=demo-viewer...', - name: 'Dojo', - cwd: path.join(gitRoot, 'typescript-sdk'), -} +// Define all prep targets keyed by a stable id +const ALL_TARGETS = { + 'server-starter': { + command: 'poetry install', + name: 'Server Starter', + cwd: path.join(integrationsRoot, 'server-starter/server/python'), + }, + 'server-starter-all': { + command: 'poetry install', + name: 'Server AF', + cwd: path.join(integrationsRoot, 'server-starter-all-features/server/python'), + }, + 'agno': { + command: 'uv sync', + name: 'Agno', + cwd: path.join(integrationsRoot, 'agno/examples'), + }, + 'crew-ai': { + command: 'poetry install', + name: 'CrewAI', + cwd: path.join(integrationsRoot, 'crewai/python'), + }, + 'langgraph-fastapi': { + command: 'poetry install', + name: 'LG FastAPI', + cwd: path.join(integrationsRoot, 'langgraph/examples/python'), + env: { + POETRY_VIRTUALENVS_IN_PROJECT: "false", + }, + }, + 'langgraph-platform-typescript': { + command: 'pnpm install', + name: 'LG Platform TS', + cwd: path.join(integrationsRoot, 'langgraph/examples/typescript/'), + }, + 'llama-index': { + command: 'uv sync', + name: 'Llama Index', + cwd: path.join(integrationsRoot, 'llamaindex/server-py'), + }, + 'mastra': { + command: 'npm install', + name: 'Mastra', + cwd: path.join(integrationsRoot, 'mastra/example'), + }, + 'pydantic-ai': { + command: 'uv sync', + name: 'Pydantic AI', + cwd: path.join(integrationsRoot, 'pydantic-ai/examples'), + }, + 'dojo': { + command: 'pnpm install --no-frozen-lockfile && pnpm build --filter=demo-viewer...', + name: 'Dojo', + cwd: path.join(gitRoot, 'typescript-sdk'), + }, +}; function printDryRunServices(procs) { console.log('Dry run - would install dependencies for the following services:'); @@ -113,18 +116,25 @@ function printDryRunServices(procs) { } async function main() { - const procs = [ - serverStarter, - serverStarterAllFeatures, - agno, - crewai, - langgraphFastapi, - langgraphPlatformTypescript, - llamaIndex, - mastra, - pydanticAi, - dojo - ]; + // determine selection + let selectedKeys = Object.keys(ALL_TARGETS); + if (onlyList && onlyList.length) { + selectedKeys = onlyList; + } + if (excludeList && excludeList.length) { + selectedKeys = selectedKeys.filter((k) => !excludeList.includes(k)); + } + + // Build procs list, warning on unknown keys + const procs = []; + for (const key of selectedKeys) { + const target = ALL_TARGETS[key]; + if (!target) { + console.warn(`Skipping unknown service: ${key}`); + continue; + } + procs.push(target); + } if (dryRun) { printDryRunServices(procs); diff --git a/typescript-sdk/apps/dojo/scripts/run-dojo-everything.js b/typescript-sdk/apps/dojo/scripts/run-dojo-everything.js index ec945ec00..599fe373b 100755 --- a/typescript-sdk/apps/dojo/scripts/run-dojo-everything.js +++ b/typescript-sdk/apps/dojo/scripts/run-dojo-everything.js @@ -9,17 +9,35 @@ const args = process.argv.slice(2); const showHelp = args.includes('--help') || args.includes('-h'); const dryRun = args.includes('--dry-run'); +function parseList(flag) { + const idx = args.indexOf(flag); + if (idx !== -1 && args[idx + 1]) { + return args[idx + 1] + .split(',') + .map((s) => s.trim()) + .filter(Boolean); + } + return null; +} + +const onlyList = parseList('--only') || parseList('--include'); +const excludeList = parseList('--exclude') || []; + if (showHelp) { console.log(` Usage: node run-dojo-everything.js [options] Options: --dry-run Show what would be started without actually running + --only list Comma-separated services to include (defaults to all) + --exclude list Comma-separated services to exclude --help, -h Show this help message Examples: node run-dojo-everything.js node run-dojo-everything.js --dry-run + node run-dojo-everything.js --only dojo,server-starter + node run-dojo-everything.js --exclude crew-ai,mastra `); process.exit(0); } @@ -27,123 +45,91 @@ Examples: const gitRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim(); const integrationsRoot = path.join(gitRoot, 'typescript-sdk', 'integrations'); -// Server Starter -const serverStarter = { - command: 'poetry run dev', - name: 'Server Starter', - cwd: path.join(integrationsRoot, 'server-starter/server/python'), - env: {PORT: 8000}, -} - -// Server Starter All Features -const serverStarterAllFeatures = { - command: 'poetry run dev', - name: 'Server AF', - cwd: path.join(integrationsRoot, 'server-starter-all-features/server/python'), - env: {PORT: 8001}, -} - -// Agno -const agno = { - command: 'uv run dev', - name: 'Agno', - cwd: path.join(integrationsRoot, 'agno/examples'), - env: {PORT: 8002}, -} - -// CrewAI -const crewai = { - command: 'poetry run dev', - name: 'CrewAI', - cwd: path.join(integrationsRoot, 'crewai/python'), - env: {PORT: 8003}, -} - -// Langgraph (FastAPI) -const langgraphFastapi = { - command: 'poetry run dev', - name: 'LG FastAPI', - cwd: path.join(integrationsRoot, 'langgraph/examples/python'), - env: { - PORT: 8004, - POETRY_VIRTUALENVS_IN_PROJECT: "false" +// Define all runnable services keyed by a stable id +const ALL_SERVICES = { + 'server-starter': { + command: 'poetry run dev', + name: 'Server Starter', + cwd: path.join(integrationsRoot, 'server-starter/server/python'), + env: { PORT: 8000 }, }, -} - -// Langgraph (Platform {python}) -const langgraphPlatformPython = { - command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --port 8005', - name: 'LG Platform Py', - cwd: path.join(integrationsRoot, 'langgraph/examples/python'), - env: {PORT: 8005}, -} - -// Langgraph (Platform {typescript}) -const langgraphPlatformTypescript = { - command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --port 8006', - name: 'LG Platform TS', - cwd: path.join(integrationsRoot, 'langgraph/examples/typescript/'), - env: {PORT: 8006}, -} - -// Llama Index -const llamaIndex = { - command: 'uv run dev', - name: 'Llama Index', - cwd: path.join(integrationsRoot, 'llamaindex/server-py'), - env: {PORT: 8007}, -} - -// Mastra -const mastra = { - command: 'npm run dev', - name: 'Mastra', - cwd: path.join(integrationsRoot, 'mastra/example'), - env: {PORT: 8008}, -} - -// Pydantic AI -const pydanticAi = { - command: 'uv run dev', - name: 'Pydantic AI', - cwd: path.join(integrationsRoot, 'pydantic-ai/examples'), - env: {PORT: 8009}, -} - -// THE ACTUAL DOJO -const dojo = { - command: 'pnpm run start', - name: 'Dojo', - cwd: path.join(gitRoot, 'typescript-sdk/apps/dojo'), - env: { - PORT: 9999, - SERVER_STARTER_URL: 'http://localhost:8000', - SERVER_STARTER_ALL_FEATURES_URL: 'http://localhost:8001', - AGNO_URL: 'http://localhost:8002', - CREW_AI_URL: 'http://localhost:8003', - LANGGRAPH_FAST_API_URL: 'http://localhost:8004', - LANGGRAPH_PYTHON_URL: 'http://localhost:8005', - LANGGRAPH_TYPESCRIPT_URL: 'http://localhost:8006', - LLAMA_INDEX_URL: 'http://localhost:8007', - MASTRA_URL: 'http://localhost:8008', - PYDANTIC_AI_URL: 'http://localhost:8009', - NEXT_PUBLIC_CUSTOM_DOMAIN_TITLE: 'cpkdojo.local___CopilotKit Feature Viewer', - } -} - -const procs = [ - serverStarter, - serverStarterAllFeatures, - agno, - crewai, - langgraphFastapi, - langgraphPlatformPython, - langgraphPlatformTypescript, - llamaIndex, - mastra, - pydanticAi, - dojo -]; + 'server-starter-all': { + command: 'poetry run dev', + name: 'Server AF', + cwd: path.join(integrationsRoot, 'server-starter-all-features/server/python'), + env: { PORT: 8001 }, + }, + 'agno': { + command: 'uv run dev', + name: 'Agno', + cwd: path.join(integrationsRoot, 'agno/examples'), + env: { PORT: 8002 }, + }, + 'crew-ai': { + command: 'poetry run dev', + name: 'CrewAI', + cwd: path.join(integrationsRoot, 'crewai/python'), + env: { PORT: 8003 }, + }, + 'langgraph-fastapi': { + command: 'poetry run dev', + name: 'LG FastAPI', + cwd: path.join(integrationsRoot, 'langgraph/examples/python'), + env: { + PORT: 8004, + POETRY_VIRTUALENVS_IN_PROJECT: 'false', + }, + }, + 'langgraph-platform-python': { + command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --host 127.0.0.1 --port 8005', + name: 'LG Platform Py', + cwd: path.join(integrationsRoot, 'langgraph/examples/python'), + env: { PORT: 8005 }, + }, + 'langgraph-platform-typescript': { + command: 'pnpx @langchain/langgraph-cli@latest dev --no-browser --host 127.0.0.1 --port 8006', + name: 'LG Platform TS', + cwd: path.join(integrationsRoot, 'langgraph/examples/typescript/'), + env: { PORT: 8006 }, + }, + 'llama-index': { + command: 'uv run dev', + name: 'Llama Index', + cwd: path.join(integrationsRoot, 'llamaindex/server-py'), + env: { PORT: 8007 }, + }, + 'mastra': { + command: 'npm run dev', + name: 'Mastra', + cwd: path.join(integrationsRoot, 'mastra/example'), + env: { PORT: 8008 }, + }, + 'pydantic-ai': { + command: 'uv run dev', + name: 'Pydantic AI', + cwd: path.join(integrationsRoot, 'pydantic-ai/examples'), + env: { PORT: 8009 }, + }, + 'dojo': { + command: 'pnpm run start', + name: 'Dojo', + cwd: path.join(gitRoot, 'typescript-sdk/apps/dojo'), + env: { + PORT: 9999, + SERVER_STARTER_URL: 'http://localhost:8000', + SERVER_STARTER_ALL_FEATURES_URL: 'http://localhost:8001', + AGNO_URL: 'http://localhost:8002', + CREW_AI_URL: 'http://localhost:8003', + LANGGRAPH_FAST_API_URL: 'http://localhost:8004', + LANGGRAPH_PYTHON_URL: 'http://localhost:8005', + LANGGRAPH_TYPESCRIPT_URL: 'http://localhost:8006', + LLAMA_INDEX_URL: 'http://localhost:8007', + MASTRA_URL: 'http://localhost:8008', + PYDANTIC_AI_URL: 'http://localhost:8009', + NEXT_PUBLIC_CUSTOM_DOMAIN_TITLE: 'cpkdojo.local___CopilotKit Feature Viewer', + }, + }, +}; function printDryRunServices(procs) { console.log('Dry run - would start the following services:'); @@ -164,18 +150,40 @@ function printDryRunServices(procs) { } async function main() { + // determine selection + let selectedKeys = Object.keys(ALL_SERVICES); + if (onlyList && onlyList.length) { + selectedKeys = onlyList; + } + if (excludeList && excludeList.length) { + selectedKeys = selectedKeys.filter((k) => !excludeList.includes(k)); + } + + // Build processes, warn for unknown keys + const procs = []; + for (const key of selectedKeys) { + const svc = ALL_SERVICES[key]; + if (!svc) { + console.warn(`Skipping unknown service: ${key}`); + continue; + } + procs.push(svc); + } + if (dryRun) { printDryRunServices(procs); } - console.log('Starting services: ', procs.map(p => p.name).join(', ')); + console.log('Starting services: ', procs.map((p) => p.name).join(', ')); - const {result} = concurrently(procs, {killOthersOn: ['failure', 'success']}); + const { result } = concurrently(procs, { killOthersOn: ['failure', 'success'] }); - result.then(() => process.exit(0)).catch((err) => { - console.error(err); - process.exit(1); - }); + result + .then(() => process.exit(0)) + .catch((err) => { + console.error(err); + process.exit(1); + }); } main(); diff --git a/typescript-sdk/apps/dojo/src/files.json b/typescript-sdk/apps/dojo/src/files.json index 63235cda3..c00d723f0 100644 --- a/typescript-sdk/apps/dojo/src/files.json +++ b/typescript-sdk/apps/dojo/src/files.json @@ -72,7 +72,7 @@ }, { "name": "agentic_generative_ui.py", - "content": "\"\"\"Agentic Generative UI feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom textwrap import dedent\nfrom typing import Any, Literal\n\nfrom pydantic import BaseModel, Field\n\nfrom ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent\nfrom pydantic_ai import Agent\n\nStepStatus = Literal['pending', 'completed']\n\n\nclass Step(BaseModel):\n \"\"\"Represents a step in a plan.\"\"\"\n\n description: str = Field(description='The description of the step')\n status: StepStatus = Field(\n default='pending',\n description='The status of the step (e.g., pending, completed)',\n )\n\n\nclass Plan(BaseModel):\n \"\"\"Represents a plan with multiple steps.\"\"\"\n\n steps: list[Step] = Field(default_factory=list, description='The steps in the plan')\n\n\nclass JSONPatchOp(BaseModel):\n \"\"\"A class representing a JSON Patch operation (RFC 6902).\"\"\"\n\n op: Literal['add', 'remove', 'replace', 'move', 'copy', 'test'] = Field(\n description='The operation to perform: add, remove, replace, move, copy, or test',\n )\n path: str = Field(description='JSON Pointer (RFC 6901) to the target location')\n value: Any = Field(\n default=None,\n description='The value to apply (for add, replace operations)',\n )\n from_: str | None = Field(\n default=None,\n alias='from',\n description='Source path (for move, copy operations)',\n )\n\n\nagent = Agent(\n 'openai:gpt-4o-mini',\n instructions=dedent(\n \"\"\"\n When planning use tools only, without any other messages.\n IMPORTANT:\n - Use the `create_plan` tool to set the initial state of the steps\n - Use the `update_plan_step` tool to update the status of each step\n - Do NOT repeat the plan or summarise it in a message\n - Do NOT confirm the creation or updates in a message\n - Do NOT ask the user for additional information or next steps\n\n Only one plan can be active at a time, so do not call the `create_plan` tool\n again until all the steps in current plan are completed.\n \"\"\"\n ),\n)\n\n\n@agent.tool_plain\nasync def create_plan(steps: list[str]) -> StateSnapshotEvent:\n \"\"\"Create a plan with multiple steps.\n\n Args:\n steps: List of step descriptions to create the plan.\n\n Returns:\n StateSnapshotEvent containing the initial state of the steps.\n \"\"\"\n plan: Plan = Plan(\n steps=[Step(description=step) for step in steps],\n )\n return StateSnapshotEvent(\n type=EventType.STATE_SNAPSHOT,\n snapshot=plan.model_dump(),\n )\n\n\n@agent.tool_plain\nasync def update_plan_step(\n index: int, description: str | None = None, status: StepStatus | None = None\n) -> StateDeltaEvent:\n \"\"\"Update the plan with new steps or changes.\n\n Args:\n index: The index of the step to update.\n description: The new description for the step.\n status: The new status for the step.\n\n Returns:\n StateDeltaEvent containing the changes made to the plan.\n \"\"\"\n changes: list[JSONPatchOp] = []\n if description is not None:\n changes.append(\n JSONPatchOp(\n op='replace', path=f'/steps/{index}/description', value=description\n )\n )\n if status is not None:\n changes.append(\n JSONPatchOp(op='replace', path=f'/steps/{index}/status', value=status)\n )\n return StateDeltaEvent(\n type=EventType.STATE_DELTA,\n delta=changes,\n )\n\n\napp = agent.to_ag_ui()\n", + "content": "\"\"\"Agentic Generative UI feature.\"\"\"\n\nfrom __future__ import annotations\n\nfrom textwrap import dedent\nfrom typing import Any, Literal\n\nfrom pydantic import BaseModel, Field\n\nfrom ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent\nfrom pydantic_ai import Agent\n\nStepStatus = Literal['pending', 'completed']\n\n\nclass Step(BaseModel):\n \"\"\"Represents a step in a plan.\"\"\"\n\n description: str = Field(description='The description of the step')\n status: StepStatus = Field(\n default='pending',\n description='The status of the step (e.g., pending, completed)',\n )\n\n\nclass Plan(BaseModel):\n \"\"\"Represents a plan with multiple steps.\"\"\"\n\n steps: list[Step] = Field(default_factory=list, description='The steps in the plan')\n\n\nclass JSONPatchOp(BaseModel):\n \"\"\"A class representing a JSON Patch operation (RFC 6902).\"\"\"\n\n op: Literal['add', 'remove', 'replace', 'move', 'copy', 'test'] = Field(\n description='The operation to perform: add, remove, replace, move, copy, or test',\n )\n path: str = Field(description='JSON Pointer (RFC 6901) to the target location')\n value: Any = Field(\n default=None,\n description='The value to apply (for add, replace operations)',\n )\n from_: str | None = Field(\n default=None,\n alias='from',\n description='Source path (for move, copy operations)',\n )\n\n\nagent = Agent(\n 'openai:gpt-4o-mini',\n instructions=dedent(\n \"\"\"\n When planning use tools only, without any other messages.\n IMPORTANT:\n - Use the `create_plan` tool to set the initial state of the steps\n - Use the `update_plan_step` tool to update the status of each step\n - Do NOT repeat the plan or summarise it in a message\n - Do NOT confirm the creation or updates in a message\n - Do NOT ask the user for additional information or next steps\n - Do NOT leave a plan hanging, always complete the plan via `update_plan_step` if one is ongoing.\n\n Only one plan can be active at a time, so do not call the `create_plan` tool\n again until all the steps in current plan are completed.\n \"\"\"\n ),\n)\n\n\n@agent.tool_plain\nasync def create_plan(steps: list[str]) -> StateSnapshotEvent:\n \"\"\"Create a plan with multiple steps.\n\n Args:\n steps: List of step descriptions to create the plan.\n\n Returns:\n StateSnapshotEvent containing the initial state of the steps.\n \"\"\"\n plan: Plan = Plan(\n steps=[Step(description=step) for step in steps],\n )\n return StateSnapshotEvent(\n type=EventType.STATE_SNAPSHOT,\n snapshot=plan.model_dump(),\n )\n\n\n@agent.tool_plain\nasync def update_plan_step(\n index: int, description: str | None = None, status: StepStatus | None = None\n) -> StateDeltaEvent:\n \"\"\"Update the plan with new steps or changes.\n\n Args:\n index: The index of the step to update.\n description: The new description for the step.\n status: The new status for the step.\n\n Returns:\n StateDeltaEvent containing the changes made to the plan.\n \"\"\"\n changes: list[JSONPatchOp] = []\n if description is not None:\n changes.append(\n JSONPatchOp(\n op='replace', path=f'/steps/{index}/description', value=description\n )\n )\n if status is not None:\n changes.append(\n JSONPatchOp(op='replace', path=f'/steps/{index}/status', value=status)\n )\n return StateDeltaEvent(\n type=EventType.STATE_DELTA,\n delta=changes,\n )\n\n\napp = agent.to_ag_ui()\n", "language": "python", "type": "file" } diff --git a/typescript-sdk/integrations/pydantic-ai/examples/server/api/agentic_generative_ui.py b/typescript-sdk/integrations/pydantic-ai/examples/server/api/agentic_generative_ui.py index e6043a554..34f52fa86 100644 --- a/typescript-sdk/integrations/pydantic-ai/examples/server/api/agentic_generative_ui.py +++ b/typescript-sdk/integrations/pydantic-ai/examples/server/api/agentic_generative_ui.py @@ -58,6 +58,7 @@ class JSONPatchOp(BaseModel): - Do NOT repeat the plan or summarise it in a message - Do NOT confirm the creation or updates in a message - Do NOT ask the user for additional information or next steps + - Do NOT leave a plan hanging, always complete the plan via `update_plan_step` if one is ongoing. Only one plan can be active at a time, so do not call the `create_plan` tool again until all the steps in current plan are completed.