Closes#2615.
## Problem
The v1 thumbs callbacks were typed `(message: Message) => void`, so a
consumer
received the message but not *what the click did*. A custom
`AssistantMessage`
that keeps its own toggle state — the case in the issue — had nowhere to
put
that value:
```tsx
onClick={() => {
setReactionValue((v) => (v === "like" ? null : "like"));
onThumbsUp?.(message); // 👈 no way to say whether this applied or retracted
}}
```
The only workaround was counting clicks per message.
The built-in path had the matching gap. `Chat.tsx` already tracked
`messageFeedback`, but `handleThumbsUp` unconditionally wrote
`"thumbsUp"`, so
the state was write-only: clicking an active button re-applied the same
value
and there was no way to un-vote.
These callbacks are live API — `RenderMessage` forwards `onThumbsUp`,
`onThumbsDown` and `feedback` to whatever component is passed as the
`AssistantMessage` prop, which is exactly the customisation the issue
describes.
## Fix
Add an optional second argument reporting the state the click
transitions to:
```ts
onThumbsUp?: (message: Message, isActive?: boolean) => void;
```
- The built-in `AssistantMessage` derives it from the message's current
`feedback`, so a second click on an active button now reports `false`
and
**retracts** the feedback rather than re-applying it.
- A custom `AssistantMessage` can pass its own value straight through.
- The parameter is optional and appended, so existing one-argument
handlers
keep type-checking and keep working unchanged.
`onFeedbackGiven` fires only on the applying click — its signature is
`(messageId, "thumbsUp" | "thumbsDown")` and has no way to express a
retraction, so reporting one would be a lie.
Note this is a small behavioural change to the built-in buttons:
previously a
repeat click was a no-op, now it clears the vote. That is what makes the
reported state meaningful, and it matches the toggle UX in the issue.
The toggle logic lives in a new `./feedback` module. This package's
vitest
project runs `environment: "node"` and only collects `*.test.ts`, so
there is no
component-rendering harness here — extracting the pure functions is what
makes
the behaviour testable at all.
## Testing
`packages/react-ui` — full suite, including the 10 new cases:
```
✓ src/components/chat/feedback.test.ts (10 tests) 3ms
✓ src/components/chat/Markdown.test.ts (29 tests) 5ms
✓ src/components/chat/Markdown.xss.test.ts (13 tests) 182ms
✓ src/css/sidebar-full-height.test.ts (4 tests) 2ms
Test Files 10 passed (10)
Tests 68 passed (68)
```
Coverage: activation from no feedback, deactivation on the
already-active
button, switching to the opposite button, retraction removing the map
entry
rather than storing a falsy value, other messages left untouched, no
mutation of
the previous map, referential stability when nothing changes, and a
toggle
round-trip.
**Mutation checks** — broke each mechanism and confirmed the tests fail,
so they
are not self-fulfilling:
| Mutation | Result |
| --- | --- |
| `isActivatingClick` → `return true` | `Tests 1 failed \| 9 passed` |
| `applyFeedbackClick` retraction branch → `if (false)` | `Tests 3
failed \| 7 passed` |
Both were reverted and the suite returned to 68 passing.
**Types/build** — `tsc --noEmit` clean, `tsdown` clean, and the widened
signature reaches the published types:
```
$ grep -n "onThumbsUp" dist/index.d.mts
102: onThumbsUp?: (message: Message, isActive?: boolean) => void;
185: onThumbsUp?: (message: Message, isActive?: boolean) => void;
272: onThumbsUp?: (message: Message, isActive?: boolean) => void;
```
The thumbs callbacks only received the message, so a consumer could not
tell whether a click applied feedback or retracted it. Custom
AssistantMessage implementations that track their own toggle state had
no way to pass that value through, and the built-in feedback state was
write-only: clicking an active button re-applied the same value.
Add an optional second argument reporting the state the click
transitions to. The built-in AssistantMessage derives it from the
message's current feedback, which also makes the built-in buttons
toggle; a custom AssistantMessage may pass its own value. The argument
is optional, so existing one-argument handlers are unaffected.
The toggle logic is extracted to ./feedback so it can be unit tested —
this package's vitest project runs in a node environment and has no
component-rendering harness.
Closes#2615
The `.copilotKitHeader` rule declared `padding-right` only inside
`@media (min-width: 640px)`, so below that breakpoint the header's
`space-between` children ran flush to the viewport edge and the close
button had no gutter.
Move the horizontal padding into the unconditional rule so it applies at
every viewport. The value matches the 24px the sm breakpoint already
used, so wide layouts are unchanged.
Fixes#2493
Fixes#261 (open since March 2024). Supersedes #4622 — @ashish4143
diagnosed the same wrappers and is credited as co-author on the commit.
## The bug
`CopilotSidebar` wraps consumer content in two divs:
- `.copilotKitSidebarContentWrapper` (`Sidebar.tsx`) — only sets
`overflow`, `margin-right`, `transition`
- `.copilotKitModalChildrenWrapper` (`Modal.tsx`) — **has no CSS rule
anywhere in the repo**
Both are auto-height blocks, so a child's `height: 100%` has no definite
containing block to resolve against and collapses to content height.
## The fix
An opt-in `fullHeightChildren` prop on `CopilotSidebar` that adds a
modifier class to the content wrapper. Two deliberate choices, both from
the review on #4622:
- **Opt-in, not default.** The content wrapper wraps the *entire*
consumer app. Making it a fixed-height flex column for everyone would
reflow apps that never asked for it.
- **A viewport unit, not `height: 100%`.** `100%` only resolves if every
ancestor (`html`/`body`/`#root`) also declares a height — react-ui
neither sets that nor can guarantee it, so `100%` would silently no-op
in a stock Next.js app. `min-height: 0` on the children wrapper clears
the flex-item `min-height: auto` floor so tall content scrolls inside
the child rather than stretching the wrapper past the viewport.
```tsx
<CopilotSidebar fullHeightChildren>
<div style={{ height: "100%" }}>...</div>
</CopilotSidebar>
```
## Testing
**Unit** — `packages/react-ui/src/css/sidebar-full-height.test.ts` (4
tests), in the repo's existing CSS-contract style. Guards both halves:
the escape hatch's rules, and that the default wrapper stays
auto-height. Also asserts the height is *not* `100%`, since that's the
regression that would make the whole feature a silent no-op.
```
✓ src/css/sidebar-full-height.test.ts (4 tests)
Test Files 9 passed (9) Tests 58 passed (58) # full react-ui suite
```
`npx tsc --noEmit` → exit 0. `oxlint` on changed files → 0 warnings, 0
errors.
**Live in Chrome** — the acceptance criterion from the #4622 review: a
stock app where **nothing** declares a height on `html`/`body`/`#root`,
loading the real built `dist/index.css` (not the source CSS), standards
mode, 762px viewport. DOM per `Sidebar.tsx:92` + `Modal.tsx:143`.
| case | child `height:100%` measures |
|---|---|
| default (no opt-in) | **17px** — collapsed, i.e. behavior unchanged
for existing consumers |
| `fullHeightChildren` | **762px** — exactly the viewport |
| `fullHeightChildren`, content 3000px tall | **762px**, scrolls inside
the child (`min-height: 0` holds) |
Also confirmed on the opt-in path: `.copilotKitSidebar` stays `position:
fixed`, and the expanded push-aside `margin-right` is still `448px`
(28rem), so the sidebar's own layout is untouched.
**Docs** — `CopilotSidebar.mdx` is auto-generated from `Sidebar.tsx`;
regenerated via `scripts/docs/gen.ts` and committed only the new
`fullHeightChildren` entry (the generator also surfaces unrelated
pre-existing drift in other reference pages, left out of this PR).
## Not covered
The issue mentions a "works in Safari, not Chrome" symptom. I verified
in Chromium only — the mechanism above is spec behavior rather than a
Chrome quirk, but I haven't measured WebKit.
Children of `CopilotSidebar` cannot use `height: 100%`. Both wrappers the
sidebar puts around your app -- `.copilotKitSidebarContentWrapper` and
`.copilotKitModalChildrenWrapper` (which had no CSS rule at all) -- are
auto-height blocks, so a percentage height on a child has no definite
containing block and collapses to content height.
Add an opt-in `fullHeightChildren` prop that gives the content wrapper a
one-viewport height and lets the children wrapper fill it. It is opt-in
because the content wrapper wraps the entire consumer app, and giving
every react-ui sidebar user a flex column with a fixed height would
reflow apps that never asked for it.
The height is a viewport unit, not `100%`: `100%` only resolves when
every ancestor (html/body/#root) also declares a height, which react-ui
neither sets nor can guarantee, so it would silently no-op in a stock
Next.js app. `min-height: 0` on the children wrapper clears the flex-item
`min-height: auto` floor so tall content scrolls inside the child instead
of stretching the wrapper past the viewport.
Fixes#261
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Ashish Shaw <77574570+ashish4143@users.noreply.github.com>
## Summary
- lowers CopilotSidebar-scoped CSS selector specificity with
`:where(.copilotKitSidebar)`
- keeps the existing sidebar styles and layout behavior unchanged
- adds a regression test to keep sidebar selectors easy for app CSS to
override
## Root cause
Sidebar-specific selectors such as `.copilotKitSidebar
.copilotKitWindow` were more specific than ordinary user overrides like
`.copilotKitWindow`, so user CSS could lose precedence in Chrome.
Fixes#263.
## Validation
- `pnpm -C packages/react-ui test`
- `pnpm exec oxfmt --check
packages/react-ui/src/css/sidebar-specificity.test.ts
packages/react-ui/src/css/window.css
packages/react-ui/src/css/header.css
packages/react-ui/src/css/input.css`
- `pnpm exec oxlint
packages/react-ui/src/css/sidebar-specificity.test.ts`
Per BenTaylorDev's review on #5805, dropping the specificity of the
sidebar variant created a tie at desktop widths with the
@media (min-width:640px) base rule (both (0,1,0)); the later-authored
base won and reverted the sidebar header's top corners from square
(border-radius:0) back to rounded 8px. Re-assert the media-scoped
sidebar override inside the @media block (mirroring window.css) so the
sidebar header stays square at all widths.
Bumps react-syntax-highlighter from ^15.6.1 to ^16.1.1. The v16 line
pulls refractor 5 and prismjs ^1.30.0, keeping react-ui's syntax
highlighting dependency chain current for downstream consumers.
The public API used by CodeBlock (the Prism/Light exports) is unchanged,
and highlighting renders the same across common languages.
Closes#2823.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Problem
The published declaration files for `@copilotkit/react-core`,
`@copilotkit/react-ui`, and `@copilotkit/react-textarea` contain imports
that TypeScript cannot resolve, so **`attw` (Are The Types Wrong)
reports `InternalResolutionError` across every resolution mode**
(`node10` / `node16` / `bundler`). In `@copilotkit/react-core` this was
being **masked in CI** by `--ignore-rules internal-resolution-error` on
the package's `attw` script — so the existing `check:packages` gate
looked green while consumers under `moduleResolution:
bundler`/`node16`/`nodenext` got broken types (the symptom reported in
#3324: `has no exported member 'useAgent'`, etc.).
Two distinct artifacts leaked into the emitted `.d.ts` / `.d.cts` /
`.d.mts` (neither affects the JS bundles):
1. **Side-effect CSS imports** — `import "./index.css"` is intentionally
kept in the JS so styles auto-load for bundler consumers, but
`rolldown-plugin-dts` also left it in the declarations, where TypeScript
can't resolve a `.css` as a typed module.
2. **Extensionless relative `./context` import** —
`@copilotkit/react-core/v2/headless` re-exports the externalized context
module; the JS bundle correctly externalizes it to
`@copilotkit/react-core/v2/context`, but the declaration kept the
relative `./context`, which is invalid in ESM declarations.
> Note: this is **not** the missing-`exports.types`-condition theory
from #3324. tsdown deliberately relies on co-located `.d.mts`/`.d.cts`
siblings; `@copilotkit/core` already resolves cleanly. The real defects
are the two leaked imports above.
## Fix
A small tsdown `build:done` hook post-processes the emitted declarations
**on disk** (after every format is written, so it catches both `.d.mts`
and `.d.cts`):
- strips side-effect CSS imports from declarations (JS keeps them);
- rewrites the relative `./context` import to the
`@copilotkit/react-core/v2/context` package path (matching how the JS
bundle externalizes it).
Also:
- **Removed the `--ignore-rules internal-resolution-error` band-aid**
from `react-core`'s `attw` script so the existing CI gate validates for
real.
- **Dropped the dead `codeSplitting` option** from the UMD configs —
tsdown never reads it (it's a rolldown-only key), and it was failing
`tsc` in the configs that type-check themselves. UMD output is unchanged
(single file).
## Verification
- All three packages build; **no CSS or relative-`./context` imports
remain in any declaration**, while the JS bundles still contain them
(styles auto-load preserved).
- `attw` + `publint` pass for all packages **with no suppression**
(`react-core`'s `/v2`, `/v2/headless`, `/v2/context` are green for
node16-cjs/esm/bundler).
- Unit tests pass.
- A standalone consumer project (real tarball install, `skipLibCheck:
false`) type-checks the public APIs — including `useAgent` /
`useFrontendTool` / `useConfigureSuggestions` — cleanly under **both
`bundler` and `nodenext`**, and the headless↔context class is nominally
identical.
## Out of scope (follow-ups)
- `@copilotkit/react-native`: its `--ignore-rules
internal-resolution-error` currently suppresses nothing (no IRE) and it
has a separate `NoResolution` flag.
- `@copilotkit/vue`: a large, genuine set of `.vue`/relative-import
declaration errors unrelated to this change.
Relates to #3324.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
The `attachments` prop supports images, audio, video, and documents —
but the JSDoc example in `Chat.tsx` only showed
`image/*,application/pdf`, and the docs configuration example used
`accept: image/*`, silently teaching users to restrict themselves to
images.
**Before (Chat.tsx JSDoc):**
```tsx
accept: image/*,application/pdf,
```
**After:**
```tsx
accept: image/*,audio/*,video/*,application/pdf,
```
The docs configuration example now also clarifies that omitting `accept`
defaults to `*/*` (all files), and the shown value includes all four
supported modalities.
## Changes
- `packages/react-ui/src/components/chat/Chat.tsx` — updated JSDoc
example to show all modalities; added note that default `accept` is
`*/*`
- `showcase/shell-docs/src/content/docs/multimodal-attachments.mdx` —
updated configuration example to show
`image/*,audio/*,video/*,application/pdf` and note that omitting
`accept` allows all types
Repairs TypeScript check-types across the monorepo and adds a CI gate so
regressions are caught going forward:
- core: bundler module resolution and strict-mode fixes
- sdk-js: bundler module resolution; keep codegen, formatter, packaging working
- react-core: fixes across components, hooks, and tests
- react-native: restore catch binding referenced by TypeError cause
- runtime: repair check-types and bound AI SDK schema inference
- web-inspector: nodenext import extensions, export Anchor
- remaining packages and node example: assorted check-types repairs
- deps: add missing type-only devDependencies
- license context driven from /info licenseStatus
- ci: run check-types in the static quality workflow
Squashed from 12 commits for a single, easily-revertable change.
Adds a durable persistence layer for @copilotkit/bot, replacing the
in-memory-only ActionStore with a pluggable StateStore.
- StateStore interface (kv/list/lock/dedup/queue) with a shared
conformance suite; MemoryStore default plus @copilotkit/bot-store-redis
and @copilotkit/bot-store-postgres backends.
- createBot({ store }): typed per-thread state via Standard Schema,
action snapshots persisted through the store, per-conversation turn
lock (onLockConflict drop|force), and inbound-event dedup keyed on a
stable eventId. ActionStore is kept as a deprecated alias.
- Cross-platform transcripts (bot.transcripts + identity resolver) with
age-bounded retention (prune on append + filter on read), and
runAgent({ transcript: true }) to auto-inject history and capture the
reply.
- createBot({ components }) re-registers components so durable actions
re-fire after a restart; restart-durability demo in examples/slack.
- Dedup is marked seen only after the turn lock is acquired, so a turn
dropped on lock-conflict does not burn its eventId (no lost retries).
- Release lockstep: bot-store-redis/postgres version with bot + bot-ui.
The legacy Markdown renderer enabled rehype-raw with no HTML sanitizer,
so raw HTML embedded in assistant/model output reached the DOM (CWE-79).
Add rehype-sanitize as the terminal rehype pass so it runs after any
consumer-supplied rehypePlugins and cannot be bypassed. Add a regression
test covering the dangerous-HTML vectors (script/style/base/form/iframe,
event handlers, javascript: URLs) and the consumer-plugin injection path,
and assert legitimate Markdown/GFM features still render. Pin react-dom to
a caret range for the SSR-based test.
Addresses review feedback on #4215 / OSS-192.
- Thread an explicit `data-testid` prop through `AutoResizingTextarea` so it
reaches the rendered <textarea>. The component destructures a fixed prop set
with no `{...rest}` spread, so the id passed from Input.tsx was silently
dropped and never landed in the DOM.
- Align selector names with the V2 components in @copilotkit/react-core:
`copilot-chat-textarea` on the textarea and `copilot-send-button` on the send
control. The legacy `data-test-id` values are preserved for back-compat.
- Add a source-level test asserting the Input wiring and that Textarea forwards
the prop (the dropped-prop guard); the package's vitest runs in a node env
with no DOM harness, matching the existing testids.test.ts convention.
Stable selectors let tests locate the controls; the headless input-driving
issue in #4215 remains a separate follow-up and should stay open.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up correction. The client publicLicenseKey/publicApiKey prop is the
header→cloud path and is NOT what activates the Intelligence runtime (that's
the server-side COPILOTKIT_LICENSE_TOKEN). So:
- Remove the `npx copilotkit@latest license` guidance from all client-prop
contexts — that CLI yields the server-side license token, not the client
prop value.
- Revert the client-prop docstrings (copilotkit-props, v2 CopilotKitProvider)
to bare one-liners; drop the premium/"requires a license key" framing from
the headless hook, react-ui observability docs, and runtime logging/onError
JSDoc rather than reframing.
- Angular: remove all `licenseKey` mentions from the README — it is no longer
a premium feature (the license watermark is disabled) and the key is not
needed to function.
Server-side license-token documentation remains deferred to the example/runtime
setup pass (Bucket B).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cloud is no longer promoted; the Intelligence license key is its replacement.
Scrub the old Copilot Cloud system from SDK JSDoc / doc-comments / console
messages / README prose so code references reflect how the license key is
obtained and used, mirroring examples/integrations/*:
- publicApiKey/publicLicenseKey docstrings (react-core props + v2 provider,
vue legacy types, copilot-context) describe the CopilotKit public license
key, acquired via `npx copilotkit@latest license` or the dashboard;
publicApiKey framed as the legacy alias of publicLicenseKey.
- Premium-feature docs (headless hook, react-ui Chat/Popup/Sidebar
observability, runtime logging/onError) drop "Copilot Cloud"/"requires a
publicApiKey" wording and the publicApiKey examples in favor of the public
license key + publicLicenseKey.
- console-styling messages and the angular README point at the license key
and the `npx copilotkit@latest license` command.
Defunct features (guardrails_c, authConfig_c, useCopilotAuthenticatedAction_c)
keep their code but lose their JSDoc (marked @internal defunct).
Functional surfaces untouched: api.cloud.copilotkit.ai endpoint, the
X-CopilotCloud-Public-Api-Key header, prop names, gating logic, tests,
CHANGELOGs. Example-app migration (Bucket B) deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The emitted declaration files for @copilotkit/react-core, react-ui and
react-textarea contained imports TypeScript cannot resolve, so `attw`
reported InternalResolutionError across every resolution mode (the error
was being masked in react-core by `--ignore-rules internal-resolution-error`):
- Side-effect CSS imports (e.g. `import "./index.css"`) leaked into the
.d.ts/.d.cts/.d.mts output. CSS is intentionally kept in the JS bundles
(styles auto-load for bundler consumers); only the declarations are cleaned.
- The headless re-export of the externalized context module was emitted as a
relative, extensionless `./context` import, which is invalid in ESM
declarations.
Fix: a tsdown `build:done` hook post-processes the emitted declarations on
disk (strips CSS side-effect imports; rewrites the relative `./context`
import to the `@copilotkit/react-core/v2/context` package path). Removed the
react-core `attw` band-aid so the existing CI gate validates for real, and
dropped the dead `codeSplitting` option (tsdown never reads it) that was
failing type-checks in the configs that include them.
Verified: all three packages build; no CSS/relative-context imports remain in
any declaration while the JS bundles are unchanged; attw + publint pass with
no suppression; tests pass; and a standalone consumer project type-checks the
public APIs cleanly under both bundler and nodenext with skipLibCheck off.