From c2543dda68abcbcfbe19206d707b0bbb3f3ac9c2 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Mon, 18 May 2026 10:44:51 -0700 Subject: [PATCH] Remove chat protocol e2e target --- .github/workflows/ci.yml | 46 +----- AGENTS.md | 2 +- examples/chat/e2e/project.json | 14 -- examples/chat/e2e/src/.gitkeep | 0 examples/chat/e2e/src/protocol.e2e.spec.ts | 157 --------------------- examples/chat/e2e/tsconfig.json | 7 - examples/chat/e2e/vite.config.mts | 16 --- scripts/ci-scope.mjs | 1 - scripts/ci-scope.spec.mjs | 20 --- scripts/ci-workflow.spec.mjs | 23 --- tsconfig.json | 3 - 11 files changed, 2 insertions(+), 287 deletions(-) delete mode 100644 examples/chat/e2e/project.json delete mode 100644 examples/chat/e2e/src/.gitkeep delete mode 100644 examples/chat/e2e/src/protocol.e2e.spec.ts delete mode 100644 examples/chat/e2e/tsconfig.json delete mode 100644 examples/chat/e2e/vite.config.mts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1ec53871..10fa471a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -253,45 +253,6 @@ jobs: path: examples/chat/angular/e2e/test-results/ retention-days: 7 - examples-chat-protocol-e2e: - name: examples/chat — protocol e2e - needs: ci-scope - if: github.event_name == 'push' || needs.ci-scope.outputs.examples_chat == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6.0.2 - - uses: actions/setup-node@v6.3.0 - with: - node-version: 22 - cache: npm - - name: Install uv - uses: astral-sh/setup-uv@v8.0.0 - with: - python-version: '3.12' - - run: npm ci - - working-directory: examples/chat/python - run: uv sync - - name: Start LangGraph dev server - working-directory: examples/chat/python - run: uv run langgraph dev --no-browser & - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} - LANGSMITH_TRACING: 'true' - LANGSMITH_PROJECT: examples-chat-protocol-e2e-ci - - name: Wait for server to be ready - run: | - echo "Waiting for LangGraph server..." - for i in {1..30}; do - curl -sf http://localhost:2024/ok && echo "Server ready" && break - echo "Attempt $i/30..." - sleep 2 - done - curl -sf http://localhost:2024/ok || (echo "Server failed to start after 60s" && exit 1) - - run: npx nx e2e examples-chat-protocol-e2e --skip-nx-cache - env: - LANGGRAPH_URL: http://localhost:2024 - cockpit-e2e: name: Cockpit — e2e needs: ci-scope @@ -374,7 +335,6 @@ jobs: cockpit-deploy-smoke, examples-chat-smoke, examples-chat-e2e, - examples-chat-protocol-e2e, cockpit-e2e, website-e2e, ] @@ -544,7 +504,7 @@ jobs: demo-deploy: name: Canonical demo → Vercel - needs: [examples-chat-smoke, examples-chat-e2e, examples-chat-protocol-e2e] + needs: [examples-chat-smoke, examples-chat-e2e] runs-on: ubuntu-latest if: ${{ always() && !cancelled() && github.ref == 'refs/heads/main' && github.event_name == 'push' }} steps: @@ -558,10 +518,6 @@ jobs: echo "::error::examples/chat — e2e finished with ${{ needs.examples-chat-e2e.result }}; refusing to deploy the canonical demo." exit 1 fi - if [ "${{ needs.examples-chat-protocol-e2e.result }}" != "success" ]; then - echo "::error::examples/chat — protocol e2e finished with ${{ needs.examples-chat-protocol-e2e.result }}; refusing to deploy the canonical demo." - exit 1 - fi - uses: actions/checkout@v6.0.2 - uses: actions/setup-node@v6.3.0 with: diff --git a/AGENTS.md b/AGENTS.md index e8570fd6d..65a104a5a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ This file is for agents working in this repository. It is contributor-facing, no - `libs/langgraph`: main Angular library (`@ngaf/langgraph`). - `apps/website`: docs and marketing site. -- `examples/chat/e2e`: protocol end-to-end coverage for the canonical chat example. +- `examples/chat/angular/e2e`: browser end-to-end coverage for the canonical chat example. ## Working in This Repo diff --git a/examples/chat/e2e/project.json b/examples/chat/e2e/project.json deleted file mode 100644 index 609fba035..000000000 --- a/examples/chat/e2e/project.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "examples-chat-protocol-e2e", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", - "projectType": "application", - "sourceRoot": "examples/chat/e2e/src", - "targets": { - "e2e": { - "executor": "@nx/vitest:test", - "options": { - "configFile": "examples/chat/e2e/vite.config.mts" - } - } - } -} diff --git a/examples/chat/e2e/src/.gitkeep b/examples/chat/e2e/src/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/chat/e2e/src/protocol.e2e.spec.ts b/examples/chat/e2e/src/protocol.e2e.spec.ts deleted file mode 100644 index 458a1ce44..000000000 --- a/examples/chat/e2e/src/protocol.e2e.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { beforeAll, describe, expect, it } from 'vitest'; -import { Client } from '@langchain/langgraph-sdk'; - -const LANGGRAPH_URL = process.env['LANGGRAPH_URL'] ?? 'http://localhost:2024'; - -interface MessageLike { - content?: unknown; - kwargs?: { - content?: unknown; - type?: string; - }; - role?: string; - type?: string; -} - -function messageText(message: MessageLike | undefined): string { - const content = message?.content ?? message?.kwargs?.content; - if (typeof content === 'string') { - return content; - } - if (Array.isArray(content)) { - return content.map((part) => { - if (typeof part === 'string') { - return part; - } - if (part && typeof part === 'object' && 'text' in part) { - return String(part.text ?? ''); - } - return JSON.stringify(part); - }).join(''); - } - return content == null ? '' : String(content); -} - -function messageType(message: MessageLike): string | undefined { - return message.type ?? message.role ?? message.kwargs?.type; -} - -function lastMessageOfType(messages: MessageLike[], type: string): MessageLike | undefined { - return [...messages].reverse().find((message) => messageType(message) === type); -} - -function messagesContainText(messages: MessageLike[], text: string): boolean { - return messages.some((message) => messageText(message).includes(text)); -} - -/** - * Protocol end-to-end coverage for the canonical chat LangGraph server. - * - * Prerequisites: - * - `langgraph dev` must be running (examples/chat/python/) - * - Set LANGGRAPH_URL=http://localhost:2024 (or deployed URL) - * - * This backs the server-side invariants behind examples/chat/smoke/CHECKLIST.md. - * Browser rendering remains covered by examples/chat/angular/e2e. - */ -describe.skipIf(!process.env['LANGGRAPH_URL'])('examples/chat protocol e2e', () => { - let client: Client; - - beforeAll(() => { - client = new Client({ apiUrl: LANGGRAPH_URL }); - }); - - it('serves the readiness endpoint used by smoke pre-flight', async () => { - const response = await fetch(new URL('/ok', LANGGRAPH_URL)); - - await expect(response.json()).resolves.toEqual({ ok: true }); - }); - - it('streams messages from the chat graph', async () => { - const thread = await client.threads.create(); - const chunks: unknown[] = []; - - for await (const chunk of client.runs.stream( - thread.thread_id, - 'chat', - { - input: { messages: [{ role: 'human', content: 'Say exactly: pong' }] }, - streamMode: 'messages', - }, - )) { - chunks.push(chunk); - } - - expect(chunks.length).toBeGreaterThan(0); - const messageChunks = chunks.filter( - (c: any) => c.event === 'messages/partial' || c.event === 'messages/metadata' - ); - expect(messageChunks.length).toBeGreaterThan(0); - }); - - it('persists messages across turns on the same thread', async () => { - const thread = await client.threads.create(); - - // First turn: plant the secret word - for await (const _ of client.runs.stream( - thread.thread_id, - 'chat', - { - input: { - messages: [{ role: 'human', content: 'My secret word is: PINEAPPLE.' }], - }, - streamMode: 'values', - }, - )) { - /* consume to completion */ - } - - // Second turn: ask for recall - const chunks: unknown[] = []; - for await (const chunk of client.runs.stream( - thread.thread_id, - 'chat', - { - input: { messages: [{ role: 'human', content: 'What is my secret word?' }] }, - streamMode: 'values', - }, - )) { - chunks.push(chunk); - } - - const valueChunks = chunks.filter((c: any) => c.event === 'values'); - const finalChunk = valueChunks[valueChunks.length - 1] as any; - const messages: MessageLike[] = finalChunk?.data?.messages ?? []; - expect(messagesContainText(messages, 'My secret word is: PINEAPPLE.')).toBe(true); - expect(messagesContainText(messages, 'What is my secret word?')).toBe(true); - expect(messageText(lastMessageOfType(messages, 'ai')).length).toBeGreaterThan(0); - }); - - it('accepts system_prompt configuration per thread', async () => { - const thread = await client.threads.create(); - const chunks: unknown[] = []; - - for await (const chunk of client.runs.stream( - thread.thread_id, - 'chat', - { - input: { messages: [{ role: 'human', content: 'What are you?' }] }, - config: { - configurable: { - system_prompt: 'You are a pirate. Always respond in pirate speak.', - }, - }, - streamMode: 'values', - }, - )) { - chunks.push(chunk); - } - - const valueChunks = chunks.filter((c: any) => c.event === 'values'); - expect(valueChunks.length).toBeGreaterThan(0); - const finalChunk = valueChunks[valueChunks.length - 1] as any; - const messages: MessageLike[] = finalChunk?.data?.messages ?? []; - expect(messagesContainText(messages, 'What are you?')).toBe(true); - expect(messageText(lastMessageOfType(messages, 'ai')).length).toBeGreaterThan(0); - }); -}); diff --git a/examples/chat/e2e/tsconfig.json b/examples/chat/e2e/tsconfig.json deleted file mode 100644 index 101625bc7..000000000 --- a/examples/chat/e2e/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "types": ["node", "vitest/globals"] - }, - "include": ["src/**/*.ts"] -} diff --git a/examples/chat/e2e/vite.config.mts b/examples/chat/e2e/vite.config.mts deleted file mode 100644 index 25a0645f4..000000000 --- a/examples/chat/e2e/vite.config.mts +++ /dev/null @@ -1,16 +0,0 @@ -import { defineConfig } from 'vite'; -import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; -import { fileURLToPath } from 'node:url'; - -const projectRoot = fileURLToPath(new URL('.', import.meta.url)); - -export default defineConfig({ - root: projectRoot, - plugins: [nxViteTsPaths()], - test: { - globals: true, - environment: 'node', // HTTP requests, not browser - testTimeout: 60000, // LLM calls can be slow - include: ['src/**/*.e2e.spec.ts'], - }, -}); diff --git a/scripts/ci-scope.mjs b/scripts/ci-scope.mjs index 1b91b4d62..d665c07d9 100644 --- a/scripts/ci-scope.mjs +++ b/scripts/ci-scope.mjs @@ -221,7 +221,6 @@ function applyFallbackPathScope(scope, changedFile) { function isGlobalCiFile(changedFile) { return ( changedFile === '.github/workflows/ci.yml' || - changedFile === '.github/workflows/e2e.yml' || changedFile === 'package.json' || changedFile === 'package-lock.json' || changedFile === 'nx.json' || diff --git a/scripts/ci-scope.spec.mjs b/scripts/ci-scope.spec.mjs index 40844d912..1ec29b9c0 100644 --- a/scripts/ci-scope.spec.mjs +++ b/scripts/ci-scope.spec.mjs @@ -52,14 +52,6 @@ const projects = [ tags: [], targets: { build: {} }, }, - { - name: 'examples-chat-protocol-e2e', - root: 'examples/chat/e2e', - sourceRoot: 'examples/chat/e2e/src', - projectType: 'application', - tags: [], - targets: { e2e: {} }, - }, ]; const workspace = { @@ -122,18 +114,6 @@ test('global CI config changes keep full PR coverage', () => { assert.deepEqual(Object.values(scope), Object.values(scope).map(() => true)); }); -test('standalone E2E workflow changes keep full PR coverage', () => { - const scope = classifyChangedFiles(['.github/workflows/e2e.yml'], workspace); - - assert.deepEqual(Object.values(scope), Object.values(scope).map(() => true)); -}); - -test('examples chat protocol e2e changes drive examples chat scope', () => { - const scope = classifyChangedFiles(['examples/chat/e2e/src/protocol.e2e.spec.ts'], workspace); - - assert.equal(scope.examples_chat, true); -}); - test('unowned docs changes do not trigger heavy CI jobs', () => { const scope = classifyChangedFiles(['docs/notes.md'], workspace); diff --git a/scripts/ci-workflow.spec.mjs b/scripts/ci-workflow.spec.mjs index 669fba1f6..157c042f9 100644 --- a/scripts/ci-workflow.spec.mjs +++ b/scripts/ci-workflow.spec.mjs @@ -15,14 +15,6 @@ describe('CI workflow', () => { ); } - async function readDemoDeployJob() { - const workflow = await readWorkflow(); - return workflow.slice( - workflow.indexOf(' demo-deploy:'), - workflow.indexOf(' production-smoke:') - ); - } - async function readProductionSmokeJob() { const workflow = await readWorkflow(); return workflow.slice( @@ -70,19 +62,4 @@ describe('CI workflow', () => { ); }); - it('runs examples chat protocol e2e before deploying the canonical demo', async () => { - const workflow = await readWorkflow(); - const demoDeployJob = await readDemoDeployJob(); - - assert.match(workflow, /examples-chat-protocol-e2e:/); - assert.match(workflow, /npx nx e2e examples-chat-protocol-e2e --skip-nx-cache/); - assert.match( - demoDeployJob, - /needs:\s*\[examples-chat-smoke,\s*examples-chat-e2e,\s*examples-chat-protocol-e2e\]/ - ); - assert.match( - demoDeployJob, - /examples\/chat — protocol e2e finished with \$\{\{ needs\.examples-chat-protocol-e2e\.result \}\}/ - ); - }); }); diff --git a/tsconfig.json b/tsconfig.json index c6692fc1f..d63728356 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -60,9 +60,6 @@ { "path": "./libs/telemetry" }, - { - "path": "./examples/chat/e2e" - }, { "path": "./tools/posthog" },