Back to Copilotkit

SKILL

skills/v1-to-v2-migration/SKILL.md

1.57.410.3 KB
Original Source

Setup

Packages do NOT rename for v2. v2 lives at the /v2 subpath of the same packages.

ts
// v1
import { CopilotKit, useCopilotAction } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

// v2
import {
  CopilotKitProvider,
  CopilotPopup,
  useFrontendTool,
} from "@copilotkit/react-core/v2";
import "@copilotkit/react-core/v2/styles.css";

Two reference files ship with this skill:

  • references/rename-table.md — full 17+-row rename table.
  • references/migration-playbook.md — step-by-step agent-executable recipe.

Read both before executing the migration.

Core Patterns

Rename table (summary — full table in references/rename-table.md)

v1 APIv2 APISubpath
CopilotKit providerCopilotKitProvider@copilotkit/react-core/v2
useCopilotAction (data)useFrontendTool@copilotkit/react-core/v2
useCopilotAction (render/HITL)useHumanInTheLoop@copilotkit/react-core/v2
useCoAgentuseAgent@copilotkit/react-core/v2
useCopilotReadableuseAgentContext@copilotkit/react-core/v2
CopilotKitErrorCode (SCREAMING)CopilotKitCoreErrorCode (snake)@copilotkit/react-core/v2
publicApiKey proppublicApiKey (unchanged; canonical) — publicLicenseKey accepted as aliasprovider prop
imageUploadsEnabled propattachments={{ enabled: true }}<CopilotChat> prop
CopilotPopup etc. from react-uisame names from react-core/v2@copilotkit/react-core/v2

Import-path audit (grep for v1 imports)

bash
grep -rE "from ['\"]@copilotkit/(react-core|react-ui|runtime)['\"]" src/
grep -rE "useCopilotAction|useCopilotReadable|useCoAgent|CopilotKit[^P]" src/

Any hit = v1 surface that needs migration. See references/migration-playbook.md for the full Phase 1 audit script.

Hook split — useCopilotAction → useFrontendTool + useHumanInTheLoop

useCopilotAction with render but no handler is a HITL surface and splits into useHumanInTheLoop. With handler and no render it becomes useFrontendTool. With both, you need one of each — this split requires judgment and cannot be codemodded blindly.

tsx
// v1
useCopilotAction({
  name: "sendEmail",
  description: "Send an email",
  parameters: [
    { name: "to", type: "string" },
    { name: "body", type: "string" },
  ],
  render: ({ args, status, handler }) =>
    status === "executing" ? (
      <button onClick={() => handler(args)}>Send to {args.to}</button>
    ) : null,
});

// v2 — approval flow, UI + respond()
import { z } from "zod";
useHumanInTheLoop({
  name: "sendEmail",
  description: "Send an email",
  parameters: z.object({ to: z.string(), body: z.string() }),
  render: ({ args, status, respond }) =>
    status === "executing" ? (
      <button onClick={async () => respond({ sent: await send(args) })}>
        Send to {args.to}
      </button>
    ) : null,
});

For a plain data tool (no user approval):

tsx
useFrontendTool({
  name: "getLocation",
  description: "Return the user's city",
  parameters: z.object({}),
  handler: async () => ({ city: "SF" }),
});

Provider rename

tsx
// v1
import { CopilotKit } from "@copilotkit/react-core";
<CopilotKit runtimeUrl="/api/copilotkit" publicApiKey="...">
  {children}
</CopilotKit>;

// v2 (publicApiKey stays — it's the canonical v2 name; publicLicenseKey
// is an accepted alias and the two resolve as `publicApiKey ?? publicLicenseKey`.)
import { CopilotKitProvider } from "@copilotkit/react-core/v2";
<CopilotKitProvider runtimeUrl="/api/copilotkit" publicApiKey="...">
  {children}
</CopilotKitProvider>;

Error-code equality migration

ts
// v1 (SCREAMING_SNAKE)
if (error.code === "API_NOT_FOUND") { /* ... */ }

// v2 (snake_case, via CopilotKitCoreErrorCode)
import type { CopilotKitCoreErrorCode } from "@copilotkit/react-core/v2";
<CopilotKitProvider
  onError={({ code }) => {
    if (code === "runtime_info_fetch_failed") { /* ... */ }
    if (code === "agent_thread_locked") { /* ... */ }
  }}
/>

Full v2 code catalog in copilotkit/debug-and-troubleshoot + references/error-codes.md of that skill.

Runtime endpoint port

ts
// v1 — Next.js route.ts
import {
  CopilotRuntime,
  copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
const runtime = new CopilotRuntime({
  /* ... */
});
export const POST = async (req: Request) => {
  const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
    runtime,
    serviceAdapter,
    endpoint: "/api/copilotkit",
  });
  return handleRequest(req);
};

// v2 — fetch-based, works on ANY runtime
import {
  CopilotRuntime,
  createCopilotRuntimeHandler,
  BuiltInAgent,
} from "@copilotkit/runtime/v2";

