Three names for one value were live in CopilotKit's own documentation, and
following the wrong one with a CLI-provisioned project yields an undefined
key:
- `INTELLIGENCE_API_KEY` — what `copilotkit project select` writes, used by
all 34 integration examples and the docs site.
- `COPILOTKIT_INTELLIGENCE_API_KEY` — the seven Channels package READMEs and
the packaged skills. Nothing ever read it.
- `COPILOTKIT_API_KEY` — the Slack and Teams examples, and the TSDoc on
`CopilotKitIntelligence` itself, which is what an IDE shows on hover.
`INTELLIGENCE_API_KEY` wins, because it is the name the CLI provisions and
changing it would break every scaffolded project in the wild.
`COPILOTKIT_INTELLIGENCE_API_KEY` is retired outright — no code read it.
`COPILOTKIT_API_KEY` stays readable as a deprecated alias in the two
examples that consume it, so an existing `.env` keeps working, and is
documented as deprecated everywhere it appears.
The skills reference also documented `organizationId`, sourced from a fourth
and fifth env name, as a `CopilotKitIntelligence` option. It is not one:
`CopilotKitIntelligenceConfig` has no such field, so the copy-pasteable
sample it appeared in would not compile. Removed from the samples, and the
prose that told readers to fetch a value for it corrected.
The Intelligence wiring itself was published only inside
`node_modules/@copilotkit/runtime/skills/`, and the only docs pages showing
`CopilotKitIntelligence` were the two Channels frontends — so a developer on
the plain web path had no page to reach it from. Adds
`/premium/connect-your-runtime`, which covers the wiring, how to confirm the
credential is actually consumed, and the self-hosted two-URL rule.
`scripts/validate-intelligence-env-names.ts` keeps this from drifting back.
It runs unfiltered in CI on purpose: the two workflows that would otherwise
cover it filter paths, and static/quality ignores `examples/**` — exactly
where the deprecated alias lives.
The #4893 hard-fail gate's loader-call detector gave WRONG VERDICTS IN BOTH
DIRECTIONS. It layered two regexes — a comment/string/template alternation that
blanked only the comment branch, and `\b(?:import|require(?:\.resolve)?)\s*\(`
over the result — then classified an argument as static from the FIRST CHARACTER
after the paren. All nine shapes below were reproduced against the real gate
before the rewrite:
false FAIL throw new Error("use require(path) instead")
false FAIL `import(${x})` inside a template
false FAIL o.import(y) / mod.require(x) (member calls, not loaders)
false PASS /https:\/\//; …import(n) (the regex's `//` blanked the
rest of the line, hiding a
real dynamic call)
false PASS import(`stream${n}`) (merely STARTS with a quote)
false PASS import("zo" + n) (same)
false PASS import(`${base}/v2/index.mjs`) (same — the fat entry)
false PASS __require(name) (no \b inside `__require`)
Replaced with `scanSource`, a single-pass tokenizer that classifies every
character as code / comment / string / template / regex and returns a
length-preserving masked view plus a literal-span list. The one surviving regex
now only ever sees code, so import-shaped TEXT cannot reach it at all; an
argument counts as static only when it is one COMPLETE literal with no
concatenation or interpolation; `__require` is matched; and a member call is
rejected both by lookbehind and by a whitespace-skipping back-scan (so
`m\n .import(x)` is not a loader either).
Proven in both directions: nine innocent/violation pairs run through the real
`assertEntryPurity`, each innocent form CLEAN and each matching real violation
FAIL. Re-proved end-to-end by prepending `import "streamdown"` to the real
dist/v2/headless.mjs — exit 1 naming all five families — then restoring it
byte-identically. On the untouched dist the scan sees 66 loader calls in the
`.cjs` graph and classifies all 66 static, so it passes because it LOOKED.
Also adds the first `.cjs` fixtures: every existing fixture was `.mjs`, leaving
the script's `format: "cjs"` branch and the `require()` shape asserted by
nothing. Tests 24 → 47.
`stripComments` is renamed `maskNonCode`, since it now blanks literals and
regexes too; it had no caller outside this script and its test. The RN guard
keeps its own copy, untouched.
dev-docs/bundle-size.md: the four holes a sibling agent documented as known
limitations this round are closed and removed from that list; what genuinely
remains (regex-vs-division heuristic, no JSX/TS, indirect loaders) replaces them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e7f3d7644d rewrote the #4893 gate from a substring scan of four inlined entry
files into an esbuild `metafile` walk over the resolved graph, but the doc was
corrected one commit earlier (2158a6f382) and so described the deleted
implementation.
Rewritten to match the code:
- Tier 3 item 1 now describes the graph assertion: esbuild with `metafile: true`,
matching on resolved input paths (never file contents), `packageNameFor`'s
last-`node_modules/` rule, external specifiers collected from
`imports[].external`, fail-loud on an unresolvable edge / an entry missing from
its own graph / graph-blinding warnings, comment stripping before the one text
scan, and the negative tests in scripts/__tests__/assert-headless-purity.test.mjs.
- Dropped the now-false claims: "substring scan of four files", "cannot follow
edges", "a forbidden name in a comment fails it", and "a dep arriving
transitively through an externalized package would pass" (the gate re-bundles
with only react/react-dom external).
- `size:headless` no longer described as never hard-failing: it exits non-zero on
an unbuilt dist, an esbuild error, and a total of 0 or under
MIN_PLAUSIBLE_BYTES. `size:headline` keeps its zero-output guard. Phase 1's
"no hard-fail" is now scoped to size *thresholds*.
- Added a Known limitations list so the doc does not overclaim in the other
direction: the first-character-after-`(` literal check (a template literal or
concatenation starting with a quote is skipped), unmatched `__require(...)`,
string literals not stripped before the text scan, workspace-sibling `dist`
counted as first-party (so a dep inlined there yields no package name), only
the four .mjs/.cjs targets asserted, and the `@`-prefix family match's
over-reach.
- Tier 3 item 2 now reflects that the RN walker also extracts dynamic
import()/require() and fails loudly on unresolvable local edges.
Workflow claims verified against .github/workflows/static_bundle_size.yml: the
action pin (2.10.0) and the "step names /v2/headless, script covers /v2/context"
note were already correct and are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Tier 3 section shipped in this PR made claims the code contradicts:
- The action pin read v2.9.1; static_bundle_size.yml pins 2.10.0 (by SHA).
- assert-headless-purity.mjs asserts four targets — dist/v2/headless.{mjs,cjs}
AND dist/v2/context.{mjs,cjs} — not just the headless pair.
- The package inventory enumerated 9 packages while the workflow glob covers 10:
react-native was neither listed nor classified, and the "no size script" claim
was wrong for it (it ships size:headless).
- "Both directions of the #4893 regression" overclaimed. The purity script is a
substring scan of four emitted files with @copilotkit/core, @copilotkit/shared,
@ag-ui/*, rxjs, zod and uuid external, so it cannot see a heavy dep arriving
through an external edge; the RN test never resolves bare specifiers, so it
cannot enter node_modules. Both limits are now stated, along with the gap
neither guard covers.
- The heading said "two tiers" while three are documented.
Docs only — no change to the script or the workflow.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
react-native was missing from static_bundle_size.yml's package glob, so its
dist/ has never been measured despite being the consumer most exposed to the
#4893 regression (Metro does not tree-shake). This adds coverage:
- Extend the compressed-size-action glob to include react-native.
- New scripts/measure-headless.mjs: an esbuild-driven gzip signal for the
@copilotkit/react-native/headless entry, mirroring react-core's
measure-copilotchat.mjs (stdin + resolveDir, gzip sum, job-summary output).
- Wire a build + measure step into the copilotchat-import-size CI job.
First baseline: @copilotkit/react-native/headless = 92.8 kB gzip
(esbuild regression signal, not a Metro figure).
No limit fields (Phase 1 policy — see dev-docs/bundle-size.md).
esbuild added as a react-native devDependency (^0.27.0, matching react-core);
the root ">=0.25.4" override keeps the monorepo on a single esbuild (0.27.3).
Two corrections to the drafted script, verified by running it:
- Fed the entry via esbuild stdin with resolveDir=pkgRoot; a temp-dir entry
cannot resolve @copilotkit/react-native/headless through workspace node_modules.
- Dropped useRenderToolCall from the import list — the RN headless surface
deliberately does not export it (DOM-dependent; see src/index.ts).
Co-Authored-By: Claude <noreply@anthropic.com>
`CopilotKitIntelligence` required `apiUrl` and `wsUrl` on every construction,
so the two correct hosts had to be found and copied by hand — which is how an
agent came to invent them. Both now default to CopilotKit's managed platform,
making `new CopilotKitIntelligence({ apiKey })` the whole managed-service setup.
Overrides are unchanged for self-hosted and non-production deployments, with two
guards that the previous required-field signature made unnecessary:
- A blank value counts as unset. These URLs are usually wired from env vars, and
a declared-but-empty variable arrives as `""`, which would otherwise produce
host-relative requests instead of falling back to the managed platform.
- Setting only one of the pair warns. The API and realtime planes are separate
hosts, so a lone override silently splits the client across two deployments —
and that failure surfaces as a hang, not an error.
Sweeps the doc, skill, README, and example surfaces to the short form so the
copy-paste path no longer hands anyone URLs to get wrong, and reattaches the
`CopilotKitIntelligence` class JSDoc, which was orphaned above an interface and
so never appeared on hover.
Linear: OSS-638
Adds two CI signals for keeping the published packages small and broadly compatible:
- Bundle size: size-limit file-mode config across packages plus a
CopilotChat import-size regression signal (gzip) so growth in the
headline consumer entrypoint is visible on every PR. A bundle-size
workflow comments results on the PR (Phase 1: no hard-fail).
- ES compatibility: a compat-check (es-check) script across 9 packages
with a root .browserslistrc, validating built .mjs/.cjs against the
es2022 build target.
The measure script is importable (measureBundle) and unit-tested. Dev
docs live under dev-docs/ (bundle-size.md, browser-compat.md). All
action refs are pinned to full commit SHAs for supply-chain safety.
17 entries across docs, backend-agent, probe plumbing, frontend/CSS,
and test infra. 7 fully descoped cells + 2 partial on the column
complete declaration. 8 follow-ups without cell impact. Each entry
includes symptom, evidence, suspected cause, owner, and next step.
Flatten all packages from packages/v1/* and packages/v2/* into packages/* —
every package now lives directly under the @copilotkit/ scope with no v1/v2
subdirectories.
- Move all v1 packages (react-core, react-ui, runtime, shared, etc.) from
packages/v1/* to packages/*
- Absorb v2 react code into packages/react-core/src/v2/ (exported via /v2 subpath)
- Absorb v2 agent code into packages/runtime/src/agent/ (exported via /v2 subpath)
- Move v2 packages (core, angular, demo-agents, etc.) to packages/*
- Replace all @copilotkitnext/* imports with @copilotkit/* equivalents
- Keep @copilotkitnext/angular as the sole exception (angular remains on next)
- Update CI workflows, renovate config, release scripts for flat structure
- No public API surface changes — all exports fields are preserved
Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
Signed-off-by: Tyler Slaton <tyler@copilotkit.ai>