Three defects, all in the credential this branch renames.
Twelve integration quickstarts read `CPK_INTELLIGENCE_API_KEY=your_license_key`,
eleven of them under "The runtime reads the license key from step 1". The project
API key and the self-hosted license token are different credentials with
different lifetimes, and ENT-1151 exists to take the license token out of managed
setup -- so a reader who goes looking for a license key to paste finds a dead end
on the very page meant to connect them. Now `cpk-...`, and "reads the project API
key from step 1".
The placeholder prefix was wrong in the other direction on five pages, and newly
pinned that way by a test: `cpk_...`, with `cpk-...` asserted absent. A
provisioned key is `cpk-<projectId>_<short>_<long>` -- see the `cpk-` keyPrefix
in Intelligence's `apps/app-api/src/api-keys.ts` and the `parseApiKeyToken`
fixtures. No key the platform issues starts with `cpk_`, so the placeholder
taught a reader to distrust their own key. Both assertions are flipped.
The new copy guard scans every MDX page rather than listing the twelve, so a page
added next month is covered the day it lands. It reports the offending file and
value, which is how the twelve above were enumerated.
Finally, the retired-name boundary check is extracted to an exported
`retiredNameReference` and unit-tested. It is the load-bearing half of that rule
and it fails in one direction only: the canonical name ends with the retired one,
so a plain substring match reports all ~250 correct sites and the guard gets
switched off. The repo-wide scan cannot cover this -- it can say "clean", not
that the boundary is what made it clean, and it goes green either way once the
last old name is gone.
Verified: guard script exit 0; guard tests 20 passed; managed-starter-docs 10
passed (was 9); oxfmt and oxlint clean on the three changed TypeScript files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## What does this PR do?
Preserves Conventional Commit bodies while collecting release changes so
breaking-change migration guidance can reach both raw and AI-generated
release notes.
The change:
- parses `git log` with explicit field and record separators, including
multiline bodies without splitting commits;
- extracts both `BREAKING CHANGE:` and `BREAKING-CHANGE:` footers and
keeps their continuation lines;
- recognizes only the Conventional Commit `!:` marker instead of
arbitrary exclamation marks;
- shares the raw release-note renderer between the release preparation
script and focused tests;
- adds a real temporary-Git-history regression test plus unit coverage
for footer-only, `!:`-only, trailer, multiline, and empty-body cases.
The implementation is intentionally limited to `scripts/release/`.
Validation completed:
- `pnpm exec vitest run scripts/release` — 14 files, 161 tests passed
- `pnpm run build`
- full test suite, with all initially environment-sensitive projects
rerun successfully
- `pnpm run check:packages`
- `pnpm run lint` — no errors
- `pnpm run check-format`
- `pnpm run release:prepare:dry`
- `bash scripts/release/verify-release-scope-dropdowns.sh`
- targeted TypeScript and oxlint checks for all six changed files
## Related PRs and Issues
- Fixes https://github.com/CopilotKit/CopilotKit/issues/6479
- Clean, release-only follow-up to
https://github.com/CopilotKit/CopilotKit/pull/6632
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation (not applicable; internal release tooling with
regression coverage)
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
Moves the published packages from 0.0.57 to the current AG-UI release across
@ag-ui/client, core, encoder and proto — 27 declarations in 18 packages.
0.0.59 is the first release carrying the subagent protocol surface
(SUBAGENT_STARTED/FINISHED/ERROR, subagentRunId) along with the null-omission
cleanup, so this is the dependency CopilotKit's subagent work needs.
Scope is packages/** plus the release script noted below. The examples and
showcases sit on a spread of older pins (0.0.40 through 0.0.58) and are left
alone.
One behavioural change comes with the bump. channels-core ships
sanitizeAgentEventStream because @ag-ui/client used to reject a TOOL_CALL_START
carrying parentMessageId: null — the shape @ag-ui/langgraph emits for an
interrupt-triggering tool call. 0.0.59 accepts that null and treats it as
absent, so the two tests asserting the run dies WITHOUT the sanitizer no longer
hold. They now assert the run survives, and the one at agent level still checks
the tool call actually arrives so it cannot pass vacuously. The sanitizer is
untouched and its coercion tests are unchanged; it is simply no longer the
thing keeping such a run alive.
The bump also broke the packed Angular consumer matrix. That job generates a
smoke app from scripts/release/lib/angular-package.ts, whose manifest restated
"@ag-ui/client": "0.0.57" as a literal while packages/angular moved to 0.0.59.
pnpm then installed both copies and the app failed to compile:
TS2322: Type 'SmokeAgent' is not assignable to type 'AbstractAgent'.
Types have separate declarations of a private property '_debug'.
The smoke app imports AbstractAgent directly, so it has to resolve the identical
copy the library ships against. Read that version off the packed manifest --
which verify-angular-package.ts already parses for the Angular support contract
-- instead of restating it, so no future AG-UI bump can desynchronise it.
When an agent is asked to fix Inspector UI, it must start the standalone
lab and take screenshots.
This PR adds `skills/inspector-workbench/SKILL.md` next to
`inspector-docs`. `AGENTS.md` and `CLAUDE.md` point at it, so CopilotKit
employee sessions load it by default.
## What does this PR do?
- Adds the `inspector-workbench` skill. The default host is `nx run
@copilotkit/web-inspector:dev:standalone` at `http://127.0.0.1:5177`.
- Requires a screenshot after each visual change. Screenshot files go in
`.inspector-workbench/` (gitignored), not the repo root.
- Cross-links `inspector-docs` when a pane is added, renamed, or
removed.
- Registers the slug in `RESERVED_LIFECYCLE_SLUGS` so `pnpm
sync:plugin-skills` does not delete the skill.
## Related PRs and Issues
None.
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
## Testing
1. Commands run:
- `pnpm check:plugin-skills` passed (`plugin skill mirror in sync`).
- `pnpm exec vitest run scripts/__tests__/sync-plugin-skills.test.ts`
passed.
- Full package tests were not run. This change is agent instructions
plus the reserved-slug list.
2. Manual test:
1. Open `skills/inspector-workbench/SKILL.md`.
2. Confirm the default command is `nx run
@copilotkit/web-inspector:dev:standalone`.
3. Ask an agent to fix Inspector UI. Confirm it starts the lab and takes
a screenshot before it claims the UI is done.
3. How this PR makes testing easy: the reserved-slug unit test now
includes `inspector-workbench`. CI `plugin-skills-check` will run on
this path.
## Risk / rollback
Low. This is agent instruction plus a gitignore folder. Revert the PR to
undo.
The marked block that wires managed Intelligence is the region a hosted reader
copies verbatim, and nothing checked it. Both gaps were deliberate: the parity
manifest lists `src/app/api/copilotkit/**` under `allowedDivergence` for every
instance it tracks, and no `docker-compose.test.yml` sets
`COPILOTKIT_LICENSE_TOKEN`, so every smoke-tested starter takes the else arm and
the `intelligence:` arm has never run in CI.
The cost was already visible. The block's code was byte-identical in 21 of 22
starters, but its warning comment had drifted into five variants and the two
`ms-agent-framework-*` starters shipped the `demo-user` stub with no warning at
all. That drift is how the localhost default of OSS-981 survived in all 22
copies at once.
Add `scripts/validate-intelligence-wiring-block.ts`, which greps the opening
marker, compares every site against the north-star starter, and fails on the
first line that differs. Two normalisations keep it usable: the block is
dedented, because `agentcore` nests it deeper, and the else arm's runner name is
masked, because `agentcore` runs `AgentCoreRunner` in front of a Bedrock session
where an in-process runner has nothing to run. Everything else, comment text
included, must match to the byte.
Then unify the warning at all 22 sites on the fullest wording, which also says
the id must exist in Intelligence or thread operations can fail.
The check passes on day one, so it is a ratchet rather than a migration. It is a
shape gate, not a content gate: 22 identically wrong copies still pass. What it
guarantees is that a fix reaches all of them or none.
Not covered: enrolling the `intelligence:` arm in the smoke path. That needs a
license token in CI and a reachable endpoint from the compose network, and is
tracked separately.
CopilotKitIntelligence resolves apiUrl/wsUrl to the managed hosts when they are
omitted, and its own docstring says leaving both unset is always correct against
the managed service. Every starter's runtime route supplied
`?? "http://localhost:4201"` instead, so a managed reader who copied the block
got a runtime aimed at a local stack that is not running -- the failure the
starter's own .env.example warns about two files away.
Replace the fallbacks with the conditional spread these same starters already use
in channel-host.mts, so a self-hosted override still works and the managed
default applies when it is absent. Three .env.example files also set the values
uncommented, two of them directly under a comment telling the reader to leave
them unset; comment those out to match the other nineteen starters.
Guard both shapes in validate-intelligence-env-names.ts, which already polices
the canonical Intelligence key name and hosts and runs unfiltered on every PR.
The rule is the pattern rather than the literal, so a staging host substituted
for localhost fails the same way. Local e2e harnesses and demo stacks that
genuinely target a local deployment are allowlisted with their reasons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DNS is case-insensitive, so a capitalized host in prose would have slipped the
literal match. Env var names stay case-sensitive — `ignoreCase` is opt-in per
rule. The per-host reason moves onto the constant so adding a third host cannot
silently inherit the wrong message.
Also restores the TSDoc's original framing of what an override is for
("non-production or future self-hosted"), matching the runtime skill's wording
rather than diverging from it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The packaged runtime skill up to v1.62.2 prescribed `api.copilotkit.ai` /
`realtime.copilotkit.ai`. The first host is a CNAME onto the legacy Copilot
Cloud ALB, where no listener rule matches it, so every request gets the ALB
default action: a 404 with an empty body. The second has no DNS record at all.
A reader who followed that page converted a working OSS install into a 502.
The hosts themselves were corrected in v1.64.0, but two shipped surfaces still
carried stale claims about the same step, and nothing stopped the hosts from
coming back a third time:
- The debug skill said Intelligence "requires ... `apiUrl`, `wsUrl`, `apiKey`,
`tenantId`". Three errors in one line: `apiUrl`/`wsUrl` have been optional
with managed defaults since v1.64.0, and `tenantId` has never existed on
`CopilotKitIntelligenceConfig` — the API key carries the project (its token
format is `cpk-{projectId}_...`) and the platform resolves the organization
server-side, so there is no org or tenant field for a caller to pass.
- `CopilotKitIntelligence`'s own TSDoc showed only `*.internal` placeholders,
so the class's hover docs never named the pair that actually serves prod.
`validate-intelligence-env-names` — already the unfiltered guard for this same
config surface (OSS-881) — now also fails on either dead host. The
channels-intelligence realtime test is allowlisted: it needs a hostname that
genuinely does not resolve, since `getaddrinfo ENOTFOUND` is the condition
under test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OSS-899 shipped 81 strict-mode errors to consumers because nothing checked what
the published .d.cts files reach for. validate-dts-ambient.ts checks their shape;
this checks their imports against the one thing that matters -- whether someone
who installed this package and nothing else can resolve them.
Flags devDependencies, optional peers, dependencies whose types live in a
devDependency @types package, relative imports of JS-only bundler chunks, and an
explicit ban on graphql-yoga, whose types drag lru-cache@10 into every consumer
program. Currently red on 18 real violations; the fixes follow.
Fixes the part of OSS-899 that is hard to defend: every `.d.cts` file we
publish from `@copilotkit/runtime` starts with a `require()` call.
## The bug
A consumer whose only source file is `import { CopilotRuntime } from
"@copilotkit/runtime";`, compiled with `strict` and `skipLibCheck:
false`, gets **81 errors** on a bare install of 1.68.3. **71 of them are
`TS1036` "Statements are not allowed in ambient contexts"**, raised
inside our own shipped declarations.
Cause is in `packages/runtime/tsdown.config.ts`. The banner that
guarantees `reflect-metadata` loads before `type-graphql` was returned
as a **string**. tsdown's `resolveChunkAddon` routes an *object* return
by chunk kind (`js` / `dts` / `css`) but applies a *string* return to
**every** emitted chunk — declarations included. So all 87 published
`.d.cts` files began:
```ts
require("reflect-metadata");
import { CopilotRuntimeLogger, ... } from "./lib/logger.cjs";
```
A `require()` call is a statement, and a `.d.ts` is an ambient context.
One error per file.
Two reasons this went unnoticed for so long:
- Every scaffolder sets `skipLibCheck: true`. Verified in genuine `ng
new` and `create-next-app` output. A developer who scaffolds normally
never sees it.
- The `.d.mts` flavour got `import "reflect-metadata";`, which is a
legal side-effect import in a declaration file. **ESM-resolving
consumers saw zero `TS1036`.** Only CJS resolution is affected.
## The fix
Return an object so tsdown routes by chunk kind — JS keeps its
`reflect-metadata` prologue, declarations get nothing.
The `fileName.includes("_virtual/_rolldown/runtime")` condition is
dropped as well, and that is the more interesting half.
`resolveChunkAddon` reassigns its own closure variable on the first
call:
```js
if (typeof chunkAddon === "function") chunkAddon = chunkAddon({ format, fileName: chunk.fileName });
```
so a function banner is evaluated **once** and its result reused for
every later chunk. The old config's comment ("propagates to all output
files per format") described that as intended behaviour, but it was
really a condition deciding the banner for the entire build based on
whichever chunk happened to be emitted first. Keying on `format` alone —
fixed per build — is order-independent.
The object form is tsdown's declared API, not a workaround:
`ChunkAddonFunction` returns `ChunkAddonObject | string | undefined`
where `ChunkAddonObject` is `{ js?, css?, dts? }`. `tsc --noEmit
--strict` on `tsdown.config.ts` against tsdown's own types is clean —
worth stating because the config is in no tsconfig `include`, so nothing
else typechecks it.
## The guard
`scripts/validate-dts-ambient.ts` parses each built declaration with the
TypeScript compiler API and fails on any top-level node that is not a
declaration, import, or export. Wired as a `check-dts` nx target shaped
exactly like the existing `publint` / `attw` / `compat-check` targets
(`dependsOn: ["build"]`, `inputs` on `dist/**`), and folded into the
`check:packages` script that the `package-quality` CI job already runs.
That job already builds runtime for `publint`, so the added cost is one
177-file parse.
Only `@copilotkit/runtime` opts in, because it is the only offender.
Running the validator itself over the built declarations of all 32
packages: **87 of runtime's 177** bad on the published 1.68.3 artifact,
and **0** in every other package. Others can opt in with the same
one-line script.
## Testing
**1. Reproduce the reported defect on the published package.** Bare `npm
install @copilotkit/runtime@1.68.3 typescript`, `probe.ts` importing
only `CopilotRuntime`, tsconfig with `strict`, `skipLibCheck: false`,
`module`/`moduleResolution` `nodenext`:
```
$ npx tsc --noEmit ; echo exit=$?
exit=1
$ grep -oE 'error TS[0-9]+' tsc.out | sort | uniq -c | sort -rn
71 error TS1036
5 error TS2416
2 error TS7016
2 error TS2307
1 error TS2694
```
81 errors, matching the issue. All 71 `TS1036` are at line 1, column 1
of a `.d.cts`.
**2. Confirm the mechanism.** Every published declaration's first line,
before the fix:
```
-- *.d.cts -- total: 87
87 require("reflect-metadata");
-- *.d.mts -- total: 90
90 import "reflect-metadata";
```
**3. Same probe across every public subpath, before and after.** Built
`packages/runtime` at 1.68.3 with this change and swapped the result
into the probe's `node_modules`. `total` is all errors; `1036` is the
subset this PR addresses.
| subpath | CJS before | CJS after | ESM before | ESM after |
| --- | --- | --- | --- | --- |
| `@copilotkit/runtime` | 81 (71×1036) | **10** (0) | 10 (0) | 10 (0) |
| `/v2` | 32 (29×1036) | **3** (0) | 3 (0) | 3 (0) |
| `/langgraph` | 15 (4×1036) | **7** (0) | 7 (0) | 7 (0) |
| `/v2/express` | 21 (18×1036) | **3** (0) | 3 (0) | 3 (0) |
| `/v2/hono` | 21 (19×1036) | **2** (0) | 2 (0) | 2 (0) |
| `/v2/node` | 22 (20×1036) | **2** (0) | 2 (0) | 2 (0) |
Zero `TS1036` on every subpath in both module modes, and **after the fix
each subpath's CJS count equals its ESM count** — the CJS-only penalty
is gone and nothing else moved. Every ESM column is untouched, which is
the expected result since `.d.mts` never carried the bad banner.
The errors that remain are the separate items catalogued on OSS-899
(optional-peer SDK types, `@types/cors`, a `lru-cache` variance error
from `graphql-yoga`, a zod namespace skew in
`@copilotkit/license-verifier`) and are not touched here.
**4. `reflect-metadata` still runs first in every JS output.** This is
what the banner exists for, so it is the thing most at risk from the
change:
```
cjs files with require("reflect-metadata") as line 1: 131 / total 131
mjs files with import "reflect-metadata" as line 1: 132 / total 132
```
**5. Nothing but the banner line changed.** Diffed every one of the 87
built `.d.cts` files against the published 1.68.3 artifact from line 2
onward. Exactly one file differs, and it is unrelated source drift — a
JSDoc env-var rename from `6f58b2c6a4` (`COPILOTKIT_API_KEY` →
`INTELLIGENCE_API_KEY`, refs OSS-881) that landed on main after 1.68.3
shipped. Line counts are also identical, so declaration sourcemaps do
not shift.
The `_virtual/_rolldown` reference count in declarations is 2 before and
2 after — that item is deliberately out of scope here.
**6. The guard catches the regression it exists for.** Reverted the
banner to its pre-fix string form, rebuilt, and ran the new target:
```
$ pnpm exec tsx ../../scripts/validate-dts-ambient.ts dist
Found 87 statement(s) in published declarations.
A .d.ts is an ambient context: only declarations, imports, and exports are
allowed. Each of these is a TS1036 error for consumers on skipLibCheck: false.
dist/agent/converters/aisdk.d.cts:1 require("reflect-metadata");
...
exit=1
```
Restored the fix and rebuilt:
```
$ pnpm exec tsx ../../scripts/validate-dts-ambient.ts dist
validate-dts-ambient: dist clean (177 files).
exit=0
```
**7. Validator unit tests, mutation-checked.**
`scripts/__tests__/validate-dts-ambient.test.ts`, 7 tests covering the
exact OSS-899 banner, the legal ESM form, every declaration form a real
`.d.ts` uses, line-number reporting, and ignoring sibling `.cjs`/`.map`
files.
```
Test Files 1 passed (1)
Tests 7 passed (7)
```
Then broke the mechanism three ways to confirm the tests are not
self-fulfilling:
| mutation | result |
|---|---|
| allow `ExpressionStatement` in the kind allowlist | 2 failed / 5
passed |
| drop the `line + 1` conversion | 2 failed / 5 passed |
| scan only `.d.ts`, not `.d.mts` / `.d.cts` | 3 failed / 4 passed |
| restored | 7 passed |
**8. Runtime suite and packaging targets, on a clean `pnpm install
--frozen-lockfile` in this worktree.**
```
$ nx run @copilotkit/runtime:test
Test Files 143 passed (143)
Tests 2073 passed (2073)
$ nx run-many -t publint,attw,check-dts --projects=@copilotkit/runtime
NX Successfully ran targets publint, attw, check-dts for project @copilotkit/runtime
```
`attw --profile node16` reports 🟢 from both CJS and ESM; the `node10`
failure is pre-existing and ignored by the profile.
**9. Formatting and types.** `oxfmt --check` clean on all three source
files; `tsc --noEmit --strict` clean on the new script.
## Overlap with #6476#6476 (`adopt TypeScript 7 and tsdown 0.22`) bumps tsdown to 0.22.14 but
does **not** touch `packages/runtime/tsdown.config.ts`, so it does not
fix this. The two PRs conflict only textually — both add lines to
runtime's `scripts` block and to the root `package.json`. This fix uses
tsdown's documented object-banner form, so it holds whether or not 0.22
changed `resolveChunkAddon`'s memoization.
No changeset: this ships through the normal release scopes.
Two things this PR was missing, both now closed.
## The Claude SDK quickstarts are unblocked
#6618 put `showcase/integrations/claude-sdk-{python,typescript}` on
`createCopilotRuntimeHandler` with `mode: "single-route"`, at the **plain**
`route.ts` path. That dissolves the coupling that forced these two pages to be
reverted earlier: `verify-shell-docs.ts` asserts each page claims a starter file
at `src/app/api/copilotkit/route.ts` AND that the file exists in the extracted
starter. Single-route keeps that path, so the prose claims and
`requiredStarterFiles` are unchanged — only the fence bodies move to v2.
The two content assertions that pinned those pages to v1
(`ExperimentalEmptyAdapter`, `copilotRuntimeNextJSAppRouterEndpoint`) now
require `createCopilotRuntimeHandler`, the `/v2` entrypoint,
`mode: "single-route"` and a `POST` export. Mutation-checked: flipping the
fixture to `mode: "multi-route"` fails with
`app/api/copilotkit/route.ts missing single-route mode`.
## Snippet gating: 1 -> 20 route fences
I previously claimed the integration pages could not be doctested because of
path aliases and per-integration deps. **That was an assumption I never
checked, and it was wrong.** Of the 52 migrated route fences, 47 import nothing
project-relative; 36 are complete, self-standing routes. 27 pages were
eligible, 20 now hold a gated fence — each extracted and typechecked by
`tsc --noEmit` against real npm-installed packages in CI.
One fence per (page, title): `extract.ts` concatenates tagged blocks sharing a
title, so a second complete route on the same page would collide.
Mutation-checked on `snippets/integrations/langsmith/index.mdx`: restoring the
v1 import in the gated fence turns the run red (20 passed, 1 failed). My first
attempt at this check was a no-op — the pattern missed because the fence is
JSX-indented — and it "passed" misleadingly. The real check asserts the mutation
reached the extracted snippet before trusting the result.
### Harness changes this needed
- `extract.ts` now finds the nearest `doctest.json` by walking up to the docs
root, instead of looking only in the page's own directory. Otherwise gating
20 pages means ~20 duplicated dependency lists that then drift. A shared list
lives at `content/doctest.json`; `docs/integrations/langgraph/` keeps its own
(Python deps) and now also carries the TS deps its page needs.
- `run.ts` installs each dependency set **once**, into
`.doctest-output/.deps/<hash>`, and links it into every snippet sharing that
set. Per-snippet installs took **7:58** for 21 snippets, uncomfortably close
to the job's 15-minute timeout; shared installs take **0:45** cold. Different
dep sets still get separate stores, so this is a dedupe, not a merge.
### `@ag-ui/*` versions have to be pinned to what the runtime expects
Unpinned, the gated fences failed with `HttpAgent is not assignable to
AbstractAgent — separate declarations of a private property '_debug'`: npm
installs a newer `@ag-ui/client` than `@copilotkit/runtime` depends on, so two
`AbstractAgent` declarations collide. The sidecar pins `@ag-ui/client@0.0.57` and
`@ag-ui/core@0.0.57` to match `@copilotkit/runtime@1.68.3`.
## Seven fences are deliberately NOT gated
Un-tagged with the reason, rather than left failing or quietly dropped:
- `docs/auth.mdx`, `docs/premium/connect-your-runtime.mdx` — illustrative
fences referencing placeholders (`myAgent`, `verifyJwt`) that cannot compile
standalone by design.
- the four langgraph-family pages and
`snippets/self-hosting-copilot-runtime-langgraph-endpoint.mdx` — these hit
`LangGraphAgent is not assignable to AbstractAgent — separate declarations of
a private property '_debug'`, which pinning does not fix.
**That last one is a real pre-existing defect, not a migration regression.** I
reconstructed the v1 form of the langgraph quickstart snippet verbatim from
`origin/main` and typechecked it against the identical installed dependencies:
it fails with the same error. So these snippets have never typechecked against
published packages — worth filing separately. It is also what the ~220
`@ts-ignore` comments across `showcase/integrations` were papering over.
## Verified
doc-tests (cold, no cache) -> 21 passed, 0 failed in 0:45
mutation check (real, verified) -> 20 passed, 1 failed
vitest extract + verify-shell-docs -> 34 passed
showcase/shell-docs typecheck -> exit 0
showcase/shell-docs build -> exit 0
structural audit -> 21/21 pages, fence + JSX identical to HEAD
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`snippets/copilot-runtime.mdx` calls itself "the recommended way to use
CopilotKit" and taught the v1 plain-POST route. A reader following it got an
endpoint that answers 405 on `GET /api/copilotkit` and does not route
`GET /api/copilotkit/info` at all, so nothing could discover the runtime.
The route fence is now the shape proved in `examples/shadcn`: catch-all path,
`createCopilotEndpoint`, a runner, and both verbs exported. The three other
fences on the page that were titled with the plain route path are retitled to
the catch-all path, so no snippet claims a path its body contradicts.
Also documents the two things the shape actually requires, both of which were
found by running it:
- `npm install @copilotkit/runtime hono` — `hono` is a dependency of the
runtime rather than a peer, so `hono/vercel` resolves under npm's flat
layout but not under pnpm.
- `useSingleEndpoint={false}` on `<CopilotKit>`. The provider defaults it to
`true` (single-route transport), which posts to the bare `runtimeUrl`; a
multi-route runtime answers that with 404. The Angular provider defaults its
transport to `"auto"` and needs no equivalent change, so that snippet is
unchanged.
## The snippet is now gated in CI
`scripts/doc-tests` already extracts fences tagged `doctest=` and, for
`component`, runs `tsc --noEmit` against real npm-installed dependencies — but
it could not handle a fence whose title is a path, which is exactly what a
Next.js route handler's title is. Two defects blocked it, both fixed here:
- `extract.ts` wrote the title as a path without creating intermediate
directories, so a titled route fence died on ENOENT.
- `run.ts` derived the npm package name from the snippet directory, and a
catch-all leaf directory is literally `[[...slug]]`, which npm rejects as an
invalid name. It also looked for `doctest.json` only in the snippet's own
directory, while the sidecar is copied once per page — so a nested snippet
silently installed no dependencies and failed with "Cannot find module".
With those fixed, the canonical route snippet is tagged `doctest="component"`
and typechecked in CI against the published `@copilotkit/runtime` 1.68.3 and
`hono`. This is the gate the docs did not have: snippet correctness no longer
rests on review alone.
Verified:
pnpm tsx scripts/doc-tests/extract.ts -> 2 snippets extracted
pnpm tsx scripts/doc-tests/run.ts -> 2 passed, 0 failed
mutation check: restoring the v1 import in the fence turns the run red
(1 passed, 1 failed), so the gate fails on the regression it exists to catch
vitest scripts/doc-tests/__tests__/extract.test.ts -> 10 passed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>