Skip to main content

asystant_ai 0.7.3 copy "asystant_ai: ^0.7.3" to clipboard
asystant_ai: ^0.7.3 copied to clipboard

An embeddable Flutter assistant with local tools and reactive_notifier.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:asystant_ai/asystant_ai.dart';

/// Reads application state without a network call or provider credential.
class ReadWorkspaceTool extends AsystantTool {
  const ReadWorkspaceTool();

  @override
  ToolDefinition get definition => const ToolDefinition(
    name: 'read_workspace',
    description: 'Read the name of the current workspace.',
    fields: [],
  );

  @override
  bool get requiresConfirmation => false;

  @override
  Future<Result<AssistantCard, AssistantFailure>> preview(
    ToolArguments arguments,
  ) async => Ok(const AssistantCard(title: 'Current workspace'));

  @override
  Future<Result<ToolOutcome, AssistantFailure>> execute(
    ToolArguments arguments,
    ToolContext context,
  ) async {
    context.checkCanceled();
    return Ok(const ToolOutcome(modelContent: 'Example workspace'));
  }
}

/// Simulates a provider without any inference request, to show the tool
/// loop anywhere. Real apps pass a provider to `init` instead.
class ExampleTransport extends AssistantTransport {
  @override
  bool get isAuthenticated => true;
  @override
  String get identity => 'offline-example';
  @override
  Stream<void> get sessionChanges => const Stream.empty();
  @override
  Future<Result<List<String>, AssistantFailure>> initialize({
    required List<ToolDefinition> tools,
    required List<AsystantSystemPrompt> prompts,
    required List<String> models,
  }) async => Ok(['simulated-response']);
  @override
  Stream<InferenceEvent> infer({
    required List<AssistantMessage> messages,
    required String model,
    required String requestId,
    List<AsystantSystemPrompt> context = const [],
    List<ToolDefinition>? tools,
  }) async* {
    yield InferenceCompleted(
      messages.last.role == .user
          ? AssistantMessage(
              role: .assistant,
              content: 'I will read the workspace using a local tool.',
              calls: [
                ToolCall(
                  id: requestId,
                  name: 'read_workspace',
                  arguments: ToolArguments.fromJson({}),
                ),
              ],
            )
          : const AssistantMessage(
              role: .assistant,
              content: 'The workspace is named Example workspace.',
            ),
    );
  }

  @override
  void cancel() {}
  @override
  Future<void> dispose() async {}
}

class ExampleAssistant extends AsystantAI {
  ExampleAssistant({required super.name});
  @override
  List<AsystantTool> get tools => const [ReadWorkspaceTool()];
}

/// How the example is run.
abstract final class ExampleOptions {
  /// `flutter run -d macos --dart-define=CLAUDE_CODE=true` answers with the
  /// Claude Code CLI installed and signed in on this machine: no key and no
  /// backend. Without it, simulated answers let the example run anywhere.
  static const useClaudeCode = bool.fromEnvironment('CLAUDE_CODE');
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  final ExampleAssistant assistant;
  if (ExampleOptions.useClaudeCode) {
    // A provider: the usual way to connect an assistant.
    assistant = ExampleAssistant(name: 'Assistant · Claude Code')
      ..init(provider: const ClaudeCodeProvider(defaultModel: 'sonnet'));
  } else {
    // Advanced: an AssistantTransport of your own, here one that simulates
    // the answers without any provider.
    assistant = ExampleAssistant(name: 'Assistant · offline demo')
      ..init(transport: ExampleTransport());
  }
  runApp(ExampleApp(assistant: assistant));
}

class ExampleApp extends StatefulWidget {
  const ExampleApp({super.key, required this.assistant});
  final ExampleAssistant assistant;
  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  @override
  void dispose() {
    widget.assistant.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => MaterialApp(
    home: Scaffold(
      appBar: AppBar(title: const Text('Local tools · simulated AI responses')),
      body: AsystantChat(
        assistant: widget.assistant,
        strings: const AsystantStrings(spanish: false),
      ),
    ),
  );
}