diff --git a/.github/workflows/static_intelligence-env-names.yml b/.github/workflows/static_intelligence-env-names.yml new file mode 100644 index 0000000000..081a5f325a --- /dev/null +++ b/.github/workflows/static_intelligence-env-names.yml @@ -0,0 +1,41 @@ +name: static / intelligence env names + +# Deliberately unfiltered. A non-canonical Intelligence key name can appear in +# any README, example, skill, or doc page, and the two workflows that would +# otherwise cover this both filter paths — plugin-skills-check by `paths:` and +# static/quality by `paths-ignore: examples/**`, which is precisely where the +# deprecated alias still lives. Scoping this job would re-open the hole it +# exists to close. +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Check Intelligence env var names are canonical + run: pnpm check:intelligence-env-names diff --git a/dev-docs/architecture/setup-intelligence.md b/dev-docs/architecture/setup-intelligence.md index 537e9e5b3f..82604ae791 100644 --- a/dev-docs/architecture/setup-intelligence.md +++ b/dev-docs/architecture/setup-intelligence.md @@ -69,7 +69,7 @@ npm install @copilotkit/runtime import { CopilotKitIntelligence } from "@copilotkit/runtime"; const intelligence = new CopilotKitIntelligence({ - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, + apiKey: process.env.INTELLIGENCE_API_KEY!, organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORGANIZATION_ID!, }); ``` @@ -84,7 +84,7 @@ layer appends `/runner` or `/client` itself. ```typescript const intelligence = new CopilotKitIntelligence({ - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, + apiKey: process.env.INTELLIGENCE_API_KEY!, organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORGANIZATION_ID!, apiUrl: "https://api.your-intelligence-host", wsUrl: "wss://realtime.your-intelligence-host", diff --git a/examples/slack/.env.example b/examples/slack/.env.example index 7abe4b9ca3..f7348a4beb 100644 --- a/examples/slack/.env.example +++ b/examples/slack/.env.example @@ -51,7 +51,8 @@ OPENAI_API_KEY=sk-... # plus the channel name chosen in code). # Project runtime API key (cpk-…). The only Intelligence value you need: the SDK # defaults both URLs to the managed platform. -COPILOTKIT_API_KEY=cpk-... +# `COPILOTKIT_API_KEY` is a deprecated alias for this variable, still read as a fallback. +INTELLIGENCE_API_KEY=cpk-... # Optional, for a self-hosted or dev Intelligence deployment only. Set BOTH or # neither: the realtime plane is a DIFFERENT host from the API (api.… vs # realtime.…), so it cannot be derived from the API URL, and setting one alone diff --git a/examples/slack/README.md b/examples/slack/README.md index 42b6e151f3..0053bfec3c 100644 --- a/examples/slack/README.md +++ b/examples/slack/README.md @@ -119,7 +119,7 @@ bot.onMention(async ({ thread, message }) => { // lifecycle — it starts the direct Slack adapter for us. const intelligence = new CopilotKitIntelligence({ // apiUrl/wsUrl default to the managed Intelligence platform. - apiKey: process.env.COPILOTKIT_API_KEY!, + apiKey: process.env.INTELLIGENCE_API_KEY!, }); const runtime = new CopilotRuntime({ agents: {}, // the Channel supplies its own agent @@ -332,7 +332,7 @@ several from one process). ```bash cp .env.example .env # Fill in (set SLACK_*, DISCORD_*, and/or TELEGRAM_BOT_TOKEN — whichever you want): -# COPILOTKIT_API_KEY (REQUIRED — owns the Channel; free tier) +# INTELLIGENCE_API_KEY (REQUIRED — owns the Channel; free tier) # SLACK_BOT_TOKEN / SLACK_APP_TOKEN (to run on Slack) # DISCORD_BOT_TOKEN / DISCORD_APP_ID (to run on Discord; DISCORD_GUILD_ID optional) # TELEGRAM_BOT_TOKEN (to run on Telegram) @@ -342,8 +342,9 @@ cp .env.example .env # NOTION_MCP_AUTH_TOKEN (any strong string; shared between the sidecar and the agent) ``` -A Channel runs only through the Intelligence runtime, so `COPILOTKIT_API_KEY` is -**required** (free tier). There are no URLs to set — the SDK defaults to the +A Channel runs only through the Intelligence runtime, so `INTELLIGENCE_API_KEY` is +**required** (free tier; `COPILOTKIT_API_KEY` is a deprecated alias, still read as a +fallback). There are no URLs to set — the SDK defaults to the managed Intelligence platform. The platform adapters stay direct — the runtime that owns the Channel starts each of them for you. Linear and Notion are independent — set only the ones you want; the agent wires up whichever credentials are present. diff --git a/examples/slack/app/index.ts b/examples/slack/app/index.ts index c0c6c13f6a..125eee815f 100644 --- a/examples/slack/app/index.ts +++ b/examples/slack/app/index.ts @@ -13,7 +13,7 @@ * rendering) is platform-agnostic and shared verbatim. * * RUN MODEL — a Channel runs ONLY through the Intelligence runtime, so this - * example needs an Intelligence key (free tier: `COPILOTKIT_API_KEY`; the + * example needs an Intelligence key (free tier: `INTELLIGENCE_API_KEY`; the * platform URLs default to the managed service). The platform adapters stay DIRECT (they keep their own * Slack/Discord/Telegram/WhatsApp credentials + transports); the runtime OWNS * the Channel's lifecycle and STARTS all of its direct adapters for us. So all @@ -73,6 +73,34 @@ const required = (name: string): string => { return v; }; +/** + * Resolves the Intelligence project key. + * + * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and + * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a + * deprecated alias, still read so an existing `.env` keeps working. + */ +const requiredIntelligenceKey = (): string => { + const key = + process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY; + if (!key) { + console.error( + "Missing required env var: INTELLIGENCE_API_KEY\n" + + "Channels run only through the Intelligence runtime, which needs an " + + "Intelligence key (free tier).\n" + + " Run `copilotkit project select` to provision one, or set it manually.\n" + + "No URLs to set: the SDK defaults to the managed Intelligence platform.", + ); + process.exit(1); + } + if (!process.env.INTELLIGENCE_API_KEY) { + console.warn( + "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.", + ); + } + return key; +}; + /** True only when every named env var is set and non-empty. */ const have = (...names: string[]): boolean => names.every((n) => Boolean(process.env[n])); @@ -283,7 +311,7 @@ async function main() { const intelligence = new CopilotKitIntelligence({ apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL, wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL, - apiKey: required("COPILOTKIT_API_KEY"), + apiKey: requiredIntelligenceKey(), }); // Declare the Channel on the Intelligence runtime, which OWNS its lifecycle: diff --git a/examples/slack/app/managed.test.ts b/examples/slack/app/managed.test.ts index 39180c030a..c84e92bf9c 100644 --- a/examples/slack/app/managed.test.ts +++ b/examples/slack/app/managed.test.ts @@ -66,6 +66,7 @@ const envKeys = [ "COPILOTKIT_INTELLIGENCE_URL", "COPILOTKIT_INTELLIGENCE_WS_URL", "COPILOTKIT_API_KEY", + "INTELLIGENCE_API_KEY", ] as const; describe("managed channel entrypoint", () => { @@ -89,7 +90,7 @@ describe("managed channel entrypoint", () => { // plane is deployed separately, so there is no derive from apiUrl. process.env.COPILOTKIT_INTELLIGENCE_URL = "http://localhost:4201"; process.env.COPILOTKIT_INTELLIGENCE_WS_URL = "ws://localhost:4401"; - process.env.COPILOTKIT_API_KEY = "cpk-test"; + process.env.INTELLIGENCE_API_KEY = "cpk-test"; let sigterm: (() => void) | undefined; vi.spyOn(process, "on").mockImplementation(((event, listener) => { @@ -105,6 +106,12 @@ describe("managed channel entrypoint", () => { await import("./managed.js"); await vi.waitFor(() => expect(sigterm).toBeTypeOf("function")); + // The canonical name reaches the client. A key that is merely present in + // the environment proves nothing; this proves it was consumed. + expect(fakes.CopilotKitIntelligence).toHaveBeenCalledWith( + expect.objectContaining({ apiKey: "cpk-test" }), + ); + // The listener is created from the NORMAL runtime handler — no realtime // gateway launcher is involved. expect(fakes.createCopilotNodeListener).toHaveBeenCalledWith( @@ -130,4 +137,31 @@ describe("managed channel entrypoint", () => { // stop() threw, so shutdown exits nonzero. expect(exit).toHaveBeenCalledWith(1); }); + + it("still reads the deprecated COPILOTKIT_API_KEY alias", async () => { + for (const key of envKeys) previousEnv.set(key, process.env[key]); + vi.resetModules(); + fakes.CopilotKitIntelligence.mockClear(); + process.env.AGENT_URL = "http://agent.test/run"; + delete process.env.INTELLIGENCE_API_KEY; + process.env.COPILOTKIT_API_KEY = "cpk-legacy"; + + vi.spyOn(process, "on").mockImplementation( + (() => process) as typeof process.on, + ); + vi.spyOn(process, "exit").mockImplementation( + (() => undefined as never) as typeof process.exit, + ); + vi.spyOn(console, "log").mockImplementation(() => {}); + vi.spyOn(console, "warn").mockImplementation(() => {}); + vi.spyOn(console, "error").mockImplementation(() => {}); + + await import("./managed.js"); + + await vi.waitFor(() => + expect(fakes.CopilotKitIntelligence).toHaveBeenCalledWith( + expect.objectContaining({ apiKey: "cpk-legacy" }), + ), + ); + }); }); diff --git a/examples/slack/app/managed.ts b/examples/slack/app/managed.ts index ccfde3517e..ff53760e43 100644 --- a/examples/slack/app/managed.ts +++ b/examples/slack/app/managed.ts @@ -50,6 +50,34 @@ const required = (name: string): string => { return v; }; +/** + * Resolves the Intelligence project key. + * + * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and + * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a + * deprecated alias, still read so an existing `.env` keeps working. + */ +const requiredIntelligenceKey = (): string => { + const key = + process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY; + if (!key) { + console.error( + "Missing required env var: INTELLIGENCE_API_KEY\n" + + "Channels run only through the Intelligence runtime, which needs an " + + "Intelligence key (free tier).\n" + + " Run `copilotkit project select` to provision one, or set it manually.\n" + + "No URLs to set: the SDK defaults to the managed Intelligence platform.", + ); + process.exit(1); + } + if (!process.env.INTELLIGENCE_API_KEY) { + console.warn( + "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.", + ); + } + return key; +}; + /** * The managed Channel `name` is chosen HERE, in code — it is the project-unique * identifier the runtime uses to derive the managed Channel's activation config @@ -132,7 +160,7 @@ async function main() { const intelligence = new CopilotKitIntelligence({ apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL, wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL, - apiKey: required("COPILOTKIT_API_KEY"), + apiKey: requiredIntelligenceKey(), }); const runtime = new CopilotRuntime({ diff --git a/examples/teams/.env.example b/examples/teams/.env.example index 78410cd5ee..426727d1c1 100644 --- a/examples/teams/.env.example +++ b/examples/teams/.env.example @@ -9,7 +9,8 @@ OPENAI_API_KEY= # starts/stops it is configured with an Intelligence key. Free tier is enough. # Project runtime API key (cpk-…). The only Intelligence value you need: the SDK # defaults both URLs to the managed platform. -COPILOTKIT_API_KEY=cpk-... +# `COPILOTKIT_API_KEY` is a deprecated alias for this variable, still read as a fallback. +INTELLIGENCE_API_KEY=cpk-... # Optional, for a self-hosted or dev Intelligence deployment only. Set BOTH or # neither: the realtime plane is a DIFFERENT host from the API (api.… vs # realtime.…), so it cannot be derived from the API URL, and setting one alone diff --git a/examples/teams/README.md b/examples/teams/README.md index a9a1274466..d8f5a6385c 100644 --- a/examples/teams/README.md +++ b/examples/teams/README.md @@ -23,7 +23,7 @@ From this directory (after `pnpm install` at the repo root): ```sh export OPENAI_API_KEY=sk-... # or add it to .env (see .env.example) -export COPILOTKIT_API_KEY=cpk-... # Intelligence key (free tier) +export INTELLIGENCE_API_KEY=cpk-... # Intelligence key (free tier) pnpm start # starts the bot on http://localhost:3978/api/messages ``` @@ -176,10 +176,11 @@ Set the environment for wherever you deploy: - `OPENAI_API_KEY` _(required)_: the bot runs a `BuiltInAgent` and exits at startup without it. - `OPENAI_MODEL` _(optional)_: defaults to `openai/gpt-5.5`. -- `COPILOTKIT_API_KEY` _(required)_: the Intelligence runtime that owns the +- `INTELLIGENCE_API_KEY` _(required)_: the Intelligence runtime that owns the Channel lifecycle. A Channel runs only through Intelligence, so the bot exits at startup without it (free tier is enough). No URLs to configure — the SDK - defaults to the managed Intelligence platform. + defaults to the managed Intelligence platform. `COPILOTKIT_API_KEY` is a + deprecated alias, still read as a fallback. - `COPILOTKIT_INTELLIGENCE_URL` / `COPILOTKIT_INTELLIGENCE_WS_URL` _(optional)_: point the bot at a self-hosted or dev Intelligence deployment. Set **both or neither**: the API and realtime planes are **separate hosts** diff --git a/examples/teams/app/index.tsx b/examples/teams/app/index.tsx index db0f987143..a020d21210 100644 --- a/examples/teams/app/index.tsx +++ b/examples/teams/app/index.tsx @@ -16,7 +16,7 @@ * `.stop()`) — there is no `bot.start()`/`bot.stop()` and no standalone path. * * Requires `OPENAI_API_KEY` (the BuiltInAgent's LLM) AND an Intelligence key - * (`COPILOTKIT_API_KEY` — free tier; the platform URLs default to the managed + * (`INTELLIGENCE_API_KEY` — free tier; the platform URLs default to the managed * service), which the runtime that owns the Channel is configured with. No * Microsoft credentials are needed to test in the M365 Agents Playground: * @@ -64,25 +64,32 @@ if (!process.env.OPENAI_API_KEY) { process.exit(1); } -// A Channel runs ONLY through the Intelligence runtime — the runtime that owns -// the Channel's lifecycle is configured with an Intelligence key. Fail fast -// here rather than deep in activation. Free tier is enough for this demo. -const required = (name: string): string => { - const v = process.env[name]; - if (!v) { +/** + * Resolves the Intelligence project key. + * + * `INTELLIGENCE_API_KEY` is the name `copilotkit project select` provisions and + * the name every other CopilotKit surface documents. `COPILOTKIT_API_KEY` is a + * deprecated alias, still read so an existing `.env` keeps working. + */ +const requiredIntelligenceKey = (): string => { + const key = + process.env.INTELLIGENCE_API_KEY ?? process.env.COPILOTKIT_API_KEY; + if (!key) { console.error( - `Missing ${name}.\n` + + "Missing required env var: INTELLIGENCE_API_KEY\n" + "Channels run only through the Intelligence runtime, which needs an " + "Intelligence key (free tier).\n" + - " export COPILOTKIT_API_KEY=cpk-... (or add it to examples/teams/.env)\n" + - "No URLs to set: the SDK defaults to the managed Intelligence platform. A\n" + - "self-hosted deployment exports COPILOTKIT_INTELLIGENCE_URL AND\n" + - "COPILOTKIT_INTELLIGENCE_WS_URL — they are DIFFERENT hosts, so the websocket\n" + - "URL cannot be derived from the API URL.", + " Run `copilotkit project select` to provision one, or set it manually.\n" + + "No URLs to set: the SDK defaults to the managed Intelligence platform.", ); process.exit(1); } - return v; + if (!process.env.INTELLIGENCE_API_KEY) { + console.warn( + "COPILOTKIT_API_KEY is a deprecated alias; rename it to INTELLIGENCE_API_KEY.", + ); + } + return key; }; const port = Number(process.env.PORT ?? 3978); @@ -248,7 +255,7 @@ bot.onMessage(async ({ thread, message }) => { const intelligence = new CopilotKitIntelligence({ apiUrl: process.env.COPILOTKIT_INTELLIGENCE_URL, wsUrl: process.env.COPILOTKIT_INTELLIGENCE_WS_URL, - apiKey: required("COPILOTKIT_API_KEY"), + apiKey: requiredIntelligenceKey(), }); // Declare the Channel on the Intelligence runtime. The runtime OWNS the diff --git a/lefthook.yml b/lefthook.yml index f2f48ee553..ff77f76a76 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -103,6 +103,16 @@ pre-commit: fi fi + check-intelligence-env-names: + tags: intelligence-env-names + # No glob: the retired names can reappear in any doc, README, example, or + # skill, so this runs on every commit rather than a path subset. + run: pnpm check:intelligence-env-names + fail_text: | + A non-canonical Intelligence env var name was found. + The canonical name is INTELLIGENCE_API_KEY. + See scripts/validate-intelligence-env-names.ts for the allowlist. + check-plugin-skills: tags: plugin-skills glob: "{packages/*/skills/**,skills/runtime/**,skills/react-core/**,skills/a2ui-renderer/**,scripts/sync-plugin-skills.ts,.claude-plugin/**,packages/runtime/package.json}" diff --git a/package.json b/package.json index a33c4d7fb6..ff14f50f52 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "attw": "nx run-many -t attw --projects=packages/**", "check:packages": "nx run-many -t publint,attw --projects=packages/**", "validate:model-names": "tsx scripts/validate-doc-model-names.ts", + "check:intelligence-env-names": "tsx scripts/validate-intelligence-env-names.ts", "check:plugin-skills": "tsx scripts/sync-plugin-skills.ts --check", "generate:channel-native-catalogs": "node scripts/channel-native-catalogs.mjs generate", "check:channel-native-catalogs": "node scripts/channel-native-catalogs.mjs check", diff --git a/packages/channels-core/README.md b/packages/channels-core/README.md index 4c3a2bf025..796448fef4 100644 --- a/packages/channels-core/README.md +++ b/packages/channels-core/README.md @@ -87,7 +87,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [channel], }); diff --git a/packages/channels-discord/README.md b/packages/channels-discord/README.md index 44bbddf253..80d97d1d71 100644 --- a/packages/channels-discord/README.md +++ b/packages/channels-discord/README.md @@ -63,7 +63,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); diff --git a/packages/channels-slack/README.md b/packages/channels-slack/README.md index da59dc3f4b..c97c56bc37 100644 --- a/packages/channels-slack/README.md +++ b/packages/channels-slack/README.md @@ -67,7 +67,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); diff --git a/packages/channels-teams/README.md b/packages/channels-teams/README.md index c91fac17e8..1f2cbd31d1 100644 --- a/packages/channels-teams/README.md +++ b/packages/channels-teams/README.md @@ -59,7 +59,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); diff --git a/packages/channels-telegram/README.md b/packages/channels-telegram/README.md index 6d879247af..f55ee65b34 100644 --- a/packages/channels-telegram/README.md +++ b/packages/channels-telegram/README.md @@ -74,7 +74,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); diff --git a/packages/channels-whatsapp/README.md b/packages/channels-whatsapp/README.md index 59115cbf99..0256ce524c 100644 --- a/packages/channels-whatsapp/README.md +++ b/packages/channels-whatsapp/README.md @@ -58,7 +58,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [bot], }); diff --git a/packages/channels/README.md b/packages/channels/README.md index c09395099c..0d431772f6 100644 --- a/packages/channels/README.md +++ b/packages/channels/README.md @@ -57,7 +57,7 @@ const runtime = new CopilotRuntime({ intelligence: new CopilotKitIntelligence({ // apiUrl and wsUrl default to the managed Intelligence platform — override // both together only for a self-hosted deployment. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, // free tier available + apiKey: process.env.INTELLIGENCE_API_KEY!, // free tier available }), channels: [channel], }); diff --git a/packages/react-core/skills/react-core/references/threads.md b/packages/react-core/skills/react-core/references/threads.md index 4cbb59e8cf..ec2903676b 100644 --- a/packages/react-core/skills/react-core/references/threads.md +++ b/packages/react-core/skills/react-core/references/threads.md @@ -128,8 +128,7 @@ import { const intelligence = new CopilotKitIntelligence({ // apiUrl / wsUrl default to the managed Intelligence platform — leave unset. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, - organizationId: process.env.COPILOTKIT_ORG_ID!, + apiKey: process.env.INTELLIGENCE_API_KEY!, }); const runtime = new CopilotIntelligenceRuntime({ diff --git a/packages/runtime/skills/runtime/references/intelligence-mode.md b/packages/runtime/skills/runtime/references/intelligence-mode.md index 67aa941d3a..89fa074b6c 100644 --- a/packages/runtime/skills/runtime/references/intelligence-mode.md +++ b/packages/runtime/skills/runtime/references/intelligence-mode.md @@ -1,13 +1,17 @@ # CopilotKit Intelligence Mode +Published version: https://docs.copilotkit.ai/premium/connect-your-runtime + Intelligence currently ships as a managed cloud service. The only supported `apiUrl` / `wsUrl` today is the CopilotKit-managed cloud Intelligence instance — the `ɵ`-prefixed runtime internals and REST/WebSocket contract that back Intelligence are still -stabilizing and `organizationId` is reserved for future self-hosted deployments. If you +stabilizing. `organizationId` is not part of `CopilotKitIntelligenceConfig` today — the key +alone scopes the project — and is reserved for future self-hosted deployments. If you need on-prem durable threads today, use SSE mode with a persistent runner (`SqliteAgentRunner` or a custom one) instead. -Obtain `apiKey` and `organizationId` from the CopilotKit Intelligence dashboard. +Obtain `apiKey` from the CopilotKit Intelligence dashboard, or let `copilotkit project select` +provision a project-scoped key into `.env` as `INTELLIGENCE_API_KEY`. ### URL format @@ -54,8 +58,7 @@ import { const intelligence = new CopilotKitIntelligence({ // apiUrl / wsUrl default to the managed Intelligence platform — leave them unset. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, - organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORG_ID!, + apiKey: process.env.INTELLIGENCE_API_KEY!, }); const runtime = new CopilotRuntime({ @@ -191,28 +194,24 @@ new CopilotKitIntelligence({ // Scheme-swapped from apiUrl — WRONG HOST. Serves no socket; hangs instead of erroring. wsUrl: apiUrl.replace(/^http/, "ws"), apiKey, - organizationId, }); new CopilotKitIntelligence({ apiUrl: "https://api.intelligence.copilotkit.ai/api", // double /api prefix wsUrl: "wss://realtime.intelligence.copilotkit.ai/socket", // /socket is not a real path apiKey, - organizationId, }); new CopilotKitIntelligence({ apiUrl: "https://internal.myco.com/intelligence", // self-hosting is not yet supported wsUrl: "wss://internal.myco.com/intelligence", apiKey, - organizationId, }); new CopilotKitIntelligence({ // Only one plane overridden — wsUrl silently stays on the managed host. Warns, then hangs. apiUrl: "https://api.intelligence.copilotkit.ai", apiKey, - organizationId, }); ``` @@ -221,8 +220,7 @@ Correct: ```typescript new CopilotKitIntelligence({ // No apiUrl / wsUrl — they default to the managed platform. - apiKey: process.env.COPILOTKIT_INTELLIGENCE_API_KEY!, - organizationId: process.env.COPILOTKIT_INTELLIGENCE_ORG_ID!, + apiKey: process.env.INTELLIGENCE_API_KEY!, }); // For on-prem durability without Intelligence: SSE mode + SqliteAgentRunner. ``` @@ -239,8 +237,9 @@ Three failure modes to avoid: Passing `apiUrl: ".../api"` produces double-prefixed `/api/api/threads`; passing `wsUrl: ".../socket"` produces a broken `.../socket/runner` upgrade path. 3. Self-hosting Intelligence is not yet supported. The `ɵ`-prefixed runtime internals - and REST/WebSocket contract are still stabilizing. `organizationId` is reserved for - future self-hosted instances. For on-prem durable threads today, use SSE mode + + and REST/WebSocket contract are still stabilizing. `organizationId` is not a constructor + option today; it is reserved for future self-hosted instances. For on-prem durable + threads today, use SSE mode + `SqliteAgentRunner` (see `copilotkit/agent-runners`). Source: `packages/runtime/src/v2/runtime/intelligence-platform/client.ts:41-46, 68-69, diff --git a/packages/runtime/src/v2/runtime/intelligence-platform/client.ts b/packages/runtime/src/v2/runtime/intelligence-platform/client.ts index 27ecc0f27e..5a78ba804a 100644 --- a/packages/runtime/src/v2/runtime/intelligence-platform/client.ts +++ b/packages/runtime/src/v2/runtime/intelligence-platform/client.ts @@ -462,7 +462,7 @@ interface ThreadEnvelope { * import { CopilotKitIntelligence, CopilotRuntime } from "@copilotkit/runtime"; * * const intelligence = new CopilotKitIntelligence({ - * apiKey: process.env.COPILOTKIT_API_KEY!, + * apiKey: process.env.INTELLIGENCE_API_KEY!, * }); * * const runtime = new CopilotRuntime({ @@ -478,7 +478,7 @@ interface ThreadEnvelope { * const intelligence = new CopilotKitIntelligence({ * apiUrl: "https://intelligence.internal", * wsUrl: "wss://realtime.intelligence.internal", - * apiKey: process.env.COPILOTKIT_API_KEY!, + * apiKey: process.env.INTELLIGENCE_API_KEY!, * }); * ``` */ diff --git a/scripts/validate-intelligence-env-names.ts b/scripts/validate-intelligence-env-names.ts new file mode 100644 index 0000000000..a46f6b7055 --- /dev/null +++ b/scripts/validate-intelligence-env-names.ts @@ -0,0 +1,151 @@ +import { execFileSync } from "node:child_process"; +import * as path from "node:path"; + +/** + * Guards the single canonical name for the Intelligence project API key. + * + * `INTELLIGENCE_API_KEY` is what `copilotkit project select` provisions into + * `.env`. Two other names were live in CopilotKit's own documentation and each + * produced an undefined key for a reader who followed it with a CLI-provisioned + * project (OSS-881): + * + * - `COPILOTKIT_INTELLIGENCE_API_KEY` — Channels READMEs and packaged skills. + * Retired outright: nothing ever read it. + * - `COPILOTKIT_API_KEY` — the Slack/Teams examples and the client's own TSDoc. + * Still read as a deprecated alias by those two examples, so it is allowed + * only at the small set of sites that implement or document that fallback. + * + * This is a documentation-drift guard, not a runtime check. It fails on a + * retired name reappearing anywhere, and on the alias appearing outside its + * allowlist. + */ + +const REPO_ROOT = path.resolve(__dirname, ".."); + +/** Never valid anywhere. Nothing has ever read this name. */ +const RETIRED = [ + "COPILOTKIT_INTELLIGENCE_API_KEY", + "COPILOTKIT_INTELLIGENCE_ORG_ID", +]; + +/** + * Deprecated but still read as a fallback. Permitted only where the fallback is + * implemented or explicitly described as deprecated. + */ +const ALIAS = "COPILOTKIT_API_KEY"; + +/** + * Paths allowed to mention {@link ALIAS}. + * + * `NEXT_PUBLIC_COPILOTKIT_API_KEY` is a different value entirely — the legacy + * Copilot Cloud public key — so files carrying only that prefixed form are + * matched and skipped by prefix rather than listed here. + */ +const ALIAS_ALLOWLIST = [ + "examples/slack/.env.example", + "examples/slack/README.md", + "examples/slack/app/index.ts", + "examples/slack/app/managed.ts", + "examples/slack/app/managed.test.ts", + "examples/teams/.env.example", + "examples/teams/README.md", + "examples/teams/app/index.tsx", + "scripts/validate-intelligence-env-names.ts", + "skills/copilotkit-setup/SKILL.md", + // The importer genuinely accepts both names; these lines document that. + "showcase/shell-docs/src/content/docs/integrations/adk/threads-import.mdx", + "showcase/shell-docs/src/content/docs/integrations/langgraph/threads-import.mdx", + "showcase/shell-docs/src/content/snippets/shared/cli/cli.mdx", + "showcase/shell-docs/src/content/snippets/shared/threads/threads-import.mdx", +]; + +interface Violation { + file: string; + line: number; + name: string; + reason: string; +} + +/** Returns `git grep -n` hits for one literal, or `[]` when there are none. */ +function grepRepo( + literal: string, +): { file: string; line: number; text: string }[] { + let out: string; + try { + out = execFileSync("git", ["grep", "-n", "--fixed-strings", literal], { + cwd: REPO_ROOT, + encoding: "utf-8", + }); + } catch { + // git grep exits 1 when there are no matches. + return []; + } + return out + .split("\n") + .filter(Boolean) + .map((row) => { + const [file, line, ...rest] = row.split(":"); + return { file: file!, line: Number(line), text: rest.join(":") }; + }); +} + +/** Collects every naming violation in the repository. */ +export function findViolations(): Violation[] { + const violations: Violation[] = []; + + for (const name of RETIRED) { + for (const hit of grepRepo(name)) { + if (hit.file === "scripts/validate-intelligence-env-names.ts") continue; + violations.push({ + file: hit.file, + line: hit.line, + name, + reason: "retired name; use INTELLIGENCE_API_KEY", + }); + } + } + + for (const hit of grepRepo(ALIAS)) { + if (ALIAS_ALLOWLIST.includes(hit.file)) continue; + // A different credential that merely shares the suffix. + if (hit.text.includes(`NEXT_PUBLIC_${ALIAS}`)) continue; + violations.push({ + file: hit.file, + line: hit.line, + name: ALIAS, + reason: "deprecated alias; use INTELLIGENCE_API_KEY", + }); + } + + return violations; +} + +function main(): void { + const violations = findViolations(); + + if (violations.length === 0) { + console.log("Intelligence env var names are canonical."); + process.exit(0); + } + + console.log( + `Found ${violations.length} non-canonical Intelligence env var reference${ + violations.length === 1 ? "" : "s" + }:\n`, + ); + for (const v of violations) { + console.log(` ${v.file}:${v.line} ${v.name} — ${v.reason}`); + } + console.log( + "\nThe canonical name is INTELLIGENCE_API_KEY — the name `copilotkit project select`\n" + + "provisions. If a site legitimately implements the deprecated alias fallback, add it\n" + + "to ALIAS_ALLOWLIST in scripts/validate-intelligence-env-names.ts.", + ); + process.exit(1); +} + +const isDirectRun = typeof require !== "undefined" && require.main === module; + +if (isDirectRun) { + main(); +} diff --git a/showcase/shell-docs/src/content/docs/meta.json b/showcase/shell-docs/src/content/docs/meta.json index 06bcd13f69..67952045e2 100644 --- a/showcase/shell-docs/src/content/docs/meta.json +++ b/showcase/shell-docs/src/content/docs/meta.json @@ -55,6 +55,7 @@ "---Intelligence Platform---", "premium/overview", "premium/managed-intelligence-platform", + "premium/connect-your-runtime", "premium/self-hosting", "premium/intelligence-platform", "---Deploy---", diff --git a/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx b/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx new file mode 100644 index 0000000000..e24e2b505d --- /dev/null +++ b/showcase/shell-docs/src/content/docs/premium/connect-your-runtime.mdx @@ -0,0 +1,111 @@ +--- +title: Connect your runtime to Intelligence +description: "Wire an existing CopilotKit runtime to the Enterprise Intelligence Platform — construct CopilotKitIntelligence with a project API key, identify users, and confirm the credential is actually being used." +icon: "lucide/PlugZap" +doc_type: how-to +premium: true +--- + +Connecting a runtime to Intelligence takes two things: construct a +`CopilotKitIntelligence` client with your project API key, and pass it to your +runtime as `intelligence`. The runtime reads the key from the client you pass, +not from the environment. + +This page is that wiring step. For what the platform is and why you would use it, +see the [Enterprise Intelligence Platform overview](/premium/overview) and the +[architecture page](/premium/intelligence-platform). + +## Before you start + +You need a project API key. Either provision one with the CLI: + +```bash title="Terminal" +npx copilotkit login +npx copilotkit project select +``` + +`project select` writes a project-scoped key to `.env` as `INTELLIGENCE_API_KEY`. +You can also copy a key from the +[cloud-hosted dashboard](/premium/managed-intelligence-platform). + +```bash title=".env" +INTELLIGENCE_API_KEY=cpk-... +``` + + + This is a server-side secret. Do not give it a `NEXT_PUBLIC_` or `VITE_` + prefix — that ships it to the browser. + + +## Wire the runtime + +Construct the client once and pass it to `CopilotRuntime` as `intelligence`. + +```ts title="app/api/copilotkit/[[...slug]]/route.ts" +import { + CopilotRuntime, + CopilotKitIntelligence, + createCopilotRuntimeHandler, +} from "@copilotkit/runtime/v2"; + +const intelligence = new CopilotKitIntelligence({ + // apiUrl and wsUrl default to the managed platform — leave them unset. + apiKey: process.env.INTELLIGENCE_API_KEY!, +}); + +const runtime = new CopilotRuntime({ + agents, + intelligence, + // Threads are per-user. Without this every visitor shares one history. + identifyUser: (request) => ({ + id: request.headers.get("x-user-id") ?? "anonymous", + }), +}); + +export const { GET, POST } = createCopilotRuntimeHandler({ runtime }); +``` + +`apiKey` is the only required field. The key scopes the project, so there is no +separate organization or project id to pass. + +## Confirm the credential is actually used + +A build that compiles and a chat that replies both prove nothing about +Intelligence — a runtime in SSE mode does all of that with the key unread. So a +green round trip in the browser is not evidence on its own. + +Confirm it from the product side instead. Open your project in the +[cloud-hosted dashboard](/premium/managed-intelligence-platform) and send a +message in your app. A thread should appear. If none does, the runtime never +reached the platform and is running in SSE mode, whatever the browser showed. + +## Self-hosted deployments + +`apiUrl` and `wsUrl` default to the managed platform. Override them **together** +or not at all — the API and realtime planes are deployed on different hosts, so +the websocket URL cannot be derived from the API URL, and setting one alone +leaves the other plane pointed at the managed host. + +```ts +const intelligence = new CopilotKitIntelligence({ + apiUrl: "https://api.intelligence.internal", + wsUrl: "wss://realtime.intelligence.internal", + apiKey: process.env.INTELLIGENCE_API_KEY!, +}); +``` + +Pass the bare websocket base: the client appends `/runner` and `/client` itself, +and prepends `/api` to every REST call. Passing `apiUrl: ".../api"` produces +double-prefixed `/api/api/threads`. + +See [Self-Hosting Enterprise Intelligence](/premium/self-hosting) for the full +deployment path. + +## Troubleshooting + +| Symptom | Cause | +| --- | --- | +| Chat works, no threads in the dashboard | `intelligence` was never passed to `CopilotRuntime`; the runtime is in SSE mode. | +| Opaque auth error on the first request | `INTELLIGENCE_API_KEY` is empty or belongs to a different project. | +| Socket sits in `connecting`, then "did not settle in time" | `wsUrl` overridden alone, or pointed at the API host. | +| `/api/api/...` in request logs | `apiUrl` included a `/api` suffix. | diff --git a/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx b/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx index cb66d2973e..9fce6a2b96 100644 --- a/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx +++ b/showcase/shell-docs/src/content/docs/premium/intelligence-platform.mdx @@ -8,7 +8,7 @@ premium: true The Enterprise Intelligence Platform is the platform backend behind production CopilotKit capabilities such as durable threads, realtime sync, project-scoped history, the hosted web app, and operational visibility. This page explains the mental model that applies to both [Cloud-Hosted Enterprise Intelligence](/premium/managed-intelligence-platform) and [Self-Hosting Enterprise Intelligence](/premium/self-hosting). -For a product-level map of features and hosting options, start with the [Enterprise Intelligence Platform overview](/premium/overview). For Kubernetes installation, go straight to [Self-Hosting Enterprise Intelligence](/premium/self-hosting). +For a product-level map of features and hosting options, start with the [Enterprise Intelligence Platform overview](/premium/overview). To wire an existing runtime to the platform, see [Connect your runtime to Intelligence](/premium/connect-your-runtime). For Kubernetes installation, go straight to [Self-Hosting Enterprise Intelligence](/premium/self-hosting).