1035 Commits

Author SHA1 Message Date
Alem Tuzlak 1ef16b6789 feat(web-inspector): pop the Inspector into its own window
Keep the same live Inspector session in a named browser popup, restore it when the popup closes, and document the workflow.
2026-08-19 14:03:12 -07:00
Ben Taylor 092058b224 docs(a2ui): require a literal-or-binding union for bound props (refs OSS-857) (#6573)
Four defects were reported from a LangGraph TypeScript + Next.js
onboarding run. Two
were unverified and one was unsound as stated, so each was reproduced or
traced to
source before anything was written. Two needed a fix, one needed a fix
plus a package
re-export, and one turned out to be correct as documented.

## Per-defect findings

**Defect 1 — bound props need a literal-or-binding union schema. REAL,
and the page said the opposite.**
Confirmed, with the mechanism. `scrapeSchemaBehavior` in
`@a2ui/web_core`'s
`GenericBinder` decides whether to resolve a `{ path }` binding by
inspecting the prop's
Zod type: a `ZodUnion` containing an object with a `path` key (and no
`componentId`)
becomes `DYNAMIC`; everything else falls through to `STATIC`, whose
handler is
`case 'STATIC': return value;`. So a bound prop declared as a plain
`z.string()` is never
resolved and the raw `{ path: "/origin" }` object reaches the renderer,
where the first
thing that renders it as text throws React error #31.

The page was not merely silent about this — it asserted the opposite:

> The A2UI binder resolves those paths *before* the React renderer runs,
so renderer
> props are typed as their resolved values (plain `z.string()`, not a
path-or-literal union).

The reference cell has declared the union all along and carries a
comment naming the exact
React error, but that comment sits *outside* the
`@region[definitions-types]` marker, and
`extractRegion` returns only the lines between the markers — so it never
reaches the page.
The rule is now stated where the reader declares the prop, with the
failure mode.

**Defect 2 — `DynamicStringSchema` is not re-exported. Explanation 2:
the symbol exists in a package that had not been searched.**
It is real and it is not a wished-for helper. It lives in
`@a2ui/web_core` at
`src/v0_9/schema/common-types`, reachable on the export map as
`@a2ui/web_core/v0_9`, and
it is a three-member union (`z.string()`, `DataBindingSchema`,
`FunctionCallSchema`) —
slightly wider than the two-member `DynString` the reference cells
hand-roll. The earlier
search was correct that it appears nowhere under `packages/`; it is a
dependency symbol.

It is also genuinely unreachable for users: `@a2ui/web_core` is a plain
`dependency` of
`@copilotkit/a2ui-renderer`, so application code cannot rely on
importing it. Re-exported
from `@copilotkit/a2ui-renderer` with its numeric/boolean/list siblings
and their types,
and noted in the docs as an alternative to hand-rolling the union.

**Defect 3 — the quickstart recommended the host form that fails. REAL,
verified independently for both runtimes.**
The advice was *"try using `0.0.0.0` or `127.0.0.1` instead of
`localhost`"*, in shared prose.
For the Node runtime that is exactly backwards, and it was verified from
source and by
running it, not taken on report. Rewritten and split across the page's
existing
Python/TypeScript language tabs so neither runtime sees the other's
advice. Also corrected
the `0.0.0.0` half, which is wrong for both: it is a bind-all address
for a server, not a
target for a client URL.

**Defect 4 — `useSingleEndpoint` guidance for the compat component. NOT
A DEFECT. Nothing changed.**
The docs are right. The compat wrapper resolves its default at

`packages/react-core/src/components/copilot-provider/copilotkit.tsx:108`:

```tsx
useSingleEndpoint={props.useSingleEndpoint ?? true}
```

and the v2 provider maps `true → "single"`, `false → "rest"`, `undefined
→ "auto"`
(`CopilotKitProvider.tsx:616-620`, again at `777-781`). So omitting the
prop really does
keep a single-route default, and `useSingleEndpoint={false}` really is
what a multi-route
Runtime needs. The same `CopilotKit` component is exported from both
`@copilotkit/react-core`
and `@copilotkit/react-core/v2`, so the guidance holds for either
import. Reported as one
observation from one run rather than an established finding — it did not
survive checking.

## Two things worth flagging

**The URL is served by the root page, not the LangGraph one.** Both
`generative-ui/a2ui/fixed-schema.mdx` and
`integrations/langgraph/generative-ui/a2ui/fixed-schema.mdx` exist, and
the resolution is the
opposite of what the directory layout suggests: all three langgraph
slugs are
`docs_mode: generated` in their manifests, and in that branch root MDX
wins
(`[framework]/[[...slug]]/page.tsx:836-841`). Confirmed live — the
LangGraph-scoped copy is a
thinner, older duplicate that is **not served at that URL for any
framework**. Left in place,
but it is a trap for the next person and probably wants deleting
separately.

**Overlap with #6569.** That PR is still open and edits both files this
one touches.
`git merge-tree` against its head merges clean, so no action needed, but
the two should be
read together.

## Testing

Worktree off `origin/main` (which already contains #6566 and #6568).

**Defect 1 — mechanism, against the locked `@a2ui/web_core@0.10.4`.**
Schema classification:

```
plain z.string()  -> {"type":"STATIC"}
literal|binding   -> {"type":"DYNAMIC"}
```

End-to-end through the real `GenericBinder`, feeding `{ path: "/origin"
}` against a data
model of `{ origin: "SFO" }`:

```
z.string()      : typeof=object value={"path":"/origin"}
literal|binding : typeof=string value="SFO"
z.string() -> renderable as a React child? NO — React throws: Objects are not valid as a
              React child (found: object with keys {path})
literal|binding -> renderable as a React child? yes
```

**Defect 2 — the re-export works from the built entry point**, and
behaves identically to the
hand-rolled union in the binder (it has a third union member, so this
needed checking):

```
DynamicStringSchema parses a literal: "SFO"
DynamicStringSchema parses a binding: {"path":"/origin"}
hand-rolled union   -> {"type":"DYNAMIC"}
DynamicStringSchema -> {"type":"DYNAMIC"}
plain z.string()    -> {"type":"STATIC"}
```

**Defect 3 — both runtimes verified from CLI source, and the Node
binding reproduced.**
`@langchain/langgraph-cli@1.4.4` `dist/cli/dev.mjs:19` defaults `--host`
to `"localhost"`
and passes it to `serve({ hostname })`; `langgraph_cli-0.4.31`
`cli.py:664-666` defaults
`--host` to `"127.0.0.1"`. Reproducing what Node does with `{ host:
"localhost" }` on this
dual-stack machine:

```
node version: v22.14.0
bound to: {"address":"::1","family":"IPv6","port":42024}
  localhost   -> CONNECTED
  127.0.0.1   -> ECONNREFUSED
  ::1         -> CONNECTED
  0.0.0.0     -> ECONNREFUSED
```

`127.0.0.1` is refused by the very server `localhost` reaches — so the
old advice broke a
working setup.

**Rendered checks (`next dev`, body-inspected — this site soft-404s, so
no status codes were trusted).**
`/langgraph-typescript/...` and
`/langgraph-python/generative-ui/a2ui/fixed-schema`: root-file
marker present, LangGraph-file marker absent, new prose and the
React-error callout present,
old wrong sentence gone. The `{path}` braces render literally inside
`<code>` and the
`#declare-the-component-definitions` anchor resolves to a real heading
id.

Quickstart troubleshooting tabs resolve per framework, so the gating is
right:

```
/langgraph-typescript/quickstart   Python selected=false   TypeScript selected=true
/langgraph-python/quickstart       Python selected=true    TypeScript selected=false
/langgraph-fastapi/quickstart      Python selected=true    TypeScript selected=false
```

The `<Tabs>` nested in a list item renders as a real `<ul><li>` with a
working tablist, not
broken MDX.

**Suites.**

| Check | Result |
| --- | --- |
| `packages/a2ui-renderer` `tsc --noEmit` | pass |
| `packages/a2ui-renderer` build (`tsdown`) | pass, 143 files |
| `packages/a2ui-renderer` `vitest run` | 4 files, 22 tests passed |
| `oxlint` on the changed source | 0 warnings, 0 errors |
| `shell-docs` `npm run typecheck` | pass (exit 0) |
| `shell-docs` `npm run lint` | pass (exit 0) |
| `shell-docs` `npm run test` | 58/59 files, 420/421 tests |

The one failing test is `channels-docs.test.ts > publishes the Channels
overview only through
provider navigation`. It is **pre-existing on `origin/main`** and
unrelated to these files —
verified by reverting all three changes to a pristine checkout and
re-running it, where it
fails identically (`1 failed | 29 passed`).

## Conventions pass

Checked the added prose against the docs tree's actual conventions
rather than by ear, which
turned up four things worth changing:

- **`Callout type="warn"`** is the house spelling (84 uses vs 11
`warning`) — already correct.
- **Code identifiers in Callout titles are backticked** (95-odd
precedents, e.g.
``title="`identifyUser` is not an authentication gate"``). Mine wasn't;
fixed. Note these
render as *literal* backticks — verified that existing titles behave
identically on `/auth`,
  so this matches the site rather than diverging from it.
- **Dropped a hand-written code fence.** The first draft illustrated the
union with a synthetic
`ts` block that (a) wasn't valid TypeScript — an orphaned object
property with no enclosing
object — and (b) duplicated the `<Snippet region="definitions-types" />`
rendered immediately
below it. Hand-copied code next to the generated snippet is exactly the
drift the snippet
architecture exists to prevent, so the prose now names `DynString` and
`Airport`'s `code` and
lets the snippet carry the code. Confirmed those two names are present
in **all 21**
integration cells that feed this page, since the root page serves every
framework.
- **Matched local line-style.** The quickstart's other troubleshooting
bullets are single
unwrapped lines, so the new bullet's prose is too; the a2ui page wraps
at ~70–80 columns and
  the new paragraphs match that.

Also tightened two things for accuracy over emphasis: the binder rule
now says "a union with a
`{ path }` member" rather than "a union containing an object with a
`path` key", which was
over-broad (a `{ componentId, path }` member is classified `STRUCTURAL`,
not `DYNAMIC`), and
the package comment was cut from nine lines to six to sit better among
that file's one-line
section labels.

Re-verified after the rewrite: `tsc` pass, `vitest` 22 passed, `oxlint`
clean, `oxfmt` clean,
shell-docs typecheck/lint pass, tests unchanged at 420/421 with the same
pre-existing channels
failure, and both pages re-rendered — anchor still resolves, tabs still
resolve per framework
(`langgraph-python` → Python, `langgraph-typescript` → TypeScript).

Out of scope and untouched: `snippets/shared/premium/inspector.mdx`. No
changeset added.
Does not close OSS-857.
2026-08-19 15:27:44 -05:00
Benjamin Taylor 4df1e3dccd docs(a2ui): require a literal-or-binding union for bound props (refs OSS-857)
Three findings from a LangGraph TypeScript onboarding run, plus the
supporting re-export.

The A2UI binder decides whether to resolve a `{ path }` binding by
inspecting the prop's Zod type: `scrapeSchemaBehavior` classifies a
`ZodUnion` containing an object with a `path` key as DYNAMIC and
everything else as STATIC, and STATIC returns the value untouched. A
bound prop declared as a plain `z.string()` therefore reaches the
renderer as the raw `{ path: "/origin" }` object, and the first thing
that renders it as text throws React error #31. The fixed-schema page
said the opposite — that renderer props are "plain z.string(), not a
path-or-literal union" — so the obvious declaration produced an opaque
crash. The reference cell already declares the union and carries a
comment explaining why, but that comment sits outside the
`definitions-types` region marker and so never reaches the page.

`DynamicStringSchema` is real; it lives in `@a2ui/web_core`, which is a
transitive dependency of `@copilotkit/a2ui-renderer` and so not
reliably importable from application code. Re-exported here with its
numeric/boolean/list siblings and their types.

The LangGraph quickstart's troubleshooting advice told everyone with a
connection problem to swap `localhost` for `0.0.0.0` or `127.0.0.1`.
That is backwards for the Node runtime: `langgraphjs dev` defaults to
`--host localhost`, which Node resolves to IPv6 and binds `::1` only,
so `127.0.0.1` is refused by the same running server. The Python CLI
defaults to `--host 127.0.0.1` and behaves the other way, so the advice
is now split across the page's existing Python/TypeScript language tabs
instead of stated once in shared prose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:48:49 -05:00
Ben Taylor 68d6c5c62d docs(inspector): document mounting the Inspector in Angular (refs OSS-857) (#6572)
## What this fixes

The Inspector is `cpk-web-inspector`, a framework-agnostic web component
from
`@copilotkit/web-inspector`. `@copilotkit/angular` does not reference
that package
and does not mount the element, so an Angular application has to create
it
itself. Nothing in the docs said so.

It was worse than a missing paragraph. `ANGULAR_DOC_REDIRECTS` mapped
the
`inspector` slug onto `guides/troubleshooting`, so
`/angular/*/inspector`
**redirected away from the Inspector**, and the Angular sidebar's
"Observe & Operate" section contained a single entry — the VS Code
extension:

```
== Observe & Operate
- VS Code Extension [vs-code-extension]
```

## What I documented

New Angular-owned page, `frontends/angular/inspector.mdx`, sourced from
`examples/integrations/adk-angular/src/app/web-inspector.ts`:

- the Inspector is a web component and `@copilotkit/angular` does not
mount it
- the mount component: `afterNextRender`, reuse-or-create, append to
`document.body`
- `inspector.core = copilotKit.core` plus `auto-attach-core="false"`,
and why —
  given no core the element hunts for development globals such as
`window.__COPILOTKIT_CORE__`, so turning the search off is what
guarantees it
  observes the app's core and never a different one
- anchoring the launcher bottom-left, clear of a chat panel's close
button
- keeping it out of production builds via `@defer (when isDev)` +
`isDevMode()`
- server rendering (`afterNextRender` + the deferred import vs.
`customElements`)
- cleanup through `DestroyRef.onDestroy`

Plus: the redirect is gone so the page is reachable, and
`frontends/angular/guides/troubleshooting.mdx` links to it.

**React's Inspector content is untouched** — not edited, not moved, not
gated.

### House style

Checked against the eleven existing Angular-owned pages rather than
written to
taste, which changed four things from my first draft:

- **`## Next steps` with a bare link list.** Every Angular guide closes
that way;
  I had `## Related` with a prose gloss per link.
- **No `<video>`.** No Angular-owned page embeds media, and none uses
`<Callout>`
or `<Steps>` either — that surface is prose, tables, and fences. I had
carried
  the Inspector video over from the shared snippet.
- **Imperative task headings**, matching "Send the current session" /
  "Validate every runtime request" in `auth.mdx`: "Mount the element",
  "Supply the application's core", "Position the launcher". I had
  "Mount it yourself" and "Hand it the application's core".
- **Declarative sentences, no rhetorical fragments.** "The mount is
yours, so the
exclusion is yours as well." and a bare "`DestroyRef.onDestroy` does."
are not
  this surface's register; both are now plain statements of mechanism.

Frontmatter (`title`/`description`/`icon`/`doc_type: how-to`), h2-only
structure, ~80-column wrapping, and the `{runtimeUrl}` placeholder
convention all
follow the siblings. `<AngularSnippet region=…>` does **not** apply —
that
component pulls code extracted from the Angular Showcase at build time,
and this
mount component is not in the Showcase. Nav needs no `meta.json` entry
either:
`frontends/meta.json` carries only a title, and the Angular sidebar is
derived in
`getAngularDocsNavTree`. Verified the entry renders anyway.

### Two deviations from the brief, both deliberate

**1. Structural gating instead of `<FrontendOnly frontend="angular">` in
the
shared snippet.** The brief described
`snippets/shared/premium/inspector.mdx` as
the real Inspector content with the per-framework pages as shims onto
it. On
current `main` that is only half true: `docs/inspector.mdx` is now a
131-line
standalone page that does **not** render `<Inspector />`, and it is what
the
Angular root and every `docs_mode: generated` framework resolve to. I
built the
`FrontendOnly` version first and it forced the Angular guide to be
duplicated
into two files that had already diverged. An Angular-owned page instead
matches
how all eleven existing Angular guides work, keeps one source of truth,
and
gates by resolution rather than by branch.

The repo's own test agrees on the direction —
`angular-docs-content.test.ts`
lists `<FrontendOnly` in `REACT_ONLY_CONTENT`, i.e. it treats the tag as
something that should not reach the Angular surface.

That test also gave me a real mutation check for free. My first attempt
leaked
React's `<CopilotKit … enableInspector={false}>` into 19 Angular pages,
and the
suite caught every one:

```
× keeps the complete Angular surface free of another frontend's code
+   "inspector: <CopilotKit
+   publicLicenseKey={process.env.NEXT_PUBLIC_COPILOTKIT_LICENSE_KEY}
+   enableInspector={false}
+ >",
× keeps every Angular and backend combination frontend-native
    expected [ …(18) ] to deeply equal []
```

**2. I document the CSS override for positioning, not
`setAttribute("anchor", …)`.**
The scaffold sets that attribute, but `cpk-web-inspector` never reads
it. Runtime
proof against the built `dist`:

```
observedAttributes: ["auto-attach-core"]
static properties keys: ["core","autoAttachCore","_capabilitiesVersion"]
'anchor' observed? -> false
```

There is no `getAttribute("anchor")` anywhere in the package, and
`defaultAnchor`
(the prop React's `CopilotKitInspector` accepts) is not consumed either.
What
actually moves the panel is the CSS in the scaffold's own `styles.css` —
as its
comment already says: "CSS in styles.css enforces this too." So the docs
describe
the mechanism that works. **The scaffold has one dead line** its owner
may want
to drop; I did not touch it (see below).

## The adk-angular dependency is discharged

`examples/integrations/adk-angular` is planned for removal, and its
`web-inspector.ts` comment was the only written record of this pattern.
That
pattern is now documented. **Whoever removes that scaffold no longer
needs to
preserve it.** I only read the scaffold — no file under
`examples/integrations/adk-angular` is modified by this PR.

## The VS Code extension claim: both halves reproduced

The report said Angular users are pointed at a VS Code extension
instead, that
its `cpk-debug-events` endpoint is documented at the wrong path, and
that it
produced no events for a real run. I verified each independently rather
than
acting on the report.

**Pointed at the extension — confirmed.** See the one-entry sidebar
above.

**Wrong path — confirmed, and fixed.** The router suffix-matches
`cpk-debug-events`, but a runtime mounted with a `basePath` rejects
anything
outside it. Against a real runtime on `basePath: "/api/copilotkit"`:

```
runtime mounted at basePath=/api/copilotkit, NODE_ENV=development
/cpk-debug-events                -> 404  application/json  {"error":"Not found"}
/api/copilotkit/cpk-debug-events -> 200  text/event-stream  ": connected\n\n"
/api/copilotkit/info             -> 200  application/json   {"version":"1.64.1",…}
```

The docs said "available at `GET /cpk-debug-events` on your CopilotKit
runtime"
and gave the panel default as the bare origin `http://localhost:4000`,
so a
reader supplying their server's origin gets a 404. Now documented as
`GET {runtimeUrl}/cpk-debug-events`, base-path-relative, with the worked
`localhost:8200` example and a `curl` check, in both
`troubleshooting/event-inspector.mdx` and `vs-code-extension.mdx`.

**No events for a real run — confirmed, cause is runtime mode.** The
debug bus is
fed from exactly one place, `handlers/shared/sse-response.ts`, reached
only by
`handlers/sse/run.ts` and `handlers/sse/connect.ts`. An
Intelligence-configured
runtime dispatches to `handlers/intelligence/run.ts` and
`handlers/intelligence/connect.ts`, which return `Response.json` and
hand the
browser a realtime connection — no AG-UI event ever passes through the
runtime's
SSE layer. Neither file mentions `debugEventBus`. So on an
Intelligence-backed
runtime the endpoint connects, emits `: connected`, and then stays
silent
forever. That is now a callout on the event-inspector page pointing
readers at
the in-app Inspector, which reads the events client-side.

I did not change runtime code for this — it is a docs-accuracy gap, and
whether
the Intelligence path *should* feed the bus is a product decision, not
mine to
make here.

## Testing

From `showcase/shell-docs`:

**`npm run test`** — 403 passed, 1 failed, and that failure is
pre-existing on
`origin/main`. Verified in a pristine `origin/main` worktree with no
changes:

```
❯ src/lib/__tests__/channels-docs.test.ts (30 tests | 1 failed)
    × publishes the Channels overview only through provider navigation
```

It asserts `channels-architecture-dark.png` in the Channels overview
source and
is unrelated to anything here. The seven `angular-docs-content.test.ts`
tests —
the ones that police frontend separation — all pass.

**`npm run typecheck`** — identical output on my branch and on a
pristine
`origin/main` worktree (5 pre-existing `@testing-library/react`
resolution
errors from my symlinked `node_modules`, all in test files I did not
touch). No
new errors.

**`npm run lint`** — exit 0, no warnings in any file I changed.

**`npx oxfmt --check`** on the one `.ts` file — "All matched files use
the
correct format."

### Render check, both namespaces

`next dev`, following redirects, checking bodies rather than status
codes since
this site soft-404s:

| URL | http | Angular mount content | React `enableInspector` |
| --- | --- | --- | --- |
| `/angular/langgraph-typescript/inspector` | 200 | yes
(`afterNextRender`, `auto-attach-core`, `cpk-web-inspector`) | **no** |
| `/langgraph-python/inspector` | 200 | **no** | yes |

Each namespace shows only its own instructions. The only `tsx` string on
the
Angular page is Next.js dev chunk filenames, not content.

Before this change `/angular/langgraph-typescript/inspector` answered
`307 -> /angular/langgraph-typescript/guides/troubleshooting`.

Also confirmed 200-with-content, no redirect, and the mount instructions
present
on `/angular/inspector`, `/angular/google-adk/inspector`, and
`/angular/mastra/inspector`; the sidebar now carries
`href="/angular/langgraph-typescript/inspector"` under "Observe &
Operate"; the
Angular troubleshooting page links to it; and the new event-inspector
callouts
render in both the React and Angular namespaces with `/inspector`
correctly
rewritten to `/angular/<backend>/inspector`.

## Notes for reviewers

- **OSS-857 stays open** — other defects on it are unresolved.
- No changeset, per this repo's release process.
- Follow-up for the adk-angular owner, not done here:
`setAttribute("anchor", "bottom-left")` in `web-inspector.ts` is a no-op
and
can be deleted; the `styles.css` rule below it is what positions the
panel.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-19 14:46:03 -05:00
Ben Taylor 3801de3708 docs(runtime): map the provider/handler pairs and guard BuiltInAgent (refs OSS-857) (#6569)
Follow-up to #6566. Fixes **defects 5 and 9** of OSS-857, plus the half
of **defect 6** that lives on the Built-in Agent quickstart. Defects **1
and 2** are deliberately left — they land with the non-interactive
`project list`/`select` work, since the real fix is tooling that
provisions and names the key, not prose.

**Do not close OSS-857 on this PR** — 1 and 2 remain.

## The finding that reframes defect 5

The three names are **not interchangeable**. They pair up, and nobody
had written the pairing down. Traced through source, not inferred:

| Provider | `useSingleEndpoint` | Transport | Needs handler |
| --- | --- | --- | --- |
| `<CopilotKit>` (v1 wrapper) | omitted → `true` | `single` |
single-route |
| `<CopilotKit>` | `{false}` | `rest` | multi-route |
| `<CopilotKitProvider>` (v2) | omitted | `auto`, detected from `/info`
| either |
| `<CopilotKitProvider>` | `{true}` | `single` | single-route |

`copilotkit.tsx:108` is the whole story:
`useSingleEndpoint={props.useSingleEndpoint ?? true}`. The v1 wrapper
renders `<CopilotKitProvider>` internally and **pins single-route
transport unless you pass the prop.** So the LangGraph quickstart is
internally coherent — v1 provider asks for single,
`copilotRuntimeNextJSAppRouterEndpoint` serves single — which is exactly
why chat works there and Threads cannot.

### The constraint nobody had documented

I swept every v1-era wrapper:

- `copilotRuntimeNextJSAppRouterEndpoint` →
`createCopilotEndpointSingleRoute`
- `copilotRuntimeNodeHttpEndpoint` → `createCopilotEndpointSingleRoute`
- `copilotRuntimeNextJSPagesRouterEndpoint`,
`copilotRuntimeNodeExpressEndpoint`, `copilotRuntimeNestEndpoint` → all
delegate to `copilotRuntimeNodeHttpEndpoint`

**Every one builds its handler with `mode: "single-route"` and exposes
no option to change it.** There is no v1-shaped multi-route handler
anywhere in the package.

The consequence is sharper than defect 5 as filed: **Rich Threads and
the Inspector are unreachable from the wiring both quickstarts teach, at
any provider setting.** Setting `useSingleEndpoint={false}` cannot fix
it — it just points the browser at routes the wrapper will not serve.
You need a v2 `CopilotRuntime` from `@copilotkit/runtime/v2` plus
`createCopilotRuntimeHandler`. That is a server-side change, not a
provider prop, and it is the structural reason defect 3's trap exists.
Worth its own ticket.

## Why I did not converge the quickstarts on v2

That was the original plan for this PR and I abandoned it after checking
the backend half. The split matters:

- **Frontend would have been free.** Both quickstarts already import
from `@copilotkit/react-core/v2`, where `CopilotKit` is labelled in
source as a *"V1 backward-compat re-export"*. `CopilotKitProvider` ships
from that same entry, and `CopilotSidebar` already depends on
`useLicenseContext` from it. Swapping is an import change.
- **Backend would not.** The multi-route handler takes a v2
`CopilotRuntimeLike`; the quickstart's v1 `CopilotRuntime` only reaches
it via an internal `.instance` getter that lazily news up a
`CopilotRuntimeVNext`. Converging means teaching v2 runtime construction
and dropping `ExperimentalEmptyAdapter` mid-quickstart — a real v1→v2
migration for every reader of the two highest-traffic pages.

v1 is supported, so the default path stays put. The mapping documents
all pairs instead, and the Threads upgrade stays a labelled, complete
recipe on the page the quickstarts already link to.

## What changed

**`backend/runtime-endpoints.mdx`** — new "Provider and handler pairs"
section: the provider table, the handlers-by-mode table, the
deprecated-alias mapping (`createCopilotEndpoint`,
`createCopilotEndpointSingleRoute`, and the Express pair), the wrapper
constraint above, and a "read the symptom" callout (a mismatch fails at
discovery — `GET {basePath}/info` 404s, or the Runtime rejects the
envelope — never in your application code).

**Both quickstarts** — a short callout naming the pair the page uses and
linking the mapping.

**Defect 9, `integrations/built-in-agent/quickstart.mdx`** — this is
what `/quickstart` actually serves (verified: both URLs return the
identical 8175-byte body; the root `quickstart.mdx` is a 17-line routing
shim that 308-redirects to `/`). `BuiltInAgent` extends `AbstractAgent`
and calls the model directly via `streamText`, so registering it as
`default` replaces the developer's agent rather than connecting to it.
Added a caution: readers with an existing agent take the frontend steps
here and the runtime wiring from their framework's quickstart.

**Defect 6, second half** — same page installed `@copilotkit/react-ui`
and never used it, importing `CopilotKit`/`CopilotSidebar` from
`@copilotkit/react-core/v2`. Dropped, matching #6566.

## Testing

```
$ npx vitest run
Test Files  1 failed | 58 passed (59)
     Tests  1 failed | 417 passed (418)
```

The one failure is `channels-docs.test.ts > publishes the Channels
overview only through provider navigation` — pre-existing, and proven so
in #6566 by stashing on a clean tree.

**I broke two tests and fixed them, which is worth recording** because
it caught a real defect in my first draft.
`angular-docs-content.test.ts` flagged:

```
built-in-agent/backend/runtime-endpoints: @copilotkit/react
langgraph-python/backend/runtime-endpoints: @copilotkit/react
... 10 surfaces total
```

`backend/runtime-endpoints.mdx` also serves the **Angular** surface, and
my provider prose named React packages there. Correct fix, not a
suppression: the provider axis is React-only — Angular's
`provideCopilotKit` has no `useSingleEndpoint` — so the provider table
is now `<FrontendOnly frontend="react">` with an Angular branch saying
only the handler half applies. Both Angular tests pass.

### Render checks

Per surface, `.md` and HTML:

| surface | provider table | Angular note | `@copilotkit/react` |
wrapper callout |
|---|---|---|---|---|
| langgraph-python | ✅ | — | 3 | ✅ |
| langgraph-typescript | ✅ | — | 3 | ✅ |
| angular | — | ✅ | **0** | ✅ |

The wrapper-constraint callout correctly stays on all three: it is a
server-side fact that applies to Angular too.

Defect 9 / 6b on `/quickstart` and `/built-in-agent/quickstart` — both
8175 bytes, caution present, `react-ui` gone from the install line, pair
pointer present.

Every link I added was **body-verified, never by status code** (this
site soft-404s with HTTP 200):

```
/langgraph-python/quickstart                 bytes=428497  soft404=0  h1=Quickstart
/                                            bytes=248255  soft404=0  h1=CopilotKit
/backend/runtime-endpoints                   bytes=375782  soft404=0  h1=Runtime HTTP endpoints
/langgraph-python/backend/runtime-endpoints   bytes=395130  soft404=0  h1=Runtime HTTP endpoints
```

New anchors confirmed present (`id="provider-and-handler-pairs"`,
`id="which-handlers-serve-which-mode"`), and the pointer rewrites into
the reader's namespace correctly — `/langgraph-python/backend/...` from
the LangGraph page, `/backend/...` from the root surface.

## Voice pass

A third commit runs a tone/voice check over everything added for
OSS-857, measured against the corpus instead of guessed. It also
corrects the wording that already landed in #6566, so the whole ticket
reads in one voice.

**Second person stays.** It is emphatically the house voice: 22 of 29
top-level and backend pages use `you`/`your`, and the three pages
involved used it **17, 23 and 64 times** before any of these edits.
Stripping it would make the new prose stand out, not blend in.
Mid-sentence `**bold**` also stays — the corpus does that 17 times.

What genuinely drifted, and is now fixed:

| Issue | Was | Now |
|---|---|---|
| British spelling | `honours` | `serves` |
| Third person on a second-person page | `A developer adding A2UI to an
agent they already wrote…` | `If you added A2UI to an agent you already
wrote…` |
| Essay register | `That default is the one thing to remember:` | plain
statement of the fact |
| Meta phrasing | `so this is the mapping` | `so this table is the
mapping` |
| Conversational | `no provider pairing to get wrong` | `to configure` |
| Conversational | `` `uvicorn` is told to listen on `8123` `` | ``
`main.py` sets uvicorn's port to `8123` `` |
| Literary | `you may also meet these deprecated aliases` | `Older code
may use these deprecated aliases` |
| Coinage | `agent construct` | `how the agent itself is built` |
| Coinage | `without that steer` | `Without it, the model tends to…` |
| Aphoristic Callout title | `Mismatched pair? Read the symptom, not the
code` | `A mismatched pair fails at discovery` |
| Epigram | `It replaces your agent; it does not connect to one.` | `It
replaces your agent rather than connecting to it.` |
| Redundancy | `nothing supplies persistence for you` | `nothing
supplies persistence` |

Two of these were objective, not stylistic: the corpus is American
English (`behavior` 66:6, `customize` 77:4, `serialize` 18:1, `organize`
15:0) and its only `honour` was mine; and it contains exactly two
instances of `a developer`, one of which was mine on a page that
addresses the reader directly throughout.

Callout titles were checked against the house set — declarative or plain
question (`v1 behaves differently`, `Three routes are not user-scoped`,
`Using a custom backend?`) — which is why the aphorism was the one
outlier.

Re-verified after the rewording: tests back to the single pre-existing
failure, every reworded string renders on the right surface, Angular
still shows **zero** React package mentions, and the `StateGraph` step
is still gated to langgraph-python + langgraph-fastapi only.

## Coordination

Draft PR #6112 (onsclom) also touches
`integrations/built-in-agent/quickstart.mdx`, but only two prose lines —
the signup sentence and the "Already have an app?" callout. My hunks are
the install line and the runtime step, so they should merge cleanly.
Flagging rather than assuming.

## Follow-ups this surfaced

- **Threads needs a v2 server migration** from either quickstart's
starting point. No v1-shaped multi-route handler exists. Own ticket.
- **Defects 1 and 2** ride the non-interactive project-selection work.
2026-08-19 14:38:47 -05:00
Benjamin Taylor 9ffe2546ce docs(inspector): document mounting the Inspector in Angular
The Inspector is the framework-agnostic `cpk-web-inspector` web component.
`@copilotkit/angular` does not reference or mount it, so an Angular app has to
create the element itself — and nothing said so. Worse, the Angular docs mapped
the `inspector` slug onto `guides/troubleshooting`, so `/angular/*/inspector`
redirected away from the Inspector entirely and the only thing left under
"Observe & Operate" was the VS Code extension.

Add an Angular-owned Inspector page covering the mount component, the
`core` handoff with `auto-attach-core="false"`, positioning, production
exclusion, server rendering, and cleanup on destroy. Drop the redirect so the
page is reachable, and point at it from the Angular troubleshooting guide.
React's Inspector content is untouched and unmoved.

Also correct the `/cpk-debug-events` path: it is relative to the runtime's
mounted `basePath`, not the server origin, and it only carries events for a
self-hosted SSE runtime — an Intelligence-backed runtime answers runs over the
platform's realtime connection, so the stream connects and stays empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:37:03 -05:00
Benjamin Taylor 6c1a9eb4b4 docs: match the house voice in the OSS-857 prose (refs OSS-857)
A voice pass over everything added for OSS-857, measured against the
corpus rather than guessed.

Second person stays: it is emphatically the house voice — 22 of 29
top-level and backend pages use you/your, and the three pages involved
used it 17, 23 and 64 times before any of these edits. Mid-sentence
`**bold**` for emphasis also stays; the corpus does that 17 times.

What actually drifted:

- `honours` → `serves`. The corpus is American English (behavior 66:6,
  customize 77:4, serialize 18:1, organize 15:0) and the single
  `honour` in it was mine.
- `A developer adding A2UI to an agent they already wrote…` → second
  person. The corpus contains exactly two `a developer`, and one was
  mine; the page around it addresses the reader directly throughout.
- Essay register: "That default is the one thing to remember:" → a plain
  statement of the fact. "so this is the mapping" → "so this table is
  the mapping".
- Conversational: "no provider pairing to get wrong" → "to configure";
  "`uvicorn` is told to listen on 8123" → "`main.py` sets uvicorn's port
  to 8123"; "you may also meet these deprecated aliases" → "older code
  may use these deprecated aliases".
- Coinages: "agent construct" → "how the agent itself is built"; "that
  steer" → "Without it, the model tends to…".
- Aphoristic Callout title "Mismatched pair? Read the symptom, not the
  code" → "A mismatched pair fails at discovery". House titles are
  declarative or plain questions ("v1 behaves differently", "Three
  routes are not user-scoped", "Using a custom backend?").
- Epigram: "It replaces your agent; it does not connect to one." → "It
  replaces your agent rather than connecting to it."
- Redundancy: "nothing supplies persistence for you" → "nothing
  supplies persistence".

The a2ui and LangGraph quickstart wording landed in #6566; those files
are corrected here so the whole ticket reads in one voice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:50:19 -05:00
Benjamin Taylor 4078a11f36 docs(runtime): map the provider/handler pairs and guard BuiltInAgent (refs OSS-857)
Fixes defects 5 and 9 from the OSS-856 phase 1 validation run, plus the
half of defect 6 that lives on the Built-in Agent quickstart. Every claim
was traced through package source.

Defect 5 — three provider/handler names presented as interchangeable.
They are not interchangeable; they pair up, and the pairing is what was
undocumented. Added a "Provider and handler pairs" section to
`backend/runtime-endpoints.mdx`:

- The v1 `<CopilotKit>` wrapper renders `<CopilotKitProvider>` internally
  and pins `useSingleEndpoint` to `true` unless the prop is passed
  (`copilotkit.tsx:108`), so it asks for single-route transport even
  against a multi-route Runtime. `<CopilotKitProvider>` with the prop
  omitted resolves to `auto` and detects from `/info`.
- A table of which handlers serve which mode, and the deprecated aliases
  (`createCopilotEndpoint`, `createCopilotEndpointSingleRoute`, and the
  Express pair) mapped to their replacements.
- The constraint nobody had written down: every `copilotRuntime*Endpoint`
  wrapper builds its handler with `mode: "single-route"` and exposes no
  option to change it. Next.js App Router and node-http call the
  single-route helper directly; pages-router, node-express and nest all
  delegate to node-http. So Rich Threads is unreachable from the wiring
  the quickstarts teach at ANY provider setting — it needs a v2
  `CopilotRuntime` plus a multi-route handler. That is the structural
  reason behind defect 3.
- Provider half is scoped to `<FrontendOnly frontend="react">` with an
  Angular branch, because this page also serves the Angular surface and
  `provideCopilotKit` has no `useSingleEndpoint`.

Both quickstarts gain a short callout naming the pair they use and
linking the mapping.

Defect 9 — the Built-in Agent quickstart (what `/quickstart` actually
serves) instantiates `new BuiltInAgent(...)` as the `default` agent with
nothing warning a reader who already has one. `BuiltInAgent` extends
`AbstractAgent` and calls the model directly via `streamText`, so
registering it replaces the developer's agent rather than connecting to
it — the `user_code_preservation` violation the ticket describes. Added a
caution telling readers with an existing agent to take the frontend steps
here and the runtime wiring from their framework's quickstart.

Defect 6, second half — the same page installed `@copilotkit/react-ui`
and never used it, importing `CopilotKit` and `CopilotSidebar` from
`@copilotkit/react-core/v2`. Dropped it, matching the LangGraph fix in
#6566.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:35:43 -05:00
copilotkit-qa-bot[bot] 573a614112 Merge main into codex/fac-126-strands-ts-starter 2026-08-19 11:34:30 -07:00
copilotkit-qa-bot[bot] b9d41c0e3a Merge main into codex/fac-126-strands-ts-starter 2026-08-19 11:31:21 -07:00
Benjamin Taylor 99d3f99f4c docs(backend): construct the Intelligence client and name its key (refs OSS-857)
Fixes OSS-857 defects 1 and 2, which are one root cause. No page on the web
path ever constructs `CopilotKitIntelligence`, so `intelligence` reads as an
undefined identifier in the `new CopilotRuntime({ agents, intelligence,
identifyUser })` example, and `INTELLIGENCE_API_KEY` reads as a credential
with no consumer. `apiKey` IS that consumer.

The construction was already documented correctly, but only on the Channels
pages (frontends/slack.mdx, frontends/teams.mdx). This lifts the same pattern
onto the web path rather than inventing a second vocabulary for it.

Verified against packages/runtime source rather than inferred:

- `CopilotKitIntelligence` is exported publicly from `@copilotkit/runtime/v2`
  via intelligence-platform -> v2/runtime/index.ts -> v2/index.ts
- `apiKey` is the only required field of `CopilotKitIntelligenceConfig`
- `apiUrl` and `wsUrl` default to the managed platform, and
  `warnOnPartialHostOverride` logs a warning when one is set without the other,
  which is why the docs now say to override both together

The Inspector page was NOT wrong to show `NEXT_PUBLIC_COPILOTKIT_LICENSE_KEY` --
that is the correct variable for that purpose. The defect is that a reader whose
.env holds `INTELLIGENCE_API_KEY` cannot tell whether the two are the same
credential. So that page disambiguates rather than substitutes: publishable
browser key versus server-side project key, with a pointer to what consumes the
latter.

Not verified: the site build and its vitest suite. A fresh worktree has no
installed toolchain (oxlint is absent), and oxlint covers JS/TS rather than MDX,
so it would not have exercised these edits. What was checked instead: <Step>,
<FrontendOnly> and code-fence balance in both files, and both new links against
existing usage -- `](/inspector)` appears 7 times and
`](/backend/runtime-endpoints)` 10 times elsewhere in the content tree. The site
soft-404s on unknown paths, so a link cannot be verified by status code.

Defects 5 and 9 remain open and are not addressed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:16:39 -05:00
Benjamin Taylor f24c4e2f22 docs(langgraph): correct the A2UI StateGraph framing after re-verification (refs OSS-857)
Three fixes found on a second pass over the docs changes:

- The new StateGraph step claimed "the snippet above is the reference
  cell's `create_agent` form". It is not: `create_agent` appears on that
  page only as an *import* — both snippet regions stop inside the tool
  body, so the agent construction is never shown at all. Reworded to say
  that, which is the sharper version of defect 11: the page never shows
  how the tool attaches to any agent, leaving `create_agent`,
  `CopilotKitMiddleware` and `ChatOpenAI` as imports the reader cannot act
  on.
- Carry over the system-prompt caveat. The reference cell steers the model
  to call `display_flight` once and stop because the tool result *is* the
  card; a StateGraph reader who drops that gets repeat tool calls.
- The install note named only two of the four packages the FastAPI tab
  adds on top of the shared line. List all four.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 12:39:11 -05:00
Benjamin Taylor 3ec309b724 docs(langgraph): fix 8 verified defects in the LangGraph onboarding docs (refs OSS-857)
Fixes defects 3, 4, 6, 7, 8, 10, 11 and 12 from the OSS-856 phase 1
validation run. Every claim below was re-verified against installed
package source or a live run, not recalled.

LangGraph quickstart (`integrations/langgraph/quickstart.mdx`):

- Route shape: a caution at the route step. The POST-only route runs the
  runtime in single-route mode, which is all chat needs; Threads and the
  Inspector need the multi-route catch-all with GET/POST/PATCH/DELETE.
  Links to the canonical runtime-endpoints section.
- Port: bare `langgraph dev` serves 2024, not 8123. Verified against both
  CLIs (`@langchain/langgraph-cli` help output, and `default=2024` in
  `langgraph_cli/cli.py`). The guide keeps `--port 8123` to stay
  consistent with every sibling page, and now says so.
- Drop `@copilotkit/react-ui` from the install list. `CopilotSidebar`
  lives in `@copilotkit/react-core/v2`; react-ui exports no `./v2` JS
  entry point and the v2 react example does not depend on it.
- Checkpointer: state the reason each tab differs. `langgraph dev` fails
  to load a graph compiled with a custom checkpointer (reproduced), while
  the FastAPI tab needs one because `ag-ui-langgraph` calls
  `graph.aget_state(...)`, which raises `ValueError: No checkpointer set`.
- Narrow the shared `uv add` line to what both tabs import, and warn that
  a project with exact pins should add them by hand.

A2UI fixed schema (`generative-ui/a2ui/fixed-schema.mdx`):

- Add the missing install step for `@copilotkit/a2ui-renderer` + `zod`,
  which the catalog/definitions/renderer snippets all import.
- Add a `StateGraph` + `ToolNode` form for developers who already have a
  hand-built graph, gated to the Python LangGraph slugs by a new
  `a2ui_agent_form` docs flag so the shared page does not show Python to
  langgraph-typescript or LangGraph code to LlamaIndex/ADK/Mastra.
- Repoint the cross-tree `/integrations/langgraph/...` link, which 301'd
  back to this same page, at the action-handler reference it promises.

Raw Markdown pipeline (`src/lib/llm-text.ts`):

- `renderPageToLlmText` never applied `filterFrameworkScopedBlocks`, so
  `/<framework>/<page>.md` emitted every `<WhenFrameworkHas>` branch with
  raw JSX tags, each carrying the one selected framework's snippet. On the
  A2UI page that produced three mutually-exclusive "how the schema is
  delivered" sections whose prose contradicted the identical code under
  each. Gate on the same framework the snippets resolve to, with a
  regression test.

Also corrects a factually wrong comment in the langgraph-python showcase
`.env.example` that claimed 8123 was the `langgraph dev` default — the
same mis-belief this ticket found in the docs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 12:19:52 -05:00
copilotkit-qa-bot[bot] c81c6e2535 fix: harden Strands TypeScript request boundaries 2026-08-19 08:45:15 -07:00
copilotkit-qa-bot[bot] 1d32e8bd31 Merge main into codex/fac-126-strands-ts-starter 2026-08-19 08:18:38 -07:00
copilotkit-qa-bot[bot] bd91313517 feat: add AWS Strands TypeScript starter 2026-08-18 15:51:47 -07:00
copilotkit-qa-bot[bot] 4879b941ab docs(deepagents): make state rendering example executable 2026-08-18 14:40:02 -07:00
copilotkit-qa-bot[bot] ed5d370936 docs: initialize shared-state rendering example 2026-08-18 14:08:48 -07:00
copilotkit-qa-bot[bot] c45a50fa71 docs: scope tool setup copy to Claude 2026-08-18 13:45:59 -07:00
copilotkit-qa-bot[bot] 40608dc01d docs: show Claude tool-rendering backend wiring 2026-08-18 12:53:15 -07:00
copilotkit-qa-bot[bot] 35f14ff4d0 fix(showcase): prune Claude test deps and clean snippets 2026-08-18 11:02:30 -07:00
copilotkit-qa-bot[bot] 778dde6627 test(showcase): cover Claude SDK MCP wiring 2026-08-18 10:01:58 -07:00
copilotkit-qa-bot[bot] 0d528d57cc docs(showcase): expose Claude fixed-schema backend wiring 2026-08-18 09:48:32 -07:00
Benjamin Taylor dc2aee1218 docs(channels): name which Microsoft bot identity Teams setup creates
The docs said only what setup does not create — no Azure subscription, no
Azure Bot resource. That answers nothing for the tenant administrator who has
to account for a new identity in their own directory, and the only way to
learn the answer was to read our CLI source and notice `--teams-managed`.

Names the kind we create and contrasts it with the two alternatives: where the
credentials live, who rotates them, that it is single-tenant, and that nothing
is billable. Also states that the choice is effectively permanent, since the
app ID is the manifest's bot ID.

Kept to one section on the tutorial page, which already carried the
bot-ownership sentence, rather than a new page. It sits after the orientation
links as an `h2` alongside the page's other sections; as an `h3` wedged between
the intro and those links, the links read as part of it.

refs OSS-833

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 20:21:04 -07:00
Benjamin Taylor 1b7d56029c docs(channels): correct the Teams package hand-off
The rewrite claimed the command "installs it to a Team you choose" and that
"there is no package to download". Neither is true. Microsoft exposes no
install API, so `runAutomaticTeamsProvision` writes
`<channel-name>-teams-app.zip`, prints upload steps, and blocks on Enter
before verifying the installation. The old prose and the Teams tutorial's
"upload the complete zip that setup produced" also contradicted each other.

Documents what the command actually hands you: the package path, that the
upload must come from the team's own Apps tab rather than the personal Apps
section — personal scope yields a working DM that cannot be promoted — and
that a tenant disallowing custom apps means Ctrl-C, get an administrator to
install it, and re-run to resume rather than create a second app.

Also covers `--teams-package`, which the wizard adds when the reader
customizes branding: the browser builds that file, the CLI validates it,
builds the app from it, and deletes the local copy. It decides how the app
looks and does not replace the Team upload.

Strikes "no Azure subscription and no Azure Bot resource are involved" from
the Intelligence step. The Teams tutorial still says it, once, where a reader
arriving with Azure expectations actually starts.

The doc test pinned that struck sentence. It now pins the package hand-off
instead, which is the claim that was wrong and would otherwise drift back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 20:21:04 -07:00
Benjamin Taylor df8ae72069 docs(channels): document the shipped Teams setup flow
The Teams docs described a flow that no longer exists: registering an app
by hand in Microsoft Entra, pointing an Azure Bot at the Intelligence
messaging endpoint, pasting a client ID, tenant ID, and secret into the
wizard, and downloading a package to upload. Setup is now a single CLI
command that creates a Teams-managed bot in the reader's own tenant,
registers the endpoint, and installs the app.

Also corrects the file permission. The manifest requests the read-only
`Files.Read.All`, not `Files.ReadWrite.All`, and it is optional: skipping
it costs only files uploaded to a Team channel, and no longer holds the
connection back from reporting ready.

The doc test pinned the old prose, so it is repointed at the new
contract — the command's flags, both permissions and which is optional,
and that Entra and Azure Bot appear only to say they are not involved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 20:21:04 -07:00
Ben Taylor 983c205feb fix: stale useCoAgent node name, silent content-part drops, and the LangGraph docs gaps (#6520)
Bundles the CopilotKit-side work from **OSS-769**, **OSS-767**
(partial), and the unshipped remainder of **OSS-609**.

## OSS-769 — `useCoAgent().nodeName` never updates

`useAgentNodeName` tracked the current node in a ref and returned
`nodeNameRef.current`. Mutating a ref schedules no render, so a
component reading `useCoAgent().nodeName` kept showing whichever node
was current at its last render and never updated on its own — it only
appeared to work when something unrelated happened to re-render it.

Backing the value with state fixes it. Five lines, no API change.

## OSS-767 (partial) — silent content-part drops (#1748)

`normalizeMessageContent` handles only `text` and `binary` parts;
anything else — the `{"type": "image", ...}` case from the report — maps
to `""` and is filtered out with no signal, so an agent emitting
structured content sees its output vanish silently.

This makes the drop visible, once per unrecognised part type so
streaming doesn't flood the log. Deliberately **not** the schema change:
carrying structured assistant content needs an `AssistantMessage`
decision upstream in `ag-ui`, which stays open on OSS-767.

## OSS-609 — the five docs gaps that never shipped

Gap #2 shipped in #6403; gaps 1, 3, 4, 5 and 6 did not.

| Gap | Where | Closes |
| --- | --- | --- |
| AWS Lambda self-hosting | `docs/deploy/aws-lambda.mdx` | #1151 |
| Per-user thread authorization | `docs/auth.mdx` (new section) | #2241
|
| Thread persistence without the platform |
`docs/threads-self-managed.mdx` | #6090 |
| DIY guardrails / DLP | `docs/integrations/langgraph/guardrails.mdx` |
#3414 |
| When you need an MCP App | `docs/agentic-protocols/mcp.mdx` (new
section) | #5991 |

The Lambda guide leads with the constraint that actually bites —
streaming is opt-in on every front door, so a chat runtime deployed with
the defaults appears to hang for the whole run and then dumps the reply
at once. It documents the Function URL + `RESPONSE_STREAM` path as the
default, and API Gateway REST + `responseTransferMode: STREAM` for
anyone who needs a REST API in front.

`threads-self-managed` follows the existing shared-snippet pattern with
per-framework wrappers, because two nav contracts require it: every
authored framework must publish the page, and every React destination
must map to an Angular one (`ANGULAR_DOC_REDIRECTS`). All nine wrappers
were confirmed necessary by deleting one and watching the suite fail.

## Review corrections

Two blockers from @MikeRyanDev, both verified against primary sources
before changing anything.

**API Gateway REST APIs can stream**
([`91b3e632d5`](https://github.com/CopilotKit/CopilotKit/commit/91b3e632d5)).
The guide was built on the pre-November-2025 limitation and claimed no
API Gateway type supports response streaming, steering readers to a
buffered `serverless-http` setup. REST gained it via
`responseTransferMode: STREAM`, which also lifts the 10 MB cap and
29-second timeout. REST and HTTP are now split; REST is documented as a
streaming front door (payload-format-1.0 event adapter, `AWS_PROXY`
integration on the `2021-11-15/.../response-streaming-invocations` URI,
CLI/CDK/SAM config), and the buffered fallback is scoped to HTTP APIs
and ALB, which still have no streaming path. Added the constraints that
actually matter for chat: the 30-second idle timeout on edge-optimized
endpoints (5 min Regional), and the console Test tab always buffering so
a working config looks broken.

**`identifyUser` is the platform's thread-scoping binding**
([`91b3e632d5`](https://github.com/CopilotKit/CopilotKit/commit/91b3e632d5)).
The section told every reader to build an ownership table and enforce it
in `onBeforeHandler`. On the Intelligence path the runtime already
resolves `identifyUser(request)` server-side and carries that id to the
platform; `listThreads` is scoped by user *and* filtered by `agentId`,
so the "every user of one project sees that project's threads" claim was
wrong. `identifyUser` is now documented as the binding, and the DIY
pattern is scoped to SSE runtimes, custom stores, and the local
in-memory runner.

**Follow-up correction — two routes are genuinely unscoped**
([`240672ff56`](https://github.com/CopilotKit/CopilotKit/commit/240672ff56)).
My rewrite then over-claimed. `handleGetThreadEvents` and
`handleGetThreadState` resolve the caller and discard it, and the
platform client takes no `userId` on either method
(`client.ts:1113`/`1135`) — unlike `getThreadMessages` at `1063`. Both
hit project-authenticated `_inspect` endpoints, so any caller
`identifyUser` accepts can read the event log and agent state of **any
thread in the project** given its id. The blanket guarantee is replaced
by a per-route table marking those two explicitly unscoped, plus an
`onBeforeHandler` guard narrowed to them.

That is a live gap in shipped runtime code, not a docs error, and it is
tracked as **OSS-851** — a platform-side `_inspect` change plus matching
runtime/client work and tests, out of scope for a docs PR. The interim
callout in `auth.mdx` comes out when OSS-851 lands.

## Not in this PR

**OSS-772** and **OSS-773** are already merged in
`oss-path-to-production` (#237, #238). Both are telemetry-sink changes
with no CopilotKit-side component. OSS-773's remaining half — re-keying
runtime `distinct_id` from email to the Clerk subject — is recorded on
the ticket as an open decision, not a task.

## Testing

**OSS-769.** New `use-agent-nodename.test.tsx`, 5 tests. Against
unmodified `origin/main`, **4 of 5 fail**:

```
× re-renders consumers on every node transition
× reports 'end' when a run errors
× resets to 'start' when a new run begins
✓ unsubscribes on unmount
× carries the agent, thread, and current node
  Tests  4 failed | 1 passed (5)
```

With the fix: `Tests 5 passed (5)`. These assert only re-render
behaviour under normal `act()` flushing — no manufactured intra-batch
window.

**Full react-core suite:** `Tests 7 failed | 1496 passed (1503)`. All 7
failures are **pre-existing** `ResizeObserver is not a constructor`
under jsdom, confined to `CopilotChatView.pinToSend` and
`use-pin-to-send` — neither of which this PR touches.

**Typecheck:** `packages/react-core` → `tsc --noEmit` exit 0, no output.

**OSS-767:** 3 new tests covering the warn, warn-once-per-type, and
no-warn-for-supported-types. `src/graphql/message-conversion/` → `Tests
125 passed (125)`.

**Docs:** `showcase/shell-docs` → `Tests 1 failed | 373 passed (374)`.
The single failure (`channels-docs > publishes the Channels overview
only through provider navigation`) is **pre-existing**; baselining with
all changes stashed reproduces it and nothing else. Re-run unchanged
after both review-correction commits.

**Review corrections.** The AWS rewrite was checked against the AWS
sources rather than written from memory — the REST streaming
announcement, `configuration-response-streaming`,
`response-transfer-mode` (endpoint-type idle timeouts, unsupported
buffered-only features), `response-streaming-lambda-configure`
(CLI/OpenAPI shapes), the CFN `Integration` reference, and the CDK
`ResponseTransferMode` enum. Two details corrected in passing:
`InvokeWithResponseStream` authorizes against plain
`lambda:InvokeFunction` (no new grant, contrary to what the streaming
URI suggests), and ALB still has no Lambda streaming path.

The auth corrections were verified by reading the handlers and the
platform client, not the tests — `resolve-intelligence-user.ts`,
`intelligence/threads.ts` (every `resolveIntelligenceUser` call site),
and `intelligence-platform/client.ts`. The existing tests assert the
`threadId`-only call shape, so they pass under the defect and could not
have surfaced it. Also corrected: there is no `threads/delete` route —
delete is `DELETE` on `threads/update` (`fetch-handler.ts:606`).

Both edited pages MDX-compile clean, and all inbound
`#thread-authorization` anchors still resolve after the h3→h4 demotions.

Two nav tests broke during this work and are fixed rather than papered
over — adding a page to the Rich Threads group violated the
cross-framework ordering contract and the React→Angular parity contract:

```
src/lib/__tests__/docs-render.test.ts
src/lib/__tests__/angular-docs-content.test.ts
  Test Files  2 passed (2)
       Tests  33 passed (33)
```

All 15 internal links in the new pages resolve against the content tree.

Closes #1151, #2241, #3414, #5991, #6090
Refs #1748, OSS-851
2026-08-17 19:17:50 -05:00
copilotkit-qa-bot[bot] eb567d44ae fix(docs): address programmatic control review feedback 2026-08-17 16:31:46 -07:00
copilotkit-qa-bot[bot] 1cc34c641a fix(docs): make programmatic control example self-contained 2026-08-17 16:09:35 -07:00
copilotkit-qa-bot[bot] ecaa5c5fd8 fix(docs): validate Claude run input before streaming 2026-08-17 15:47:46 -07:00
Benjamin Taylor 21ab0c6eb6 docs(auth): add agent/stop to the unscoped routes, demote identifyUser
The previous commit claimed every route resolves the caller through
identifyUser. It does not. handleStopAgent never resolves a user at all:
it calls runtime.runner.stop({ threadId }) directly, and
IntelligenceAgentRunner.stop looks the run up by threadId alone. Any
caller who learns an active threadId can abort that run mid-flight, and
an application relying on identifyUser as its authentication backstop is
not covered on this route because the callback is never invoked.

Audited every route on the Intelligence path rather than patching the one
that was reported. agent/stop is the only additional gap: agent/suggest
deliberately skips thread persistence, and threads/clear is a no-op off
the in-memory runner, which is the only runner exposing local thread
endpoints.

Drop the false universal-resolution claim, add agent/stop to the No row
and the targeted guard, and state plainly that onRequest is the
authentication gate while identifyUser only names an already
authenticated caller. The guard uses a switch rather than an array
membership test so route narrows and route.threadId type-checks; the
includes form fails with TS2339, verified against the real RouteInfo.

Refs OSS-851

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:29:17 -05:00
Benjamin Taylor 383c3f697c docs(auth): flag threads/events and threads/state as unscoped
The revised thread-authorization section claimed the Intelligence runtime
scopes every thread-bearing route to the resolved user. It does not.

handleGetThreadEvents and handleGetThreadState resolve the caller through
identifyUser and then discard the result, calling getThreadEvents({
threadId }) / getThreadState({ threadId }). The platform client takes no
userId on either method — both hit project-authenticated _inspect
endpoints — so any caller identifyUser accepts can read the event log and
agent state of any thread in the project given its id.

Replace the blanket guarantee with a per-route table, and document the
onBeforeHandler guard narrowed to those two routes so the Intelligence
path is not told there is nothing to build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor 9ea276b740 docs: correct API Gateway streaming and Intelligence thread scoping
Two review corrections.

API Gateway REST APIs gained Lambda response streaming in November 2025
via responseTransferMode: STREAM, which also lifts the 10 MB payload cap
and the 29-second integration timeout. The guide claimed no API Gateway
type can stream and steered readers to a buffered serverless-http setup.
Split REST from HTTP, document the REST streaming path (payload format
1.0 adapter, AWS_PROXY integration on the response-streaming-invocations
URI, CLI/CDK/SAM config), and scope the buffered fallback to HTTP APIs
and ALB, which still have no streaming path.

The thread-authorization section told every reader to build an ownership
table and enforce it in onBeforeHandler. On the Intelligence path the
runtime already resolves identifyUser(request) server-side and passes
that user id to run, connect, the thread reads, the mutations, and the
subscription token; listThreads is scoped by user and filtered by
agentId. Document identifyUser as the platform binding and scope the DIY
pattern to SSE runtimes, custom stores, and the local in-memory runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor 6548d59f40 docs(mcp): distinguish when an MCP App is and is not needed
mcp.mdx treated all non-App MCP servers generically, leaving #5991's
question unanswered: readers could not tell whether their server needed a
UI resource at all.

Separates the three cases — read-only context servers (no renderer needed),
action servers (renderer plus human-in-the-loop approval), and interactive
MCP Apps — with the test for choosing between them.

Closes #5991

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor 98e02e9a4c docs(langgraph): add a guardrails and DLP how-to
Only adjacent coverage existed — the AG-UI middleware guide and a
third-party governance cookbook — so #3414's question about input/output
screening had no first-class answer.

Covers the built-in PIIMiddleware first, then custom AgentMiddleware for
input screening (before_model + jump_to), output and DLP screening
(wrap_model_call), and tool-call screening, plus why guardrails must be
ordered ahead of CopilotKitMiddleware.

Closes #3414

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor e996328f15 docs(threads): answer the bring-your-own-thread-backend question
#6090 asked how to point CopilotKit threads at a different database. There
is no such extension point, and nothing said so — the honest answer is that
Rich Threads are an Enterprise Intelligence Platform capability, and the DIY
path is own-the-threadId plus framework-layer persistence.

Documents that path and what it costs relative to Rich Threads. Body lives
in a shared snippet with the usual per-framework wrappers, since the nav
parity contract requires every authored framework to publish the page and
every React destination to map to an Angular one.

Closes #6090

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor 0e724231ec docs(auth): document per-user thread authorization
auth.mdx covered token forwarding and the onRequest gate but stopped at
authentication. Nothing tied a threadId to a user, so a caller who learned
another user's thread id could reach it through every thread route.

Adds the ownership-table pattern and the onBeforeHandler enforcement point,
including which routes carry threadId on `route` versus in the request body,
and why the thread list has to be filtered server-side.

Closes #2241

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
Benjamin Taylor 2e1b125049 docs(deploy): add an AWS Lambda self-hosting guide
The generic adapter guide covers Lambda only by implication, and the part
that actually bites is undocumented: API Gateway and ALB buffer responses,
so a chat runtime behind either appears to hang for the whole agent run and
then dumps the reply at once.

Documents the Function URL + RESPONSE_STREAM path as the default, with the
awslambda.streamifyResponse wiring that bridges the runtime's Fetch Response
into the Lambda response stream, and keeps the serverless-http/API Gateway
path for the cases that genuinely do not stream.

Closes #1151

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:25:01 -05:00
copilotkit-qa-bot[bot] e8aa97649c fix(docs): parse Claude request before streaming 2026-08-17 15:12:58 -07:00
Murat Sari 703944880d feat(angular): expose agent capabilities 2026-08-16 22:11:36 +02:00
Murat Sari 5720cd7fdc feat(angular): support fetch credentials 2026-08-16 21:03:52 +02:00
Tyler Slaton 4093ab6289 docs(shell-docs): add LangSmith Platform deploy guide (LangGraph + ADK) (#6114)
## What

Adds a **LangSmith Platform** deploy guide to the CopilotKit docs,
modeled after the existing AWS AgentCore deploy page.

It's a self-contained, agent-side guide: deploy a **LangGraph** or
**Google ADK** agent to the LangSmith Platform, then point the
CopilotKit Runtime at it. LangSmith has no frontend-hosting offering, so
the guide covers only the agent side plus wiring the runtime.

## Pages

- **Canonical:** `deploy/langsmith.mdx` — renders in the Overview →
Deploy sidebar.
- **Per-framework wrappers** (thin, like AgentCore):
- `integrations/langgraph/deploy-langsmith.mdx` → `<Content
framework="langgraph" .../>`
- `integrations/adk/deploy-langsmith.mdx` → `<Content framework="adk"
.../>`
- Both registered in their `meta.json` under a new `---Deploy---`
section.
- **Shared walkthrough snippet:**
`snippets/integrations/langsmith/index.mdx` — single source of truth for
all three pages; framework-aware via the `Content` loader scope. Reuses
the existing `langgraph-platform-deployment-tabs` snippet for the "grab
your deployment URL" step.

## Structure (mirrors agentcore.mdx)

Intro → How it works (ASCII flow `Browser → CopilotKit Runtime →
LangSmith deployment → your agent`) → What you get → Quickstart
`<Steps>` inside a `<TailoredContent>` (deploy-new vs already-deployed)
→ `<Callout>`s for the API key/URL and the LangSmith docs authority →
framework tabs (LangGraph / Google ADK) for the deployable-app step →
Troubleshooting `<Accordions>` → What's next `<Cards>`.

## Registry glue

- Generalized the `Content` MDX component to accept an optional
`partial` prop (defaults to the AgentCore partial; existing AgentCore
wrappers unchanged).
- Registered a `LangGraphPlatformDeploymentTabs` stub so the existing
deployment-tabs snippet is reusable.

## Verification

- Commands/flags (`uv tool install langgraph-cli`, `langgraph new
--template new-langgraph-project-python`, `langgraph deploy
--name/--deployment-type dedicated`, deployment API URL) verified
against the live LangChain quickstart.
- ADK path (`pip install "deployments-wrap-sdk[google-adk]"`,
`saf_sdk.adk` `wrap()` + `LangsmithSessionService`, `langgraph.json`
export) verified against the live [Deploy Google ADK
agents](https://docs.langchain.com/langsmith/deploy-google-adk) guide.
- Runtime wiring (`LangGraphAgent` from `@copilotkit/runtime/langgraph`
with `deploymentUrl` / `graphId` / `langsmithApiKey`) matches the repo's
LangGraph quickstart.
- `oxfmt` (format) clean, `oxlint` exits 0, `tsc` clean; registry /
search-href / link-rewrite tests pass. (Pre-existing failures in this
worktree from an uninstalled `react-icons` and unfetched git-LFS assets
are unrelated.)

## Note (small extra)

The LangGraph `deploy-agentcore.mdx` wrapper already existed but was
orphaned (not in any `meta.json`). The new `---Deploy---` section
surfaces it alongside `deploy-langsmith`, matching how AWS Strands
already exposes it.

Ticket: GROW-540
2026-08-14 15:01:52 -07:00
Maximiliano Korp eb3f430ae1 feat(runtime): mark Learning config experimental 2026-08-14 10:43:51 -07:00
Mike Ryan a9f283ab55 feat(runtime): assign threads to Learning Containers 2026-08-14 10:34:27 -07:00
Ran Shemtov fa13d52502 Merge branch 'main' into codex/crewai-full-d6 2026-08-14 09:37:42 +02:00
Tyler Slaton 83bd1f9088 Revert "docs: define public AEO surface contract (#6458)"
This reverts commit d21aebc6e2, reversing
changes made to b075704c77.
2026-08-13 10:45:31 -07:00
Alem Tuzlak 4e9eee3094 feat(runtime): add MiniMax built-in models (#6464)
Reason: Add the current MiniMax text models to BuiltInAgent model
resolution.

- Register MiniMax-M3 and MiniMax-M2.7 as built-in model identifiers.
- Resolve MiniMax model strings through the global endpoint with API key
and regional base URL configuration.
- Document both model specifiers and cover global and China endpoint
selection.

Checks:
- `node_modules/.bin/nx run @copilotkit/runtime:test --
src/agent/__tests__/resolve-model-baseurl.test.ts`
- `node_modules/.bin/nx run @copilotkit/runtime:check-types`
- `pnpm validate:model-names`
- `node_modules/.bin/nx format:check
--files=packages/runtime/src/agent/index.ts,packages/runtime/src/agent/__tests__/resolve-model-baseurl.test.ts`
- `git diff --check`
2026-08-13 18:57:51 +02:00
Alem Tuzlak 19fb1329b7 fix(shell-docs): repair 15 reader-visible doc defects (#6425)
Fifteen defects in the shell-docs tree, each verified against the
running site or the source of truth rather than pattern-matched. Found
while root-causing
[PDX-313](https://linear.app/copilotkit/issue/PDX-313).

Scoped deliberately: this is content only. The checker changes that
surfaced these follow separately.

## Snippet components used with props but never imported (7)

The subtlest item here, and invisible to anyone skimming the source.

`<FrontendTools components={…} framework="pydantic-ai" />` without an
import falls through to `stubWithPartial` in the global mdx-registry,
which drops props "on the floor" by design. So `framework` never reached
the partial and the shared snippet rendered **untailored** — the reader
got generic content on a framework-specific page.

The `mastra` and `ag2` siblings were already correct. All seven broken
ones are in authored trees, matching the template-residue pattern from
OSS-777.

## Tutorial cross-links that land on the homepage (4)

`/tutorials/ai-todo-app` and `/tutorials/ai-powered-textarea` have no
`index.mdx`, so they `307 -> /`. A reader clicking "next: the todo app
tutorial" gets the docs homepage. The pages are at `/overview`.

## Dead `YouTubeVideo` imports (2)

The component is provided globally by `mdx-registry.tsx`, and four other
pages render it with no import at all. These two imported a module that
has never existed in the repo.

## Stale `byoc-*` demo ids (2)

Renamed to `declarative-*` in 70e2fb31 (2026-05-10, *"rename byoc-\*
slugs to declarative-\*"*); the docs were never updated, so the ids
resolve against nothing in the registry. Only the three registry ID
references per page change — `snippet_cell`, `InlineDemo`,
`IntegrationGrid`.

## What was cut, and why

An earlier revision of this PR also rewrote nine `/integrations/<fw>/*`
links to their canonical URLs. Checking production, those were never
broken:

```
/integrations/adk/quickstart  ->  301  /google-adk/quickstart
```

`seo-redirects.ts` keeps that retired surface alive for inbound SEO
traffic, so readers always landed correctly. Canonicalizing them is
still worth doing — a 301 costs a round trip and couples internal
navigation to a legacy surface — but it is cosmetic, and it was padding
a diff whose value is the defects above. Dropped; tracked separately.

## Left alone deliberately

The `runtimeUrl` / `agent` code samples on `generative-ui/hashbrown.mdx`
and `generative-ui/json-render.mdx`. The API routes were renamed to
`copilotkit-declarative-*`, but the agent ids were **not** renamed
consistently:

| demo | agent id |
| --- | --- |
| `declarative-hashbrown` | `agent="declarative-hashbrown-demo"`
(renamed) |
| `declarative-json-render` | `AGENT_ID = "byoc_json_render"` (not
renamed) |

A blind find-and-replace over `byoc-` would have shipped a broken
copy-paste sample. Needs an owner's call.

## Review notes

15 files, +17/-12. The seven import additions are the only changes that
affect what renders; the rest are identifier strings and link targets.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-13 18:50:31 +02:00
Alem Tuzlak 47ad5e34a3 refactor(react-native)!: converge tool-call rendering onto CopilotKit's shared registry (#6438)
## What does this PR do?

`@copilotkit/react-native` maintained a **private tool-call render
registry** (`hooks/RenderToolContext.tsx`) alongside the canonical one
that `CopilotKitCoreReact` already provides — and which every React
Native app already ships, unused. This PR deletes the fork and points
React Native at the shared registry.

That fork caused three bugs:

| Bug | Symptom | Cause |
|---|---|---|
| **Tool renders never streamed** | A component registered with
`useRenderTool` / `useComponent` painted nothing until the tool call
completed | `CopilotChat` used `JSON.parse` on the argument buffer.
While a model writes a tool call that buffer is *invalid JSON by design*
— AG-UI delivers `TOOL_CALL_ARGS` deltas that are concatenated
client-side — so the parse threw on every delta, warned, and fell back
to `{}` |
| **`useComponent` rendered nowhere** | Silently, with no error | It
writes to core's registry; React Native's chat read React Native's
private `Map` |
| **Chat history degraded** | Navigating away from the registering
screen turned earlier tool calls into a `Called: <name>` placeholder |
The private `Map` deleted renderers on unmount; core deliberately keeps
them |

`@copilotkit/react-core` has used `partialJSONParse` on this path since
v2 shipped. React Native diverged because `useRenderToolCall` was
excluded from its re-exports on the stated grounds that it "depends on
DOM elements via `DefaultToolCallRenderer`" — a claim that was never
true of the hook itself. It was only ever reachable through the fat
`/v2` entry, whose weight is the real hazard (#4893). #5883 moved it
into `/v2/headless` on 2026-07-23; the exclusion comment was rewritten
the next day without revisiting the reason.

### What changed

- **One registry.** `useRenderTool` registers through `useFrontendTool`
into `CopilotKitCoreReact.renderToolCalls`. `CopilotChat` and any custom
surface consume react-core's `useRenderToolCall`.
- **Types are derived, not declared.** `RenderToolProps` is now
`React.ComponentProps<ReactToolCallRenderer<T>["render"]>`, so React
Native cannot drift from `ReactToolCallRenderer` — the contract every
registered renderer is actually invoked against. Change that contract
and `check-types` names every React Native renderer the change breaks.
React Native narrows only the *return* type to `ReactElement | null`,
which `FlatList`'s `renderItem` genuinely requires.
_Scope of that guarantee (corrected during review):_ it does **not**
extend to the type react-core publicly exports under the same name.
Web's `RenderToolProps<S>`
(`react-core/src/v2/hooks/use-render-tool.tsx`) is a separate
hand-declared union, generic over a schema, carrying arguments under
`parameters` (not `args`) and declaring `status` as string literals
rather than `ToolCallStatus` members. Both divergences are live today
and nothing type-checks them shut — the one place the shapes meet,
react-core's own bridge, compiles because a string-enum member is
assignable to its own literal type but not the reverse. Aligning web's
alias is a breaking web API change, filed separately.
- **`RenderToolContext.tsx` deleted** (−150 lines), along with 15 tests
that described the removed subsystem. One of them — `unregisters the
render function on unmount` — asserted the chat-history bug as a
requirement.
- **Two structural CI guards for #4893**, in opposite directions: a test
failing if any React Native source imports the fat `/v2` entry, and a
script failing if react-core's `/v2/headless` or `/v2/context` chunks
ever link shiki/mermaid/cytoscape/katex/streamdown. Both were verified
able to fail by deliberately introducing the regression. These are
*structural* assertions, not size budgets — `dev-docs/bundle-size.md`
freezes `limit` fields until OSS-122.
- **`react-native` added to the bundle-size glob**, which it had never
been in, plus a `size:headless` measurement.

React Native also gains capabilities it lacked: render props inferred
from your schema, `name`/`toolCallId` on render props, and `result` on
completed calls.

**Corrected during review — two capabilities this originally claimed are
not delivered:**

- **Wildcard (`"*"`) renderers do not work on React Native.** Because
`useRenderTool` routes through `useFrontendTool` (which calls
`addTool`), `name: "*"` registers a frontend tool literally named `*` —
advertised to the model, and colliding with core's separate
wildcard-executable-tool path. react-core's `useRenderTool` is
renderer-only and special-cases the wildcard; React Native's is not. The
guide now advises against it.
- **`followUp` (and `available`) are not forwarded**, and the handler's
`context` argument is dropped, so `stopAgent()`'s abort signal is
unreachable from an RN handler.

Both are tracked in § Known limitations for the follow-up that converges
React Native onto react-core's hooks — deleting RN's `useRenderTool` in
favour of re-exporting `useFrontendTool` (tool + renderer) and
react-core's `useRenderTool` (renderer-only, wildcard-capable). That is
an API change with its own migration note, so it is not in this PR.

### ⚠️ Breaking (in a minor)

`useRenderToolRegistry` and `RenderToolProvider` are **removed**. Both
are documented on the docs site, so this is a real break — see the
`BREAKING CHANGE:` footer on `db67ccf`, which is what the release notes
derive from, plus the rewritten reference pages.

```diff
- const registry = useRenderToolRegistry();
- const renderer = registry.get(toolCall.function.name);
- return renderer ? renderer({ args, status }) : null;
+ const renderToolCall = useRenderToolCall();
+ return renderToolCall({ toolCall });
```

Also note two semantic changes: `args` is `Partial<T>` **only** while
`status` is `"inProgress"`, and a render function is now captured at
registration — if it closes over changing values you must declare them
in `deps` (React Native previously refreshed the closure on every
render).

**Known limitation:** agent-scoped renderer resolution does not take
effect on React Native. `CopilotChatConfigurationProvider` is not in
RN's provider tree, so `agentId` always resolves to the default.
Renderers still resolve by name; two agents registering the same tool
name resolve arbitrarily. Filed separately.

### A data point worth recording

Adding `useRenderToolCall` to the measured headless entry moved the
bundle **92.8 kB → 92.7 kB**. Flat. The hook React Native spent months
not using was already inside the chunk every RN app resolves whole —
Metro doesn't tree-shake, so the fork never saved a byte. It cost them.

### Testing

- `@copilotkit/react-native`: **253 passing / 22 files** ·
`@copilotkit/react-core`: **1480 passing / 123 files** · `check-types`
and `build` green for both.
- Each of the three bugs has a deterministic test driving a real
`CopilotKitCoreReact` — no mocking of the code under test.
- Both #4893 guards carry mutation evidence: introduce the regression,
watch them fail, revert, watch them pass.

### Follow-up

`useRenderTool`'s JSDoc is split across two blocks, which orphans the
primary description from IDE hover (the `@param deps` warning still
surfaces). One-line fix, deliberately left out of the final fix wave.

## Related PRs and Issues

- **Supersedes #6346** (@davidmckayv) — its diagnoses were correct and
its test assertions are ported here, re-driven through the real registry
rather than a mocked local one. Credited via `Co-Authored-By` on
`4104bd1`.
- Addresses the React Native half of **#4893**.
- Builds on **#5883**, which created the lean `/v2/headless` entry this
PR consumes.

## Checklist

- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-13 18:49:32 +02:00
Maxim 9eacbfdec3 docs(react-native): name the TS18048 args break in useRenderTool
The PR's reviewer asked that the `RenderToolProps` shape change be covered where upgraders
actually read it. A `BREAKING CHANGE:` footer on ec42161670 already describes it, but that
footer reaches no reader: `scripts/release/lib/changes.ts:43` collects commits with
`--format=%H %s`, and `grep -rn "BREAKING" scripts/release/` returns zero hits, so no footer
in this repo has ever reached generated release notes. The docs page is the destination that
does reach users. (The collector is a release-pipeline bug, filed separately.)

Checked every item in ec42161670's inventory against the page. All were present and accurate
except one: the page said renderers "must now tolerate missing fields while in progress" and
stopped there, naming no error code and never mentioning `check-types`. That is the half of
the change most existing renderers trip over, and it breaks the build, not the screen.

Added to the migration section: on the un-narrowed union `args` is `Partial<T> | T`, so
`args.foo` reads as `T["foo"] | undefined` and a strict `tsc --noEmit` rejects any use needing
the field present — TS18048 when dereferencing or calling it, TS2322/TS2345 when passing it
into a slot typed without `undefined`. Deliberately NOT claimed as a blanket "every read
fails": a bare JSX interpolation still compiles because an element accepts `undefined`
children, and the page's own Usage example does exactly that, so the blanket form would have
contradicted a working example on the same page. Added a fix diff narrowing on `status`, and
a cross-link to the two Behavior-section breaks a migration-only reader would otherwise
miss (render captured at registration, unmount keeping the renderer).

Verified against source, not just the footer: the three-arm union in
react-core/src/v2/types/react-tool-call-renderer.ts, the effect keyed on
JSON.stringify(deps) and the cleanup that removes only the tool in use-frontend-tool.tsx,
and `"strict": true` plus `check-types` in the react-native package.

Docs only; one file, no restructuring, nothing removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:04:50 +02:00