chat_pilot_kit_dart 1.0.0-beta.6 copy "chat_pilot_kit_dart: ^1.0.0-beta.6" to clipboard
chat_pilot_kit_dart: ^1.0.0-beta.6 copied to clipboard

Headless chat SDK for AI-powered conversation apps. Pure Dart, no Flutter dependency. Supports streaming, plugins, and multiple transport layers.

chat_pilot_kit_dart #

pub package License: MIT

English | 简体中文

Headless chat SDK for AI-powered conversation apps. Pure Dart, no Flutter dependency.

Supports streaming, plugin extensions, conversation persistence, and multiple transport layers.

Flutter UI? See chat_pilot_kit_flutter for ready-made widgets.

Install #

dependencies:
  chat_pilot_kit_dart: ^1.0.0-beta.1

Quick Start #

import 'package:chat_pilot_kit_dart/chat_pilot_kit_dart.dart';

// 1. Implement your agent service
class MyAgentService extends BaseAgentService {
  @override
  Future<void> query(
    String text, {
    List<AttachmentInput>? attachments,
    QueryOptions? options,
  }) async {
    onData(AgentMessageData(
      answer: 'Hello from AI!',
      nodeType: 'markdown',
      queryId: queryId,
      sessionId: sessionId,
    ));
    onCompleted(queryId: queryId, sessionId: sessionId);
  }

  @override
  void abort([String? reason]) {}
}

// 2. Create controller
final controller = createChatPilotKit(
  agentService: MyAgentService(),
);

// 3. Listen to events
controller.events.listen((event) {
  switch (event) {
    case ConversationAddEvent(:final role):
      print('New conversation: $role');
    case NodeAddEvent(:final nodeType):
      print('Node added: $nodeType');
    case ConversationChangeEvent(:final completed):
      if (completed) print('Response complete');
    case ErrorEvent(:final error):
      print('Error: $error');
    default:
      break;
  }
});

// 4. Send a query
await controller.query('What is Dart?');

// 5. Read conversations
for (final conv in controller.conversations) {
  for (final node in conv.nodes) {
    print('[${node.type}] ${node.content}');
  }
}

// 6. Clean up
controller.dispose();

Architecture #

┌─────────────────────────────────────────────┐
│              ChatPilotKitController          │
│  ┌──────────┐  ┌────────────────────────┐   │
│  │  Agent    │  │  ConversationService   │   │
│  │  Service  │  │  ┌──────────────────┐  │   │
│  │  (yours)  │──│  │   Extensions     │  │   │
│  └──────────┘  │  │  text│md│image│…  │  │   │
│                │  └──────────────────┘  │   │
│  Events ◄──── Stream<ChatEvent>          │   │
└─────────────────────────────────────────────┘
  • Controller — orchestrates queries, manages sessions, emits events
  • AgentService — your backend adapter (SSE, WebSocket, REST, etc.)
  • ConversationService — stores conversations and routes data through extensions
  • Extensions — transform AgentMessageData into typed ConversationNodes

Agent Service #

Subclass BaseAgentService and implement query() and abort():

class SSEAgentService extends BaseAgentService {
  @override
  Future<void> query(
    String text, {
    List<AttachmentInput>? attachments,
    QueryOptions? options,
  }) async {
    final stream = connectToSSE(text);

    await for (final chunk in stream) {
      onData(AgentMessageData(
        answer: chunk.text,
        nodeType: chunk.type,  // 'markdown', 'thinking', 'tool_call', ...
        queryId: queryId,
        sessionId: sessionId,
        // Streaming: omit nodeCompleted (defaults to false)
        // Final chunk: set nodeCompleted: true
      ));
    }

    onCompleted(queryId: queryId, sessionId: sessionId);
  }

  @override
  void abort([String? reason]) {
    cancelSSEConnection();
  }
}

Callbacks #

Method When to call
onData(AgentMessageData) Each data chunk from the AI
onCompleted(queryId, sessionId) Query finished successfully
onError(error, queryId, sessionId) Query failed
onTtft(timestamp, queryId) Time-to-first-token metric

Built-in Extensions #

Extension Node Type Content Type Streamable
TextExtension text String No
MarkdownExtension markdown String Yes
ImageExtension image ImageContent No
AudioExtension audio AudioContent No
VideoExtension video VideoContent No
FileExtension file FileContent No
ThinkingBlockExtension thinking ThinkingContent Yes
ToolCallExtension tool_call ToolCallContent No

Custom Extension #

class ChartExtension extends MessageExtensionConfig<GenericNode> {
  @override
  String get name => 'chart';

