Upgrade to v0.8
Hashbrown v0.8 adds AG-UI activities to the message list and gives user and assistant messages an id. It also moves the hashbrown object on the AG-UI run input from the top level into forwardedProps.hashbrown, the AG-UI extension point for values the agent reads and intermediaries pass through untouched. The object itself, responseSchema and ui, is unchanged. A v0.8 client sends only the new shape, so upgrade the server's adapter together with the client. The OpenAI and Azure adapters also move to the Responses API with reasoning.
Your React components and hooks keep their APIs, but code that switches on a message role needs an 'activity' case. The other breaking change lands in server code that reads the run input itself: a custom provider, a custom transport, or a route that inspects input.hashbrown before handing the input to an official adapter. Apps that use an official adapter and never read the extension only need the first step.
Checklist
- Update Hashbrown packages together.
- Read the extension from
forwardedProps.hashbrown. Only if your server, provider or transport readsinput.hashbrown. - Move
transformRequestOptionsto the Responses API. Only if you use the OpenAI or Azure adapter. - Stop expecting activity messages in run input. Only if your server reads
role: 'activity'messages frominput.messages. - Handle
role: 'activity'in code that switches on message role. Only if your app readsChat.Messagevalues. - Expect
idon user and assistant messages. Only if you compare whole messages or build them by hand.
Prefer to let an AI coding assistant do the mechanical parts? Jump to Migrate with an AI assistant.
Update Packages
Hashbrown packages are released together, and every @hashbrownai/* package in your app must be on the same version. The official adapters on v0.8 read only forwardedProps.hashbrown, so update the server's adapter together with the client.
npm install @hashbrownai/core@0.8.0 @hashbrownai/react@0.8.0
Read the Extension from forwardedProps
Any code that reads the extension from the top level sees undefined on v0.8. Replace it with the forwardedProps path.
const responseSchema = input.hashbrown?.responseSchema;
const responseSchema = input.forwardedProps?.hashbrown?.responseSchema;
If your server rewrites the extension, for example to pin the response schema, write it back under forwardedProps and keep the rest of forwardedProps intact, since other values of yours may travel there too.
return {
...input,
forwardedProps: {
...input.forwardedProps,
hashbrown: { ...input.forwardedProps?.hashbrown, responseSchema },
},
};
The custom provider guide shows the full input type.
OpenAI and Azure on the Responses API
The OpenAI and Azure adapters now stream from OpenAI's Responses API instead of Chat Completions, and request reasoning summaries from reasoning models. Chat history, tools, and structured output work as before. Four things can need changes on the server. Both packages raise their openai peer dependency to ^6.45.0, so update that too.
transformRequestOptions receives Responses params
transformRequestOptions now receives and returns OpenAI.Responses.ResponseCreateParamsStreaming. Messages, including system messages, become input items, and max_tokens and max_completion_tokens become max_output_tokens. To add server instructions, you can set instructions.
transformRequestOptions: (options) => ({
...options,
max_completion_tokens: 2048,
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
...options.messages,
],
}),
transformRequestOptions: (options) => ({
...options,
max_output_tokens: 2048,
instructions: ['You are a helpful assistant.', options.instructions]
.filter(Boolean)
.join('\n\n'),
}),
Chat Completions-only servers are no longer supported
@hashbrownai/openai requires a server that implements the Responses API. If you point baseURL at a Chat Completions-only server, for example vLLM, OpenRouter, or LM Studio depending on the server and mode, requests fail. Use a server that supports the Responses API, or keep that server on Hashbrown v0.7.
Reasoning is on by default for reasoning models
When you don't set reasoning, models whose names start with o and a digit, gpt-5, or gpt- followed by a major version of 5 or later, other than -chat variants such as gpt-5-chat-latest, get reasoning: { summary: 'auto' }. The client then receives AG-UI reasoning events, and assistant messages expose reasoning and reasoningDetails. Hashbrown never sends effort unless you set it.
To stop requesting summaries, set reasoning: false. This does not disable reasoning in the model: a reasoning model may still return encrypted reasoning, which Hashbrown replays on the next turn. To tune it, pass reasoning: { effort: 'low' }.
HashbrownOpenAI.stream.text({
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5',
input,
reasoning: false,
});
Every request now sends store: false, and Hashbrown replays earlier reasoning from the client's history.
Azure endpoint and deployment
HashbrownAzure.stream.text keeps clientOptions: AzureClientOptions. Set endpoint and an apiVersion that supports the Responses API (for example '2025-04-01-preview'), and remove clientOptions.deployment: setting it now fails the run with a RUN_ERROR. The model option is the deployment name.
HashbrownAzure.stream.text({
clientOptions: {
apiKey: process.env.AZURE_API_KEY!,
endpoint: process.env.AZURE_ENDPOINT!,
apiVersion: process.env.AZURE_API_VERSION!,
deployment: process.env.AZURE_DEPLOYMENT,
},
model: process.env.AZURE_MODEL!,
input,
});
HashbrownAzure.stream.text({
clientOptions: {
apiKey: process.env.AZURE_API_KEY!,
endpoint: process.env.AZURE_ENDPOINT!,
apiVersion: process.env.AZURE_API_VERSION ?? '2025-04-01-preview',
},
model: process.env.AZURE_DEPLOYMENT!,
input,
});
Because the deployment name decides whether Hashbrown requests reasoning, set reasoning explicitly when the name doesn't reveal the model, and reasoning: false for a non-reasoning deployment whose name happens to match. Azure does not stream partial images and does not guarantee a reasoning summary on every request.
Activity Messages Stay on the Client
AG-UI activity messages (role: 'activity') are display material for the client. The AG-UI specification says a client must not send them back to the agent. Hashbrown now removes them from input.messages before each run. Before 0.8, an activity message that reached Hashbrown through a MESSAGES_SNAPSHOT event was sent back on later runs.
Hashbrown's own providers already ignored these messages, so most apps need no change. If your server read activity messages from the run input, keep that state on the server or send it through AG-UI state instead.
Activity Messages in the Message List
AG-UI activities now reach your app. Chat.Message gains a fourth variant, ActivityMessage, with role: 'activity'. Activity messages appear in chat.messages where they arrived, with an id, an activityType, untyped content, and links to the run, assistant message and tool call they belong to. Before 0.8, Hashbrown dropped them from the message list.
Most code that switches on message.role still type checks. What breaks is an exhaustive check that assigns the leftover role to never, and an else or default branch that assumes the message is an assistant message. Add an 'activity' case, even if you ignore it, so an activity is not mistaken for a user or assistant message.
switch (message.role) {
case 'user':
case 'assistant':
case 'error':
// ...
}
switch (message.role) {
case 'user':
case 'assistant':
case 'error':
// ...
break;
case 'activity':
// Render it by `message.activityType`, or ignore it.
break;
}
Two behaviors follow from this:
reloadand the logic that decides whether the last message needs an answer skip trailing activities. An activity is never the last user or assistant message.- Activity messages round-trip through
setMessagesand the initialmessages, so a stored transcript keeps them. They are still never sent to the agent.
React apps that persist chat.messages to storage need no change, but the stored value may now contain activity messages. See Activities for rendering them and for groupTurns.
User and Assistant Messages Have an id
User and assistant messages now expose a stable id, the canonical AG-UI message id. Activities link to it through links.assistantMessageId, and groupTurns keys by it. The id of a streaming assistant message does not change when the message is committed. Error messages are synthetic and have no id.
On input, id is optional. setMessages, the initial messages option and sendMessage keep an id you supply and mint one only when it is absent, so a transcript you save and restore keeps its links. Code that builds messages by hand needs no change.
Code that compares whole message objects, for example in tests with toEqual, now sees the extra id field. Match on the fields you care about, or use expect.any(String) for the id.
Migrate with an AI Assistant
This upgrade is mostly mechanical. Paste this prompt into an AI coding assistant such as Claude Code from the root of your repository. It inventories your code first and shows you a plan before it changes anything.
Upgrade this repository from Hashbrown v0.7 to v0.8. The full guide is at https://hashbrown.dev/docs/react/migrations/v0-8 (React) and https://hashbrown.dev/docs/angular/migrations/v0-8 (Angular). Read the guide for my framework before changing anything, and do not invent APIs that are not in it.
First, inventory. Search the repository and list every place that:
1. Depends on any `@hashbrownai/*` package, with its current version.
2. Reads `hashbrown` from an AG-UI run input, such as `input.hashbrown`, `input.hashbrown?.responseSchema` or a destructured `{ hashbrown }`.
3. Writes a `hashbrown` key onto a run input, for example to pin or replace a response schema.
4. Declares a type that extends `RunAgentInput` with a top-level `hashbrown` member.
5. Passes `transformRequestOptions` to `HashbrownOpenAI` or `HashbrownAzure`, sets `baseURL` on `HashbrownOpenAI`, or sets `clientOptions.deployment` on `HashbrownAzure`.
Show me the inventory and a short plan before editing.
Then make these changes, only where the inventory found something:
- Update every `@hashbrownai/*` package to exactly 0.8.0, together.
- Read the extension from `input.forwardedProps?.hashbrown` instead of `input.hashbrown`, and remove any `?? input.hashbrown` fallback.
- Write the extension back under `forwardedProps.hashbrown`, spreading the existing `forwardedProps` so other values in it survive.
- Move the top-level `hashbrown` member of your own input types under `forwardedProps`.
- Rewrite `transformRequestOptions` for `OpenAI.Responses.ResponseCreateParamsStreaming`: `messages`, including system messages, become `input` items, `max_tokens` or `max_completion_tokens` become `max_output_tokens`, and server instructions, if any, can go in `instructions`.
- Remove `clientOptions.deployment` from `HashbrownAzure` and pass the deployment name as `model`.
- Add a `'activity'` case to every switch on a chat message `role`, rendering `message.activityType` or ignoring the message, and match messages in tests without assuming they have no `id`.
- Flag any `baseURL` that points at a Chat Completions-only server so I can decide what to do. Do not change it yourself.
Finally, run the project's type check, build, lint and tests, fix what fails, and summarize every file you changed and anything you could not migrate automatically.
Review the inventory and plan it shows you before letting it continue.
Toolchain
@hashbrownai/core 0.8 reads its installation id through a package imports entry (#development-install). Your bundler and test runner must support package imports: Node 20 and later, Vite, webpack 5, Next.js, the Angular CLI, Jest 29.4 and later, and Vitest all do. Older toolchains, such as webpack 4 or Jest before 29.4, cannot load @hashbrownai/core 0.8; upgrade them before you upgrade Hashbrown.