Skip to content
Back to archipelago.banuacoder.com

Feature SDKs (Pro)

ai_chat_sdk Pro ​

Tier
Proversion 2.0.5
Command
archipelago generate ai_chat_sdk
Generates
  • features/ai_chat/ai_chat_anthropic_impl
  • features/ai_chat/ai_chat_api
  • features/ai_chat/ai_chat_impl
  • features/ai_chat/ai_chat_openai_impl
Generates first
storage_sdkwhen needed and missing, so there is nothing to install first
Depends on
shared_identifiersgenerate these first

Streaming LLM chat UI with multi-provider support (OpenAI, Anthropic). Includes chat bubbles, a streaming indicator, and storage_sdk-backed conversation persistence.

Variables ​

VariableTypeDefaultDescription
uiKitPackageNamestringapp_ui_kitName of the UI kit package (resolved from archipelago.yaml project.ui_kit_package_name, auto-detected from packages/*_ui_kit when missing)
aiChatProviderenumopenaiWhich AI provider(s) to include: openai, anthropic, both
defaultModelstringgpt-4o-miniDefault model ID sent to the provider (e.g. gpt-4o-mini, claude-3-haiku-20240307)

Prerequisite: storage_sdk ​

ai_chat_impl persists conversation history through storage_sdk_api's StorageProvider rather than registering its own storage instance. hooks/pre_gen.dart self-generates storage_sdk automatically (SubBrickGenerator) if it isn't already installed — archipelago generate ai_chat_sdk works standalone into a bare workspace, no manual prerequisite step.

Generated Packages ​

PackageConditionDescription
ai_chat_apiAlwaysAiChatSDK contract, AiChatProvider interface, models (ChatMessage, ChatRequest, ChatStreamEvent, ChatRole, AiChatConfig, AiChatProviderId)
ai_chat_implAlwaysOrchestrator (AiChatSDKImpl), chat screen + input bar/message list/message bubble/streaming-indicator widgets (built on the ui_kit typed context extensions), storage_sdk-backed history, router, DI
ai_chat_openai_implopenai or bothOpenAI streaming provider (Chat Completions SSE)
ai_chat_anthropic_implanthropic or bothAnthropic streaming provider (Messages API SSE)

The provider package(s) you did not select are removed by the post-generation hook, not merely left ungenerated.

Usage ​

Interactive ​

bash
archipelago generate ai_chat_sdk

Non-interactive (CI) ​

bash
archipelago generate ai_chat_sdk --config my_config.json

Generated Structure ​

text
features/
└── ai_chat/
    ├── ai_chat_api/
    │   └── lib/src/
    │       ├── models/
    │       ├── utils/                       # AiChatInjectorKey
    │       └── ai_chat_sdk.dart             # AiChatSDK contract
    ├── ai_chat_impl/
    │   └── lib/src/
    │       ├── data/                        # ChatHistoryStorage
    │       ├── di/                          # AiChatImplGlobalModule
    │       ├── presentation/
    │       │   └── widgets/
    │       ├── router/
    │       └── ai_chat_sdk_impl.dart
    ├── ai_chat_openai_impl/                 # if aiChatProvider=openai or both
    │   └── lib/src/
    │       ├── di/register_module.dart      # vendor-configured Dio
    │       └── openai_chat_provider.dart
    └── ai_chat_anthropic_impl/              # if aiChatProvider=anthropic or both
        └── lib/src/
            ├── di/register_module.dart
            └── anthropic_chat_provider.dart

API ​

dart
abstract interface class AiChatSDK {
  Future<void> launch(BuildContext context); // opens /ai-chat
  Set<AiChatProviderId> get availableProviders;
  AiChatProviderId get currentProvider;
  void setProvider(AiChatProviderId provider); // throws ArgumentError if not registered
  Stream<List<ChatMessage>> get conversationStream;
  List<ChatMessage> get currentMessages;
  Future<String> sendMessage(String content);  // returns the request id
  void cancelInflight();
  Future<void> clearConversation();
  Future<void> retryLastFailed();
  AiChatConfig get config;
}
dart
final chat = getIt<AiChatSDK>();
await chat.launch(context);          // navigate to the chat screen

// Or drive it programmatically:
await chat.sendMessage('Hello!');    // streams the assistant reply
chat.conversationStream.listen((messages) => setState(() => _messages = messages));
chat.cancelInflight();               // cancel an in-flight request
await chat.clearConversation();      // clear memory + persistence

Multi-provider registration ​

Each provider package implements AiChatProvider (Stream<ChatStreamEvent> streamChat(ChatRequest request)) and registers itself tagged with a distinct @Named(AiChatInjectorKey.openai / .anthropic) key. This is required, not cosmetic: get_it throws on a duplicate unnamed type+registration, so aiChatProvider: both would otherwise crash at boot (fixed in 1.3.2 — before that, both provider packages registered AiChatProvider unnamed). The orchestrator discovers every registered provider via getIt.getAll<AiChatProvider>() and switches with setProvider():

dart
chat.setProvider(AiChatProviderId.anthropic); // throws if not registered

DI and wiring notes ​

ai_chat_impl/pubspec.yaml declares its archipelago: di_modules block for monorepo_toolkit generate host-registry:

yaml
archipelago:
  di_modules:
    - class: AiChatImplPackageModule       # generated; registers AiChatConfig only
    - class: AiChatImplGlobalModule        # hand-written orchestrator
      depends_on:
        - ai_chat_openai_impl              # if useOpenai
        - ai_chat_anthropic_impl           # if useAnthropic
        - storage_sdk_shared_preferences

AiChatImplGlobalModule registers AiChatManifest (the /ai-chat route) in FeatureSDKRegistry and AiChatSDK as a lazy singleton. Its init resolves nothing: getIt.getAll<AiChatProvider>() and StorageProvider are read the first time something resolves AiChatSDK, such as opening /ai-chat or calling launch(). The depends_on list still places it after the provider modules.

AiChatSDKImpl starts with an empty conversation and loads the stored history in the background. When the load finishes, the history is emitted on conversationStream, placed before anything sent in the meantime. sendMessage, clearConversation and retryLastFailed wait for the load first, so the request context and a clear always see the full history. A failed read starts an empty chat.

API keys ​

Neither provider package wires a real secret by default. Each registers its own vendor-configured Dio (base URL + auth header) as a named singleton (@Named('openai') / @Named('anthropic')) from String.fromEnvironment('OPENAI_API_KEY', defaultValue: 'YOUR_OPENAI_API_KEY'), with a per-request Dio interceptor that rejects calls while the placeholder key is still in place (checked per request, not at DI build time, so the graph resolves without a key):

dart
// ai_chat_openai_impl/lib/src/di/register_module.dart
@Named('openai')
@lazySingleton
Dio get openAiDio {
  const key = String.fromEnvironment('OPENAI_API_KEY', defaultValue: 'YOUR_OPENAI_API_KEY');
  return Dio(BaseOptions(baseUrl: 'https://api.openai.com', headers: {'Authorization': 'Bearer $key'}))
    ..interceptors.add(/* rejects the request while key is the placeholder */);
}

