Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ai_ui_stream

An unofficial, transport-agnostic Dart client for the AI SDK "UI Message Stream" protocol (the SSE format produced by createUIMessageStream / consumed by @ai-sdk/react's useChat).

It gives you the pieces the JS SDK gives the web, minus any UI framework or backend assumptions:

  • UiMessageStreamParser — decodes the SSE byte stream into typed UIStreamEvents.
  • MessageReducer — folds those events into UIMessages (text, reasoning, tool calls).
  • ChatController — orchestrates a conversation: appends the user message, streams the reply, tracks status, captures the server conversationId, and surfaces tool calls awaiting human approval.

It is not affiliated with or endorsed by Vercel.

Usage

Implement a ChatTransport for your backend (URL, auth, body serialization):

class MyTransport implements ChatTransport {
  @override
  Stream<List<int>> send(ChatRequest request) async* {
    final req = http.Request('POST', Uri.parse('https://api.example.com/chat'))
      ..headers['authorization'] = 'Bearer $token'
      ..headers['content-type'] = 'application/json'
      ..headers['accept'] = 'text/event-stream'
      ..body = jsonEncode({
        'conversationId': request.conversationId,
        'trigger': request.trigger,
        'messages': request.messages.map((m) => m.toJson()).toList(),
        // ...map request.approvalDecisions to your backend's approval fields
      });
    final res = await http.Client().send(req);
    if (res.statusCode != 200) throw Exception('HTTP ${res.statusCode}');
    yield* res.stream;
  }
}

Drive a conversation:

final chat = ChatController(
  transport: MyTransport(),
  idGenerator: () => const Uuid().v4(), // backend requires a UUID conversationId
);

chat.snapshots.listen((snapshot) {
  // render snapshot.messages, snapshot.status, snapshot.error
});

await chat.sendText('Hello!');

// Human-in-the-loop tool approval:
for (final approval in chat.pendingApprovals) {
  await chat.respondToApprovals([
    ApprovalDecision(
      toolCallId: approval.toolCallId,
      toolName: approval.toolName,
      args: approval.input ?? const {},
      approved: true,
    ),
  ]);
}

Scope & caveats

  • Assumes one single-line JSON object per SSE data: frame.
  • The approval-requested state is inferred when a turn finishes with a tool's input available but no output (see ToolState). Validate against a captured stream for your backend.
  • Rendering is the consumer's job; this package only models and reduces the stream.

License

Apache-2.0. See LICENSE.

About

Unofficial, transport-agnostic Dart client for the AI SDK UI Message Stream protocol: SSE parser, message reducer, and a chat controller with human-in-the-loop tool approval.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages