Connecting a client
Introduction
Section titled “Introduction”The client never builds a connection by itself. You hand the session a ChatTransport, and a CredentialsProvider that returns the current bearer token. With @pilotoo/ui/chat both are done for you from a URL and a token function; this page is for the headless path with @pilotoo/chat-core, and for custom channels.
Connecting with Socket.IO
Section titled “Connecting with Socket.IO”The demo web app (apps/web) creates one transport and hands it to the session provider:
import { ChatSessionProvider } from '@pilotoo/chat-core';import { createSocketIoTransport } from '@pilotoo/chat-core/socket-io';import { useState, type ReactNode } from 'react';
export function ChatSession({ apiUrl, credentials, children }: Props) { const [transport] = useState(() => createSocketIoTransport(apiUrl, { credentials })); return <ChatSessionProvider transport={transport}>{children}</ChatSessionProvider>;}The mobile app does the same with a URL only: createSocketIoTransport(resolveApiUrl()).
Sending the user’s token
Section titled “Sending the user’s token”A CredentialsProvider returns the current access token:
import type { CredentialsProvider } from '@pilotoo/chat-core';
export const credentials: CredentialsProvider = () => auth.currentAccessToken();Pass it as { credentials } to each client piece that talks to the server: createSocketIoTransport, createConversationCatalog, createAgentCatalog, createAttachmentUploader, createAttachmentImageResolver, createBlobAttachmentImageResolver and createPresentationSource.
The demo web app builds all its REST clients the same way:
const conversations = createConversationCatalog(apiUrl, undefined, { credentials });const agents = createAgentCatalog(apiUrl, undefined, { credentials });The provider is called for every request and every socket (re)connection, so a refreshed token is used. How the server turns the token into an identity is covered in Authentication.
Handing a token to a desktop window
Section titled “Handing a token to a desktop window”The desktop app starts a local backend that prints a URL and a random token per launch, and injects them into the page as window.__PILOTOO_BACKEND__. The web app reads them and uses a constant token:
const backend = window.__PILOTOO_BACKEND__;const credentials = backend ? () => backend.token : undefined;const apiUrl = backend?.url ?? buildTimeApiUrl;See apps/web/src/features/chat/backend-handoff.ts.
Writing your own transport
Section titled “Writing your own transport”A host on another channel (IPC, a test script) implements the ChatTransport type from packages/chat-core/src/transport.ts:
| Member | Contract |
|---|---|
connected |
Whether the connection is up right now. |
send(command) |
Sends a ChatCommand (history, run-start, run-cancel, run-detach, run-resume). |
subscribe(listener) |
Delivers ChatTransportEvents; the returned function ends the connection. |
Events are connected, disconnected, connect-failed, protocol-error, protocol-hello, run-event and task-status. Pass it with <ChatSessionProvider transport={...}>, or as the transport prop of ChatProvider.
The command names are kebab-case (run-start); the messages on the Socket.IO wire are run:start, run:resume and so on, and the built-in transport maps one to the other. A custom transport that talks to the kit’s gateway does the same mapping. See the protocol.
Options
Section titled “Options”ChatSessionProvider props
Section titled “ChatSessionProvider props”| Prop | Default | Meaning |
|---|---|---|
transport |
required | The ChatTransport. |
clientTools |
none | See Client tools. |
protocolGraceMs |
2000 |
How long a connection waits for the gateway’s protocol:hello before treating it as compatible. |
What the kit guarantees
Section titled “What the kit guarantees”@pilotoo/chat-corenever importssocket.io-client; it lives only behind the/socket-ioentry (an optional peer).- Every payload a transport delivers is validated against
@pilotoo/chat-protocolbefore it reaches state. - An event type the client does not know is counted and ignored.
- No command is sent before the gateway’s protocol announcement or the
protocolGraceMsfallback. - A token is sent as
Authorization: Bearer <token>. An empty or absent token sends no header.
Next steps
Section titled “Next steps”- Authenticate the token on the server.
- Add conversation and agent lists with catalogs.
- Persist drafts with storage.