After generation, set OPENAI_API_KEY / ANTHROPIC_API_KEY in .env and run melos run env:generate, then replace the String.fromEnvironment read with the generated Env.openAiApiKey / Env.anthropicApiKey per the TODO(user) left in each register_module.dart.

The provider classes take their own Dio constructor parameter with an explicit @Named(...) qualifier, so each resolves its vendor-configured client even when an unnamed Dio is registered elsewhere (fixed in 1.3.3).

Persistence ​

Conversation history is persisted via StorageProvider.forNamespace('ai_chat') — non-secure, StorageScope.application (the default). It is not StorageScope.user, so it is never swept by a logout-clear coordinator; clear it explicitly with clearConversation().

Dependencies ​

  • storage_sdk_api — self-generated automatically if not already installed
  • storage_sdk_shared_preferences — listed in the depends_on of the DI module
  • ui_kit package (uiKitPackageName) — the chat widgets use context.colors, context.typography, context.spacing, context.radius
  • dio — both provider packages speak HTTP via Dio, independent of the project's networkLibrary choice (they do not use network_sdk_api's HttpClient port)

Known Limitations ​

  • No integration tests ship for either provider's SSE parsing — both source files carry a VERIFY comment noting Dio's streamed-response type is hard to mock, and ask you to add a live-API integration test before shipping to production.
  • Both provider packages talk to Dio directly; they don't go through the project's chosen network_sdk transport, so network-layer concerns configured there (auth interceptor, logging) don't apply to chat requests.

Changelog ​

Run archipelago brick info ai_chat_sdk to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.