  @override
  bool canProcess(AgentMessageData data) => data.nodeType == 'chart';

  @override
  GenericNode process(AgentMessageData data, [Map<String, dynamic> options = const {}]) {
    return GenericNode(
      customType: 'chart',
      content: Map<String, dynamic>.from(data.nodeData ?? {}),
    );
  }

  @override
  GenericNode? hydrate(ConversationNodeSnapshot snapshot) {
    if (snapshot.type != 'chart') return null;
    return GenericNode(
      customType: 'chart',
      content: Map<String, dynamic>.from(snapshot.content as Map? ?? {}),
      id: snapshot.id,
      createdAt: snapshot.createdAt,
      completed: snapshot.completed,
    );
  }
}

// Register
final controller = createChatPilotKit(
  agentService: myService,
  extensions: [MessageExtensionInstance(ChartExtension())],
);

Node Behaviors #

Behavior Effect
NodeBehavior.create Force create a new node
NodeBehavior.append Append content to the last node of the same type
NodeBehavior.replace Replace the last node's content entirely
NodeBehavior.remove Remove the last node of the same type

Target Node #

Direct updates to a specific node:

// By ID
targetNode: AgentTargetNode.byId('node-123')

// By matcher
targetNode: AgentTargetNode.byMatcher(
  (node) => node.metadata['key'] == 'value',
)

Events #

controller.events.listen((event) {
  switch (event) {
    case ConversationAddEvent(:final conversationId, :final role):
      print('New conversation: $role');
    case NodeAddEvent(:final nodeId, :final nodeType):
      print('Node added: $nodeType');
    case NodeUpdateEvent(:final nodeId):
      print('Node updated: $nodeId');
    case NodeRemoveEvent(:final nodeId):
      print('Node removed: $nodeId');
    case NodeInteractionEvent(:final type, :final data, :final timestamp):
      print('Interaction: $type at $timestamp');
    case ConversationChangeEvent(:final completed):
      if (completed) print('Response complete');
    case ErrorEvent(:final error):
      print('Error: $error');
    case ClearEvent():
      print('Cleared');
    case HistoryImportEvent(:final count, :final position):
      print('Imported $count conversations ($position)');
    default:
      break;
  }
});

Attachments #

The url field accepts any URI scheme: file://, https://, or blob://.

// Local file
await controller.queryWithAttachments(
  'Analyze this image',
  [
    AttachmentInput(
      url: 'file:///path/to/photo.jpg',
      fileName: 'photo.jpg',
      fileSize: 1024000,
      fileType: 'image/jpeg',
    ),
  ],
);

// Remote URL
await controller.queryWithAttachments(
  'Analyze this image',
  [
    AttachmentInput(
      url: 'https://example.com/photo.jpg',
      fileName: 'photo.jpg',
      fileSize: 1024000,
      fileType: 'image/jpeg',
    ),
  ],
);

// Blob (e.g. picked from device)
await controller.queryWithAttachments(
  'Analyze this image',
  [
    AttachmentInput(
      url: 'blob:https://example.com/abc-123',
      fileName: 'photo.jpg',
      fileSize: 1024000,
      fileType: 'image/jpeg',
    ),
  ],
);

Conversation Persistence #

// Export
final snapshots = controller.exportConversations();
final json = snapshots.map((s) => s.toJson()).toList();

// Import (prepend, default)
controller.importConversations(inputs);

// Import (replace all)
controller.importConversations(
  inputs,
  options: const ImportOptions(position: ImportPosition.replace),
);

Configuration #

final controller = createChatPilotKit(
  agentService: myService,
  options: const ChatPilotKitOptions(
    sessionTimeout: Duration(minutes: 30),
    enableDebugMode: false,
  ),
  overrideExtensions: [MessageExtensionInstance(MyTextExtension())],
  extensions: [MessageExtensionInstance(ChartExtension())],
);

Error Handling #

controller.events
    .where((e) => e is ErrorEvent)
    .cast<ErrorEvent>()
    .listen((e) {
      final err = e.error;
      if (err is ChatPilotKitError) {
        print('${err.category} [${err.severity}]: ${err.message}');
      }
    });

License #

MIT

0
likes
150
points
501
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Headless chat SDK for AI-powered conversation apps. Pure Dart, no Flutter dependency. Supports streaming, plugins, and multiple transport layers.

Homepage
Repository (GitHub)

Topics

#chat #ai #sdk #streaming

License

MIT (license)

Dependencies

freezed_annotation, json_annotation, meta, uuid

More

Packages that depend on chat_pilot_kit_dart