Migrating to 0.4.0
0.4.0 is meant to be a drop-in upgrade from 0.3.x. The new features are additive, off until a prop or the server turns them on, and a server that never pauses, cancels or limits looks exactly as before. A handful of behaviours did change; this page lists them so a test or a style does not surprise you.
bash
npm install @cognipeer/chat-ui@0.4.0Keep importing @cognipeer/chat-ui/styles.css: it gained the status-colour variables and a few rules.
Checklist
- Upgrade the package and run your type check. See TypeScript for the two places a union grew.
- Search your code and tests for the behaviour changes below.
- If you match on
error.message, switch toerror.code(Errors). - If you target the history rows with CSS or tests, read History.
- Decide which of the new opt-in features you want (What's new).
- Upgrade
@cognipeer/agent-serverto 0.3.0 or later to get approvals, reattach, server-side Stop, usage limits, raw uploads and the portal routes. Older servers keep working without them.
Behaviour changes
Stop
stop()now stops the run on the server on a transport that can cancel (AgentServerClient): it callsPOST /conversations/:id/cancel, closes the stream, and keeps the text so far as a message marked stopped, which is then replaced by what the server kept. Before, it only detached the browser. A server without the route (404) behaves as before. Other transports are unchanged.- Stop while a message's files are still going up aborts the upload and gives the composer its files and text back; no cancel request is made (the message was never sent).
Files
| Before (0.3.0) | Now |
|---|---|
| Files travelled base64 in the message body | With fileTransport="upload" / "upload-on-select" they are stored first and the message names them (fileIds). "inline" stays the default and unchanged |
| Uploads were base64 JSON | Where the transport supports it (AgentServerClient) a file is sent as the request body with its own Content-Type, with progress. rawUpload: false restores the JSON form |
fileTransport="upload" showed the bubble after the upload | The bubble appears at once with progress on its chips; a failed upload takes it back |
| A refused upload or message cleared the composer | The typed text comes back, the optimistic message is removed and the files stay attached |
image/* in allowedFileTypes | Matches by media-type prefix |
Refusals (too many, too large, wrong type) only reached onError | They also show in the error banner. A 0-byte file is refused with its own message |
| Image attachments with a URL were file chips | They render as thumbnails (PNG, JPEG, GIF, WebP only; an SVG is a chip). Other files show their size |
| The send button needed text | It is also enabled for files without text (attachmentsOnlyMessage is sent) |
| File links used the address as given | Only http:, https:, blob: and relative addresses are linked; a data: URL (such as the inline bytes of an A2A file) has no Download link |
| A JSON file uploaded as itself | Goes up as application/octet-stream with ?mimeType= in the query, because a JSON Content-Type is the server's JSON upload form |
Approvals and questions
- A question's
requirednow defaults to true (the agent SDK's rule). A question withoutrequiredis marked with an asterisk and Skip is offered only when every question saysrequired: false. A server that wants an optional question sendsrequired: false. - An answer refused with
action_expiredoraction_cancelledturns the card into Expired / Cancelled (asaction_not_pendingalready did) and frees the composer; any other error puts the card back to waiting. - A request the server refused on its own after it lapsed (
resolvedBy: "system:timeout") reads Expired, not Denied.
Errors
- A failed request with a known
codeshows a localized message.error.messageis now that message; the server's own text iserror.serverMessage, anderror.code,error.statusanderror.detailsare on the error. Code that comparederror.messageagainst the server's English text should compareerror.code. See Error codes. budget_reachedmakes the page fetch the conversation again, like the other conflicts (conversation_busy,action_pending).- Errors thrown for failed requests carry
statusandcodewhen the server sends them.
Messages
- Text parts of a message's
contentarray are joined with a line break instead of nothing. - The live turn is always drawn below the stored messages, so a continuation (Continue after a usage limit) no longer replaces the answer it carries on.
- Opening a conversation from the history, or through
conversationId, selects that conversation's agent (so the header, the paperclip, the limits and the composer mode match it) and callsonAgentChangewhen the agent changes.
History
ChatHistoryrows are real<button>s in a list (they were clickabledivs), and the delete button is named after the conversation ("Delete conversation: ..."). Update selectors, tests and CSS that targeted the old markup ([data-chat-conversation]marks a row).- Rows can carry a status badge; turn it off with
showStatus={false}.
i18n
SupportedLocaleis now"en" | "tr" | (string & {}): any locale code works once you supplycustomMessages; missing keys fall back to English per key.Chathonours an enclosingChatI18nProvider(its locale andcustomMessages), has its owncustomMessagesprop and works without any provider.useI18nworks outside a provider (English) and hashas(key).
Theming
- New CSS variables and tokens
success,danger,warning,info(and...Bg) back the error banner, card outcomes, the usage notice and the history badges. If you overrode--chat-*variables you may want to define these too. See Theming.
TypeScript
PendingActionandActionResolutiongained aclient_toolvariant, andStreamActionResolvedEvent.resolution.typecan be"client_tool". An exhaustiveswitchoveraction.typeorresolution.typeneeds a case for it (a host-page tool has no card, is not "waiting for you" and does not close the composer).ChatTransportgained many optional methods; transports you wrote keep compiling.AgentInfogainedcapabilities,ConversationListItemgainedruntime,budget,spentUsd,pinned,metadata;Messagegainedstatus,stopReason,stopDetail,actionStates,feedback;FileAttachmentgainedpreviewUrl,status,progress,uploadError,expiresAt,expired,conversionStatus. All optional.ChatTheme["colors"]gained the status keys (optional).pendingModeonChatInputstill works;state(ComposerState) is the new, wider form and wins when both are given. The oldermetadatakeys on an agent (enableFileUpload,maxFileSize, ...) keep working;capabilities.attachmentswins when both are present.
Things that did not change
- The
ChatandChatMinimalprops you used in 0.3.0, the default theme, the default file limits (10 MB, 10 files, any type) andfileTransport="inline". AgentServerClientmethods you called in 0.3.0.- The package formats (ESM and CommonJS) and the peer dependencies (React 18+).
Rolling back
Pin @cognipeer/chat-ui@0.3.0. The state the new features keep in the browser is limited to cp:announcement:dismissed:* and cp:notify:prefs (only when you turn those features on); ratings, pins, budgets and approvals live on the server and are unaffected by the client version.

