hashbrown

Getting Started

Migrations

Guide

  1. 1. Basics of AI
  2. 2. System Instructions
  3. 3. Message History
  4. 4. Skillet Schema
  5. 5. Streaming
  6. 6. Tool Calling
  7. 7. Structured Output
  8. 8. Generative UI
  9. 9. JavaScript Runtime

Recipes

  1. Natural Language Forms
  2. UI Chatbot with Tools
  3. UI Kits
  4. Predictive Suggestions
  5. Remote MCP
  6. Threads
  7. Magic Text
  8. JSON Parser
  9. Local Models

Platforms

Upgrade to v0.6

Hashbrown v0.6 connects your app to your model provider through AG-UI and nothing else. Your server receives an AG-UI RunAgentInput and streams AG-UI events back. The binary frame protocol from earlier releases is gone.

In 0.x releases the minor version is where breaking changes land. This guide walks through each one in the order most apps hit them. Most Angular apps only need the first five steps.


Checklist

  1. Update every Hashbrown package to 0.6.0 together.
  2. Rewrite your server endpoint to speak AG-UI.
  3. Point the client at the new endpoint.
  4. Move model selection to the server.
  5. Remove structured output controls.
  6. Replace thread loading and saving. Only if you used Hashbrown's thread persistence.
  7. Rename the core runtime. Only if you call @hashbrownai/core directly.
  8. Adopt the AG-UI transport contract. Only if you wrote a custom transport or provider.
  9. Replace Magic Text parser imports. Only if you imported parser helpers from core.
  10. Replace the Writer provider. Only if you used the Writer provider.

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.

npm install @hashbrownai/core@0.6.0 @hashbrownai/angular@0.6.0

On the server, update your provider adapter and add the AG-UI packages. The adapter returns AG-UI events, and @ag-ui/encoder turns them into server-sent events.

npm install @hashbrownai/openai@0.6.0 @ag-ui/core @ag-ui/encoder

Swap @hashbrownai/openai for anthropic, azure, bedrock, google or ollama as needed. Every adapter takes the same input, model and signal options.


Rewrite the Server Endpoint

This is the change every app needs.

In v0.5 the client sent a Hashbrown completion request, the adapter returned encoded binary frames, and your route piped them out as application/octet-stream.

Before:

import { HashbrownOpenAI } from '@hashbrownai/openai';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/chat', async (req, res) => {
  const stream = HashbrownOpenAI.stream.text({
    apiKey: process.env.OPENAI_API_KEY!,
    request: req.body, // Chat.Api.CompletionCreateParams
  });

  res.header('Content-Type', 'application/octet-stream');

  for await (const chunk of stream) {
    res.write(chunk);
  }

  res.end();
});

app.listen(3000);

In v0.6 the client sends an AG-UI RunAgentInput. The adapter returns AG-UI events, and your route encodes each one as a server-sent event.

After:

import type { RunAgentInput } from '@ag-ui/core';
import { EventEncoder } from '@ag-ui/encoder';
import { HashbrownOpenAI } from '@hashbrownai/openai';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/run', async (req, res) => {
  const abortController = new AbortController();
  req.once('aborted', () => abortController.abort());
  res.once('close', () => abortController.abort());

  const stream = HashbrownOpenAI.stream.text({
    apiKey: process.env.OPENAI_API_KEY!,
    model: 'gpt-5-mini',
    input: req.body as RunAgentInput,
    signal: abortController.signal,
  });
  const encoder = new EventEncoder();

  res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
  res.header('Content-Type', encoder.getContentType());
  res.header('Connection', 'keep-alive');
  res.flushHeaders();

  for await (const event of stream) {
    res.write(encoder.encodeSSE(event));
  }

  if (!res.writableEnded) {
    res.end();
  }
});

app.listen(3000);

A few things to note:

  • The request body is now an AG-UI RunAgentInput. It carries the thread ID, run ID, messages and tools. Hashbrown adds a hashbrown field with the structured output schema when your resource has one.
  • The adapter option is input, not request, and it no longer accepts a Hashbrown completion request.
  • model is required and lives here, on the server. See step 4.
  • The abort signal cancels the provider call when the browser disconnects or the user stops the run.
  • EventEncoder picks the content type and encodes each event. Do not write raw event objects to the response.
  • The route is POST /run because that is what the client calls by default. You can keep another path if you point the client at it.

