Moves all 239 showcase integration routes off
`copilotRuntimeNextJSAppRouterEndpoint`, the deprecated v1 Next.js adapter, so
the v1 entrypoint has no remaining code-level users under
`showcase/integrations/`.
const copilotHandler = createCopilotRuntimeHandler({
runtime,
basePath: "/api/copilotkit-x",
mode: "single-route",
});
...
return await copilotHandler(req);
## Why single-route, and why this handler
**Single-route** because these demos' frontends are
`<CopilotKit runtimeUrl="/api/copilotkit-x">` with no transport prop, and every
released provider pins the single-route transport. Single-route mode is
therefore a drop-in for the v1 adapter: no frontend change, no path change, no
`GET` export, and nothing here probes `/info`. Migrating to multi-route instead
would have required editing every demo page in lockstep for no functional gain.
**`createCopilotRuntimeHandler`** rather than `createCopilotEndpointSingleRoute`
because that helper is itself deprecated in favour of the `mode` option (see the
deprecated-aliases table in `docs/backend/runtime-endpoints.mdx`), and because
the fetch handler needs no `hono` dependency and composes directly with the
wrappers these routes already have.
The statement is rewritten in place, inside whatever wrapper it already sat in,
so `withForwardedHeaders`, the try/catch envelopes, `wrapStreamingResponse` and
`withCvdiagBackend` are all untouched. 75 of these routes construct the runtime
inline in the call; rewriting in place preserves that per-request construction
exactly as v1 did. No `runner` is added — it is optional, and none of these
routes passed one before.
13 `copilotkit-auth/[[...slug]]` routes already use the v2 fetch handler and are
left alone; they only mention the v1 name in explanatory comments.
## Collateral
- `mastra`'s main route declared a module-level
`const serviceAdapter = new ExperimentalEmptyAdapter()` plus a startup log
about the adapter choice. V2 has no service adapters, so both are gone and the
comment now explains that there is nothing to configure.
- The three `mastra` vitest suites mocked `@copilotkit/runtime` and the v1
`{ handleRequest }` return shape; they now mock `@copilotkit/runtime/v2` and
`createCopilotRuntimeHandler`, which returns the handler directly.
- 27 `@ts-expect-error` directives guarded the **v1** `CopilotRuntime` agents
type ("wraps Record in MaybePromise<NonEmptyRecord<...>>"). Under `/v2` that
hole is gone, which makes the directive unused — a hard error. They are
demoted to `@ts-ignore`, which compiles whether or not the mismatch survives
in a given integration, because 19 of these apps cannot be built locally to
prove it either way. Removing all ~220 now-stale suppressions is left as
follow-up once CI has built every integration green.
## Verified
`mastra` is the one integration installed and exercised locally (19 routes, the
`withCvdiagBackend` main route, and the only vitest suites that touch routes).
Measured against `origin/main` in the same tree:
tsc --noEmit baseline: errors in 10 files
after: errors in 9 files
new errors introduced: NONE
fixed: src/app/api/copilotkit-mcp-apps/route.ts, whose
@ts-expect-error was ALREADY unused on main
vitest run baseline: 2 files failed, 13 tests failed, 21 passed
after: 2 files failed, 13 tests failed, 21 passed
→ test-neutral; those 13 failures are pre-existing on main
Structural audit over all 239 routes: none still imports the v1 root, uses the
v1 adapter, references `ExperimentalEmptyAdapter` or `handleRequest` in code, or
is missing `createCopilotRuntimeHandler` / `basePath` / `mode: "single-route"`.
The shape itself was proved end-to-end before the rollout, in a real running
app with an untouched provider (aimock as the model backend):
`POST /api/copilotkit` -> 200 twice, chat turn rendered.
## Two pre-existing problems found on the way
- `npm ci` fails in `showcase/integrations/mastra`: `Missing:
@types/http-errors@2.0.5 from lock file`. Its Dockerfile uses
`npm ci --legacy-peer-deps`, which does succeed, so the image still builds —
but a plain `npm ci` does not. Untouched here; no manifest or lockfile is in
this diff.
- `mastra`'s vitest suite is red on `main` (13 failures, mostly
`extractXHeaders` dereferencing `req.headers` on a `{}` fake request).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 integrations' `src/app/demos/layout.tsx` hardcoded "LangChain - Python"
in `generateMetadata` — a copy-paste leftover from langgraph-python, which
the file was cloned from. Every `/demos/*` page in mastra, strands, ag2,
agno and 10 others rendered `<title>LangChain - Python</title>`.
Each now uses the display name from its own `manifest.yaml` `name:` field,
matching the convention the already-correct integrations use
(langgraph-typescript -> "LangGraph (TypeScript)", strands-typescript ->
"AWS Strands (TypeScript)").
langgraph-python itself is included: its manifest name and root layout both
say "LangGraph (Python)", so "LangChain - Python" (the legacy Notion
partner-column label) was stale there too.
Option A removed the two-stage server-side A2UI planner
(generate_a2ui_via_llm, _get_a2ui_llm, _resolve_a2ui_model, _A2uiError,
_A2uiErrorKind, _RENDER_A2UI_FUNCTION_SPEC, _RENDER_A2UI_TOOL_SPEC,
_a2ui_error). The CopilotKit JS runtime A2UIMiddleware now intercepts
generate_a2ui before it reaches Python and drives the render_a2ui LLM
pass itself.
test_generate_a2ui.py was importing the removed symbols at collection
time, causing an ImportError that failed CI's "Run showcase package
Python unit tests" step for Python 3.12.
Fix: remove all tests for the deleted two-stage planner infra; rewrite
GenerateA2UITool.handle() tests to the Option A contract (handle fires
only on middleware regression, returns structured error JSON, logs
ERROR); retain all tests that remain valid: _ToolErrorKind enum identity,
backend tool handle() happy+error paths, create_agent wiring, module
hygiene (no openai at load time, clean subprocess import).
Supersedes #6058's two-stage approach (outer generate_a2ui → secondary Python
LLM call → render_a2ui), which severed across the prod streaming boundary.
Option A: `injectA2UITool` (default true) causes CopilotKit's A2UIMiddleware to
inject `render_a2ui` into RunAgentInput.tools. The langroid agui_adapter now
merges those injected tools into its OpenAI call so the LLM can call render_a2ui
directly. The middleware intercepts the tool call stream, builds a2ui_operations,
and fires RUN_FINISHED — no secondary Python LLM pass needed.
Changes:
- agent.py: remove ~550 lines of two-stage A2UI infrastructure (generate_a2ui_via_llm,
_a2ui_error, _resolve_a2ui_model, _get_a2ui_llm, etc.); replace GenerateA2UITool.handle
with a stub that logs loudly if middleware interception regresses
- agui_adapter.py: merge run_input.tools (AG-UI-injected) into the OpenAI tools list
so render_a2ui is visible to the LLM; remove set_last_user_message call
- route.ts: remove injectA2UITool: false; keep defaultCatalogId pin
- gen-ui-declarative.json: replace 9 two-stage fixtures with 4 single-stage fixtures
matching toolName: render_a2ui + context: langroid (mirrors crewai-crews Option A)
Mirrors the crewai-crews fix from #6067.
The langroid `declarative-gen-ui` D6 cell was red at turn 1 with
`reason=surface-missing`: the demo was still on the pre-D6 (D5-era) shape
while the google-adk / strands siblings had moved to the two-stage
dynamic-schema A2UI pattern. Four independent defects each blocked the
surface from painting; all four are fixed here to bring langroid to
sibling parity.
Root causes (each verified against a live isolated stack):
1. Stale suggestions + fixtures. `suggestions.ts` still offered the old
D5 pills ("Show a KPI dashboard", "pie chart of sales by region", …)
and `aimock/d6/langroid/gen-ui-declarative.json` only mocked those old
prompts. The D6 driver sends the four current business-question pills
("Show me my sales dashboard for this quarter.", etc.). Re-authored both
to the four current prompts, mirroring the google-adk north-star
(outer `generate_a2ui` no-arg → inner forced `render_a2ui` → outer
narration, three fixtures per pill).
2. Required `context` on the outer tool. `GenerateA2UITool.context` was a
required pydantic field, so the mocked outer call's `arguments: {}`
raised `ValidationError` before the tool could run — no inner call, no
surface. Made it optional (default "") to match the no-arg sibling tools.
3. Legacy functions API hid the inner tool from aimock's matcher. The
inner planner call used langroid's `functions=`/`function_call=` (legacy
OpenAI) path; aimock's `toolName` matcher only inspects the modern
`tools[]` array, so the inner `render_a2ui` fixture never matched and the
call fell through to the outer `generate_a2ui` fixture (empty surface,
wrong catalogId). Switched the inner call to the modern
`tools=`/`tool_choice=` API via a new `_RENDER_A2UI_TOOL_SPEC`. The
response extractor already reads the modern `oai_tool_calls` path first.
4. Inner call could not be discriminated per pill. langroid has no
framework middleware to forward the run's conversation into the inner
call (unlike ag_ui_adk / ag_ui_strands), so its inner user message was a
fixed generic string across all four pills. Added an explicit
last-user-turn thread (ContextVar set by the adapter, consumed by the
planner) so the pill prompt rides as the inner `userMessage` — the same
discriminator the sibling fixtures rely on.
Renderer/catalog parity: added the missing `declarative-info-row` testid
to InfoRow (turn 4) and a full `DataTable` definition + renderer
(`declarative-data-table`, turn 2), plus `trendValue` on Metric. Added
`sales-context.ts` (byte-identical dataset + composition rules to the
strands/google-adk siblings) and wired it via `chat.tsx`.
Red-green (isolated control-plane, slot 16):
- RED (3 runs, pre-fix): `d6:langroid/gen-ui-declarative` red,
turn-1 surface-missing.
- GREEN (2 runs, post-fix, --rebuild): 4/4 turns pass. aimock journal
confirms all four pills' outer `generate_a2ui` + inner `render_a2ui`
calls return 200 and emit the correct `declarative-gen-ui-catalog`
surfaces. Backend SSE verified per turn: turn-1 4 metrics + pie + bar,
turn-2 DataTable, turn-4 7 InfoRows.
Restore the 12 Python integration tools/ dirs to symlinks into
shared/python/tools. They had eroded to real, drifting copies via an
accidental stage_shared() leak (commit 534cd1efa7) — the structural root
cause of showcase divergence bugs. Symlinking re-establishes the single
source of truth; content is identical to shared (only render_a2ui naming
and the shared roll_dice/sanitize additions are adopted).
Add showcase/AGENTS.md documenting the 4 iron rules and the single-source
symlink mechanism, plus a validate-shared-symlinks CI guard (shrink-only
baseline) that fails on any NEW erosion.
Two bugs fixed:
1. Tool result event path: the `a2ui_operations` container was emitted
inside a `TextMessageContentEvent` block. The A2UI middleware only scans
`TOOL_CALL_RESULT` events for the container, so the card never mounted
and the raw JSON appeared as plain text in the chat. Fixed by emitting a
`ToolCallResultEvent` (matching the claude-sdk-python peer).
2. Operation shape: the ops used the legacy flat form
(`{"type": "create_surface", ...}`) which the renderer silently ignores.
Updated to the v0.9 nested form (`{"version": "v0.9", "createSurface":
{...}}`) used by every other working peer (claude-sdk-python, strands,
google-adk).
Also adds the missing langroid aimock D6 fixture for `gen-ui-a2ui-fixed`
(`display_flight` → tool result → confirmation text) so the D6 probe has
a mock response to drive the full surface-render assertion.
D6 cell: d6:langroid/gen-ui-a2ui-fixed red → green
Gates per-request POST + 2xx Response-status + GET health-probe logs behind SHOWCASE_ROUTE_DEBUG across 19 integrations to stay under Railway's 500-logs/sec cap, while logging non-2xx responses unconditionally so production errors stay visible.
The auth demo capped at D4 across integrations because the post-sign-out
rejection banner never rendered. The post-sign-out `agent_run_failed` is
delivered only on the agent-scoped `<CopilotChat onError>` channel — never the
provider-level `<CopilotKit onError>` the demos listened on — so the D5/D6 auth
probe's rejection-surface assertion failed and the cell was capped at D4.
Fix (applied to all 19 integrations whose auth demo reproduced the bug): wire a
stable `handleAuthError` onto the agent-scoped `<CopilotChat onError>` (keeping
the provider handler), key the error surface off auth-error STATE alone with a
clear-on-auth effect (removing the `&& !isAuthenticated` cross-slice race), and
harden the rejection-banner message fallback against nullish error events.
Scope: 19 of 20 integrations. built-in-agent already passes (renders via its
ChatErrorBoundary); claude-sdk-python adapted to its legacy/error-boundary shape.
Bump the canonical CopilotKit pin across all showcase integrations + shell
to 1.61.2 (canonical-pins.json, every package.json + package-lock.json),
which carries CopilotKit#5611: passing a catalog to the provider
(`<CopilotKit a2ui={{ catalog }}>`) now auto-enables A2UI and defaults tool
injection on, so the runtime no longer needs an explicit `a2ui` config.
Demonstrate the feature on the A2UI dynamic (declarative-gen-ui) demos by
removing the now-redundant runtime `a2ui` block (`injectA2UITool: true` +
`defaultCatalogId`) from:
- langgraph-python, langgraph-fastapi, langgraph-typescript
- strands, strands-typescript
- google-adk
The forwarded catalog supplies its own catalogId (sdk-js A2UI middleware
auto-derives `defaultCatalogId` from it), so the previous "Catalog not found"
fallback no longer applies.
Verified: validate-pins drift ratchet unchanged (38 / same hash);
langgraph-python D6 `gen-ui-declarative` green end-to-end (no Catalog-not-found).
Bump every @copilotkit/* dependency across the showcase integrations and
the shell from 1.60.2 (and stray "latest" override pins) to an exact
1.61.1 pin, and move the canonical pin source of truth to match.
Regenerate each standalone npm package-lock.json with the same
--legacy-peer-deps flag the Dockerfiles use for "npm ci".
- showcase/integrations/*/package.json + package-lock.json
- showcase/integrations/langgraph-typescript/src/agent/*
- showcase/shell/package.json + package-lock.json
- showcase/scripts/showcase-canonical-pins.json: canonical 1.60.2 to 1.61.1
aimock stays on its own version line (1.26.1). The Python copilotkit SDK
was already 0.1.94 across every requirements.txt, so no change there.
validate-pins ratchet is unchanged (FAIL=38, identical hash);
validate-parity, validate-fixture-tool-surface, and the showcase/scripts
vitest suite (2102 tests) all pass.
Aligns dependency versions across all 19 showcase integrations to current
released minor versions for the 1.60.2 release cycle.
Package families:
- @copilotkit/{a2ui-renderer, react-core, react-ui, runtime, shared, sdk-js, voice}
1.59.4 -> 1.60.2 (18 integrations already staged; ms-agent-harness-dotnet
catches up from 1.57.2)
- @ag-ui/{client, core, encoder} 0.0.55 -> 0.0.57
- @ag-ui/mastra 0.2.1-beta.2 -> 0.2.4 (stable on 0.x; 1.0.x major held back)
Includes the previously-missed ms-agent-harness-dotnet integration in the
@copilotkit/* bump, plus the @copilotkit/web-inspector override pin.
Lockfile-only reconciliation via npm install --package-lock-only
--legacy-peer-deps (cmdk@0.2.1 pre-existing react^18 peer-dep is unaffected).
Each integration's interrupt-headless demo defines a local useHeadlessInterrupt
hook around the framework useInterrupt. Slot-2 originally identified 8
quarantined integrations (claude-sdk-typescript, langgraph-{fastapi,python,
typescript}, langroid, pydantic-ai, spring-ai, strands); review-round
follow-ups extended the sweep to llamaindex, mastra, ag2, agno, and
crewai-crews (5 more integrations sharing the same byte-identical hook).
The demo-local resolve() previously fire-and-forgot copilotkit.runAgent(...)
via `void runAgent(...).catch(() => {})`. Mirroring the framework fix:
- Make resolve async, return await copilotkit.runAgent(...).
- Use a pendingRef so resolve has stable identity (drop pending from
useMemo deps).
- Type signature: resolve: (response: unknown) => Promise<unknown>.
- Wrap in try/catch + setPending(null) + console.error + rethrow,
symmetric with the framework hook.
- onRunFailed also setPending(null).
13 integrations patched byte-identically.
The injected render_a2ui tool guide instructs models to omit catalogId
("the catalog id is set by the host"), and backend-owned generate_a2ui
tools see real models omit or late-stream it. Without defaultCatalogId
the a2ui middleware falls back to the spec basic catalog, which no
showcase page registers — surfaces fail with "Catalog not found:
https://a2ui.org/specification/v0_9/basic_catalog.json" (reported on
beautiful-chat / langgraph-python).
Pin each route to the catalog its page registers: beautiful-chat ->
copilotkit://app-dashboard-catalog, declarative-gen-ui ->
declarative-gen-ui-catalog. Routes with no a2ui block never attach the
middleware and are left untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Annotates 14 integration manifests with d6_not_supported_features entries
and aligns d6_supported_features with what each backend actually implements,
so the D6 fleet probe only enumerates demos that the backend can serve.
_extract_user_input in the ag2, crewai-crews, and langroid reasoning agents
documented a str return but passed AG-UI message content straight through.
Multimodal content can be a list of parts, which would flow unmodified into
the single caller in each file (_run_reasoning_agent ->
messages=[{"role": "user", "content": user_input}]) sent to the OpenAI
chat-completions API. Coerce: str passes through, a list joins its text
parts (dict or attr form), anything else falls back to str().
Callers (one per file):
- ag2/src/agents/reasoning_agent.py:104 -> chat message at :119
- crewai-crews/src/agents/reasoning_agent.py:108 -> chat message at :123
- langroid/src/agents/reasoning_agent.py:103 -> chat message at :118
Move gen-ui-interrupt + interrupt-headless from features: to
not_supported_features: across affected integration manifests, and align
the generate-registry/generate-catalog scripts tests to the resulting
wired-feature counts (derive expected lengths from the parsed manifest
rather than hardcoding pre-quarantine numbers).
Bump canonicalCopilotKitVersion 1.59.2 -> 1.59.4 and pin every
integration's @copilotkit/* to 1.59.4 (locks regenerated). Keeps the
whole showcase on one version instead of letting the langgraph A2UI
demos deviate. Existing per-slug overrides (built-in-agent pkg.pr.new,
ms-agent-harness-dotnet 1.57.2) unchanged.
reasoning-default-render.spec.ts and shared-state-write.spec.ts navigate to
/demos/reasoning-default-render and /demos/shared-state-write respectively,
but neither demo directory exists in any integration (including the
langgraph-python gold reference) and neither is declared in any manifest.
They are stale, non-canonical leftovers from the #5127 page-mirror.
Removed from all baselines where present: reasoning-default-render from 8
(langgraph-fastapi never had it) and shared-state-write from all 9. The
parity validator stays green (0 fail) and langgraph-fastapi's prior
spec-under-coverage warning clears once its phantom spec is gone and the 5
canonical specs are added.
The 9 baseline integrations (ag2, agno, crewai-crews, langgraph-fastapi,
langroid, llamaindex, mastra, spring-ai, strands) were page-mirrored from
langgraph-python but the mirror omitted 5 LGP-canonical Playwright specs
whose demos are present on disk:
- declarative-hashbrown
- declarative-json-render
- reasoning-custom
- reasoning-default
- threadid-frontend-tool-roundtrip
Copied each spec verbatim (byte-identical) from langgraph-python, which the
baselines mirror. All 5 backing demo directories exist in every baseline.
The specs are framework-agnostic (navigate by route + testid), so no
per-integration edits are needed. Restores apples-to-apples spec parity.
Move framework-incapable features from features[] into not_supported_features[]
so the D6 harness reclassifies them as skipped-incapable instead of red:
- mastra: gen-ui-interrupt, interrupt-headless, agentic-chat-reasoning,
reasoning-default-render, tool-rendering-reasoning-chain
- langroid: mcp-apps, tool-rendering-reasoning-chain
- ag2: gen-ui-interrupt, interrupt-headless
- crewai-crews: gen-ui-interrupt, interrupt-headless, mcp-apps
- llamaindex: gen-ui-interrupt, interrupt-headless, hitl-in-chat-booking
- spring-ai: byoc-json-render
All entries already documented as incapable in each integration's PARITY_NOTES.md.
Validated via 'npm run validate-manifests' (no features/NSF overlap).