mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
44d54c65d6
Fixes #6383. Fixes #6243.
Both issues land in the same 35 lines of `useCopilotReadable`, so they
are fixed together. This PR also covers a third defect neither issue
reports.
All of it traces to a single commit: 80dffec4e7 ("feat: Reimplement
CopilotKit on top of refreshed internals (v1.50.0)", #2638), which
repointed the hook from the v1 context tree onto the v2 flat context
store. The pre-1.50 implementation was correct on every count below.
## Fixes
**`available` was missing from the effect deps** (#6383)
The effect body read `available` but the deps were `[description, value,
convert]`, so toggling between `"enabled"` and `"disabled"` after mount
did nothing. It is back in the deps, along with the `available =
"enabled"` default the port dropped.
**`convert` was called with one argument** (#6243)
`(convert ?? JSON.stringify)(value)` invoked a user's `(description,
value) => string` as `convert(value)`, so it received the value as
`description` and `undefined` as `value`. The branches are now split
rather than passing two arguments to the combined expression —
`JSON.stringify(description, value)` would treat the second argument as
a *replacer*, not a value.
**`dependencies` was accepted and ignored** (#6243)
The second positional argument was destructured but never reached the
deps array. Now spread, matching `useCopilotAdditionalInstructions`.
**The `found` dedup branch was dead code** (unreported)
It compared `JSON.stringify({ description, value })` against a stored
entry whose `value` had already been serialized by `addContext`
(`packages/core/src/core/context-store.ts:36`). That never matches — for
objects or strings — so the branch and its cleanup-skipping early return
were unreachable. Deleted rather than repaired: making the comparison
work would newly let component A's unmount remove a context entry
component B is still relying on. The test `keeps separate entries for
identical readables in two components` locks that in, and it passes
against the pre-fix hook, which is what confirms the branch never fired.
## `parentId` / `categories`
Both are still in `UseCopilotReadableOptions` and were still documented
— the top-of-file JSDoc example was a `parentId` tutorial — but the same
v1.50 commit dropped them from the hook body. They have been no-ops
since.
This PR does not implement them. Real support needs parent/child
modelling in the v2 context store, which is flat by design
(`getContextForAgent` emits `{ description, value }` only). Instead both
are marked `@deprecated` and the JSDoc example is rewritten to document
behavior that exists. Tracked in #6408.
## Not addressed
Two pre-existing behaviors left alone to keep this a bugfix:
- `value` is in the deps raw, so an inline object literal re-registers
the entry on every render. Pre-1.50 depended on the serialized string
instead.
- The hook returns `undefined` on first render, since the ref is
assigned inside the effect.
## Testing
`useCopilotReadable` had no test file. This adds one — 12 tests, using a
fake that mirrors `ContextStore` semantics (`addContext` assigns an id
and stores the already-serialized value).
Full project suite — `nx run @copilotkit/react-core:test`:
```
Test Files 124 passed (124)
Tests 1487 passed (1487)
NX Successfully ran target test for project @copilotkit/react-core and 17 tasks it depends on
```
Each fix is covered by a test that fails against the pre-fix hook.
Reverting only `use-copilot-readable.ts` and re-running the new file:
```
✓ registers the context on mount
✓ removes the context on unmount
✓ available > registers nothing when mounted as disabled
× available > removes the context when flipped to disabled after mount
→ expected [ { description: 'employees', …(1) } ] to deeply equal []
× available > re-adds the context when flipped back to enabled
→ expected [] to deeply equal [ { description: 'employees', …(1) } ]
× convert > is called with (description, value) in that order
→ expected "spy" to be called with arguments: [ 'employees', …(1) ]
× convert > is used in place of JSON.stringify
→ Cannot read properties of undefined (reading 'map')
✓ convert > serializes the value alone when convert is omitted
× dependencies > re-runs the effect when a dependency changes
→ expected "spy" to be called 2 times, but got 1 times
✓ dependencies > does not re-run the effect when the dependency is unchanged
✓ re-registers when the description changes
✓ keeps separate entries for identical readables in two components
Test Files 1 failed (1)
Tests 5 failed | 7 passed (12)
```
The two that still pass pre-fix are deliberate: `serializes the value
alone when convert is omitted` guards the `JSON.stringify` replacer trap
in the fix itself, and `keeps separate entries…` is the evidence that
the `found` branch was dead.
With the fix applied:
```
✓ src/hooks/__tests__/use-copilot-readable.test.tsx (12 tests) 15ms
Test Files 1 passed (1)
Tests 12 passed (12)
```
Types — `pnpm --filter @copilotkit/react-core check-types`:
```
> @copilotkit/react-core@1.66.2 check-types
> tsc --noEmit
```
(no diagnostics)
Formatting — `oxfmt --check` on both files:
```
Checking formatting...
All matched files use the correct format.
Finished in 16ms on 2 files using 18 threads.
```
`oxlint` reports one warning, on `...(dependencies || [])` in the deps
array. The same pattern already warns in
`use-copilot-additional-instructions.ts`, `use-frontend-tool.ts` and
`use-coagent-state-render.ts`; CI runs `oxlint .` without
`--deny-warnings`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The `convert` and `dependencies` fixes were independently found and
fixed first by @jwgrsol in #6246, opened a week before this PR. Credited
below.
Co-authored-by: jwgrsol <wefhio1985@gmail.com>
CopilotKit - React Core
✨ Why CopilotKit?
- Minutes to integrate - Get started quickly with our CLI
- Framework agnostic - Works with React, Next.js, AGUI and more
- Production-ready UI - Use customizable components or build with headless UI
- Built-in security - Prompt injection protection
- Open source - Full transparency and community-driven
🧑💻 Real life use cases
Deploy deeply-integrated AI assistants & agents that work alongside your users inside your applications.
🖥️ Code Samples
Drop in these building blocks and tailor them to your needs.
Build with Headless APIs and Pre-Built Components
// Headless UI with full control
const { visibleMessages, appendMessage, setMessages, ... } = useCopilotChat();
// Pre-built components with deep customization options (CSS + pass custom sub-components)
<CopilotPopup
instructions={"You are assisting the user as best as you can. Answer in the best way possible given the data you have."}
labels={{ title: "Popup Assistant", initial: "Need any help?" }}
/>
// Frontend actions + generative UI, with full streaming support
useCopilotAction({
name: "appendToSpreadsheet",
description: "Append rows to the current spreadsheet",
parameters: [
{ name: "rows", type: "object[]", attributes: [{ name: "cells", type: "object[]", attributes: [{ name: "value", type: "string" }] }] }
],
render: ({ status, args }) => <Spreadsheet data={canonicalSpreadsheetData(args.rows)} />,
handler: ({ rows }) => setSpreadsheet({ ...spreadsheet, rows: [...spreadsheet.rows, ...canonicalSpreadsheetData(rows)] }),
});
Integrate In-App CoAgents with LangGraph
// Share state between app and agent
const { agentState } = useCoAgent({
name: "basic_agent",
initialState: { input: "NYC" }
});
// agentic generative UI
useCoAgentStateRender({
name: "basic_agent",
render: ({ state }) => <WeatherDisplay {...state.final_response} />,
});
// Human in the Loop (Approval)
useCopilotAction({
name: "email_tool",
parameters: [
{
name: "email_draft",
type: "string",
description: "The email content",
required: true,
},
],
renderAndWaitForResponse: ({ args, status, respond }) => {
return (
<EmailConfirmation
emailContent={args.email_draft || ""}
isExecuting={status === "executing"}
onCancel={() => respond?.({ approved: false })}
onSend={() =>
respond?.({
approved: true,
metadata: { sentAt: new Date().toISOString() },
})
}
/>
);
},
});
// intermediate agent state streaming (supports both LangGraph.js + LangGraph python)
const modifiedConfig = copilotKitCustomizeConfig(config, {
emitIntermediateState: [
{
stateKey: "outline",
tool: "set_outline",
toolArgument: "outline",
},
],
});
const response = await ChatOpenAI({ model: "gpt-4o" }).invoke(
messages,
modifiedConfig,
);
🏆 Featured Examples
Documentation
To get started with CopilotKit, please check out the documentation.