There is no compatibility export for the binary frame API, so migrate the server route and the client URL together. See the platform page for your provider, such as OpenAI, for Fastify, Next.js and other server shapes.


Point the Client at the Endpoint

Hashbrown's HTTP transport posts to /run when you don't configure a URL. If your route lives somewhere else, point provideHashbrown at it.

Before:

export const appConfig: ApplicationConfig = {
  providers: [
    provideHashbrown({
      baseUrl: '/api/chat',
    }),
  ],
};

After:

export const appConfig: ApplicationConfig = {
  providers: [
    provideHashbrown({
      baseUrl: '/api/run',
    }),
  ],
};

provideHashbrown still accepts middleware, so request headers such as authorization work the same way, and a resource's apiUrl option still overrides the URL for that resource. To replace HTTP entirely, pass a transport instead of a baseUrl.


Move Model Selection to the Server

Angular resources no longer accept a model option. The client sends an AG-UI run to the configured transport, and the server adapter chooses the provider and model.

Before:

readonly chat = chatResource({
  model: 'gpt-4o-mini',
  system: 'You are a helpful assistant.',
});

After:

readonly chat = chatResource({
  system: 'You are a helpful assistant.',
});

These core exports were removed along with it:

  • ModelInput, ModelSpec, ModelResolver and ModelSpecFactory
  • KnownModelIds and the per-provider lists, such as OpenAiKnownModelIds

Local browser models are now transports. Replace model: experimental_local(...) with transport: experimental_local(...), and do the same for experimental_chrome and experimental_edge. Arrays that mixed local and hosted models are no longer supported. Route between models on the server, or pick an explicit transport on the client.


Remove Structured Output Controls

A structured resource's schema is now the only client-side structured output control. Hashbrown still parses, validates and streams against it. The schema travels to the server in RunAgentInput.hashbrown.responseSchema, and the server adapter decides whether to use native schema enforcement or JSON mode.

Remove these when you upgrade:

  • structuredOutput from structuredChatResource, structuredCompletionResource, uiChatResource and other structured resources.
  • emulateStructuredOutput from provideHashbrown.
  • StructuredOutputMode, StructuredOutputOptions, ResponseFormatMode and CompletionCreateParams from Chat.Api.

Hashbrown also no longer creates a reserved output tool. If you define a tool named output, it now behaves like any other tool.


Replace Thread Loading and Saving

Hashbrown no longer loads or saves conversations for you. The adapters dropped their loadThread and saveThread options, and the resources dropped these signals:

  • isLoadingThread and isSavingThread
  • threadLoadError and threadSaveError

threadId is still there, and it is now an opaque AG-UI thread identity. To persist a chat, load the saved messages from your own storage, pass them to the resource with messages or call setMessages(...), and save the updated messages through your own data layer. Track loading and saving state the way you track any other request in your app.

See Persist and Resume Threads for a complete example.


Rename the Core Runtime

Skip this step if you only use the Angular resources and provideHashbrown. It applies when you call @hashbrownai/core directly.

v0.5 v0.6
fryHashbrown(options) createChatRuntime(options)
Hashbrown type ChatRuntime type
runtime.sizzle() runtime.start()
apiUrl and middleware options transport: createHttpTransport(...)

The old names were removed without aliases.

import { createChatRuntime, createHttpTransport } from '@hashbrownai/core';

const runtime = createChatRuntime({
  system: 'You are a helpful assistant.',
  transport: createHttpTransport({
    baseUrl: '/api/run',
    middleware: [addAuthorization],
  }),
});

const teardown = runtime.start();

Omit transport to use AG-UI over POST /run.


Custom Transports and Providers

A custom transport receives a TransportRequest and returns AG-UI events.

  • TransportRequest.input is now required. It is the AG-UI RunAgentInput, plus the optional hashbrown field.
  • TransportRequest.params was removed.
  • The response is an async iterable of AGUIEvent from @ag-ui/core, not frames or bytes.

A custom provider maps your model SDK's native stream to canonical AG-UI events, and your route encodes them at the HTTP boundary as in step 2. Core no longer exports encodeFrame, decodeFrames, Frame or the frame types, and there is no compatibility layer.

See Custom Provider for the event lifecycle.


Magic Text and JSON Parser Internals

Hashbrown's Markdown and JSON parsers now come from Cacheplane. The MagicText component, injectJsonParser and injectImperativeJsonParser work as before. What changed is the low-level surface that @hashbrownai/core used to export.

