-- allium: 1 -- bDS AI Integration -- Scope: core (one-shot operations), extension Bucket C (chat + streaming) -- Distilled from: src/main/engine/ChatEngine.ts, ai/providers.ts, -- ai/chat.ts, ai/tasks.ts, SecureKeyStore.ts -- Note: Rust rewrite simplifies providers to two configurable -- OpenAI-compatible endpoints (online + airplane mode), -- replacing the TypeScript app's 4 named providers. use "./post.allium" as post use "./media.allium" as media entity AiEndpoint { kind: online | airplane url: String api_key: String? -- encrypted via SecureKeyStore; null for local models model: String -- online: cloud provider (OpenAI, Anthropic-via-proxy, etc.) -- airplane: local model (Ollama, LM Studio, etc.) } entity SecureKeyStore { -- Encrypts API keys using OS keychain -- macOS: Keychain, Windows: DPAPI, Linux: libsecret -- Stored as base64 in settings table with __encrypted_ prefix -- No plain-text fallback } entity ChatConversation { title: String model: String created_at: Timestamp updated_at: Timestamp messages: ChatMessage with conversation = this } entity ChatMessage { conversation: ChatConversation role: system | user | assistant | tool content: String token_usage_input: Integer? token_usage_output: Integer? created_at: Timestamp } -- One-shot AI tasks (core scope, no streaming) -- All use OpenAI Chat Completions wire format. -- When airplane_mode: use airplane endpoint. When online: use online endpoint. -- When no endpoint configured for current mode: disable AI, show toast. rule AnalyzeTaxonomy { when: AnalyzeTaxonomyRequested(post) requires: not airplane_mode -- All AI activities gated by offline mode -- Suggests tags and categories for a post ensures: TaxonomySuggestion(tags, categories) } rule AnalyzeImage { when: AnalyzeImageRequested(media) requires: not airplane_mode requires: is_image(media.mime_type) -- Checks mime type is an image type -- Vision model generates alt text and caption ensures: ImageAnalysisResult(alt, caption) } rule AnalyzePost { when: AnalyzePostRequested(post) requires: not airplane_mode -- Generates title, excerpt, slug suggestions ensures: PostAnalysisResult(title, excerpt, slug) } rule DetectLanguage { when: DetectLanguageRequested(text) requires: not airplane_mode ensures: LanguageDetectionResult(language_code) } rule TranslatePost { when: TranslatePostRequested(post, target_language) requires: not airplane_mode -- Translates title, excerpt, content to target language ensures: TranslationResult(title, excerpt, content) } rule TranslateMedia { when: TranslateMediaRequested(media, target_language) requires: not airplane_mode -- Translates title, alt, caption to target language ensures: MediaTranslationResult(title, alt, caption) } -- Chat (extension Bucket C scope, with streaming and tool use) rule StartChat { when: StartChatRequested(model) ensures: ChatConversation.created(model: model) } rule SendChatMessage { when: SendChatMessageRequested(conversation, content) requires: not airplane_mode ensures: ChatMessage.created(conversation: conversation, role: user, content: content) ensures: AiStreamingResponse(conversation) -- AI SDK v6 streamText() with tool-call loop (max 10 rounds) -- Blog data tools for post/media querying during chat -- Token usage tracking (input, output, cache read/write) } -- Model catalog rule RefreshModelCatalog { when: RefreshModelCatalogRequested(endpoint) -- Queries the endpoint's model list API -- 5-minute cache TTL ensures: ModelCatalogUpdated(endpoint) } invariant AirplaneModeGating { -- All AI activities must be gated by airplane (offline) mode -- When airplane_mode = true: only the airplane endpoint is used -- When airplane_mode = false: the online endpoint is used -- If the active endpoint is not configured: AI is unavailable, -- show toast notification to user } invariant TwoEndpointModel { -- Two configurable OpenAI-compatible endpoints: -- online: for cloud providers (requires API key) -- airplane: for local models (no API key required) -- Both use the OpenAI Chat Completions wire format. -- This replaces the TypeScript app's 4 named provider model. } invariant SecureKeyStorage { -- API keys are never stored in plain text -- Always encrypted via OS keychain before DB storage }