Syncs the branch with main (304 commits) to resolve CI type-check failure.
main changed extractForwardableHeaders to require a forwarding policy and
added the mergeForwardableHeaders helper (#5712); handle-suggest now uses
mergeForwardableHeaders(agent.headers, request, runtime.forwardHeadersPolicy ??
resolveForwardHeadersPolicy(undefined)) to match the run handler — fixing the
drift and adopting the server-headers-win / infra-header denylist behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the stateless /suggest transport to reuse the AG-UI SSE pipeline
instead of a buffered JSON response, resolving the streaming + state review
feedback:
- server runs the provider agent directly and streams its events via
createSseEventResponse (the runner's event pipeline minus GLOBAL_STORE
persistence), gated with captureTelemetry:false so suggestions stay out of
run telemetry
- client drives a stock HttpAgent against /agent/:id/suggest, so chips stream
progressively via onMessagesChanged and the run never routes through the
Intelligence websocket delegate (still no thread persistence)
- forward the consumer's deep-cloned messages + state onto the suggestion run
(was state: {}), matching the clone fallback
Net -68 LOC of production code; the stateless and fallback paths now share one
runAgent flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Problem — the leak
The v2 runtime's `shouldForwardHeader` forwarded `authorization` **and
any header whose name starts with `x-`** onto the outgoing agent call.
In a real deployment the inbound request has already traversed a
browser, CDN/edge, load balancer, and hosting platform — each stamping
its own `x-*` headers — so the wide `x-*` wildcard silently forwarded:
- **Hop-by-hop / topology:** `x-forwarded-for`, `x-real-ip`,
`x-forwarded-proto/host/port`
- **Cloud / CDN tracing:** `x-amzn-trace-id`, `x-amz-cf-id`,
`x-cloud-trace-context`, `x-azure-*`, `x-fastly-*`, `x-request-id`
- **Platform-injected:** `x-vercel-*`, `x-middleware-*`
- **CopilotKit Cloud platform credential:**
`x-copilotcloud-public-api-key`
The last item is a real credential-exfiltration concern: a platform key
scoped to Copilot Cloud reaching a third-party agent URL. This is the
**breadth** half of #5712 (option 3); the **precedence** half was fixed
in #5782.
## Design — denylist default + config knob, both paths
- **Default denylist (safe default).** Keep the `authorization` + `x-*`
base eligibility, but strip a curated, greppable set of known
infra/proxy/platform headers (exact names + prefix families) before
forwarding. Legitimate custom `x-*` application headers (`x-tenant-id`,
`x-api-key`, …) keep flowing untouched. The authoritative list is a
single exported constant in `header-utils.ts`.
- **Configurable policy (`forwardHeaders` runtime option).**
- `useDefaultDenylist?: boolean` (default **true**) — `false` restores
the previous wide-open behavior.
- `deny?` / `denyPrefixes?` — extend the default denylist.
- `allow?` — opt into strict allowlist mode (only listed headers
forward).
- **Resolve once.** The constructor resolves `forwardHeaders` into a
`forwardHeadersPolicy: ResolvedForwardHeadersPolicy` field (mirroring
the existing `debug` → `ResolvedDebugConfig` resolve-once), exposed on
`CopilotRuntimeLike` / `BaseCopilotRuntime` with a passthrough getter on
the `CopilotRuntime` shim.
- **Both paths.** The resolved policy is read at **/run**
(`configureAgentForRequest`) and **/connect** (`handleSseConnect`) via
`mergeForwardableHeaders`, so the two can never diverge. Server-wins
precedence and server-self case-dedup from #5782 are untouched.
## Semver
**Minor with an opt-out.** Removing a leak is a fix, not a contract
change, and we ship a documented escape hatch: `new CopilotRuntime({
agents, forwardHeaders: { useDefaultDenylist: false } })` restores the
prior behavior. Custom-header forwarders (the common case) are
unaffected.
## Red-green proof (real surface, both paths)
RED — with the predicate reverted to the old wide-open `authorization ||
x-*` (policy ignored), the new behavior assertions fail; the leak
reproduces (`x-forwarded-for: 203.0.113.7` forwards on both /run and
/connect):
```
❯ header-utils.test.ts (19 tests | 8 failed)
× strips known infra/proxy/platform headers by exact name → expected true to be false
× strips known infra/platform header families by prefix → expected true to be false
× strips denylisted headers case-insensitively → expected true to be false
× deny extends the default set → expected true to be false
× denyPrefixes extends the default set → expected true to be false
× allow switches to allowlist mode → expected true to be false
× extractForwardableHeaders drops denylisted x-* infra → expected {…4} to deeply equal {…1}
❯ agent-utils-header-forwarding.test.ts (/run) (10 tests | 1 failed)
× strips denylisted infra/platform headers (#5712 breadth) → expected '203.0.113.7' to be undefined
❯ sse-connect-agent-id.test.ts (/connect) (5 tests | 1 failed)
× strips denylisted infra/platform headers → expected '203.0.113.7' to be undefined
```
GREEN — with the real policy in place:
```
✓ header-utils.test.ts (19 tests)
✓ agent-utils-header-forwarding.test.ts (10 tests) # /run path
✓ sse-connect-agent-id.test.ts (5 tests) # /connect path
✓ agent-header-precedence.test.ts (2 tests)
Test Files 4 passed (4)
Tests 36 passed (36)
```
Full `@copilotkit/runtime` suite: **113 files / 1593 tests passed.**
Typecheck, oxlint (0 errors), oxfmt, and build all green.
## Builds on #5782
This branches off #5782's head (`636bcad05`) and reuses that PR's
`mergeForwardableHeaders` (server-wins precedence + server-self
case-dedup). It should land **after #5782**. It addresses the
**forwarding-breadth half of #5712** — #5712's precedence core is fixed
by #5782; this is the breadth follow-up (not `Fixes #5712`).
## Problem
When a self-hosted v2 `CopilotRuntime` is configured with a server-side
agent (an `@ag-ui/client` `HttpAgent` with static `headers` for
service-to-service auth), the runtime forwards inbound
`authorization`/`x-*` request headers onto the agent's outgoing call
**and lets them override the headers the server configured** — silently
breaking service-to-service auth to a secured backend (e.g. a private
Cloud Run agent behind IAM).
`Fixes #5712`
## Root cause
`packages/runtime/src/v2/runtime/handlers/shared/agent-utils.ts:125-128`
merged forwarded inbound headers **last**, so they won on collision:
```ts
agent.headers = {
...agent.headers, // server-configured
...extractForwardableHeaders(request), // inbound — overrode the above
};
```
There are actually **two** failure modes:
1. **Same-case collision** — inbound `authorization` overwrites a server
`authorization` (last-write-wins).
2. **Case-mismatch collision** — `extractForwardableHeaders` lowercases
inbound keys (`authorization`), while the server typically configures
canonical casing (`Authorization`). A plain spread treats those as
*distinct* keys and emits **both** — which undici downstream comma-joins
into a single invalid `"Bearer A, Bearer B"` ("multiple JWTs") value.
Flipping the spread order alone does **not** fix this case.
## Fix
In `agent-utils.ts`, make server-configured `agent.headers`
authoritative on collision, matched **case-insensitively**: drop any
forwarded inbound header whose name (case-insensitively) is already set
on the agent, and let non-colliding inbound headers pass through
unchanged. This preserves the existing forward-for-auth behavior for
headers the server does *not* set, while guaranteeing a server-set token
is never overridden or duplicated.
The merge logic lives in a shared
`mergeForwardableHeaders(serverHeaders, request)` helper in
`packages/runtime/src/v2/runtime/handlers/header-utils.ts` so the
precedence semantics are defined in exactly one place.
### Scope note
This is the conservative precedence + case-insensitive-dedup fix (the
issue's suggested fix#1). I did **not** tighten the default allowlist
to drop hop-by-hop/platform `x-*` headers (`x-serverless-*`,
`x-forwarded-*`, …) or add an opt-out — those alter existing forwarding
behavior and are worth a separate, deliberate change. The precedence fix
alone resolves the reported breakage (the server-set token now wins
regardless of what the platform injects on a colliding header name).
A documented workaround already exists for users on released versions:
pass a custom `fetch` to the `HttpAgent` that builds outgoing headers
from scratch (it runs after `configureAgentForRequest` and survives the
per-request `agent.clone()`).
## Red-green proof (the real fix — `/run` path)
The load-bearing assertion: there must be exactly **one** authorization
header carrying the **server** value.
### RED (fix stashed, against unmodified `agent-utils.ts`)
```
❯ src/v2/runtime/__tests__/agent-header-precedence.test.ts (2 tests | 1 failed)
× configureAgentForRequest — header precedence (#5712) > server-configured agent headers win over a colliding inbound header
AssertionError: expected [ 'Authorization', 'authorization' ] to have a length of 1 but got 2
81| expect(authKeys).toHaveLength(1);
Test Files 1 failed (1)
Tests 1 failed | 1 passed (2)
```
The pre-existing `agent-utils-header-forwarding.test.ts` also failed,
because it explicitly encoded the buggy behavior
(`expect(...["x-aimock-context"]).toBe("new-context")` — inbound
winning):
```
FAIL src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts > ... > request forwardable headers override matching pre-existing agent headers
AssertionError: expected 'old-context' to be 'new-context'
```
### GREEN (fix applied)
```
✓ src/v2/runtime/__tests__/agent-header-precedence.test.ts (2 tests) 2ms
✓ src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts (8 tests) 3ms
Test Files 2 passed (2)
Tests 10 passed (10)
```
The colliding test (`agent-utils-header-forwarding.test.ts`) was updated
from asserting the old bug to asserting corrected precedence + a new
case-insensitive-dedup guard. The non-colliding-forward test is retained
unchanged as a regression guard.
## Quality gates
```
NX Successfully ran target check-types for project @copilotkit/runtime
NX Successfully ran target test for project @copilotkit/runtime — Test Files 113 passed (113), Tests 1576 passed (1576)
```
---
## `/connect`-path change — forward-looking plumbing, inert today
The original issue and a prior eval flagged the same forwarding pattern
at `handlers/sse/connect.ts`. To keep the two paths' merge semantics
consistent, the `/connect` path now builds the same server-wins merged
headers (via the shared `mergeForwardableHeaders` helper) and passes
them into `runner.connect()`.
**This is not an active auth fix, and it is not red-green-proven as one
— because there is no live bug to fix on the connect path today.** No
shipped runner consumes the `headers` field of
`AgentRunnerConnectRequest`: the in-memory, intelligence, telemetry, and
sqlite runners all destructure only `threadId` from the connect request
and ignore `headers` entirely. Connect is a thread replay/reconnect, not
a fresh outgoing agent call. So whatever headers we pass into
`runner.connect()` are dropped on the floor by every runner that ships.
What this change actually does:
- Threads the per-request agent clone through `handle-connect.ts →
handleSseConnect` so the connect path *has access to* the
server-configured `agent.headers` (it previously did not).
- Passes `mergeForwardableHeaders(agent?.headers, request)` into
`runner.connect()` — the correct, server-wins argument **shape** for a
future outbound-connecting runner that *would* consume connect-path
headers.
- Rewrites the comments/JSDoc on this path to say this plainly, rather
than implying an active auth fix. It also documents that the
connect-site `cloneAgentForRequest` call is the sole `agentId`-existence
guard (the intelligence branch never re-validates the id), and documents
`cloneAgentForRequest`'s `AbstractAgent | Response` (404) dual-return
contract that both callers depend on.
The real outbound header forwarding — the thing that fixes#5712 — is
the `/run` path's `agent.headers` mutation described above. The connect
change is staged plumbing so that if/when a runner starts honoring
connect-path headers, it inherits the same server-wins precedence
without a second fix.
### Tests on the `/connect` path
The connect tests assert the *merge shape* that reaches
`runner.connect()` (server value wins on collision, exactly one
`authorization` key, non-colliding `x-*` still forwards) and that the
agent-undefined case (no server `agent.headers`) degrades to forwarding
allowlisted inbound headers only and does not crash. These verify the
argument we construct is correctly shaped — not that any shipped runner
consumes it.
## Files
- `packages/runtime/src/v2/runtime/handlers/header-utils.ts` — shared
`mergeForwardableHeaders` helper (case-insensitive, server-wins).
- `packages/runtime/src/v2/runtime/handlers/shared/agent-utils.ts` —
`/run` path uses the helper so server headers win on collision (**the
real fix**).
- `packages/runtime/src/v2/runtime/handlers/sse/connect.ts` — `/connect`
path uses the helper; forward-looking plumbing, inert until a runner
consumes connect-path headers.
- `packages/runtime/src/v2/runtime/handlers/handle-connect.ts` — threads
the per-request agent clone into `handleSseConnect`.
-
`packages/runtime/src/v2/runtime/__tests__/agent-header-precedence.test.ts`
— `/run` regression test exercising the real `configureAgentForRequest`
surface with a real `HttpAgent`.
-
`packages/runtime/src/v2/runtime/__tests__/agent-utils-header-forwarding.test.ts`
— updated the test that encoded the old (buggy) precedence; added a
case-mismatch dedup guard.
-
`packages/runtime/src/v2/runtime/handlers/sse/__tests__/sse-connect-agent-id.test.ts`
— connect-path merge-shape + agent-undefined coverage.
### Notes
- A documented `@ag-ui/client` `HttpAgent` `fetch` workaround already
exists for attaching service-to-service auth the runtime can't override
(see the issue). This change makes the workaround unnecessary for the
`/run` precedence case.
- Conservative scope: this is the **precedence flip on `/run`** plus
forward-looking connect plumbing. Tightening the default allowlist
(dropping hop-by-hop / platform `x-serverless-*`, `x-forwarded-*`,
`x-cloud-trace-context`, …) and an opt-out switch — issue suggestions
#2/#3 — are intentionally left as a follow-up to keep the
security-policy change minimal.
Resolve the single conflict in packages/web-inspector/src/index.ts by keeping
both additions: this branch's CpkMemoryList memory-tab element and main's
ɵCpkThreadDetails back-compat alias (independent top-level declarations).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The suggest path called configureAgentForRequest, which conditionally
attaches A2UI/MCPApps/OpenGenerativeUI middleware. MCPApps setup can
incur a listTools network round-trip per suggestion under
available:"always" — contradicting the handler's side-effect-free
contract. Since suggest runs force toolChoice: copilotkitSuggest, those
middleware-injected tools are dead weight. Replace the call with only
the header forwarding it needs (extractForwardableHeaders).
Also wire request.signal to agent.abortRun() so an aborted client
request cancels the server-side provider run instead of letting it run
to completion (best-effort; the listener never throws).
Tests: assert no middleware is attached (agent.use never called),
forwarded headers land on agent.headers, and aborting the signal calls
agent.abortRun(). Runtime mock is now typed (no as any); runner spies
kept to prove the direct-run path is preserved.
## What does this PR do?
This PR fixes build failures on Windows by replacing Unix-only shell
commands (`rm -rf`, `cp`, `mkdir -p`) in `package.json` scripts with
cross-platform Node.js `fs` built-in commands.
This follows the project's existing codebase pattern for cross-platform
operations, as seen in `packages/react-ui/package.json` (line 45).
### 🛠️ Changes:
- **`packages/runtime`**: Replaced `rm -rf` in `generate-graphql-schema`
with `fs.rmSync`.
- **`packages/vue`**: Replaced `cp` in `build:types` and `rm -rf` in
`clean` with `fs.cpSync` and `fs.rmSync`.
- **`packages/angular`**: Replaced `mkdir -p` and `cp` in `build:css`
with `fs.mkdirSync` and `fs.cpSync`.
- **`examples/v1/next-openai`, `next-pages-router`, `state-machine`**:
Replaced `rm -rf` clean commands with a single Node.js loop that deletes
`.turbo`, `node_modules`, `dist`, and `.next`.
All modified packages now build successfully on Windows.
## Related PRs and Issues
- Closes#5601
## Checklist
- [x] I have read the [Contribution
Guide](https://github.com/copilotkit/copilotkit/blob/master/CONTRIBUTING.md)
- [ ] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] "Allow edits by maintainers" is checked (lets us help iterate on
your PR directly — faster turnaround for everyone)
Empty/whitespace-only allow/deny/denyPrefixes entries are trimmed and
dropped before the allowlist-mode decision, so allow: [""] stays in
denylist mode rather than switching on exclusive allowlist mode. Also
document that allowlist mode bypasses the built-in default denylist, so
integrators must not allow-list protected/platform headers unintentionally.
`shouldForwardHeader` returned early on `policy.allow` and silently ignored
the integrator's `deny`/`denyPrefixes`, so a header listed in BOTH `allow`
and `deny` still forwarded — a footgun on a security feature.
Rework the predicate so the integrator's own `deny`/`denyPrefixes` (exact,
case-insensitive, and prefix) always strip, including in allowlist mode:
`allow` selects the candidate set, `deny` subtracts from it. The built-in
default denylist is unchanged and still applies only in denylist mode (an
explicit `allow` is a deliberate opt-in), so only the integrator's OWN deny
subtracts from an allowlist.
Also harden `resolveForwardHeadersPolicy`: trim and drop empty/whitespace-only
entries from `deny`/`denyPrefixes`/`allow`. A stray `denyPrefixes:[""]` made
`startsWith("")` true for every header (silently denying ALL forwarding), and
`allow:[""]`/`allow:[" "]` seeded the exclusive allowlist with an entry that
could never match — both integrator typos that now can't silently break
forwarding. Entries are lowercased consistently with existing handling.
- agent-utils: assert configureAgentForRequest does not mutate the shared
registered agent; only the per-request clone carries merged inbound headers
(guards against a cross-request bearer-token leak, #5712).
- header-utils: direct mergeForwardableHeaders unit test for server Authorization
vs inbound lowercase authorization with a different value — exactly one
authorization-family key survives carrying the server value (#5712).
- header-utils: shouldForwardHeader boundary tests for the bare 'x' name and the
empty-string name under both denylist and allowlist policies.
The published CopilotRuntimeLike interface (v2 export surface) added
forwardHeadersPolicy as a REQUIRED field, which breaks any external
implementor of the interface — inconsistent with the minor-release
classification. The /run (agent-utils) and /connect (sse/connect) read
sites dereferenced runtime.forwardHeadersPolicy with no coalesce, so a
policy-less object crashed with "Cannot read properties of undefined
(reading 'allow')".
Make the field optional on the interface and coalesce both read sites to
the default resolved policy (resolveForwardHeadersPolicy(undefined),
default-on denylist) when absent. Concrete runtimes (BaseCopilotRuntime)
still always resolve and set it, so behavior is identical for all real
runtimes; the interface is now non-breaking and crash-proof.
Adds a red-green test driving configureAgentForRequest with a runtime
whose forwardHeadersPolicy is undefined: asserts no throw and that the
default denylist applies (x-forwarded-for dropped, custom x-* and
authorization forwarded).
- header-utils.test.ts: new coverage for the default denylist (exact names +
prefix families, case-insensitive), custom x-* still forwarding, config
overrides (useDefaultDenylist:false, deny, denyPrefixes, allow allowlist mode),
and the breadth/precedence interaction. Migrate the inverting assertions
(x-request-id / X-Forwarded-For now stripped; extract result drops x-request-id)
and add the new required policy arg to all call sites.
- agent-utils-header-forwarding.test.ts + sse-connect-agent-id.test.ts: /run and
/connect integration coverage — denylisted infra/platform headers dropped,
custom x-* + authorization still forward, and a runtime-supplied forwardHeaders
policy is actually applied (plumb-through). Swap denylisted filler headers for
non-denylisted custom headers in the precedence regression tests.
- handle-run / handle-connect / intelligence-run-telemetry / get-runtime-info:
add the resolved forwardHeadersPolicy to mock runtimes that route through the
header merge so they satisfy the now-required policy.
Tighten which inbound HTTP headers the v2 runtime forwards onto the outgoing
agent call. The old `authorization` + `x-*` allowlist leaked infrastructure,
proxy, and platform headers (x-forwarded-*, x-real-ip, x-amzn-trace-id,
x-vercel-*, and the Copilot Cloud platform key x-copilotcloud-public-api-key)
to arbitrary configured agent URLs (#5712, breadth half).
- header-utils.ts: add DEFAULT_DENY_HEADER_NAMES + DEFAULT_DENY_HEADER_PREFIXES
constants and a policy-aware shouldForwardHeader; thread ResolvedForwardHeadersPolicy
through extractForwardableHeaders and mergeForwardableHeaders. Add the public
ForwardHeadersConfig and resolveForwardHeadersPolicy (useDefaultDenylist defaults
to true; deny/denyPrefixes extend the default; allow switches to allowlist mode).
Server-wins precedence and server-self case-dedup are unchanged.
- runtime.ts: add forwardHeaders?: ForwardHeadersConfig to BaseCopilotRuntimeOptions,
resolve it once in the constructor into forwardHeadersPolicy (mirroring the
debug -> ResolvedDebugConfig resolve-once), expose it on CopilotRuntimeLike /
BaseCopilotRuntime, and add a passthrough getter on the CopilotRuntime shim.
- Apply the resolved policy at both call sites: /run (configureAgentForRequest)
and /connect (handleSseConnect), so the two paths can never diverge.
Default-on in a minor with { useDefaultDenylist: false } as the documented opt-out.
When an agent is configured with both case-variants of the same header
in agent.headers (e.g. Authorization and authorization), the prior
{ ...base } spread kept both keys — the exact undici comma-join hazard
the function guards against for inbound collisions. Collapse server-self
case-collisions to a single first-occurrence-wins entry; server-wins-over
-inbound and case-insensitive inbound suppression are unchanged.
Cover the #5712 header-precedence behavior across both paths:
- agent-header-precedence.test.ts: server-configured agent.headers win
over forwarded inbound headers on collision (case-insensitive), with
single-key uniqueness assertions for both authorization and the x-*
family (exactly one surviving key carrying the SERVER value).
- agent-utils-header-forwarding.test.ts: the /run path merges via
mergeForwardableHeaders so server values are authoritative and inbound
headers fill only unset keys.
- sse/__tests__/sse-connect-agent-id.test.ts: the /connect path applies
the same merge, plus the agent-undefined case (no server agent.headers)
degrades to forwarding allowlisted inbound headers only and does not
crash.
The /connect path now builds the same server-wins merged headers as the
/run path and passes them into runner.connect(). This does NOT fix
connect-path auth: no shipped runner consumes the headers field of
AgentRunnerConnectRequest today. The in-memory, intelligence, telemetry,
and sqlite runners all read only threadId from the connect request and
ignore headers entirely. The real outbound header forwarding lives on the
/run path, where agent.headers is mutated before the agent runs.
Passing merged headers here is the correct argument shape for a future
outbound-connecting runner, and keeps the connect path's merge semantics
consistent with /run. The comments and JSDoc are rewritten to state this
plainly rather than implying an active auth fix: the connect-site
cloneAgentForRequest call is documented as the sole agentId-existence
guard (the intelligence branch never re-validates the id), and
cloneAgentForRequest's AbstractAgent | Response (404) dual-return contract
that both callers depend on is now documented.
When a request hits the /run path, inbound headers are forwarded to the
agent. Previously, forwarded inbound headers could clobber the
server-configured agent.headers on a key collision, letting a client
override server-set values (e.g. authorization). This is the #5712 bug.
Introduce mergeForwardableHeaders (header-utils.ts): a case-insensitive
merge where server-configured agent.headers always win on collision,
regardless of header-name casing. agent-utils.ts now uses this helper on
the /run path so server-configured values are authoritative and inbound
headers only fill keys the server did not set.
Fixes#5712
ɵsubscribeToMemories mirrored ɵsubscribeToThreads and sent userId in the
body, but the platform's memory routes resolve the app user from the
x-cpki-user-id header (like listMemories/createMemory), not the body — so
POST /api/memories/subscribe returned 401 AUTH_UNAUTHENTICATED. Send the
user via the header instead; the body is now empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The memory store client POSTs /memories/subscribe to mint realtime join
credentials, but the runtime router had no such route — the path fell
through to /memories/:id (memories/mutate), so a POST returned 405 Method
Not Allowed. Threads had the full chain (route → handleSubscribeToThreads →
platform); memory was missing the runtime proxy between the client and the
platform's POST /api/memories/subscribe.
Add `memories/subscribe` to the route union, match it before /memories/:id
(and exclude "subscribe" from the :id rule, mirroring threads), add
handleSubscribeToMemories returning { joinToken, joinCode } (memory delivers
the join code here, unlike threads where it rides the thread-list response),
and ɵsubscribeToMemories on the platform client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Memory handlers now forward client-actionable 4xx platform statuses
verbatim (404 not-found, 409 conflict, 422 unprocessable) so a useMemories
consumer can distinguish those from a server error, and map a platform 5xx
(or malformed status) to 502 rather than collapsing everything to 500 — the
runtime is healthy, its dependency failed. This also avoids a Response
RangeError on an out-of-range status.
parseMemoryBody and the platform client's createMemory/updateMemory now
treat scope as optional, deferring the default to the platform.
Adds POST /memories (create), PATCH /memories/:id (supersede), and
DELETE /memories/:id (retire) to the intelligence runtime, mirroring the GET
read route: CopilotKitIntelligence.{createMemory,updateMemory,removeMemory}
proxy the platform's /api/memories with the identifyUser-resolved user via the
x-cpki-user-id header. Completes the REST surface the client memory store's
addMemory/updateMemory/removeMemory call.
memories/list fell through to the POST-only default in validateHttpMethod,
so GET /memories returned 405. Add it to the GET-allowed group alongside
threads/list.
Adds a runtime-native memory read endpoint mirroring /threads: route
(memories/list) → handleListMemories → CopilotKitIntelligence.listMemories,
which proxies the platform's GET /api/memories with the project API key and
the user resolved via identifyUser (scoped through the x-cpki-user-id header,
never a client-supplied id). The client memory store's {runtimeUrl}/memories
fetch now resolves out of the box for any intelligence runtime — no per-app
BFF code. Read-only for now (list); mutations to follow.
Reverts 2cad274. The real fix for models filling optional tool params is to
use OpenAI's Chat Completions API (the Responses API fills every declared
optional); preserving format/pattern in convertJsonSchemaToZodSchema was
unnecessary (OpenAI ignores format) and broadened behavior for every tool.
convertJsonSchemaToZodSchema dropped every string constraint except enum,
so a tool param like { type: "string", format: "uuid" } reached the model
as a bare optional string. Models then fill such optionals with "" instead
of omitting them, and the loosened model-side validation accepts "" — only
for a stricter downstream (e.g. an MCP server) to reject it.
Carry format (uuid/email/url/date-time), pattern, and minLength/maxLength
through to the generated Zod schema so the model sees the field's real
shape and the model-side validation matches the server's.