Core no longer exports the Magic Text parser helpers, such as createMagicTextParserState, parseMagicTextChunk and finalizeMagicText, or the Magic Text node types. It also no longer exports the JSON parser internals, such as createParserState, parseChunk, finalizeJsonParse and the JsonAstNode types.

If you parsed Markdown yourself, use createPartialMarkdownParser from @cacheplane/partial-markdown:

import { createPartialMarkdownParser } from '@cacheplane/partial-markdown';

const parser = createPartialMarkdownParser();

parser.push('Hello **wor');
parser.push('ld**');
parser.finish();

const document = parser.root;

parser.root is the Markdown document node, and it updates as you push chunks. Nodes link to their parent, so walk the tree rather than passing it to JSON.stringify. For JSON, keep using injectJsonParser and injectImperativeJsonParser, which already expose resolved values and parser state.


Writer Provider

Hashbrown's Writer provider package is no longer published, and its known-model ID list was removed from core. Move to another supported provider, or wrap Writer's SDK in a custom provider.


New in v0.6

Once you're on v0.6, a few new capabilities are worth a look:


Migrate with an AI Assistant

Most of this upgrade is mechanical, which makes it a good job for an AI coding assistant such as Claude Code. Paste this prompt into your assistant 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.5 to v0.6. The full guide is at https://hashbrown.dev/docs/angular/migrations/v0-6 (React) and https://hashbrown.dev/docs/angular/migrations/v0-6 (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. Imports from any `@hashbrownai/*` package, with its current version.
2. Calls a provider adapter such as `HashbrownOpenAI.stream.text(...)` on the server, and the HTTP route that calls it.
3. Passes `model`, `structuredOutput` or `emulateStructuredOutput` to a Hashbrown hook, resource, provider or runtime.
4. Reads `isLoadingThread`, `isSavingThread`, `threadLoadError` or `threadSaveError`, or passes `loadThread` / `saveThread` to an adapter.
5. Uses `fryHashbrown`, the `Hashbrown` type, `.sizzle()`, or direct core `apiUrl` / `middleware` options.
6. Implements a custom transport or provider, or uses `encodeFrame`, `decodeFrames` or `Frame`.
7. Imports Magic Text or JSON parser helpers from `@hashbrownai/core`, such as `createMagicTextParserState`, `parseMagicTextChunk`, `finalizeMagicText` or `JsonAstNode`.
8. Uses the Writer provider package, or a tool named `output`.

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.6.0, together. On the server, also install `@ag-ui/core` and `@ag-ui/encoder`.
- Rewrite each server route to accept an AG-UI `RunAgentInput` body, pass it to the adapter as `input` along with the provider `model` and an abort `signal`, and stream the returned events as server-sent events with `EventEncoder` from `@ag-ui/encoder`. Hashbrown's client posts to `/run` by default; either move the route there or point the client at it.
- Remove `model` from the client and set it on the server adapter. Replace `model: experimental_local(...)` with `transport: experimental_local(...)`.
- Remove `structuredOutput` and `emulateStructuredOutput`. Keep `schema`; the server adapter now owns provider-specific enforcement.
- Remove thread loading and saving state. Load saved messages yourself and pass them to the hook or resource (`messages` or `setMessages(...)`), and save them through the app's own data layer.
- If core is used directly: `fryHashbrown` becomes `createChatRuntime`, `Hashbrown` becomes `ChatRuntime`, `.sizzle()` becomes `.start()`, and a custom endpoint moves into `transport: createHttpTransport({ baseUrl, middleware })`.
- Custom transports receive a `TransportRequest` whose `input` is required and has no `params`, and return AG-UI events. Custom providers map their SDK stream to AG-UI events; delete any frame encoding.
- Replace Magic Text parser helpers from core with `createPartialMarkdownParser` from `@cacheplane/partial-markdown`, or with the framework's Magic Text component. Keep using the framework JSON parser hooks or functions.
- Replace the Writer provider with another supported provider.

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.


Next Steps

Upgrade to v0.5

Migrate persisted v0.4 structured output and UI JSON.

Platforms

Server endpoints for each provider adapter.

Upgrade to v0.6 Checklist Update Packages Rewrite the Server Endpoint Point the Client at the Endpoint Move Model Selection to the Server Remove Structured Output Controls Replace Thread Loading and Saving Rename the Core Runtime Custom Transports and Providers Magic Text and JSON Parser Internals Writer Provider New in v0.6 Migrate with an AI Assistant Next Steps