Contents

nerdsnipe-inc/aichatkit

A Swift package that gives every app a single, unified chat interface across cloud and on-device AI providers. OpenAI, Anthropic, and Apple Intelligence all share the same protocol, message model, and optional SwiftUI components — swap providers with a one-line change.

Products

| Product | Description | |---|---| | AIChatCore | Protocol, message model, shared types | | AIChatOpenAI | OpenAI-compatible provider (OpenAI, OpenRouter, llama-server, …) | | AIChatAnthropic | Anthropic Messages API with extended thinking support | | AIChatFoundationModels | Apple Intelligence on-device (macOS 26+ / iOS 26+) | | AIChatUI | ChatSession ViewModel + MarkdownMessageView + optional ChatView drop-in |


Installation

// Package.swift
.package(url: "https://github.com/NerdSnipe-Inc/AIChatKit", from: "0.1.0")

// Target dependencies — add only what you need
.product(name: "AIChatCore",             package: "AIChatKit"),
.product(name: "AIChatOpenAI",           package: "AIChatKit"),
.product(name: "AIChatAnthropic",        package: "AIChatKit"),
.product(name: "AIChatFoundationModels", package: "AIChatKit"),
.product(name: "AIChatUI",               package: "AIChatKit"),

Drop-in UI

If you have no design requirements, ChatView handles everything:

import AIChatUI
import AIChatOpenAI

ChatView(session: ChatSession(
    provider: OpenAIProvider(apiKey: "sk-…"),
    model: "gpt-4o"
))

Markdown rendering

AIChatUI exports MarkdownMessageView — a fully styled markdown renderer built on swift-markdown-ui and Splash. It handles fenced code blocks with syntax highlighting, a language label, and a copy button.

import AIChatUI

// In a custom AI bubble:
case .aiMessage(let e):
    MarkdownMessageView(text: e.text)

Supports: headings, bold/italic, inline code, fenced code blocks (syntax-highlighted), blockquotes, lists, tables, links.


Restoring saved conversations

Use loadSnapshot(entries:history:) to restore a persisted conversation into both the display entries and provider history in a single call:

var snapshotEntries: [ChatSession.Entry] = []
var snapshotHistory: [ChatMessage] = []

for msg in storedMessages {
    if msg.role == "user" {
        snapshotEntries.append(.userMessage(ChatSession.UserEntry(id: msg.id, text: msg.content)))
        snapshotHistory.append(ChatMessage(id: msg.id, role: .user, content: msg.content))
    } else {
        snapshotEntries.append(.aiMessage(ChatSession.AIEntry(id: msg.id, text: msg.content, isStreaming: false)))
        snapshotHistory.append(ChatMessage(id: msg.id, role: .assistant, content: msg.content))
    }
}

session.loadSnapshot(entries: snapshotEntries, history: snapshotHistory)
// session.send() now has full history context

Providers

OpenAI

import AIChatOpenAI

let provider = OpenAIProvider(apiKey: "sk-…")

// OpenAI-compatible endpoint (OpenRouter, llama-server, Groq, …)
let provider = OpenAIProvider(
    apiKey: "your-key",
    endpoint: URL(string: "https://openrouter.ai/api/v1/chat/completions")!
)

// Local llama-server — disable stream_options
let provider = OpenAIProvider(apiKey: "", endpoint: llamaURL, streamUsage: false)

Anthropic

import AIChatAnthropic

let provider = AnthropicProvider(apiKey: "sk-ant-…")

// Extended thinking
ChatRequestOptions(maxTokens: 16000, thinkingBudget: 8000)

Apple Intelligence (Foundation Models)

import AIChatFoundationModels
import FoundationModels

@available(macOS 26.0, iOS 26.0, *)
guard case .available = SystemLanguageModel.default.availability else { return }
let provider = FoundationModelsProvider()

Context window is 4,096 tokens on-device. When the conversation exceeds this limit, FoundationModelsProvider surfaces LanguageModelSession.GenerationError.exceededContextWindowSize. Callers should catch this and either summarize history or switch to a higher-context provider (e.g. MLXProvider from AIChatKitMLX, or PrivateCloudComputeLanguageModel when it becomes available in the public SDK).

session.$error
    .sink { error in
        if let genError = error as? LanguageModelSession.GenerationError,
           case .exceededContextWindowSize = genError {
            // switch to higher-context provider and restore history
        }
    }

Tool use

import AIChatCore

let tool = ChatRequestOptions.ToolDefinition(
    name: "get_weather",
    description: "Get the current weather for a city.",
    parameters: .object(
        properties: ["city": .string(description: "City name")],
        required: ["city"]
    )
)

ChatRequestOptions(tools: [tool], toolChoice: .auto)

When a .toolCall entry has status == .running, execute the tool and call:

session.submitToolResult(toolCallId: entry.id, content: resultString)

Error handling

ChatSession surfaces errors as .activity entries automatically. For custom error UI:

.onChange(of: session.error) { _, error in
    guard let error else { return }
    // show your own alert
}

License

MIT

Package Metadata

Repository: nerdsnipe-inc/aichatkit

Default branch: main

README: README.md