const runtime = new CopilotRuntime({
  agents: {
    default: new BuiltInAgent({
      /* ... */
    }),
  },
});
const handler = createCopilotRuntimeHandler({
  runtime,
  basePath: "/api/copilotkit",
});
export const GET = handler;
export const POST = handler;

Common Mistakes

CRITICAL @copilotkitnext/ scope for non-Angular packages

Wrong:

ts
import { CopilotKitProvider } from "@copilotkitnext/react-core";
import { CopilotRuntime } from "@copilotkitnext/runtime";

Correct:

ts
import { CopilotKitProvider } from "@copilotkit/react-core/v2";
import { CopilotRuntime } from "@copilotkit/runtime/v2";
// Only Angular uses the @copilotkitnext/ scope:
// import { ... } from "@copilotkitnext/angular";

ONLY @copilotkitnext/angular uses the @copilotkitnext scope. Every other CopilotKit package is under @copilotkit/. Agents over-generalize from the Angular example and hallucinate scope names like @copilotkitnext/react-core, @copilotkitnext/runtime, etc. — all of which resolve as unresolved modules or install as unrelated packages. This is the most-seen v2 migration hallucination per the maintainer.

Source: packages/angular/package.json (the ONE @copilotkitnext package); all other packages/*/package.json

CRITICAL installing @copilotkit/react-ui for v2 chat

Wrong:

ts
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

Correct:

ts
import { CopilotPopup } from "@copilotkit/react-core/v2";
import "@copilotkit/react-core/v2/styles.css";

In v2, chat components ship from @copilotkit/react-core/v2. @copilotkit/react-ui v2 is CSS-only (a stylesheet package).

Source: docs/snippets/shared/troubleshooting/migrate-to-v2.mdx:42-56

CRITICAL importing v2 hooks from @copilotkit/react-core root

Wrong:

ts
import { CopilotKit, useCopilotReadable } from "@copilotkit/react-core";

Correct:

ts
import { CopilotKitProvider, useAgentContext } from "@copilotkit/react-core/v2";

The root of @copilotkit/react-core is the v1 surface; v2 ships under /v2. Mixed imports compile but route through different implementations with different semantics.

Source: packages/react-core/src/index.tsx:1-8

CRITICAL useCopilotAction in v2

Wrong:

tsx
useCopilotAction({ name, parameters, handler, render });

Correct:

tsx
useFrontendTool({ name, parameters, handler });
useHumanInTheLoop({ name, parameters, render });

useCopilotAction is split into two v2 hooks — judgment required on which side each v1 call belongs to.

Source: packages/react-core/src/v2/hooks/index.ts:5,9

CRITICAL useCoAgent in v2

Wrong:

tsx
const { state, setState, running } = useCoAgent({ name: "research" });

Correct:

tsx
const { agent } = useAgent({ agentId: "research" });
const state = agent?.state;
const isRunning = agent?.isRunning;
agent?.setState({ ...agent.state, foo: "bar" });

useAgent returns { agent } only — agent can be undefined while the runtime is still loading, so guard with optional chaining. State, isRunning, and mutation live on the agent instance itself.

Source: packages/react-core/src/v2/hooks/use-agent.tsx:333-335 (return { agent })

HIGH checking against v1 SCREAMING_SNAKE error codes

Wrong:

ts
if (error.code === "API_NOT_FOUND") {
  /* never matches */
}

Correct:

ts
onError: ({ code }) => {
  if (code === "runtime_info_fetch_failed") {
    /* ... */
  }
};

v2 CopilotKitCoreErrorCode values are snake_case strings. v1 SCREAMING_SNAKE values never equal v2 codes.

Source: packages/core/src/core/core.ts:71-105; packages/shared/src/utils/errors.ts:44-57

HIGH renaming @copilotkit/runtime imports to @copilotkit/runtime-v2

Wrong:

ts
import { CopilotRuntime } from "@copilotkit/runtime-v2";

Correct:

ts
import {
  CopilotRuntime,
  createCopilotRuntimeHandler,
} from "@copilotkit/runtime/v2";

The package didn't rename; v2 lives at the /v2 subpath.

Source: packages/runtime/src/v2/index.ts:1-6

MEDIUM imageUploadsEnabled in v2

Wrong:

tsx
<CopilotChat imageUploadsEnabled />

Correct:

tsx
<CopilotChat attachments={{ enabled: true }} />

Attachments API replaces the v1 image-upload prop; it supports images, files, drag-and-drop, and paste.

Source: docs/content/docs/(root)/migration-guides/migrate-attachments.mdx

MEDIUM installing @ag-ui/client just for types

Wrong:

ts
import type { AbstractAgent, HttpAgent } from "@ag-ui/client";

Correct:

ts
import type { AbstractAgent, HttpAgent } from "@copilotkit/react-core/v2";

Types are re-exported from @copilotkit/react-core/v2 — no extra dependency needed.

Source: packages/react-core/src/v2/index.ts:9

References