Commit Graph

2 Commits

Author SHA1 Message Date
Benjamin Taylor d8f9190f03 fix(examples): map Channel thread ids onto UUIDs for the LangGraph starters
As shipped, neither LangGraph starter could answer a single Channel turn.

A Channel's thread id identifies a conversation on the provider, so it is a
provider-shaped string (channels-core sets it from
`adapter.getCanonicalThreadId?.(replyTarget) ?? conversationKey`). Both
starters assigned it to `agent.threadId` verbatim. But `LangGraphAgent` is the
platform client: it calls `client.threads.get` then `client.threads.create`
with that id, and both UUID-validate, so every turn died inside
`getOrCreateThread` with `422 Invalid thread ID: must be a UUID` before the
model was reached. The user saw only the host's generic catch-block apology.

This is not LangGraph Cloud only — `AGENT_URL` defaults to localhost:8123, so
a local `langgraph dev` fails identically.

`toPlatformThreadId` derives a deterministic RFC 4122 v5 UUID under a fixed
namespace. Deterministic rather than random because the same conversation must
always resolve to the same LangGraph thread, or checkpointed history is lost on
every turn and again on every restart. Ids that are already UUIDs pass through
untouched, so the web path — whose clients mint UUIDs — is unaffected.

The namespace constant is a compatibility contract: it decides where every
checkpointed conversation is filed. It must be reused verbatim when this moves
upstream (OSS-771), which is where it belongs — this patch duplicates the
helper across two starters and does nothing for anyone writing their own host
over `@copilotkit/runtime/langgraph`.

Refs OSS-770.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 10:23:29 -05:00
Benjamin Taylor e1f88ebc12 refactor(examples): split the Channel out of the host, drop its HTTP server
Addresses review feedback that channel-host.mts is doing too much.

Two changes, both scoped to the starters:

1. Channel construction moves to a new `channels.mts` beside `agent.ts` —
   name resolution, `createChannel`, and the `onMessage` handler. That is
   also the file to edit to customise a Channel (commands, reactions,
   onMention), which previously meant editing the host.

   The per-framework agent import moves with it, so `channel-host.mts` is now
   byte-identical in all 15 starters rather than 13 + 2.

2. The host no longer stands up an HTTP server. Its comment claimed the
   server was what "keeps the lifecycle-owning process alive"; that is false.
   An open undici WebSocket holds the event loop on its own — verified with a
   standalone repro where a process with no HTTP server and no timers of its
   own stayed up indefinitely on a single WebSocket connection. The server was
   therefore serving a second, uncalled copy of the runtime API on port 8300
   for no reason.

   With the server gone, `createCopilotNodeListener` was the wrong factory —
   it builds a request listener purely for its activation side effect. The
   host now uses `createCopilotRuntimeHandler` + `ready()`, which is the
   documented long-running-host pattern (see fetch-handler.ts). This also
   drops `node:http`, `basePath`, and the CHANNEL_PORT env var.

Behaviour is unchanged: same Channel, same agent, same status reporting, and
the same non-zero exit on activation failure.

Verified: 14/14 starters with a `typecheck:channel` script pass; mastra has no
such script by design (166dc94691) and its pre-existing Mastra `Memory` type
error is byte-identical before and after. `npm run channel` exercised on both
failure paths — missing channels.json, and missing INTELLIGENCE_API_KEY with a
name supplied — confirming the new `./channels.mjs` specifier resolves under
tsx as well as tsc. `parity:check` output identical to the pre-change baseline.

Refs #6315

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 12:41:46 -05:00