Help Instance Help

m8ty_client_chat

Package overview

m8ty_client_chat solves this by connecting the app to server-managed, streaming AI chat. Customers receive conversational help while the server retains responsibility for prompts, authorized tools, and response generation.

The m8ty_client_chat package connects Flutter applications to the m8ty AI Chat endpoint. The client sends the user-visible conversation history; the m8ty server owns the system prompt, LLM configuration, MCP tool discovery, tool execution, and final response generation.

Before you start

Check the API documentation for the generated classes and models. The package source is available in the m8ty_client_chat GitLab project.

Add the package to the application and import the client:

dependencies: m8ty_client_chat: ^1.189.0
import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:m8ty_client_chat/m8ty_client_chat.dart';

Client setup

The default base URL is https://api.m8ty.eu/api/v1. Configure the user's OAuth token before every request:

final client = M8tyClientChat(); client.setOAuthToken('ApiOAuth2', accessToken); final aiChatApi = client.getAiChatApi();

Use basePathOverride for non-production environments:

final client = M8tyClientChat( basePathOverride: 'https://staging.example.com/api/v1', ); client.setOAuthToken('ApiOAuth2', accessToken);

Do not put access tokens, cookies, raw tool payloads, or hidden instructions in message content or logs.

API surface

AiChatApi

Method

Description

streamAiChat

Sends chronological conversation history to POST /ai/chat and returns the SSE response as Response<String>

The generated streamAiChat() method buffers the response into a string. Use it when the complete SSE transcript is sufficient. For incremental UI updates, use the package client's exposed Dio instance with ResponseType.stream, as shown below.

Request model

AiChatRequestModel contains a required messages list. Each AiChatMessageModel has:

Field

Type

Description

role

AiChatMessageRoleModel

user for customer input or assistant for a previous answer

content

String

Non-blank natural-language text visible to the user

Send messages in chronological order. The latest message should normally have the user role. Do not send system, developer, or tool messages; the server creates and manages those internally.

final request = AiChatRequestModel((b) => b ..messages.addAll([ AiChatMessageModel((m) => m ..role = AiChatMessageRoleModel.user ..content = 'What accounts do I have?'), AiChatMessageModel((m) => m ..role = AiChatMessageRoleModel.assistant ..content = 'You have three accounts.'), AiChatMessageModel((m) => m ..role = AiChatMessageRoleModel.user ..content = 'Which one had the latest transaction?'), ]));

Receive the complete response

try { final response = await aiChatApi.streamAiChat( aiChatRequestModel: request, ); final sseTranscript = response.data; if (sseTranscript == null) { throw StateError('AI chat response was empty.'); } } on DioException catch (e) { throw StateError( 'AI chat request failed (status: ${e.response?.statusCode}).', ); }

Stream incremental updates

For a token-by-token chat UI, serialize the generated request model and request a streamed Dio response. The secure metadata keeps the generated OAuth interceptor active.

final cancelToken = CancelToken(); final body = client.serializers.serializeWith( AiChatRequestModel.serializer, request, ); final response = await client.dio.post<ResponseBody>( '/ai/chat', data: body, cancelToken: cancelToken, options: Options( responseType: ResponseType.stream, headers: const {'Accept': 'text/event-stream'}, extra: const { 'secure': [ {'type': 'oauth2', 'name': 'ApiOAuth2'}, ], }, ), ); final responseBody = response.data; if (responseBody == null) { throw StateError('AI chat response stream was empty.'); } String? eventName; await for (final line in responseBody.stream .transform(utf8.decoder) .transform(const LineSplitter())) { if (line.startsWith('event:')) { eventName = line.substring('event:'.length).trim(); continue; } if (!line.startsWith('data:')) { continue; } final data = jsonDecode(line.substring('data:'.length).trim()) as Map<String, dynamic>; switch (eventName) { case 'content': final delta = data['content'] as String?; if (delta != null) { appendAssistantText(delta); } break; case 'done': markAssistantResponseComplete(); break; case 'error': throw StateError(data['error'] as String? ?? 'AI chat failed.'); default: break; } }

Keep the CancelToken with the active request so the UI can stop generation when the user leaves the chat or taps a cancel action.

SSE events

The response uses text/event-stream:

Event

Client behavior

content

Append the content delta to the current assistant message

tool_calls

Treat as server-side tool progress; do not execute the tool on the client

done

Mark the assistant response as complete

error

Stop processing and show an appropriate error state

An HTTP 200 only confirms that the stream was opened. The stream can still end with an error event, so always handle both HTTP failures and SSE errors.

Conversation ownership

The server does not own the client conversation history. The application should:

  1. Store only user-visible user and assistant messages.

  2. Send the relevant history in chronological order with every request.

  3. Append content deltas to a pending assistant message.

  4. Persist that assistant message only after a successful done event.

  5. Exclude incomplete responses after cancellation or an error.

The client must not discover or call MCP tools for this chat flow. The server selects and executes tools using the authenticated user context.

03 September 2026